Skip to content
Parishruthi Ganesh

    to navigate · to open · Esc to close

    Featured Prototype 2026

    Sentinel Memory

    An incident-response agent whose memory is a transactional database rather than a context window — it remembers consequences, not conversations.

    What it does

    • Counterfactual card surfacing the closest historical precedent, its similarity score and what it cost
    • Retrieval telemetry shown in the UI — cosine distance, latency, index versus exact scan
    • Four-write atomic approval under SERIALIZABLE isolation with application-owned retry
    • Agent handoff rebuilding incident state entirely from the database

    Problem

    During industrial incidents, personnel rotate and agents restart. Each handoff loses the record of what was already attempted and what it caused, so a reasonable-looking action gets repeated — including the one that did damage last time. An agent whose memory is a context window has the same failure mode, and reaches it faster.

    Motivation

    The narrower question behind the project was not "how do we give agents memory?" but what specifically does an incident agent need to remember? The answer turned out not to be conversations. It was consequences: what a given action, taken in a given situation, actually caused.

    Architecture

    A three-tier system with no ORM, no separate vector database, no queue and no cache layer:

    • Client — Next.js App Router UI (React 19, Tailwind CSS).
    • Server — Node.js API routes holding the orchestration logic.
    • Services — CockroachDB Cloud for persistence, Amazon Bedrock for reasoning and embeddings.

    The agent loop lives in a single file: embed the proposed action → retrieve precedents from CockroachDB by cosine distance → ground the Bedrock call on what came back → validate the structured response with Zod → persist the recommendation together with the memory IDs it cited. Every step writes a durable memory event, including the retrieval itself, so the timeline shows that memory was consulted rather than merely that an answer appeared.

    The one architectural decision that mattered was making DataStore, EmbeddingProvider and Reasoner interfaces rather than integrations. Each has a real implementation and a clearly labelled local fallback, which makes the interface previewable without credentials and the transaction logic unit-testable without a live cluster.

    Method

    Episodic and semantic memory are treated as two problems, not one. Episodic memory records what happened in an incident, in order; semantic memory records action_taken → outcome → lesson_learned as distinct fields. Storing incident summaries and embedding them gets you "similar incidents"; storing the action-outcome triple gets you "this specific action caused this specific damage", which is the question a responder actually has.

    Keeping both in one database means retrieval returns the memory and its provenance — incident code, severity, date — in a single query, and the human decision that follows is transactional against the same rows.

    Safety is enforced after the model, not requested from it. The prompt states the rules; enforceSafetyFloor then runs on every response, forces human approval on any high-risk physical action regardless of what the model returned, and filters citations down to memory IDs that were genuinely retrieved, so a hallucinated reference cannot reach a responder.

    Results

    • 94 tests covering validation, retrieval, transactions, agents and health checks — none requiring a live cluster or an AWS account.
    • The approval path commits four writes in one transaction with SELECT … FOR UPDATE locking and application-owned 40001 retry with exponential backoff and full jitter, with tests proving rollback leaves the store unchanged.
    • The handoff demonstration reconstructs full incident state from the database alone after the primary agent disconnects — nothing is read from a transcript.

    Error analysis

    Two failure modes documented during development:

    • Retrieval returned one story instead of three. Vector search over a corpus where a single past incident contributes four memories returns four angles on that one incident and crowds out every other precedent. Fixed by over-fetching and then capping per source incident in application code.
    • The local fallback embedder produces honest but unimpressive scores. Bag-of-words similarity between a short query and a long document is bounded, landing around 0.4–0.65 where a learned model would give 0.85–0.95. The raw cosine is kept and explained rather than rescaled to look better.

    Limitations

    Documented in the repository rather than hidden:

    • No authentication and no RBAC — every visitor acts as the configured responder, which is exactly as trustworthy as the audit trail's decided_by field then is.
    • Rate limiting is in-process and therefore not multi-instance safe.
    • Seeded incidents are synthetic, not real incident reports.
    • The vector index is version-gated (CockroachDB v25.2 preview, GA in v25.3+); where it cannot be created the app degrades to an exact scan and says so in the health endpoint and the UI.

    Responsible use

    A prototype built during a hackathon, seeded with synthetic incidents. It is a demonstration of a memory architecture, not an operational safety system, and the repository's limitations section is written to keep that distinction clear. Every fallback — in-memory store, local reasoner, deterministic embeddings — is labelled in the UI, the health endpoint and every API response, so nothing claims to be an integration it isn't.

    Running it yourself

    Runs locally with no credentials, using an in-memory store and local reasoning:

    git clone https://github.com/ParishruthiGanesh/sentinel-memory.git
    cd sentinel-memory
    npm install
    cp .env.example .env.local
    npm run dev
    

    To wire up real services, set DATABASE_URL and run npm run db:migrate and npm run db:seed, then set AWS_REGION and BEDROCK_MODEL_ID. curl -s localhost:3000/api/health reports which implementation is actually serving each interface.

    Future work

    1. Close the learning loop — capture whether an approved action produced its predicted outcome and write that back as a new memory.
    2. Authentication and RBAC, without which decided_by cannot be trusted.
    3. Database-enforced immutability for memory and audit events.
    4. Real ingestion from SCADA/historian feeds instead of manual entry.
    5. Retrieval evaluation with a labelled set and precision@k tracking.

    Screenshots

    Sentinel Memory command centre showing an active incident, the proposed action, and a counterfactual memory card describing what the same action caused during a previous incident.
    The command centre. The counterfactual card renders server-side on load, so the precedent is visible before anything is clicked.
    Memory explorer listing retrieved memories with cosine distance scores, source incident codes and retrieval latency.
    Memory explorer, showing retrieval telemetry — distance, latency, and whether the vector index or an exact scan served the query.
    Incident timeline reconstructed from durable memory events, showing observations, retrievals, recommendations and human decisions in order.
    The incident timeline is reconstructed from durable events, including the retrieval steps themselves.
    Agent handoff briefing rebuilt from the database, listing what happened, what was attempted, what must not be repeated and what remains open.
    After the primary agent disconnects, the backup rebuilds incident state from the database — nothing comes from a transcript.
    Architecture diagram showing the Next.js client, Node.js API routes, and the CockroachDB and Amazon Bedrock services behind the DataStore, EmbeddingProvider and Reasoner interfaces.
    The architecture page rendered inside the application itself.