Skip to content

MemoryMesh Agent

A market-surveillance multi-agent system whose memory never goes down β€” because it isn't in-process, it's CockroachDB.

Built for the πŸͺ³ CockroachDB Γ— AWS Hackathon. LangGraph orchestrates a team of Strands Agents (reasoning via Anthropic's API directly), and every layer of memory β€” short-term conversation state, durable audit history, and long-term semantic case memory β€” is backed by CockroachDB, the one system of record in the whole stack. Model inference never touches AWS: Amazon Bedrock AgentCore hosts the runtime, and a small supporting cast of AWS services (CodeBuild, ECR, ECS, IAM) handles build automation and hosting β€” see "AWS services used" below.

The market-surveillance domain (agent personas, mock data, report schemas) is forked from AWS's own public sample, "Market Surveillance Agent with LangGraph and Strands on AgentCore". Everything about memory and model inference is new.

Why this is more than "add a database"

Most multi-agent demos treat memory as a chat-history footnote. Here, memory drives behavior:

  1. Every query starts with a recall step. Before the orchestrator even sees a question, recall_case_memory runs a semantic similarity search over every investigation the system has ever completed β€” not just this session β€” using CockroachDB's native VECTOR type and distributed C-SPANN index. "Have we seen this pattern before?" is answered from real, growing history, across every user and every thread.
  2. Every finished investigation writes itself back into memory. persist_case_memory embeds the synthesizer's finding and stores it, so the system's memory compounds with use instead of resetting per session.
  3. The agent can inspect its own memory β€” two different ways. memory_ops is wired to the CockroachDB Cloud Managed MCP Server and answers "how many cases do we have on file for this broker?" by actually querying the cluster, read-only. case_triage takes a different path into the same vector index β€” it calls recall_similar_investigations itself, mid-reasoning, to decide how urgent the current investigation is based on how similar past ones were resolved. One agent reads the cluster; the other reads the case history it's built from β€” memory isn't a single bolted-on step, it's a resource multiple agents reach for.
  4. State survives the process. LangGraph's checkpointer (AsyncCockroachDBSaver) snapshots the entire workflow state into CockroachDB after every node β€” a crashed container, a scaled-out AgentCore replica, or a restart mid-investigation all resume exactly where they left off.
  5. Every turn is auditable. A separate, human-readable message_store table (CockroachDBChatMessageHistory) logs each completed turn β€” the kind of immutable record a real market-surveillance workflow needs for compliance, independent of LangGraph's internal checkpoint format.

Architecture

flowchart TB
    U["Browser β€” React UI (web/)"] -->|"fetch /api/chat/stream (SSE)"| FAPI

    subgraph FAPI["FastAPI (server/) β€” the UI/agent boundary"]
        BR["agent_bridge.py"]
    end

    FAPI -->|"local dev: in-process"| WF
    FAPI -->|"deployed: boto3 invoke_agent_runtime"| AC
    FAPI -->|"read-only SELECTs"| CRDB

    subgraph AC["Amazon Bedrock AgentCore Runtime (the only AWS service used)"]
        API["api.py β€” BedrockAgentCoreApp entrypoint"]
        WF["LangGraph workflow (workflow.py)"]
        API --> WF
    end

    subgraph GRAPH["StateGraph"]
        R["recall_case_memory"] --> O["orchestrator"]
        O -->|routes| S1["security_monitor"]
        O -->|routes| S2["broker_monitor"]
        O -->|routes| S3["risk_monitor"]
        O -->|routes| S4["intel_analyst"]
        O -->|routes| S5["memory_ops"]
        O -->|routes| S6["compliance_officer"]
        O -->|routes| S7["case_triage"]
        O -->|routes| S8["audit_reviewer"]
        S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8 --> SYN["synthesizer"]
        SYN --> P["persist_case_memory"]
    end

    WF --> GRAPH

    S1 & S2 & S3 & S4 & S6 -->|"strands Agent()"| ANTH["Anthropic API\n(claude-sonnet-4-6)"]
    O --> ANTH
    SYN --> ANTH
    S5 -->|"MCPClient"| MCP["CockroachDB Cloud\nManaged MCP Server"]
    S7 -->|"recall_similar_investigations tool"| CASE
    S8 -->|"get_session_audit_trail tool"| HIST

    subgraph CRDB["CockroachDB β€” the memory layer"]
        CKPT["checkpoints / checkpoint_blobs\n/ checkpoint_writes\n(AsyncCockroachDBSaver)"]
        HIST["message_store\n(CockroachDBChatMessageHistory)"]
        CASE["case_memory + C-SPANN\ndistributed vector index\n(AsyncCockroachDBVectorStore)"]
    end

    GRAPH -->|checkpoint every node| CKPT
    P -->|embed + write| CASE
    R -->|similarity search| CASE
    WF -->|record each turn| HIST
    MCP -->|read-only SQL, schema, cluster info| CRDB

The web UI: Chat + Dashboard

web/ is a two-view app behind a left nav rail β€” Chat for the streaming multi-agent conversation, Dashboard for an operational view of the same CockroachDB tables. Five features exist specifically to make memory visible rather than a number on a stat tile:

  • Vector Memory Map (web/src/components/MemoryMap.tsx, server/vector_map_routes.py, server/pca.py) β€” every case_memory embedding, pulled straight off the 384-dim VECTOR column and projected to 2D with a small server-side PCA (pure numpy, no ML framework), rendered as a point cloud. Type a pattern into the search box and it calls the same recall_similar_cases function recall_case_memory/case_triage use internally, plots your query on the identical cached basis so it lands next to its real nearest neighbors, and lists the ranked matches. This is the distributed vector index as something you can look at and query directly, not an implementation detail.
  • Checkpoint time-travel viewer (web/src/components/TimeTravel.tsx, server/checkpoint_routes.py) β€” the History button in the Chat header opens a scrubber over the current session's actual AsyncCockroachDBSaver checkpoint history: every node transition, in order, with the state snapshot at that exact step. LangGraph checkpointing already makes this possible; nothing else in the UI made it visible before.
  • Live agent pipeline (web/src/components/AgentTrace.tsx) β€” each assistant message now shows a compact node graph (colored, pulsing while active) of exactly which agents ran and how many tool calls each made, built live from the same SSE event stream, instead of a flat trace list.
  • Explainable triage citations β€” when case_triage's recall_similar_investigations tool returns matches, the UI parses that tool result into clickable citation chips (case + similarity score) directly under the pipeline, so "why this priority" is inspectable rather than buried in prose.
  • Fixed agentβ†’color identity (agentColor() in web/src/types.ts) β€” every chart and the pipeline graph color an agent from the same fixed-order slot, never by its rank in a sorted list (a real bug in an earlier version of the agent-activity bar chart: colors were assigned by count-sorted position, so an agent's color would silently change as rankings shifted between polls β€” exactly the failure mode the dataviz method's "color follows the entity, never its rank" rule exists to catch).

The two dashboard charts (agent-activity bars, cases-per-day) are hand-rolled (no charting library) against a categorical palette run through the dataviz skill's validator for this app's dark surface (#0a0c10) β€” fixed hue order, direct value labels (never color alone, since one adjacent pair sits in the CVD warn band), hover tooltips, hairline gridlines. Every dashboard endpoint (server/memory_routes.py, server/vector_map_routes.py, server/checkpoint_routes.py) is a read-only query against the same tables the agents write to β€” this is a window into real state, not a mock.

Why a FastAPI layer sits between the UI and the agents

The browser can't call AgentCore's invoke_agent_runtime directly β€” that API needs a SigV4-signed request with AWS credentials, and those must never ship to client-side JS. server/ (FastAPI) exists to:

  1. Hold AWS credentials server-side and proxy chat requests to AgentCore.
  2. Give the UI one stable streaming contract (POST /api/chat/stream, SSE) regardless of which backend mode is active β€” see server/agent_bridge.py.
  3. Also run the LangGraph workflow in-process when no AgentCore runtime is configured yet (AGENT_BACKEND_MODE=local, the default). Same API, same UI, zero AWS β€” this is what make dev uses.
  4. Expose read-only CockroachDB endpoints (/api/memory/*) so the UI's memory panel is a live view of the actual tables the agents write to, not a mock.

Compare with the reference architecture this project follows β€” "Market Surveillance Agent with LangGraph and Strands on AgentCore" β€” except the memory layer (previously AgentCore Memory) is now CockroachDB, and the model layer (previously Bedrock-hosted Claude) is now Anthropic's API called directly from Strands.

Specialist agents

Agent Focus Tools
security_monitor Single security, single day mock report catalog
broker_monitor Single security, multiple days mock report catalog
risk_monitor Broker activity, single day mock report catalog
intel_analyst External market research http_request
memory_ops The system's own memory β€” case counts, schema, cluster health CockroachDB Cloud MCP Server (read-only)
compliance_officer Checks findings against explicit regulatory thresholds, issues CLEAR/FLAGGED verdicts mock report catalog + get_regulatory_thresholds
case_triage Assigns investigation priority by explicitly querying case memory itself β€” a second, agent-driven way of using the vector index, distinct from the automatic recall step recall_similar_investigations β†’ AsyncCockroachDBVectorStore
audit_reviewer Summarizes the durable audit trail for a session, from CockroachDB β€” not from its own recollection get_session_audit_trail β†’ CockroachDBChatMessageHistory

compliance_officer, case_triage, and audit_reviewer are the newest additions, and each one exists to put a different CockroachDB memory component directly in an agent's hands as a callable tool β€” not just something the graph does automatically before/after the LLM ever runs.

CockroachDB tools used

The hackathon requires at least two of the four listed tools. This project uses three:

Tool Where What the agent actually does with it
Distributed Vector Indexing src/memory/case_memory.py Every investigation is embedded (local ONNX model, no external API) and written to a VECTOR column; a C-SPANN index (CSPANNIndex) keeps cosine-similarity recall fast as case history grows. recall_case_memory queries it on every incoming request before routing; persist_case_memory writes to it after every synthesis.
CockroachDB Cloud Managed MCP Server src/memory/mcp_memory_tools.py, src/agents/memory_ops_agent.py The memory_ops Strands agent is given an MCPClient bound to https://cockroachlabs.cloud/mcp (service-account bearer token). It calls read-only MCP tools (list_tables, get_table_schema, select_query, show_running_queries, …) to answer questions about the memory cluster itself β€” safe-by-default, fully audited, no custom SQL proxy.
ccloud CLI (agent-ready) scripts/provision_cluster.sh Provisions the CockroachDB Cloud cluster and SQL user end-to-end from the terminal with JSON output at every step (ccloud cluster create, ccloud cluster sql-user create, ccloud cluster sql --connection-params) β€” the same automation-friendly CLI an agent could drive itself.

Two more CockroachDB integrations round out the memory layer (not on the four-tool checklist, but core to "Agentic Memory Design"): LangGraph checkpointer (AsyncCockroachDBSaver) for workflow state, and chat message history (CockroachDBChatMessageHistory) for a durable, queryable audit log.

We also point contributors at the CockroachDB Agent Skills Repo (npx skills add cockroachlabs/cockroachdb-skills) for anyone doing schema/ops work on this project with Claude Code, Cursor, or another MCP-compatible client β€” see Contributing below.

AWS services used

Amazon Bedrock AgentCore hosts the agent runtime β€” and only the runtime. api.py wraps the LangGraph workflow in a BedrockAgentCoreApp entrypoint. Model inference never touches Bedrock: every Strands agent calls Anthropic's API directly, and there is deliberately no bedrock:InvokeModel permission in deployment/permissions-policy.json.

A small supporting cast of AWS services handles build automation and hosting around that runtime, none of which are in the inference path:

  • AWS CodeBuild builds the ARM64 container natively (no local Docker or QEMU) straight from this GitHub repo β€” see deployment/deploy-codebuild.sh, which also runs unattended from a stock AWS CloudShell session via deployment/cloudshell_deploy.sh for a one-command deploy with nothing installed locally.
  • Amazon ECR stores the built container images (one repo for the AgentCore runtime, one for the web UI).
  • IAM roles gate every step above, each scoped to only what that step needs β€” see deployment/permissions-policy.json.
  • Amazon ECS Express Mode optionally hosts the public FastAPI + React web UI as a Fargate service behind an internet-facing ALB with automatic HTTPS β€” see "7. Host the web UI publicly" above. (We originally scoped this as AWS App Runner, but App Runner is closed to new customers as of 2025, so ECS Express Mode is the direct replacement AWS itself documents.)

None of the above ever calls a model. Inference is 100% Anthropic API, end to end.

Deployment topology

Two independent deploys, both built from this same GitHub repo on AWS CodeBuild β€” no local Docker required for either one:

flowchart TB
    REPO["GitHub β€” this repo"]

    subgraph BUILD["AWS CodeBuild β€” native compute, no local Docker/QEMU"]
        CB1["buildspec.yml (ARM64)\nbuilds Dockerfile β†’ AgentCore image"]
        CB2["buildspec-web.yml (x86_64)\nbuilds Dockerfile.web β†’ web UI image"]
    end
    REPO -->|clone at build time| CB1
    REPO -->|clone at build time| CB2

    CB1 -->|docker push| ECR1[("Amazon ECR\nagentcore-repo")]
    CB2 -->|docker push| ECR2[("Amazon ECR\nweb-repo")]

    ECR1 -->|"deploy-runtime.py"| AC
    ECR2 -->|"deploy-ecs-web.sh"| ECS

    subgraph AC["Amazon Bedrock AgentCore Runtime"]
        WF["LangGraph workflow (api.py)\nno public URL β€” SigV4 invoke only"]
    end

    subgraph ECS["Amazon ECS Express Mode"]
        WEB["FastAPI + React UI (server/main.py)\nFargate + ALB, automatic HTTPS"]
    end

    JUDGE["Judge's browser"] -->|"https://β€Ήserviceβ€Ί.ecs.β€Ήregionβ€Ί.on.aws/"| WEB
    WEB -->|"boto3 invoke_agent_runtime (SigV4)"| AC
    WEB -->|"direct SQL β€” /api/health, /api/memory/stats"| CRDB
    AC -->|"checkpoints Β· chat history Β· case memory + C-SPANN"| CRDB[("CockroachDB Cloud")]

    CLOUDSHELL["AWS CloudShell\ncloudshell_deploy.sh"] -.->|"one-shot wrapper"| CB1

Full walkthrough of both paths, including the one-time GitHub source-credential setup and IAM roles each one creates: Setup Guide Β§8 and Β§9.

Project layout

memorymesh-agents/
β”œβ”€β”€ api.py                    # AgentCore entrypoint (BedrockAgentCoreApp)
β”œβ”€β”€ app.py                    # Streamlit UI (legacy β€” quick ops view)
β”œβ”€β”€ server/                    # FastAPI: the UI/agent boundary
β”‚   β”œβ”€β”€ main.py                    # app wiring, CORS, serves web/dist in prod
β”‚   β”œβ”€β”€ auth.py                     # shared-password judge access gate
β”‚   β”œβ”€β”€ agent_bridge.py            # local-workflow / AgentCore-proxy, one event stream
β”‚   β”œβ”€β”€ chat_routes.py             # POST /api/chat/stream (SSE)
β”‚   β”œβ”€β”€ memory_routes.py           # GET /api/memory/* β€” live reads against CockroachDB
β”‚   β”œβ”€β”€ vector_map_routes.py       # embedding-map + semantic search (the vector memory map)
β”‚   β”œβ”€β”€ pca.py                     # numpy PCA + cached basis for the memory map
β”‚   β”œβ”€β”€ checkpoint_routes.py       # time-travel over a session's LangGraph checkpoints
β”‚   └── config.py                  # backend-mode auto-detection
β”œβ”€β”€ web/                        # Modern UI: React + Vite + TypeScript + Tailwind
β”‚   └── src/
β”‚       β”œβ”€β”€ App.tsx                 # nav rail + Chat/Dashboard view switch + auth gate
β”‚       β”œβ”€β”€ lib/useChat.ts          # turns the raw multi-agent event stream into
β”‚       β”‚                           # a clean answer + a separate agent/tool trace
β”‚       β”œβ”€β”€ components/             # ChatPanel, MessageBubble, AgentTrace, MemorySidebar, Login, …
β”‚       β”‚   β”œβ”€β”€ Dashboard.tsx           # stat tiles, charts, memory map, cases table, health
β”‚       β”‚   β”œβ”€β”€ MemoryMap.tsx           # vector memory map + semantic search
β”‚       β”‚   β”œβ”€β”€ TimeTravel.tsx          # checkpoint scrubber (History button in Chat)
β”‚       β”‚   └── charts/                 # hand-rolled BarChart, TimeSeriesChart, Sparkline
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ memory/                # <-- the graded core
β”‚   β”‚   β”œβ”€β”€ db.py                  # shared CockroachDBEngine connection pool
β”‚   β”‚   β”œβ”€β”€ checkpointer.py        # AsyncCockroachDBSaver (LangGraph state)
β”‚   β”‚   β”œβ”€β”€ chat_history.py        # CockroachDBChatMessageHistory (audit log)
β”‚   β”‚   β”œβ”€β”€ case_memory.py         # AsyncCockroachDBVectorStore + C-SPANN index
β”‚   β”‚   β”œβ”€β”€ embeddings.py          # local ONNX embeddings (fastembed)
β”‚   β”‚   └── mcp_memory_tools.py    # CockroachDB Cloud MCP client for Strands
β”‚   β”œβ”€β”€ agents/                 # Strands agents + LangGraph workflow
β”‚   β”‚   β”œβ”€β”€ model_provider.py      # Anthropic API model factory
β”‚   β”‚   β”œβ”€β”€ memory_ops_agent.py    # the MCP-powered "introspect my own memory" agent
β”‚   β”‚   └── workflow.py            # StateGraph wiring it all together
β”‚   β”œβ”€β”€ prompts/, tools/, data_catalog/, utils/  # reused market-surveillance domain
β”‚   └── config/
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ provision_cluster.sh   # ccloud CLI cluster + user provisioning
β”‚   β”œβ”€β”€ init_memory_schema.py  # create/migrate all CockroachDB memory tables
β”‚   β”œβ”€β”€ seed_case_memory.py    # seed a few past cases for an instant demo
β”‚   └── chat_cli.py            # local chat loop, no AWS required
└── deployment/                 # AgentCore IAM/ECR/runtime scripts (local Docker or AWS CodeBuild)

Setup

For a longer walkthrough, see the dedicated Setup Guide (every .env variable explained, troubleshooting table) and User Guide (how to use the Chat and Dashboard views once it's running). The quick version:

1. Prerequisites

  • Python 3.11+ and Node.js 18+ (for the web UI)
  • An Anthropic API key
  • A CockroachDB cluster β€” CockroachDB Cloud (free tier is fine) or local cockroach demo
  • For deployment only: AWS CLI v2 with credentials, Docker Desktop (ARM64 build) β€” or skip local Docker entirely with make deploy-codebuild (see below)

2. Install

git clone https://github.com/akashtalole/MemoryMesh-Agents.git
cd MemoryMesh-Agents
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env

Edit .env: set ANTHROPIC_API_KEY and COCKROACHDB_URL at minimum.

3. Provision CockroachDB (optional β€” skip if you already have a cluster)

make provision-cluster   # ccloud CLI: creates a cluster + SQL user, prints a connection string

Paste the resulting connection string into COCKROACHDB_URL in .env.

4. Initialize the memory schema

make init-memory   # checkpoints, message_store, case_memory + C-SPANN index β€” all idempotent
make seed-memory   # optional: seed 3 past cases so recall has something to find immediately

5. Run the web UI locally (no AWS required)

make dev

Opens the FastAPI backend on :8000 and the Vite dev server on :5173 together (Ctrl-C stops both) β€” visit http://localhost:5173. With no AGENTCORE_RUNTIME_ARN set, the backend runs the LangGraph workflow in-process, so this is a full end-to-end demo with zero AWS involved. Ask the same question twice in different sessions and watch the memory sidebar's case count tick up, then ask a similar question and see the agent's routing context include the recalled prior case.

Prefer a terminal? python scripts/chat_cli.py runs the same workflow without any HTTP layer.

6. Deploy to AWS Bedrock AgentCore

make deploy

This creates the IAM role, ECR repo, builds/pushes the container, and provisions the AgentCore runtime. Set ANTHROPIC_API_KEY and COCKROACHDB_URL as environment variables on the AgentCore runtime (via the AgentCore console or update_agent_runtime β€” they are intentionally not baked into the image).

No local Docker? make deploy-codebuild does the same thing but builds the ARM64 image on AWS CodeBuild's native ARM64 compute instead of emulating it locally β€” see Setup Guide Β§8 for details. In a hurry and deploying from AWS CloudShell? make deploy-cloudshell does the whole thing β€” build, deploy, and wiring the runtime's env vars β€” in one command.

Deploying somewhere publicly reachable? Set JUDGE_ACCESS_PASSWORD first to gate the app behind a single shared password β€” see Setup Guide, Restricting access before deploying publicly.

deploy-runtime.py writes the resulting runtime ARN into config/dynamic-config.yaml, which server/config.py reads automatically β€” the same make dev / uvicorn server.main:app command now proxies chat requests to the deployed AgentCore runtime instead of running locally, no code changes needed. For a single-process production-style run:

make web-build   # builds web/dist
uvicorn server.main:app --host 0.0.0.0 --port 8000   # serves API + built UI together

The legacy Streamlit view (make start-client, :8501) still works as a quick ops look at the same AgentCore runtime.

7. Host the web UI publicly

The AgentCore runtime above has no browsable URL β€” it only accepts SigV4-signed requests. To give judges (or anyone) a public link:

make deploy-web

Builds Dockerfile.web (server/ + web/, separate from the AgentCore image) via CodeBuild and deploys it to Amazon ECS Express Mode β€” one command, a Fargate service + load-balanced HTTPS URL + autoscaling, no manual VPC/ALB setup. (We use ECS Express Mode rather than AWS App Runner because App Runner is closed to new customers as of this writing β€” Express Mode is AWS's own recommended replacement.) See Setup Guide Β§9 for the full walkthrough, prerequisites, and IAM roles it creates.

Sample queries

# Ordinary investigation (writes a new case into memory)
What was the trading activity for AAPL on March 15, 2024 and which brokers were most active?

# Ask a near-duplicate later β€” watch the orchestrator's context include recalled prior findings
Which brokers were most active trading AAPL in mid-March 2024?

# Memory introspection β€” routes to memory_ops, which queries CockroachDB via MCP
How many past investigations do we have stored, and have we looked at broker
risk on MSFT before?

# Multi-agent
Analyze MSFT's price movement and broker risk scores between March 10-15, 2024,
and check for any related market news.

# Compliance + triage β€” routes to compliance_officer and case_triage, the
# latter explicitly querying case memory (not just the automatic recall step)
Is ALPHA_CAPITAL's AAPL trading on March 15, 2024 a compliance issue, and how
urgent is it?

# Audit β€” routes to audit_reviewer, which reads message_store directly
Give me an audit summary of everything asked and answered in this session.

Judging-criteria notes

  • Agentic Memory Design β€” CockroachDB is the only memory system: no Redis, no separate vector DB, no AgentCore Memory. Checkpoints, audit history, and semantic case memory are three distinct CockroachDB-backed stores, each earning its keep.
  • Technical Implementation β€” parameterised queries throughout (the reused run_report tool never lets an LLM write raw SQL), MCP access is read-only and scoped by service-account key, and TTL is available on checkpoints via CHECKPOINT_TTL_DAYS.
  • Real-World Impact β€” market surveillance is a compliance-critical, audit-heavy domain; "has this pattern happened before, across every past investigation" and "give me an immutable record of every turn" are real requirements, not demo flourishes.
  • Production Readiness β€” least-privilege IAM (no Bedrock model permissions at all, since this project never calls Bedrock for inference), graceful degradation when COCKROACHDB_MCP_API_KEY is unset, and a connection pool (not a connection-per-request) backing the vector store.
  • Creativity & Originality β€” the memory_ops agent turning "introspect your own memory cluster" into an actual tool call, and case memory informing routing (not just chat continuity), are the two ideas this project leans on hardest.

Contributing: CockroachDB Skills

If you're working on this project's schema or CockroachDB operations with Claude Code, Cursor, or another MCP-compatible client, pull in the CockroachDB Agent Skills:

npx skills add cockroachlabs/cockroachdb-skills

License

MIT β€” see the LICENSE file.