CGraph gives a coding agent durable memory by letting it write checkpoints into the graph daemon — a short distilled summary plus links to the code the work touched — that survive a /clear, a daemon restart, and a full rescan. Because the daemon and its sidecar files live outside the agent's context window, a checkpoint written before you compact a session is still there afterward. Recall it and you get the thread back in a few kilobytes instead of re-reading every file.
The problem: context windows forget, codebases don't
A long agent session accumulates state — what you tried, what worked, which functions matter — and then /compact or /clear throws most of it away. The next session starts cold and re-derives the same understanding, burning tokens rediscovering what it already knew an hour ago. The codebase didn't change; only the agent's memory of it evaporated.
The fix is to put the durable part somewhere durable. CGraph already keeps a resolved code graph warm in a daemon; session memory rides on the same daemon as a small, explicit overlay. You checkpoint the distilled outcome, clear your context, and recall it — the daemon was never cleared.
Two tools: remember and recall
Session memory is two MCP tools over the daemon's remember and recall ops.
graph_remember {title, body, touches?, tags?} writes one checkpoint. The body is a distilled markdown summary; touches lists the code symbols the work concerns. Here's a real write against CGraph's own repo:
cgraph-client --root . remember '{
"title": "Session memory overlay is idempotent",
"body": "Confirmed sidecar re-overlay is first-occurrence-wins; repeated rebuilds converge to one node per checkpoint.",
"touches": ["remember_checkpoint"],
"tags": ["memory"]
}'
The op creates exactly one node in the memory:checkpoint: namespace, writes the body to a markdown file under cgraph-out/memory/, and — for each touches entry that resolves to a real node — adds a concerns edge to it. The response is precise about what resolved:
{ "id": "memory:checkpoint:1784408900418333000",
"label": "Session memory overlay is idempotent",
"source_file": ".../cgraph-out/memory/...-session-memory-overlay-is-idempotent.md",
"concerns": 1, "unresolved": [], "written": true }
A symbol that doesn't resolve is reported in unresolved and produces no edge — the op never invents a placeholder node to hang a dangling edge on. graph_recall {query?, limit?} returns checkpoints newest-first, each with its body snippet and a brief of every code node it concerns; pass a query and it filters by lexical match over title, tags, and body.
It's genuinely durable, not just alive until /clear
The easy version of this would keep memory in RAM and lose it when the daemon dies. CGraph writes each checkpoint to disk as two files under cgraph-out/memory/ — the markdown body and a sidecar fragment — and the sidecar is the source of truth. After every rebuild (startup fast-load, an incremental edit, or a full rescan) the daemon re-overlays the sidecars onto the live graph. So a checkpoint survives a restart, and re-applying one is idempotent: first-occurrence-wins merge means repeated overlays converge to exactly one node and one edge.
The subtle, important part: the persisted graph.json does not contain memory nodes at all. I verified this on the live daemon — after writing two checkpoints, the sidecars were on disk and in the live snapshot, but graph.json held zero memory: nodes:
Keeping memory out of graph.json means there's one source of truth (the sidecars), and it keeps the code snapshot pristine for everything else that reads it.
Memory never pollutes retrieval
The reason you can checkpoint freely is that memory nodes are inert to code analysis. Per the session-memory spec, memory: nodes are excluded from degree-centrality, god-node ranking, and community detection, and are never gathered by pack_context or returned by query. Adding, linking, or removing a checkpoint leaves every code node's centrality and every retrieval ranking identical.
That inertness is what separates this from stuffing notes into the graph as ordinary nodes. Your checkpoints are recall-able and code-linked, but they can never distort what impact analysis or context packing return for the actual code.
The discipline: distill, don't dump
Memory is only useful if it's clean. The rule is to checkpoint a distilled outcome — what you did and what's next — and never persist raw tool output, DOM snapshots, or chain-of-thought. Those are ephemeral; only the conclusion belongs in a checkpoint. The daemon enforces its own guards too: the body is sandboxed to cgraph-out/memory/ (a title with .. or a path separator can't escape it), and an oversized body is rejected with no file written and no node created.
For visibility, the status op carries a memory block — checkpoint_count, sidecar_count, recall_count, recall_zero_hits, and last_overlay_count — and remember/recall are recorded in the durable op-stats ledger, so you can see how often recall actually pays off.
Try it
Start a daemon on a repo, checkpoint before you clear, and recall after:
cgraph-client --root . remember '{"title":"...","body":"...","touches":["SymbolName"]}'
cgraph-client --root . recall '{"limit":5}'
Recall restores the thread; follow up with graph_context on a linked symbol to reload just enough code, budget-bounded, instead of re-reading files. The remember/recall ops, the sidecar overlay, and the inertness guarantees are all open source — read the code on GitHub — and the Memory doc covers where session memory sits relative to the warm graph.