Local code graph and solution memory for coding agents via CLI and MCP.
Runs on your machine from its package; the steps are below.
At most: creates or changes data: records, files, settings.
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).
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"
]
}
}
}Code graph + memory · surgical reads · 100% local · MCP + CLI
Kernel written in Rust
Usage details: main agent vs subagent · MCP configs for each IDE · 中文说明
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:
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.
Need Rust first? Install it from rustup.rs, then:
cargo install --git https://github.com/HanochZhu/codeagent_memory --locked
cam --help
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.
| Environment | How to use it |
|---|---|
| Cursor | Paste into Agent / Chat; it runs the install in the terminal |
| VS Code + Copilot / Continue / Cline | Same: paste into Chat / Agent |
| Claude Code / Windsurf / Codex | Paste 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 terminal | Skip 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.
git clone https://github.com/HanochZhu/codeagent_memory.git
cd codeagent_memory
cargo install --path . --locked
No Rust toolchain required. Pull the published image and run the stdio server with the current directory mounted at /work:
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
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.
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.
{ "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).
cam has two retrieval planes. Each is scored against the closest open system on the same corpus. Do not fold them into one number.
| Plane | Closest analogue | Shared bench |
|---|---|---|
| Memory | agentmemory hybrid search + tokenized grep | coding-agent-life-v1 |
| Long-term memory | BM25 / dense memory retrievers | LongMemEval-S |
| Code graph | codegraph | LongMemCode clap |
| Workflow retrieval | lexical / BM25 file retrieval | Agent Retrieval Bench V2 edit2ripple |
| Token cost | full context dump | DeepSeek 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.
| cam | agentmemory | codegraph | |
|---|---|---|---|
| What it stores | code graph + memory tree | session / chat memories | code graph |
| Query | recall / ls / read / ref | smart-search / remember | explore / callers / callees |
| Interface | MCP + CLI | MCP + REST + hooks | MCP + CLI |
| Fusion | vector + BM25 + RRF | BM25 + embed + rerank | FTS5 + graph walk |
| Local / cost | 100% local, retrieval $0 | local server | local |
15 sessions, 15 queries, k=5. Same formula as agentmemory score.ts. P@5 ceiling is 0.240.
| system | Hit rate | R@5 | P@5 | source |
|---|---|---|---|---|
| grep (tokenized substring) | 15 / 15 | 0.967 | 0.227 | this tree, 2026-09-23 |
| cam min-max sum (legacy) | 15 / 15 | 0.933 | 0.213 | historical result, hash embed |
| cam RRF | 15 / 15 | 1.000 | 0.240 | this tree, hash embed |
| agentmemory hybrid | 15 / 15 | 1.000 | 0.240 | published v0.9.26 |
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).
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.
| k | R@k | P@k | newest R@k | stale latest |
|---|---|---|---|---|
| 5 | 1.000 | 0.240 | 1.000 | 0 |
| 10 | 1.000 | 0.120 | 1.000 | 0 |
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.
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@1 | R@5 | R@10 | Hit@1 | Hit@5 | Hit@10 | MRR |
|---|---|---|---|---|---|---|
| 0.260 | 0.512 | 0.652 | 0.420 | 0.670 | 0.810 | 0.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.
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.
| system | Recall@5 | Recall@10 | Recall@20 | MRR | BCY@8k |
|---|---|---|---|---|---|
| cam graph | 0.292 | 0.320 | 0.364 | 0.291 | 0.292 |
| lexical | 0.412 | 0.536 | 0.588 | 0.243 | 0.447 |
| BM25 | 0.207 | 0.287 | 0.499 | 0.154 | 0.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.
clap v4.6.1, 536 scenarios, no LLM. cam one-hop vs codegraph on the same SCIP gold.
| slice | n | cam | codegraph |
|---|---|---|---|
| supported (one-hop) | 478 | 0.769 | 0.769 |
| lookup / file_symbols | 293 / 55 | 0.808 / 0.915 | 0.808 / 0.915 |
| callers / callees | 67 / 18 | 0.397 / 0.811 | 0.379 / 0.811 |
| implementors | 40 | 0.119 | 0.094 |
| raw / weighted | 536 | 0.709 / 0.704 | — / 0.702 |
P95 ≈ 6.5 ms, $/1k = 0.
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.
| track | n | full acc. | cam acc. | full tokens | cam tokens | saving |
|---|---|---|---|---|---|---|
| memory (coding-agent-life) | 15 | 1.00 | 1.00 | 23673 | 13255 | 44% |
code (this repo after cam index) | 6 | 1.00 | 0.50 | 169838 | 6432 | 96% |
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.
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
| Code graph in SQLite | tree-sitter parse into .cam/cam.db — list, read, and hop callers/callees without opening whole files |
| Live sync | cam watch incrementally updates the graph when source files change |
| Virtual paths | src/main.rs is a file; src/main.rs/main is a symbol in that file |
| Hybrid recall | Vector + BM25 fused with RRF (k=60) |
| Revision expansion | Recall pulls in the newest replacement of whatever matched and hides older revisions by default; --no-expand disables expansion and --include-superseded keeps history |
| Ebbinghaus retention | R = 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 + CLI | Main agent: cam_* tools. Subagent: cam --json …. Same binary |
| 100% local | No API keys. SQLite + an optional on-disk embedding model under ~/.cam/models/ |
| 9 languages | Rust, Python, TypeScript, JavaScript, Go, Java, C, C++, C# |
ls / read / ref walk a virtual filesystem. read returns an outline or a symbol slice; --full is the whole file.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.add stores an English summary + body. --parent adds hierarchy; --supersedes replaces an older revision while preserving it for history.Design notes (Chinese): DESIGN.md.
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):
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).
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
| Command | What it does |
|---|---|
cam index | Parse with tree-sitter into SQLite (.cam/cam.db); creates .cam/ if needed |
cam sync | Incrementally update the graph from content hashes |
cam watch | Debounced 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|out | One-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 status | Resolved 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 mcp | Stdio 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.
stale_days in ~/.cam/config.toml (default 30) are marked staleR = exp(-t / S): the sort score is relevance × (0.9 + 0.1 × R); R < 0.3 is marked needs_updateneeds_update = false) refreshes C0 and multiplies S by 1.7. Expanded revisions and filtered older revisions are not refreshedsupersedes chain is latest; older revisions are hidden by default and available with --include-superseded--parent for branching hierarchy and --supersedes for a complete replacement revision~/.cam/config.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/.
cam is a Rust binary. cargo install builds it for the machine you are on:
| Platform | Install |
|---|---|
| Windows | cargo install --git … --locked |
| macOS | cargo install --git … --locked |
| Linux | cargo install --git … --locked |
See Get Started for the full command.
Main agent: register cam mcp (stdio). Subagent: same CLI, no extra wiring.
| Host | MCP config | Notes |
|---|---|---|
| Cursor | .cursor/mcp.json or ~/.cursor/mcp.json, or the codeagent-memory plugin | Task / explore subagents use CLI |
| Claude Code | .mcp.json / ~/.claude.json / claude mcp add | Limited-tool subagents use CLI |
| Codex | ~/.codex/config.toml → [mcp_servers.cam] | codex exec uses CLI |
| Windsurf | ~/.codeium/windsurf/mcp_config.json | Cascade = MCP |
| GitHub Copilot | .vscode/mcp.json / ~/.copilot/mcp-config.json | Chat = MCP; CLI scripts = CLI |
| Continue | ~/.continue/config.yaml | |
| Cline | ~/.cline/mcp.json | |
| JetBrains | AI Assistant → MCP | Use an absolute cam.exe if PATH is empty |
| Gemini CLI / Antigravity / OpenCode / Zed / Droid | see 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.
| Language | Extension | What is indexed |
|---|---|---|
| Rust | .rs | functions, structs, enums, traits, calls |
| Python | .py | functions, classes, calls |
| TypeScript | .ts, .tsx | functions, methods, classes, calls |
| JavaScript | .js, .jsx | functions, methods, classes, calls |
| Go | .go | functions, methods, structs, calls |
| Java | .java | functions, methods, classes, interfaces, calls |
| C | .c | functions, structs, calls |
| C++ | .cc, .cpp, .cxx, .h, .hpp | functions, methods, classes, structs, calls |
| C# | .cs | functions, methods, classes, interfaces, calls |
MIT
Made for AI coding agents — Claude Code, Cursor, Codex, Windsurf, Copilot, and JetBrains