MCP server: One MCP server.

codeagent-memory

@hanochzhuMCP server · External

Local code graph and solution memory for coding agents via CLI and MCP.

Code searchv0.1.3MITRust
Freeno charge per call

Runs on your machine from its package; the steps are below.

What it does

  1. Reads
  2. Writes
  3. High impact

At most: creates or changes data: records, files, settings.

What it costs

Free

Odel charges nothing per call. The publisher may charge for its own service.

Runs locally over stdio

This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).

Dockerghcr.io/hanochzhu/codeagent-memory:0.1.3

Run

docker run -i --rm ghcr.io/hanochzhu/codeagent-memory:0.1.3

MCP client config

{
  "mcpServers": {
    "codeagent-memory": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "ghcr.io/hanochzhu/codeagent-memory:0.1.3"
      ]
    }
  }
}
README13 min read
cam logo

cam

English · 中文

A local multi-turn conversational memory system purpose-built for coding agents

Code graph + memory · surgical reads · 100% local · MCP + CLI

Kernel written in Rust

License: MIT Rust Install

Windows macOS Linux

Claude Code Cursor Codex Windsurf GitHub Copilot Continue Cline JetBrains

Contents

Usage details: main agent vs subagent · MCP configs for each IDE · 中文说明

Why cam?

When an AI agent needs to understand code — or reuse what it already learned about this project — it discovers structure the slow way: grep, glob, and Read, one file at a time. Next session it does the same work again.

cam hands the agent two things it can query in one shell call:

  1. A code graph — every indexed file and symbol as a virtual filesystem, plus one-hop callers / callees.
  2. A memory tree — habits, project facts, designs, write-ups; recalled with vector + BM25 so the agent does not re-learn the same project.

Surgical reads, not a file-by-file search. Memories stay on disk, 100% local.

One binary, two doors: the main agent uses MCP (cam mcp); subagents usually have no MCP, so they run the same CLI. Configs: docs/mcp.md. Usage: docs/agents.md.


Get Started

1. Install the CLI

Need Rust first? Install it from rustup.rs, then:

bash
cargo install --git https://github.com/HanochZhu/codeagent_memory --locked
cam --help
Paste this into an AI instead (Cursor, Claude Code, Copilot, …)
text
Install cam from https://github.com/HanochZhu/codeagent_memory: require a working Rust toolchain (rustup), run `cargo install --git https://github.com/HanochZhu/codeagent_memory --locked`, ensure ~/.cargo/bin (or %USERPROFILE%\.cargo\bin on Windows) is on PATH, then verify with `cam --help`. In this repo run `cam index`. For the main agent, register MCP stdio: command `cam`, args `["mcp"]` (Cursor: .cursor/mcp.json; Claude Code: .mcp.json). Subagents call `cam --json` in the shell. Do not add extra docs.
EnvironmentHow to use it
CursorPaste into Agent / Chat; it runs the install in the terminal
VS Code + Copilot / Continue / ClineSame: paste into Chat / Agent
Claude Code / Windsurf / CodexPaste in the conversation and let it run the shell
JetBrains (IntelliJ / RustRover / GoLand)AI Assistant, or run the same cargo install in the built-in terminal
Local terminalSkip the AI; run the command above

The installer puts cam in ~/.cargo/bin (Windows: %USERPROFILE%\.cargo\bin) but doesn't change your current shell — open a new terminal if cam is not found.

Build from a local clone
bash
git clone https://github.com/HanochZhu/codeagent_memory.git
cd codeagent_memory
cargo install --path . --locked
Docker

No Rust toolchain required. Pull the published image and run the stdio server with the current directory mounted at /work:

bash
docker pull ghcr.io/hanochzhu/codeagent-memory:0.1.3
docker run -i --rm -v "$PWD:/work" -w /work ghcr.io/hanochzhu/codeagent-memory:0.1.3

Or build it from this repository: docker build -t cam https://github.com/HanochZhu/codeagent_memory.git

2. Index the project

bash
cd your-project
cam index

cam index creates .cam/ if needed and parses the tree with tree-sitter into .cam/cam.db. Other commands do the same: they auto-init on first use. Leave cam watch running if you want the graph to follow later edits.

3. Ask the graph, then remember the answer

bash
cam ls src/
cam read src/main.rs/main
cam ref main --dir in
cam recall "how does hybrid recall fuse BM25 and vectors"
cam add --summary "..." < notes.md

Main agent (the chat session): wire cam mcp once, then call cam_* tools. Subagent (Task / explore / delegated CLI): run the same commands in the shell. Output is compact by default; pass --json when you need structured results.

json
{ "mcpServers": { "cam": { "command": "cam", "args": ["mcp"] } } }

Paste that into Cursor .cursor/mcp.json, Claude Code .mcp.json, or the host's MCP settings. Per-tool files: docs/mcp.md. Who should call MCP vs CLI: docs/agents.md.

The first recall / add downloads potion-multilingual-128M. If that default model is unavailable, cam falls back to a hash embedder. Set another model2vec model with cam config set embedding_model <repo-or-path> (see Configuration).


Benchmarks

cam has two retrieval planes. Each is scored against the closest open system on the same corpus. Do not fold them into one number.

PlaneClosest analogueShared bench
Memoryagentmemory hybrid search + tokenized grepcoding-agent-life-v1
Long-term memoryBM25 / dense memory retrieversLongMemEval-S
Code graphcodegraphLongMemCode clap
Workflow retrievallexical / BM25 file retrievalAgent Retrieval Bench V2 edit2ripple
Token costfull context dumpDeepSeek multi-turn

Mem0, Zep, and Letta are general chat memories. They are not on these two corpora, so they are not in the score tables.

How the systems differ

camagentmemorycodegraph
What it storescode graph + memory treesession / chat memoriescode graph
Queryrecall / ls / read / refsmart-search / rememberexplore / callers / callees
InterfaceMCP + CLIMCP + REST + hooksMCP + CLI
Fusionvector + BM25 + RRFBM25 + embed + rerankFTS5 + graph walk
Local / cost100% local, retrieval $0local serverlocal

Memory — coding-agent-life-v1

15 sessions, 15 queries, k=5. Same formula as agentmemory score.ts. P@5 ceiling is 0.240.

systemHit rateR@5P@5source
grep (tokenized substring)15 / 150.9670.227this tree, 2026-09-23
cam min-max sum (legacy)15 / 150.9330.213historical result, hash embed
cam RRF15 / 151.0000.240this tree, hash embed
agentmemory hybrid15 / 151.0000.240published v0.9.26

coding-agent-life headline R@5 and P@5 / ceiling

coding-agent-life R@5 by question type

RRF matches the published hybrid ceiling and beats grep on temporal (q-015). Min-max sum still drops the second gold on temporal and multi-session-causal (q-011).

Revision chains

The corpus above is flat: one memory per session, no --supersedes. --lineage re-ingests every conversation turn as a revision that supersedes the previous one and adds two metrics. newest R@k asks whether the top-k carried the newest node of each gold chain; stale latest counts returned hits that claimed latest while a newer revision existed.

kR@kP@knewest R@kstale latest
51.0000.2401.0000
101.0000.1201.0000

Hash embedder, 2026-09-29. Separating structural hierarchy from revision replacement and collapsing older revisions before top-k raised newest R@5 from 0.567 to 1.000 and newest R@10 from 0.800 to 1.000, while stale latest stayed at zero.

Long-term memory — LongMemEval-S

Cleaned LongMemEval-S, 100 non-abstention questions sampled with seed 42. Each raw dated session is stored as one memory; retrieval uses hash embeddings + RRF at k=10, without an LLM reader.

R@1R@5R@10Hit@1Hit@5Hit@10MRR
0.2600.5120.6520.4200.6700.8100.527

MRR = Mean Reciprocal Rank. Under the 8-worker run, retrieval P50 / P95 was 1.28 / 2.18 s and mean per-question ingestion was 69.6 s. This is a retrieval-only result: it measures whether gold sessions are returned, not final answer quality. The run also exposed repeated tokenizer initialization during cam add as the dominant ingestion cost.

Workflow retrieval — Agent Retrieval Bench V2

edit2ripple: 58 anchored-change queries over 44 frozen repository snapshots, no LLM. The cam adapter ranks files connected to symbols in the anchored file. All 58 questions are scored; 55 anchor files use supported languages and three Java anchors remain misses.

systemRecall@5Recall@10Recall@20MRRBCY@8k
cam graph0.2920.3200.3640.2910.292
lexical0.4120.5360.5880.2430.447
BM250.2070.2870.4990.1540.243

BCY = Budgeted Context Yield, using the benchmark's canonical 8K-token file-packing protocol. cam graph has the best first-hit rank, while lexical retrieval has much better broad gold coverage; this points to rank fusion rather than graph-only retrieval. cam query P95 was 11.6 ms; indexing the 44 snapshots took 327.6 s with 8 workers.

Code graph — LongMemCode clap

clap v4.6.1, 536 scenarios, no LLM. cam one-hop vs codegraph on the same SCIP gold.

slicencamcodegraph
supported (one-hop)4780.7690.769
lookup / file_symbols293 / 550.808 / 0.9150.808 / 0.915
callers / callees67 / 180.397 / 0.8110.379 / 0.811
implementors400.1190.094
raw / weighted5360.709 / 0.704— / 0.702

P95 ≈ 6.5 ms, $/1k = 0.

LongMemCode clap accuracy bars

LongMemCode clap accuracy by operation

Multi-turn tokens — DeepSeek

Same conversation twice: dump every session / every src/*.rs file, or inject cam recall (plus read / ref on code) for the current turn only. Hash embedder, 2026-09-15.

tracknfull acc.cam acc.full tokenscam tokenssaving
memory (coding-agent-life)151.001.00236731325544%
code (this repo after cam index)61.000.50169838643296%

DeepSeek multi-turn prompt token bars

DeepSeek multi-turn prompt tokens

Mean prompt tokens / turn: memory 1521 → 838; code 28250 → 1004. Code misses were retrieval gaps (callers of the fusion helper, INITIAL_STABILITY_DAYS, the cam add update rule), not the model ignoring snippets.

Corpus locations for every script below live in one file, eval/datasets.toml. coding-agent-life-v1 is not redistributed here and defaults to an agentmemory checkout next to this repository; point the entry elsewhere, or override a single run with CAM_EVAL_CODING_LIFE.

bash
python3 eval/coding_life/run.py --adapter grep
python3 eval/coding_life/run.py --hash-embed              # RRF
python3 eval/longmemeval/run.py --sample 100 --seed 42 --workers 8
git clone --depth 1 https://github.com/eyuansu62/agent-retrieval-bench \
  eval/agent_retrieval/vendor/agent-retrieval-bench
python3 -m pip install -e eval/agent_retrieval/vendor/agent-retrieval-bench
arb download-benchmark --version v2_edit2ripple \
  --local-dir eval/agent_retrieval/data --force
python3 eval/agent_retrieval/run.py --workers 8
python3 eval/llm_multiturn/run.py --track both   # needs DEEPSEEK_API_KEY
python3 eval/longmemcode/run.py --corpus clap
python3 eval/charts/generate.py

Key Features

Code graph in SQLitetree-sitter parse into .cam/cam.db — list, read, and hop callers/callees without opening whole files
Live synccam watch incrementally updates the graph when source files change
Virtual pathssrc/main.rs is a file; src/main.rs/main is a symbol in that file
Hybrid recallVector + BM25 fused with RRF (k=60)
Revision expansionRecall pulls in the newest replacement of whatever matched and hides older revisions by default; --no-expand disables expansion and --include-superseded keeps history
Ebbinghaus retentionR = exp(-t / S) moves the recall score by at most 10%; stale and forgotten entries are flagged, never deleted
Memory tree--parent creates branching structural hierarchy; --supersedes creates a linear replacement revision
MCP + CLIMain agent: cam_* tools. Subagent: cam --json …. Same binary
100% localNo API keys. SQLite + an optional on-disk embedding model under ~/.cam/models/
9 languagesRust, Python, TypeScript, JavaScript, Go, Java, C, C++, C#

How It Works

How cam works

  1. Extraction — tree-sitter walks the project and stores nodes (functions, types) and edges (calls) in SQLite.
  2. Surgical read — ls / read / ref walk a virtual filesystem. read returns an outline or a symbol slice; --full is the whole file.
  3. Recall — recall requires an English query, embeds it, runs English-normalized BM25, drops entries far below each path's best, and fuses the two ranked lists with RRF (k=60). The result is scaled by 0.9 + 0.1 × R. The newest replacement of each top match is pulled in and older revisions are folded before top-k.
  4. Write-back — after the agent learns something worth keeping, add stores an English summary + body. --parent adds hierarchy; --supersedes replaces an older revision while preserving it for history.

Design notes (Chinese): DESIGN.md.


Agent Workflow

recall first. If nothing hits, walk the code graph. After you learn something worth keeping, add.

Main agent (MCP): cam_recall → cam_find / cam_ls / cam_read / cam_ref → cam_add. Do not shell out unless MCP is down. Full text: docs/agents.md.

Subagent (CLI):

text
cam --json index
cam --json sync
cam --json watch
cam --json ls src/
cam --json read src/memory/recall/fuse.rs/fuse_rrf
cam --json ref fuse_rrf --dir in
cam --json recall "how to fuse BM25 and vector recall"
cam --json add --summary "..." --body "..."
cam --json mem tree
cam --json mem show <id>
cam --json status
cam mcp

Global flags: --project <dir>, --json (compact JSON), --pretty (pretty JSON, implies --json). Without --project, resolution is CAM_PROJECT, then walk up for .cam / .git. With --json, failures print {"error":{"code":…,"message":…}} to stdout and exit non-zero. Start the MCP server with cam mcp (optional --project).


CLI Reference

bash
cam index                                # Parse the project with tree-sitter into SQLite
cam sync                                 # Incrementally update the graph for changed files
cam watch [--debounce-ms N]              # Watch source files; --json streams one report per sync
cam ls [virt_path]                       # List directories / files / symbols
cam read <virt_path> [--full]            # File outline or symbol body
cam ref <symbol> --dir in|out [--file SUBSTR] [--kind KIND] [--scope DIR]  # One-hop callers (in) or callees (out); --callers / --callees aliases
cam find <query> [--kind KIND] [--file SUBSTR] [--scope DIR] [--limit N]   # Symbols whose name contains <query>
cam recall "<query>" [--limit N] [--no-expand] [--include-superseded] [--snippets]  # Hybrid recall: vector + BM25 fused with RRF
cam add --summary "..." [--parent ID] [--supersedes ID] [--body TEXT | --file PATH]  # Store a memory (body: --body, --file, or stdin)
cam mem tree                             # Print the memory tree
cam mem show <id>                        # Show one memory
cam status                               # Resolved project, database counts, and config
cam config get | config set stale_days N | config set embedding_model <model> # Read or update ~/.cam/config.toml
cam mcp                                  # MCP stdio server for the main agent
CommandWhat it does
cam indexParse with tree-sitter into SQLite (.cam/cam.db); creates .cam/ if needed
cam syncIncrementally update the graph from content hashes
cam watchDebounced file watcher that runs sync on source changes
cam ls [path]List directories / files / symbols
cam read <path>File outline or symbol body; --full for the whole file
cam ref <symbol> --dir in|outOne-hop callers / callees. A name shared by several definitions returns status: ambiguous with ranked candidates; re-run with a candidate id, or narrow with --file / --kind / --scope
cam find <query>Case-insensitive substring search over symbol names (not body text). Exact names first, then prefixes; each hit has a virtual path and id for read / ref
cam recall "<one English sentence>"Vector + BM25 fused with RRF (k=60); --no-expand skips revision expansion; --include-superseded keeps history; --snippets clips long bodies below the top hit (body_truncated)
cam add --summary "..." [--parent ID] [--supersedes ID]Store an English memory; parent is structural and supersedes replaces an older revision
cam mem tree / cam mem show <id>Browse the memory tree
cam statusResolved project root, db path, node/edge/memory counts, config
cam config get / cam config set stale_days N / cam config set embedding_model <model>Read or update ~/.cam/config.toml
cam mcpStdio MCP server (main agent). See docs/mcp.md

Virtual paths: src/main.rs is a file; src/main.rs/main is a symbol in that file. Node ids (src/main.rs:function:main:12) from an ambiguous answer are accepted wherever a symbol is.

Multi-repo folders. .gitignore and .camignore (same syntax) are honored at any depth, even when the root itself is not a git repo. Submodules and linked worktrees (directories whose .git is a file) are skipped; nested standalone clones are indexed, and cam ls lists them with kind project so you can pass that path as --scope.


Memory Rules

  • Entries older than stale_days in ~/.cam/config.toml (default 30) are marked stale
  • Ebbinghaus retention R = exp(-t / S): the sort score is relevance × (0.9 + 0.1 × R); R < 0.3 is marked needs_update
  • A successful returned match (needs_update = false) refreshes C0 and multiplies S by 1.7. Expanded revisions and filtered older revisions are not refreshed
  • Memories are never deleted. The newest node of each linear supersedes chain is latest; older revisions are hidden by default and available with --include-superseded
  • Use --parent for branching hierarchy and --supersedes for a complete replacement revision
  • Recall queries and stored memory summaries/bodies must be in English. Inputs containing non-Latin scripts are rejected
  • BM25 splits snake_case, kebab-case, and camelCase identifiers and indexes both surface and English-stemmed terms

Configuration

~/.cam/config.toml:

toml
stale_days = 30
# embedding_model = "minishlab/potion-base-8M"  # Hugging Face model2vec repo
# embedding_model = "/path/to/model2vec"         # local tokenizer.json + config.json + model.safetensors
# embedding_model = "hash"                       # no download; lexical hash only

embedding_model defaults to minishlab/potion-multilingual-128M. default clears a custom value. CAM_EMBED_MODEL overrides the file for one process. The first recall after a model change re-embeds stored memories with the new model before scoring, so vector recall keeps working. Downloaded models are cached under ~/.cam/models/.


Supported Platforms

cam is a Rust binary. cargo install builds it for the machine you are on:

PlatformInstall
Windowscargo install --git … --locked
macOScargo install --git … --locked
Linuxcargo install --git … --locked

See Get Started for the full command.


Supported Agents

Main agent: register cam mcp (stdio). Subagent: same CLI, no extra wiring.

HostMCP configNotes
Cursor.cursor/mcp.json or ~/.cursor/mcp.json, or the codeagent-memory pluginTask / explore subagents use CLI
Claude Code.mcp.json / ~/.claude.json / claude mcp addLimited-tool subagents use CLI
Codex~/.codex/config.toml → [mcp_servers.cam]codex exec uses CLI
Windsurf~/.codeium/windsurf/mcp_config.jsonCascade = MCP
GitHub Copilot.vscode/mcp.json / ~/.copilot/mcp-config.jsonChat = MCP; CLI scripts = CLI
Continue~/.continue/config.yaml
Cline~/.cline/mcp.json
JetBrainsAI Assistant → MCPUse an absolute cam.exe if PATH is empty
Gemini CLI / Antigravity / OpenCode / Zed / Droidsee docs/mcp.md

Paste the one-sentence install into the agent, then add the MCP block. Copy-paste files: docs/mcp.md. Agent prompts: docs/agents.md.

The Cursor plugin (.cursor-plugin/plugin.json) registers the same cam mcp server and a skill. Install the cam binary first and keep it on PATH; the plugin does not ship the binary.


Supported Languages

LanguageExtensionWhat is indexed
Rust.rsfunctions, structs, enums, traits, calls
Python.pyfunctions, classes, calls
TypeScript.ts, .tsxfunctions, methods, classes, calls
JavaScript.js, .jsxfunctions, methods, classes, calls
Go.gofunctions, methods, structs, calls
Java.javafunctions, methods, classes, interfaces, calls
C.cfunctions, structs, calls
C++.cc, .cpp, .cxx, .h, .hppfunctions, methods, classes, structs, calls
C#.csfunctions, methods, classes, interfaces, calls

License

MIT


Made for AI coding agents — Claude Code, Cursor, Codex, Windsurf, Copilot, and JetBrains

Report Bug · Request Feature

Similar modules

Browse Code search