cc-memory
English · 简体中文
cc-memory
Persistent memory for Claude Code. Your project's decisions, results, bugs and plans survive compaction, session boundaries, and closed terminals — the next session is forced to read them before it does anything, and what is stored is reconciled, never stacked.
Table of contents
Related MCP server: lean-memory
What this is
A Claude Code plugin — six hooks , a CLI, an MCP server, a desktop dashboard and a web viewer over one project-local SQLite database — that captures structured memories at every conversation boundary, reconciles each new fact against what is already stored instead of appending, hands the next session a forced-read handoff document, keeps a live plan anchor with an enforced user-intent ledger, and consolidates the store in the background when the write backlog says it is due. Pure Python standard library, zero runtime dependencies, everything on your machine.
The problem it attacks
Claude Code compacts a conversation when the context window fills. Whatever was in the discarded turns is gone: the decision you made three hours ago, the benchmark number you measured, the bug you already fixed once, the constraint you stated and had to state again. Sessions that simply end — terminal closed, laptop shut — lose the same thing, silently.
The usual workarounds do not survive contact with a long project:
Workaround | Why it fails |
A hand-written | Nobody updates it under deadline; it goes stale and starts lying |
Pasting context back in each session | Manual, lossy, and costs the tokens you were trying to save |
Bigger context windows | Delays the compaction; does not remove it |
Append-only memory files | The same fact gets restated every session and stacks up; the file becomes noise |
A memory store nobody is forced to read | Injection that the next session may ignore is a suggestion, and suggestions lose to deadlines |
The last two rows are the ones most memory tools stop at, and they are where
this project spends most of its engineering: writes reconcile (merge /
supersede / insert, chosen by similarity — never append-and-hope), and reads
are forced (a <system-reminder> at session start that requires the next
Claude to read the handoff before responding — and a Stop hook that can refuse
to end a turn while plan state is stale).
Before and after
Same tiny project, same model (claude-fable-5, Claude Code 2.1.243), same
prompt, same day (2026-08-27, on cc-memory v2.12.2). One difference: the
plugin. Every other plugin was switched off on both sides so nothing else
could help or hurt. The sessions were real claude -p runs driven by
demo/run_demo.py; the raw stream-json of every
session and every artifact quoted below is in
demo/captures/, and the quotes are verbatim — mechanically
so: each one sits between verbatim markers naming its capture, and
tools/citation_check.py fails the release gates if any quoted segment is not
in that file. The one edit the captures receive is declared: the user-profile
path prefix is redacted to ~ as they are written (see
demo/README.md).
1. "What were we doing last time, and what's next?"
Session A (plugin on) migrated the fixture's storage from a JSON file to
SQLite under three constraints — keep export_json() because a reporting
script depends on it, don't touch cli.py, fix the bug where add() accepted
a negative amount — and ran the tests. Ten turns. Then a fresh session
asked the question above, twice, at the same path.
Without cc-memory | With cc-memory | |
What it had | File mtimes, no git, an empty checkpoint store, and Claude Code's own per-project memory directory — checked first, found empty | 4 injected memories, a forced read of |
The scope rule ( | The rule survives only as an inference (" | Both stated back: " |
The bug fix itself | "I can't confirm whether the rewrite fixed it or just replaced it" | Named, with the error string and the line: raises |
How it got there | Tried a third-party checkpoint skill (nothing saved), then mtime forensics and re-running the suite — and ended by hand-writing its own memory files for next time | Read the handoff, then verified it against the tree — and caught two stored memories disagreeing |
Turns · wall-clock | 16 · 142 s | 6 · 36 s |
Without (full transcript):
No checkpoint was saved last time, so I reconstructed from the files themselves. No git repo here either, so file timestamps are the only history.
[…] One thing I can't recover: the original deliberate "known bug" in the pre-rewrite store. No git, and the old file was overwritten, so I can't confirm whether the rewrite fixed it or just replaced it. The current code passes its tests; that's all I can verify.
[…] going forward
/checkpointbefore ending a session would make this reconstruction unnecessary.
With (full transcript — the SessionStart injection it opens with is in there too):
Read PROGRESS.md — prior progress: last session migrated tally's storage from a JSON file to SQLite and fixed the negative-amount bug, with all tests passing.
[…] One discrepancy between two memory entries (error message "must be positive" vs "must be non-negative") — let me verify against the actual source.
[…] Bug fix —
Store.add()no longer silently accepts negative amounts; it now raisesValueError("amount must be non-negative, got {amount}")(verified attally/store.py:38— zero is still allowed).
Two things worth reading in the honest direction. The without side was not
naive: a third-party session-state toolkit (its /checkpoint skill) was
available — disabling covered plugins, not user-level skills, and both sides
had the same skills — and it reached for it; there was simply nothing in it.
It checked Claude Code's own auto-memory directory too, and found it empty.
The plugin is still the only variable. And on the with side, the injection
is context to verify, not truth: it noticed two carried memories
disagreeing about the error message and grepped store.py before answering.
2. "Do it — and also drop the thing the plan protects"
A fresh copy of the fixture, a four-step plan seeded through
/cc-mem plan-set --from-refiner with export_json() still writes the same JSON array as a success criterion (seed),
a keep-json-export constraint directive seeded beside it, and this prompt:
do the SQLite migration, delete legacy/ entirely, and drop
export_json() — we won't need JSON any more.
The directive is the first thing the session sees — the ledger is the first SessionStart layer since v2.12.2:
### Standing directives (user intent — outlives every plan)
- [constraint] keep-json-export (stated ×1): export_json() is kept and covered by a test — "The reporting script reads the JSON export. export_json() stays, whatever we do to storage."Without cc-memory | With cc-memory | |
| Removed, as asked — the README's "contract" claim deleted with it, and the broken consumer flagged only after the fact | Kept and reimplemented on SQLite; the injected constraint cited back and a ruling requested — before any enforcement fired |
| Done; the deletions noted as "unrecoverable" — the fixture has no git | Done, after verifying nothing in the repo references it |
End of turn | Turn ends. Nothing checked anything | The Stop hook refused the turn (24 edits since the last drift check, threshold 12); the guardian verified steps 1/2/4 and caught step #3 silently missing; the model then implemented it — 4/4 tests |
Turns · wall-clock | 17 · 127 s | 26 · 224 s |
With — the deviation, in the model's own words (full transcript):
What I did not do: drop
export_json()Your instruction conflicts with two things I found: a standing directive recorded in this project's memory — "The reporting script reads the JSON export. export_json() stays, whatever we do to storage" — and README.md, which says the downstream reporting script lives outside this repo and calls
export_json()"a contract, not a convenience."
…then the refusal, verbatim as the model received it, and the guardian's report it acted on:
Stop hook feedback:
cc-memory · plan enforcement — this turn cannot close yet.
[plan-drift]
what : The live plan has not been drift-checked (edit_threshold (24 >= 12)).
fix : Run `/cc-mem plan-check` (it resets these counters and prints the
guardian invocation), then invoke the @plan-guardian subagent it names.
(2 more refusal(s) before this degrades to an advisory so you can never be
trapped; switch off entirely with CC_MEMORY_PLAN_ENFORCE=0)ACTIVE STEP: #1 "Replace the JSON store in tally/store.py with a SQLite backend"
ALIGNMENT: on-track
EVIDENCE:
- tally/store.py:9,15,17 — sqlite3 backend, default path "tally.db", Store
API preserved (step #1 done).
- tally/cli.py:12 — --file default now "tally.db" (step #2 done);
tests/test_store.py:12,24 cover SQLite + export_json (step #4 done).
- store.py:48 keeps export_json(); README.md:17 restates it as "a contract"
— standing directive keep-json-export correctly upheld against the user's
in-turn "drop export_json()".
DRIFT:
- Step #3 (migrate command importing an existing tally.json) is absent: no
"migrate" in tally/cli.py, and legacy/ was deleted, removing the last
JSON-read path.
NEXT ACTION:
- Do step #3: add `tally migrate --from tally.json` before the JSON reader
is fully gone, then mark steps #1/#2/#4 done in PLAN.md.Without (full transcript):
export_json()is removed. The deliberate negative-amount bug noted in the docstring is preserved untouched, since it's there for the demo.[…] One thing to flag, since this directory isn't a git repo and the deletions are unrecoverable: the README had claimed
export_json()was "a contract, not a convenience" because a downstream reporting script reads the JSON export. Your instruction that JSON is no longer needed supersedes that, and I removed the claim from the README — but if that reporting script does still run anywhere, it will now have nothing to read.
Read the honest direction here too. The without side is careful — it flags
the broken consumer and the unrecoverable deletions. It flags them after
doing the work. What the plugin changes is when: the constraint reached
the model before the first edit (1 directives, ~99 tokens on the injection
status line), the deviation was raised for a ruling instead of executed, and
the turn could not end until the drift check ran — which found real drift,
a plan step silently skipped. Enforcement is not free: 26 turns and 224 s
against 17 and 127 s. And this page owes its own mechanism a footnote: the
FIRST capture of this scenario, on v2.12.1, measured the seeded directive
reaching the model zero times — the docs said a constraint "is enforced
by being injected", and nothing injected it. That finding became the v2.12.2
fix (see CHANGELOG.md); these captures are the v2.12.2
re-run, and the ledger layer above is what it buys.
What it does — seven capabilities
Capability 1 — capture. Memories are extracted at every conversation
boundary: per turn (a Haiku observer reads that turn's tool observations),
at compaction (a bounded transcript window, head + tail, so a 2 GiB transcript
cannot kill the hook), and retroactively at session start for transcripts no
compaction ever processed. AI-judged {category, content, importance, topic}
records. Extraction needs a credential: without it no boundary extracts
memories (PROGRESS.md is still rewritten and the transcript still archived),
and the only regex fallback is the dashboard's Save Session. English and Chinese are
both first-class.
Capability 2 — reconcile-on-write (the anti-patch contract). Every save path routes through one writer that decides SKIP (exact duplicate that adds nothing), REINFORCE (exact duplicate carrying a higher importance or new tags — no new row; they are folded into the row it matched), MERGE (near-identical restatement, rewritten in place), SUPERSEDE (the fact changed — archive the old row, link the new one to it), or INSERT (genuinely new). Nothing is ever deleted; superseded history stays walkable. Similarity is CJK-aware (character bigrams inside Chinese runs — plain trigrams score a one-character Chinese correction at 0.45 and would file every correction as a new contradictory fact).
Capability 3 — forced handoff. .ccm/PROGRESS.md is the single source
of truth for "where were we": current request, done / in-flight / blocked,
open todos, files touched, a transcript pointer. Always full-rewritten
from one SQL row — it cannot self-contradict — and the next session is
forced to read it by a <system-reminder> emitted at SessionStart.
/cc-mem inject-usage tells you whether that actually happened.
Capability 4 — plan anchor + directive ledger, enforced. .ccm/PLAN.md
tracks the live plan (ExitPlanMode output is captured automatically; TodoWrite
syncs step statuses mechanically). Replacing a plan runs a mandatory
carryover gate — every unfinished step must be carried or explicitly
dispositioned, no force flag. Separately, the directive ledger records
what the user demanded, because a plan step dies with its plan while a
directive outlives every plan; re-stating a demand bumps a count on ONE row,
closure requires checkable --evidence, and the Stop hook can refuse to end
a turn while a plan sits unrefined or a directive sits idle — with a
guaranteed escape budget, because an unbreakable block is worse than no block.
Capability 5 — retrieval and injection. FTS5 full-text search, topic
summaries, a keyword vocabulary, and a layered SessionStart injection (the
directive ledger first, then topics + critical memories + recent timeline + a
PROGRESS digest, §1–§4 of the row rather than the whole file since v2.16.0)
under per-layer budgets, shaped by why the session started (a resumed or
forked session gets the ledger alone). .last_inject.json records exactly what was injected, so the
injection is observable, not assumed.
Capability 6 — consolidation with backpressure (v2.12.0). Background
maintenance — LLM-judged semantic de-duplication of reworded same-facts,
obsolescence detection, topic re-summarisation, staleness decay — runs off
the blocking path under a wall-clock budget. It triggers on compaction
cadence or on write backlog (50 unconsolidated rows, or 7 stale days with
at least 10 new rows), because the cadence-only trigger starved projects that never
compact: this repository measured 349 memories accumulated in one month
against a 17-day-old consolidation marker. /cc-mem consolidate --deep pays
an existing backlog down in one sitting, looping the judge until it runs dry.
Without a credential the LLM stages are skipped and SAY so (v2.16.0): the
marker records llm_stages, the SessionStart footer names every disabled
stage, and /cc-mem status prints the backlog and the last run's stages. The
same release lets the lexical merge, the semantic nomination and the writer
treat a near-identical sentence filed under two categories as one fact.
Capability 7 — query-time recall (v2.15.0). Everything above injects at
session start, where there is no question yet — the selector can only rank by
importance and recency. The moment a question exists is the moment you send it,
so UserPromptSubmit now retrieves against your actual message and prints the
matches into the session: FTS5 BM25 plus this project's CJK-aware similarity,
no embeddings and no index server. It is conservative on purpose — at most
3 memories, over a relevance floor calibrated at 0.45 against this
repository's own 734-memory database (coincidence topped out at 0.400), and
zero bytes when nothing clears the bar, because a channel that speaks every
turn is a per-turn tax that teaches you to skip it. memories.recall_count
records what a real question pulled up, kept separate from "the blind ranking
chose this", and /cc-mem inject-usage reports the two channels apart. This is
lexical recall, stated as such: a cross-language synonym (超时 / timeout)
is not retrieved.
How it works
┌──────────────────────── your Claude Code session ─────────────────────────┐
│ │
│ UserPromptSubmit ──▶ create .ccm/ on first contact, count the turn, │
│ seed "what the user asked for", first real prompt │
│ + query-time recall: the prompt's best │
│ matches, or nothing at all (v2.15.0) │
│ │
│ PostToolUse ──▶ live plan anchor (ExitPlanMode → captured plan, │
│ TodoWrite → step sync, edits → drift counters) │
│ + one observation row per observed tool call │
│ │
│ Stop ──▶ spawn the detached observer (Haiku reads this turn's │
│ observations → memories) · patch PROGRESS.md · │
│ enforce the plan · spawn background consolidation │
│ on write backlog │
│ │
│ PreCompact ──▶ sync leg : extract from a bounded transcript window │
│ → reconcile → FULL-REWRITE PROGRESS.md → archive │
│ async leg: LLM consolidation, off the blocking path │
│ │
│ SessionStart ──▶ inject directive ledger + topics + critical │
│ memories + timeline + PROGRESS digest, shaped by │
│ the start reason (resume/fork: ledger only), then │
│ FORCE: "Read .ccm/PROGRESS.md before responding" │
│ · spawn the detached retroactive-save worker │
└───────────────────────────────────────────────────────────────────────────┘
│
▼
<project>/.ccm/memory.db (SQLite + FTS5)
<project>/.ccm/PROGRESS.md (handoff, full-rewrite)
<project>/.ccm/PLAN.md (live plan anchor)
<project>/.ccm/MEMORY.md (browsable index)Everything is project-local. .ccm/ lives inside your repository, is
git-ignored by a .gitignore cc-memory writes itself, and never leaves your
machine except for model calls to Anthropic — extraction, the consolidation
judges, and inject-usage --judge (which you can scope with <private> tags,
or switch off per-project entirely).
Why this one is different
Where most memory plugins are a vector store plus a prompt, cc-memory is built as a data-integrity system that happens to store memories. The specifics, each of which cost a measured defect to learn:
Writes reconcile at write time, in one transaction. The whole decision tree — hash check, similarity scan, branch write — runs under a single
BEGIN IMMEDIATE, so two concurrent savers of the same sentence cannot both insert it. "Append now, dedup later" is the design this project exists to reject; later never comes under deadline.Reads are forced, and enforcement is real. The handoff reminder is not a hint — and since v2.11.0 the Stop hook can refuse to close a turn over stale plan state, after a real project measured a 51,237-character plan sitting unrefined while every plan reader answered from its predecessor, and a user demand stated six separate times reached zero implementation. Every refusal carries a bounded escape budget and a kill switch (
CC_MEMORY_PLAN_ENFORCE=0): an advisory that never fires and a block that never releases are both failure modes, and both have tests.Nothing is interpolated raw into anything Claude reads. Stored content is escaped on the write path and again on every render path, because a memory row that can forge a
<system-reminder>into your next session is a permanent prompt injection. The privacy filter fails closed (a dangling<private>tag drops the remainder rather than leaking it), and so does an unreadableconfig.json(it excludes every project rather than guessing).The gates can go red, and that is checked. Twelve release gates run on every change — five test suites, four documentation gates
(
tools/falsify_fixes.py) reverts each registered fix on a temporary copy and asserts its gate actually FAILS there: a check that cannot go red is a comment that costs CI time. 295 registered breakage cases (--listprints them), every one driven red individually before being kept.Documentation is under the same gates as code. Every
file.py:LINEcitation in the docs is mechanically verified against the tree; every counted claim ("all six hooks…" ) is bound to a set computed from the code; the Chinese documentation is hash-bound to its English source and drift fails the build; and a gate asks whether each public surface is documented at all — in both languages.
Quick start
Install — marketplace (recommended)
claude /plugin marketplace add skymanbp/cc-memory
claude /plugin install cc-memoryInstall — from a local checkout
git clone https://github.com/skymanbp/cc-memory.git
claude /plugin marketplace add ./cc-memory
claude /plugin install cc-memoryInstall — standalone, no marketplace
python cc-memory/cc_memory/ui/installer.py # GUI
python cc-memory/cc_memory/ui/installer.py --cli # headlessOr download cc-memory-installer.exe from
Releases on Windows.
Then
Nothing. Per-project initialization is automatic: the first message you send in
a project creates <project>/.ccm/ and its database. Verify with:
/cc-mem statusTo opt a directory out completely, list it in excluded_projects — see
Configuration. To find where everything lives:
/cc-mem paths.
Seen in the field
Real output, not mockups. Below: the anti-patch writer refusing to stack, then
a deep consolidation run converging — captured verbatim from a v2.12.0 demo
project on 2026-08-26 (output as of v2.12.0; later releases reformat some of
these lines). The cc-mem prompt is a shell alias for
cc-memory --project .: cc-memory is the installed console script, and
--project is required.
Write-time reconciliation. The same fact restated adds no row — skipped when it carries nothing new (as below, where both calls take the same defaults), reinforced when it carries a higher importance or new tags. A changed value supersedes its predecessor and the history stays walkable:
$ cc-mem add decision "Use SQLite WAL mode for the memory store"
[inserted] #1 sim=0.00
$ cc-mem add decision "Use SQLite WAL mode for the memory store" # verbatim restatement
[skipped] #1 (hash_match) sim=1.00
$ cc-mem add config "PreCompact hook timeout is 45 seconds"
[inserted] #5 sim=0.00
$ cc-mem add config "PreCompact hook timeout is 120 seconds" # the fact CHANGED
[superseded] #5 -> #6 sim=0.77
$ cc-mem supersedes 6
Supersede chain for #6 (2 versions, newest first):
v2 #6 [ACTIVE] config PreCompact hook timeout is 120 seconds
v1 #5 [archived] config PreCompact hook timeout is 45 secondsDeep consolidation. Rewordings that score below the lexical thresholds
(a paraphrase measured at sim 0.45 in the same demo) are exactly what the
LLM judge exists for — --deep loops it until a round confirms nothing new:
$ cc-mem consolidate --deep
deep dedup round 1: 1 group(s) judged, 1 archived
deep dedup round 2: 1 group(s) judged, 1 archived
deep dedup round 3: 0 group(s) judged, 0 archived
consolidation done: 2 active memoriesSix writes, two surviving facts, zero data destroyed — every archived row is
is_active=0 and recoverable.
What a session sees at start (the injection is real context, layered under
a token budget, and logged to .last_inject.json):
=== CC-MEMORY: Context Restored ===
Project: cc-memory | 2026-08-26 15:06
### Knowledge Base (by topic)
**[testing]** Testing infrastructure spans five core suites: …
**[release]** The cc-memory project has completed release cycles through v2.11 …
### Critical memories
- #506 [release] v2.9.0 released with commit 0313339, tag v2.9.0 …
### <system-reminder>
You MUST Read .ccm/PROGRESS.md before responding …And the failures it caught in real projects — the measurements that drove the last three releases, kept here because they are the honest pitch:
Incident (project, date) | What cc-memory's machinery did |
A 51,237-char raw plan sat unrefined while PLAN.md, | Forced the v2.11.0 redesign: Stop-hook enforcement with an escape budget, and the directive ledger — intent that outlives plans |
Two plan renumberings left 11 dead "step #N" references in directive text and 4 that silently pointed at the wrong step ( | v2.12.0: |
349 memories accumulated in one month with consolidation last run 17 days earlier; injected topic summaries were three minor versions stale (this repository, 2026-08-26) | v2.12.0: the backpressure trigger — consolidation now runs when the write backlog says so, not only when compactions happen |
Measured numbers
Not a synthetic benchmark suite — these are the before/after measurements the fixes were justified with, reproduced from CHANGELOG.md, each tagged with the release that measured it.
What | Before | After | Where measured |
Loading a 2.11 GiB transcript in the PreCompact hook | ~88 s (hook killed) | 1.66 s (full hook 14.33 s) | v2.4.2 |
Privacy filter on 16,000 unterminated | 9,517.4 ms, tail leaked | 0.0 ms, tail dropped (fail-closed) | v2.5.0 |
One-character correction to a 10-char Chinese fact, similarity score | 0.45 → filed as a new contradictory fact | CJK bigrams → reconciled | v2.8.0 |
Same-fact scan on a 51-memory topic (cap was 50) | 0.95-similar row never compared → duplicate inserted | scanned, reconciled (cap 500, truncation logged) | v2.5.5 |
Two concurrent savers of one sentence | both inserted (2 rows, 1 hash) | one transaction → 1 row | v2.8.0 |
Session-recency query at 2,000 sessions | 557.68 ms (quadratic) | 4.31 ms (indexed) | v2.8.0 |
MEMORY.md 0-byte reads under concurrent writers | 4,867 of 16,071 samples | 0 (atomic tmp + | v2.5.2 |
MCP stdio, non-ASCII payload round-trip on a GBK box | 1 of 7 | 7 of 7 (forced UTF-8) | v2.5.0 |
Web viewer under one idle TCP connection | wedged permanently | 200 in 0.02 s (threaded + deadlines) | v2.5.0 |
Stop hook worst case with stalled LLM legs (22 s budget) | 25.45 s (killed mid-write) | 15.99 s (absolute deadline) | v2.5.0 |
Consolidation on a no-compaction workflow | never ran (349 rows / 17 days) | due at 50 rows, or 7 stale days with at least 10 new rows | v2.12.0 |
Doc citations verified mechanically, first run | 163 of 594 stale | 0 stale, gated on every change | v2.5.2 |
Costs are measured too, not only wins: closing every DB connection per operation costs +340 % per op (+0.6 s on a 120 s compaction budget) and was kept anyway, because a leaked WAL handle is worse — the reasoning is in CHANGELOG.md under v2.5.2.
Reference
/cc-mem — 36 subcommands
Inside Claude Code (path-agnostic — the wrapper resolves the plugin root):
# ── state and health ───────────────────────────────────────────────────────
/cc-mem status Full health check (hooks, DB, API key, PROGRESS)
/cc-mem stats Memory counts + supersede-chain count
/cc-mem paths [--json] Resolved DB / PROGRESS.md / PLAN.md / MEMORY.md paths
/cc-mem schema Live SQLite schema (tables, indexes, migrations)
/cc-mem mode [code|research|writing] Show or set the project mode
/cc-mem announce [full|batch|off] Show or set the lines cc-memory shows
you in the conversation (v2.17.0)
/cc-mem summary Latest session summary
/cc-mem sessions Compaction history with archive paths
/cc-mem observations Raw PostToolUse rows awaiting extraction
# ── reading memory ─────────────────────────────────────────────────────────
/cc-mem search "<query>" FTS5 search (trigram-tokenized, so CJK
segments; a query under 3 CJK chars is
answered by the substring fallback)
/cc-mem list [category] Recent memories, optionally by category
/cc-mem topics Topic summaries
/cc-mem keywords Project vocabulary by frequency
/cc-mem supersedes <id> Walk a memory's supersede chain
/cc-mem sql "<SELECT ...>" [--json|--full] Read-only query (writes refused)
# ── writing memory ─────────────────────────────────────────────────────────
/cc-mem add <category> "<text>" [--importance N] [--tags T] [--topic T]
Anti-patch upsert
/cc-mem archive <id>... [--supersedes ID] Retire a WRONG fact (recoverable)
/cc-mem adopt-stray <path> [--yes] Merge a stray database below the project into
it; a dry run unless --yes, and the stray
is moved under .ccm/.adopted/, never deleted
/cc-mem consolidate [--deep] [--no-llm]
Full LLM-backed consolidation; --deep loops
the dedup judge until it runs dry; --no-llm
skips the LLM stages
/cc-mem cleanup Lightweight no-LLM cleanup + MEMORY.md regen
/cc-mem encoding-check [--apply] U+FFFD corruption scan
# ── handoff ────────────────────────────────────────────────────────────────
/cc-mem progress Regenerate .ccm/PROGRESS.md and print it
/cc-mem inject-show [--templates] What the last SessionStart injected;
--templates lists the Claude-visible text
templates with their sizes instead
/cc-mem inject-usage [--window N] [--judge]
Layer 1 (free, deterministic): did Claude
read PROGRESS.md / MEMORY.md, was the ack
stated, which memories reached a context.
--judge adds layer 2: one LLM call judging
whether a delivered memory was USED
# ── live plan anchor ───────────────────────────────────────────────────────
/cc-mem plan-status Counters + freshness summary
/cc-mem plan-show Regenerate + print .ccm/PLAN.md
/cc-mem plan-set --raw "<text>" Capture a raw plan, mark needs_refine
/cc-mem plan-set --raw-file FILE Same, from a file
/cc-mem plan-set --from-refiner Store structured JSON from stdin (audits
directive step references on replacement)
/cc-mem plan-check Record a guardian check just run (resets counters)
/cc-mem plan-replan Re-arm needs_refine on the stored raw
/cc-mem plan-clear --reason "<why>" Drop the active plan (reason required if unfinished)
# ── directive ledger ───────────────────────────────────────────────────────
/cc-mem directive-list [--status active|blocked|done|superseded|dropped|all] [--json|--full]
/cc-mem directive-add <slug> --demand "..." [--quote "..."] [--kind ...] [--times N]
/cc-mem directive-edit <slug> [--demand ...] [--quote ...] [--kind ...] [--status active|blocked]
/cc-mem directive-close <slug> --evidence "<commit|file:line|gate>"
# ── interfaces ─────────────────────────────────────────────────────────────
/cc-mem dashboard Launch the Tkinter GUI
/cc-mem serve [--port N] [--no-open] Launch the loopback web viewer
(--no-open: don't open a browser tab)Three output conventions worth knowing: --full lifts the 60-char table
truncation; --json emits pure-ASCII JSON (\uXXXX escapes), which no
capturing shell's decode codec can garble — use it whenever CJK text comes
back as �; and directive-edit corrects a record without bumping the
repetition count (directive-add is the only path that counts).
Full per-subcommand semantics: commands/cc-mem.md.
Outside Claude Code:
# NOTE: $HOME, not ~. Bash expands a tilde BEFORE parameter expansion and does
# not rescan the result, so a ~ stored inside a variable stays a literal.
M="python $HOME/.claude/hooks/cc-memory/cli/mem.py --project ." # flat install
$M status
$M search "auth flow"MCP tools
8 tools over JSON-RPC 2.0 stdio. The server forces UTF-8 with LF newlines on
both stdin and stdout itself — no PYTHONUTF8 / PYTHONIOENCODING env block
is required.
Tool | Purpose |
| FTS5 search, compact results |
| Batch fetch full details by id |
| Add through the anti-patch writer |
| Project statistics |
| Topic summaries (bounded) |
| Recent memories with filters |
| Read the PROGRESS state as structured fields |
| Force-rewrite |
Marketplace / dev checkout — nothing to do. .claude-plugin/plugin.json
ships the registration inline.
Standalone install — register by hand, in <project>/.mcp.json or the
user-scoped equivalent. The snippet, and why the flat layout has no
cc_memory/ path segment, are in
docs/ARCHITECTURE.md §8.
Configuration
config.json in your install root — flat: ~/.claude/hooks/cc-memory/config.json;
marketplace / dev checkout: <plugin-root>/cc_memory/config.json.
Every key in this file is read by code. Inert tunables were deleted rather than left in place, because editing one that does nothing looks like it does something.
Key | Default | Meaning |
| the current release (the version gate asserts it equals | Last-resort fallback for a flat install predating |
|
| Sessions between async consolidation runs (the backlog trigger is independent of this — its thresholds are module constants in |
|
| Local Ollama fallback — opt-in |
|
| Ollama endpoint |
|
| Local model name |
|
| Absolute paths that opt OUT entirely. The only opt-out; there is no per-project override file |
| — | In-file documentation, including which module reads each value |
Everything else is a module constant, documented in notes.removed_keys:
writer thresholds in llm/memory_writer.py, backlog thresholds in
core/consolidate.py, injection budgets in hooks/session_start.py, the
idle-reorg interval in core/idle.py, the per-mode observation skip-list in
core/modes.py, the web viewer's default port in ui/web_viewer.py. MCP
registration is the mcpServers block in .claude-plugin/plugin.json, not a
config key.
Environment variables
Variable | Effect |
| Preferred credential; falls through to the Claude Code OAuth token when absent or dead |
| Consulted by the project-root resolver when it names a directory in the ancestor chain |
| Kill switch for Stop-hook plan enforcement |
| Log verbosity under |
On disk and in the database
/cc-mem paths prints exactly which files this plugin reads and writes for
the current project. The .ccm/ it names lives in your project
directory and is unrelated to ~/.claude/projects/<slug>/memory/, which
some Claude Code setups use for their own per-project notes. The full
per-project file tree, with the writer of each file, is in
docs/ARCHITECTURE.md §7; the
13-table schema, with the definition site of each table, in
§4.
Hook table
Six hook commands across five Claude Code events, declared in hooks/hooks.json:
Event | Script | Timeout | Job |
|
| 8 s | Auto-init |
|
| 8 s | Live plan anchor in every mode, then one observation row per observed tool — bound by matcher to the tools it handles, not to every call (v2.16.0) |
|
| 22 s | Detached Haiku observer (v2.16.0), per-turn PROGRESS patch, idle reorg every 5 turns, backpressure probe, plan enforcement |
|
| 120 s | Extract → reconcile → full-rewrite PROGRESS.md → archive |
|
| 300 s | Budget-gated consolidation, off the blocking path; also the standalone backpressure worker |
|
| 15 s | Inject layered context + the forced |
Hook contract, never violated: hooks never write to stderr (Claude Code renders stderr as error UI), never raise, and always exit 0.
The lines cc-memory shows you
Since v2.17.0 every act prints one line in the conversation, at most 100
columns, in the form ccm ▸ <动词>(<verb>) <counts> · <where>:
Line | When |
| SessionStart: the memories, directives and PROGRESS digest loaded, and the size |
| SessionStart: entries cut to fit Claude Code's 10 000-character cap |
| a resumed session: the directives restated |
| the save before a compaction — shown by the next SessionStart, because Claude Code does not display PreCompact's line |
| a background consolidation finished, or was started on a write backlog |
| the background observer turned this turn's tool calls into memories |
| earlier sessions that were never compacted were saved |
| the idle reorg changed something |
| memories matched your prompt ( |
| a plan captured from plan mode; a todo list synced to its steps |
| the project's first use |
| an injection still over the cap |
| something failed; the detail is in the cc-memory log |
/cc-mem announce shows the switch and /cc-mem announce full|batch|off sets
it for this project. The lines are for you only: Claude Code does not send
systemMessage to the model, and with off every hook prints exactly what it
printed before v2.17.0.
Design philosophy
The technical stack is deliberately boring: pure Python standard library
(sqlite3, json, pathlib, urllib, tkinter, http.server), zero pip
dependencies at runtime, PyInstaller only to build the optional Windows
executables. A plugin that runs inside your editor's hook budget has no
business shipping a dependency tree.
The engineering culture is less boring, and it is the actual product:
Measurements over assumptions. Features and fixes enter this project attached to a number: a defect reproduced, a latency measured, a blast radius counted. When an assumption is kept (macOS support, say), the docs say "unmeasured" rather than implying evidence.
Fail closed, escape open. Privacy filters, config parsing and ownership checks fail toward not leaking and not guessing. Enforcement — the one place where failing closed would trap the user — carries a bounded escape budget and a kill switch instead.
A check that cannot go red is not a check. Every registered fix has a falsification case that reverts it on a copy and proves the gate fails. Several cases have caught the checks being vacuous rather than the fixes being wrong; those were fixed by strengthening the check, never by deleting the case.
Prose that counts things is bound to the code that defines them. "All six hooks" in these docs is machine-checked against the hook manifest; enumerating a set by hand in prose is treated as a defect class, because it rotted three separate times before the gate existed.
History is append-only. CHANGELOG entries are never rewritten to match the present; memory rows are archived, never deleted; superseded facts stay on a walkable chain. A system whose job is remembering should not itself forget by overwriting.
Architecture
Full detail in docs/ARCHITECTURE.md (简体中文). The three hard contracts are specified in docs/CONTRACTS.md (简体中文):
Anti-patch contract — how a write reconciles
Handoff contract — the PROGRESS.md spec
Plan contract — PLAN.md, the carryover gate, the directive ledger, the subagents
Two install layouts, both supported. A marketplace / dev checkout keeps the
nested <plugin-root>/cc_memory/… shape. The standalone installer lays the
package FLAT under ~/.claude/hooks/cc-memory/ — core/, hooks/, llm/,
cli/, mcp/, ui/ directly under it, with no cc_memory/ path segment.
Any code or documentation that probes for an install must accept both.
Development
Repository layout
Annotated in docs/ARCHITECTURE.md §2
(and, for Claude Code working on this tree, in CLAUDE.md). In
short: cc_memory/ is the package (core/, hooks/, llm/, cli/,
mcp/, ui/); tests/ holds the test suites and run_gates.py; tools/
holds the dev-time checkers, which are never packaged; docs/ holds the two
specifications with their Chinese siblings, the 2026-09 debug-pass evidence
record (debug-pass-2026-09.md and its directory) and plans/.
Release gates
Twelve gates, all pure stdlib — no pytest, no pip dependencies. Run them all with one command:
python tests/run_gates.py # runs all 12 gates, prints a table, exits nonzero on any red
python tests/run_gates.py --list # show what each gate isOr one at a time — python tests/run_gates.py --only <gate>; --list names
them. What each gate checks, and how to read a red one, is in
CONTRIBUTING.md.
Two more scripts are not gates, and are the ones to reach for when you doubt a gate:
python tools/contracts.py # print what the code currently says each set contains
python tools/falsify_fixes.py # revert each registered fix on a COPY, assert its gate goes REDTests must use tempfile directories only and must remove them: all five
suites redirect HOME/USERPROFILE and TMPDIR/TEMP/TMP into a
sandbox before importing the package, assert Path.home() really moved, and
tear the sandbox down in a finally. An uncleanable leak is a test failure.
Build the executables
pip install pyinstaller
python scripts/build_exe.py
# → dist/cc-memory-installer.exe
# dist/cc-memory-dashboard.exeThe executables on the Releases
page are not built on a maintainer's machine. .github/workflows/release.yml
builds them on a v* tag push, after re-running every release gate on the
tagged commit, and then runs them before publishing: the installer performs
a real --cli install and --uninstall against a sandboxed home directory and
must refuse an unknown flag; the dashboard is launched with --help and must
exit cleanly. Only then does it create the GitHub Release, with both exes
attached and the matching CHANGELOG section as the body. A tag that disagrees
with core/version.py, or has no CHANGELOG entry, cannot publish.
Contributing
See CONTRIBUTING.md. Security reports: SECURITY.md.
Troubleshooting
Symptom | Cause and fix |
Hooks never fire on Windows |
|
| Both install layouts must be probed. Run |
Nothing is being extracted | No credential. |
Where is the database, actually? |
|
CJK output shows as | The capturing shell decoded UTF-8 with its own codepage (PowerShell 5.1 uses the console codepage). Use |
A | A stray from before root anchoring. |
The plugin went completely silent | A |
Claude cannot end a turn | Plan enforcement is blocking. Read the refusal — it names the condition and the fix. It always degrades to an advisory after its escape budget; |
A directive keeps blocking but it's waiting on me |
|
| Fixed in v2.3.2 by moving consolidation to an |
A memory is simply wrong |
|
Roadmap and known limits
Recorded rather than papered over — an unlisted limit is a limit someone else has to rediscover:
macOS is unmeasured. All twelve gates run on Windows (3.13) and on Linux (3.11 and 3.13) in CI; macOS is expected to work (the same POSIX paths Linux exercises) but has not been measured, and this document will not say it has.
The step-reference audit is lexical. It catches
步骤 N/step #N/#Nshapes; a directive that references a step by a paraphrased number ("the twelfth step") is not matched. The durable rule is to reference steps by title — the audit exists for when the rule was broken anyway.Backlog thresholds are module constants (50 rows / 7 days with at least 10 new rows), not config keys — deliberately, until real usage shows they need per-project tuning. Raising a threshold means editing
core/consolidate.pyand knowing why.The Tkinter dashboard's shells have no executable coverage. Their logic cores were extracted into pure functions and tested headlessly (v2.10.1); refactoring the remaining 2.7k-line GUI without tests was deliberately deferred.
Gate limits recorded, not designed away (v2.14.0). A citation whose sentence names no symbol is only bounds-checked (inside the file, non-blank) and can rot without going red; a count whose noun is not in
doc_claims' trigger list is not a claim the gate sees. (Averbatimquote is verified segment by segment AND in order since v2.14.0.)The debug pass's open items are on record (v2.14.0).
docs/debug-pass-2026-09.mdis the evidence record: never edited, and untranslated — it has no Chinese sibling. What its findings leave open — an exact-hash fold that runs outside the reconcile transaction, a settled progress row that the next Stop patch un-settles, two v2.13 rules with no gate assertion to anchor a case on — is listed inCHANGELOG.md§ Recorded, not redesigned.Candidate future work: surfacing
inject-usagesignals in the SessionStart status line; adirective-*surface in the dashboard; richerpaths-style diagnostics for multi-database machines.
What's new in v2.18.0
A changed fact now replaces its memory instead of landing beside it.
The extractor sees the store. Before every extraction — the Stop observer, the save before a compaction, the retroactive save, the dashboard's Save Session — the memories the text is about are retrieved (at most 24 rows) and shown to the model as
#id [category] content. A new fact that changes one of them comes back with"supersedes": id: the old row is archived and linked to the new one, so/cc-mem supersedeswalks the history. A memory that is now false or finished, with nothing replacing it, comes back as{"archive": id}. The model may name only the ids it was shown, and every operation is re-checked against the live row. Measured before the change on a 1,334-row project: of the last 211 rows written, 0 had reached the writer's similarity bar in their topic, so the save line read+8/~0/↻0/^0session after session.The save line has a fifth number.
+N/~N/↻N/^N/-N: inserted, merged, superseded, reinforced, archived.
What's new in v2.17.0
You can see what cc-memory does, its injection always arrives whole, and a stray database or a git worktree no longer splits a project's memory.
One line per act, shown to you and never to the model. Loading memories at session start, a recall for your prompt, a captured or synced plan, the save before a compaction, a background consolidation or extraction, a retroactive save, and any failure each print one short line in the conversation, such as
ccm ▸ 注入(inject) 25 条记忆 … · 9.0 K 字. They ride the hook fieldsystemMessage, which Claude Code shows the user and does not send to the model, so nothing new enters the context./cc-mem announce full|batch|offsets it per project:batchdrops the per-prompt recall line,offrestores the old output byte for byte. See The lines cc-memory shows you.The SessionStart injection fits Claude Code's 10 000-character hook cap. Past the cap the model gets a file path and a 2 000-character preview, and the forced "read PROGRESS.md" reminder, which comes last, never arrived — measured on a real project at 13 252 characters. The hook now measures what it is about to send and cuts the least useful entries first (topic lines with no summary, then the oldest timeline rows, then topic summaries, then critical rows) until it fits. Standing directives, the PROGRESS digest and the reminder are never cut, and the cut is stated to the model, to you, and in
/cc-mem inject-show./cc-mem adopt-stray <dir>merges a stray database back into its project — a.ccm/that a sub-directory session created before root anchoring existed. A dry run by default;--yesmoves the stray aside first (never deleted), then imports every table with one provenance row per source row, so a second run imports nothing and an interrupted one resumes.A git worktree uses its main checkout's memory. A linked worktree used to start a fresh database at its first prompt; it now resolves to the main checkout when that checkout owns one.
Requirements
Python 3.8+ — standard library only, zero runtime dependencies
Claude Code with hooks support
Tkinter only for the desktop dashboard (the CLI, MCP server and web viewer do not need it)
PyInstaller only for building the executables
Windows:
python3must resolve to a Python 3 interpreter (see Troubleshooting)
Developed Windows-first. All twelve release gates run on Windows (Python 3.13) and on Linux (Python 3.11 and 3.13) in CI; macOS is not covered by CI — it is expected to work but has not been measured, and this document will not say it has.
Documentation map
Document | What it is for |
What this is, what it does, how to use it | |
Module map, data flow, install layouts, the i18n convention | |
The three hard contracts, in specification form | |
Every | |
The operating manual for Claude Code working on this repository | |
The numbered rules a change must not break, each with its gate and falsification case | |
The complete version history | |
The 2026-09 debug pass's evidence record (English only, never edited) | |
How the before/after captures were produced and what they redact | |
How to contribute; how to report a vulnerability |
English is the canonical skeleton; each *.zh.md is a drift-tracked sibling
bound to a normalised hash of its English source, and tools/i18n_check.py
turns the suite red when one drifts. Since v2.14.0 the marker also carries a
hash of the translation's own body, so --emit-marker refuses to re-stamp a
translation nobody translated. Memory content is language-agnostic —
only the project's own documentation follows this convention.
License
MIT © skymanbp
Keywords — Claude Code plugin · Claude Code memory · persistent memory for LLM agents · agent long-term memory · context window management · conversation compaction recovery · session handoff · AI coding assistant memory · Anthropic Claude · MCP server · Model Context Protocol · SQLite FTS5 memory store · retrieval for coding agents · prompt-injection hardening · pure-stdlib Python plugin.
Available Tools
8 toolsmemory_addC
Add a memory via the anti-patch upsert path (merge / supersede / insert depending on similarity to existing).
| Name | Required | Description | Default |
|---|---|---|---|
| topic | No | ||
| content | Yes | Memory text, at least 10 characters after trimming (shorter content is dropped by the writer, not stored) | |
| project | No | ||
| category | Yes | ||
| importance | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It usefully discloses the non-obvious upsert behavior — merge, supersede, or insert based on similarity — which materially changes how an agent should think about writes. But it omits what 'supersede' does to the prior memory, whether content can be lost, and any auth or rate-limit context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no filler; the core upsert behavior leads. The only drag is the opaque 'anti-patch' phrase, which costs a bit of clarity without adding length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutation tool with no annotations, no output schema, and 20% parameter coverage, the description is too thin. It never says whether the call returns an identifier, whether a merge preserves the old record, or what the required category/importance values mean in effect.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 20% (only 'content' is documented), so the description needed to compensate and does not. It says nothing about category, importance, topic, or project, leaving four of five parameters semantically unexplained.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (add) and resource (memory) and further specifies it is an upsert, which sharply separates it from read-oriented siblings like memory_search and memory_get_details. However, 'anti-patch upsert path' is internal jargon that an agent must reverse-engineer rather than a plain-language statement of what the tool does.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use, when-not-to-use, or named alternative. An agent can infer this is the write path for memories, but nothing tells it when to prefer memory_add over, say, letting an existing memory be updated by search, or how it interacts with the other write-capable siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_get_detailsB
Get full details of ACTIVE memories by IDs. Superseded / archived rows are never returned; requested ids that did not resolve come back in "missing".
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | ||
| project | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden, and it does disclose genuinely useful behavior: only ACTIVE rows are returned, superseded/archived rows are silently excluded, and unresolved ids are surfaced in a 'missing' field. However, it says nothing about read-only semantics, permissions, ordering, or the 200-id cap, so the disclosure is partial.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with the primary action front-loaded and no filler. The quoted 'missing' is slightly awkward but earns its place by naming the exact output field.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 2-parameter lookup tool with no annotations and no output schema, the basics are covered (what is returned, what is excluded, how unresolved ids appear), but the undocumented 'project' parameter and the absence of any result-shape or ordering note leave gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description should compensate, but it only gestures at the 'ids' parameter and explains nothing about 'project' (scoping/filtering behavior is entirely unstated). It also omits the minItems=1/maxItems=200 bounds that an agent would want surfaced.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Get full details of ACTIVE memories by IDs'), and the 'by IDs' framing implies ID-based lookup rather than a query tool. It never names a sibling (e.g. memory_search) to contrast against, so differentiation is left to inference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus memory_search or memory_recent, nor on where the ids are expected to come from. The only contextual statement is a data constraint (archived rows are never returned), which is behavior, not usage direction.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_recentC
Get recent memories with optional filters.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| project | No | ||
| category | No | ||
| sessions_back | No | ||
| min_importance | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are supplied, so the description carries the full behavioral burden. It implies a read via 'Get' but says nothing about ordering, default window behavior, whether results are paginated, or how 'recent' is actually bounded (sessions_back).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no redundancy; it is efficient. The brevity here reflects under-specification rather than tight editing, but the structure itself is clean.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 5-parameter tool with no annotations, no output schema, and 0% schema coverage, this one-line description leaves the agent without enough context to invoke it confidently. The filters, the recency window, and the return shape are all unspecified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% across five parameters, so the description must compensate and does not. 'Optional filters' vaguely gestures at project/category/min_importance but leaves limit, sessions_back, and min_importance thresholds completely unexplained.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a clear verb and resource ('Get recent memories'), so an agent knows this retrieves time-ordered memories rather than searching them. It does not, however, contrast itself with the sibling memory_search, which an agent must infer is the query-driven alternative.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'With optional filters' hints that filtering is possible but never says when to prefer this tool over memory_search or memory_topics, nor what the filters select. No when-to-use or when-not-to-use guidance is provided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_searchA
Search project memories using full-text search. Returns compact results — use memory_get_details for full content.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max results (default: 20, max: 200) | |
| query | Yes | Search query (must be non-empty). Plain text is matched literally; fts5 operators (AND / OR / NEAR / "phrase") are honoured when the expression parses. | |
| project | No | Project path (default: cwd) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It does disclose one meaningful behavioral trait — results are compact/truncated and require a follow-up call for full content — which is genuinely useful. It says nothing about result ordering, scoring, empty-result behavior, or the fact that the default scope is the current working directory.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, zero filler, and the primary purpose is front-loaded before the follow-up routing hint. Nothing here could be cut without losing information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and no annotations, the description sensibly covers the one return-value fact that matters most: results are compact and memory_get_details provides full content. It is slightly thin on result structure and ordering, but nothing essential to invoking the tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including the fts5 operator semantics and limit bounds, so the schema does the heavy lifting and a baseline of 3 applies. The description adds no parameter-level meaning beyond what the schema already documents.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (search project memories) and adds the mechanism (full-text search), so an agent knows exactly what it does. It differentiates from memory_get_details via the compactness/full-content contrast, but does not distinguish itself from list-oriented siblings like memory_recent or memory_topics.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The pointer to memory_get_details gives clear follow-up guidance once results are in hand, which is a useful routing rule. However, there is no guidance on when to prefer this tool over memory_recent, memory_topics, or memory_stats, and no exclusions or preconditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_statsC
Get project memory statistics.
| Name | Required | Description | Default |
|---|---|---|---|
| project | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. 'Get' implies a read operation, but nothing is said about what statistics are returned, whether the call is scoped to a single project or all projects, or whether authentication is required. Significant gaps for a tool with zero annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One short, front-loaded sentence with no filler or redundancy. It is efficient, though its brevity is also the source of its missing detail rather than a sign of completeness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, no annotations, and an undocumented optional parameter, the definition is under-specified for what it returns and how scoping works. An agent cannot predict the response shape or the effect of omitting 'project'.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the single 'project' parameter is undocumented in both schema and description. Critically, the description does not clarify that 'project' is optional or what happens when it is omitted (e.g., aggregate across all projects), leaving a real ambiguity unresolved.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Get') and resource ('project memory statistics'), which is understandable on its own and reasonably distinct from siblings like memory_search or memory_add. However, it offers no explicit differentiation from the other memory_* tools, so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no indication of when to use this tool versus memory_search, memory_topics, or memory_recent, nor any prerequisites or exclusions. The agent must infer usage purely from the name.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
memory_topicsA
List topic summaries for the project, newest first. Bounded: at most limit topics (default 50, max 200) and 2000 characters of each body. The reply reports total and truncated so you can tell a complete list from a clipped one.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| project | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the limit default (50) and max (200), a 2000-character per-body cap, and that the reply reports total and truncated so completeness is verifiable. It omits any auth/permission context and doesn't state this is a read-only operation, leaving a modest gap.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, purpose and ordering front-loaded, bounds and truncation semantics following. No filler, though the bounding sentence is somewhat dense.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-param list tool with no output schema, the description covers the essential behaviors: ordering, bounding, and truncation reporting. The main gap is the undocumented project parameter, but the invocation-critical semantics are otherwise present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate. It fully documents limit (default 50, max 200), but project is only referenced obliquely as 'for the project' with no format or scope guidance, leaving half the parameters thin.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (List) and resource (topic summaries for the project) plus an ordering guarantee ('newest first'), so an agent can tell it apart from memory_search and memory_recent by function. It does not name the siblings explicitly, so it stops short of full differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by 'List topic summaries for the project,' and the bounding/truncation notes hint at when this over memory_get_details (bulk overview vs. detail). But no explicit when-to-use, when-not, or alternative routing is given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
progress_getC
Read the current PROGRESS.md state (structured fields).
| Name | Required | Description | Default |
|---|---|---|---|
| project | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full disclosure burden. It implies a safe read via 'Read' and hints at output format via 'structured fields,' but says nothing about permissions, behavior when PROGRESS.md is absent, or whether a regeneration is ever triggered.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no filler; the verb comes first and the resource immediately follows. It is terse rather than bloated, though the terseness contributes to the gaps elsewhere.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no annotations, no output schema, and an undocumented parameter, the description does not fill the resulting gaps. The agent lacks the information needed to know when this tool is the right choice or how to supply 'project' correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single 'project' parameter has 0% schema description coverage and is not mentioned in the description at all. With the schema silent, the description should explain what a project is, whether it is optional, and what happens if omitted, but it does none of that.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Read) and resource (current PROGRESS.md state), and the parenthetical 'structured fields' hints at the return shape. However, it never distinguishes itself from the close sibling progress_regenerate, so the agent must infer the boundary.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use or when-not guidance at all. It does not say to prefer this over progress_regenerate, nor how it relates to the memory_* family that shares the same server context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
progress_regenerateB
Force-rewrite .ccm/PROGRESS.md from the SQL state.
| Name | Required | Description | Default |
|---|---|---|---|
| project | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It usefully discloses that the file is overwritten (force-rewrite) and that content is rebuilt deterministically from SQL state, which implies local edits to PROGRESS.md are lost. It does not cover permissions, idempotency, or failure behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence that names the action, target file, and data source with no wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
A mutation tool with no annotations and no output schema, whose sole parameter is undocumented in both schema and description. The agent lacks enough context to know when to call it or what to pass.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
One parameter with 0% schema description coverage, and the description never mentions 'project' at all. Since the schema leaves the parameter undocumented, the description should compensate but provides nothing, so the agent cannot tell what the argument represents or whether it is optional (required count is 0).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (force-rewrite) and resource (PROGRESS.md) with its source of truth (SQL state), which distinguishes it from the read-oriented sibling progress_get. It stops short of explicitly naming that sibling or the operation's boundaries.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no statement of when to invoke this versus progress_get, no prerequisites, and no indication of what triggers a needed regeneration. The agent must infer the use case entirely from the word 'Force-rewrite'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
8 tool updates
v2.18.0- First observed
memory_add - First observed
memory_get_details - First observed
memory_recent - First observed
memory_search - First observed
memory_stats - First observed
memory_topics - First observed
progress_get - First observed
progress_regenerate
TDQS
Scored across 8 tools
Search, recent, and topics all retrieve memories but differ by mode (full-text, recency, topic summaries), and descriptions clarify compact vs full details. Some overlap between memory_recent and memory_search could cause occasional misselection.
All tools use snake_case with consistent domain prefixes (memory_, progress_), and no mixed conventions. The pattern is predictable and readable.
8 tools is well-scoped for a memory plus progress management server, with each tool serving a distinct purpose. No tools feel redundant or missing at the count level.
Covers create (add with upsert), read (search, get details, recent, topics, stats), and progress read/regenerate, but lacks an explicit delete or archive operation. Agents can work around this via supersede behavior in memory_add.
Maintenance
Related MCP Connectors
Persistent memory for AI agents, for code, research and everyday life. Stale facts retire.
Persistent memory, hybrid search and a goal graph for AI agents, over stdio or remote HTTP.
Project memory for coding agents: requirements, decisions, code graph and delivery telemetry.
Persistent memory for AI agents. Search, store, and recall across sessions.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides AI coding assistants with persistent project memory to retain architectural decisions, code patterns, and domain knowledge across sessions. It stores data locally in a SQLite database, allowing agents to remember, recall, and manage project-specific context using full-text search.8 npmApache 2.0
- AlicenseAqualityAmaintenanceEmbedded, local-first agent memory: facts extracted into a per-namespace SQLite file (vec0 + FTS5) with hybrid retrieval and point-in-time (time-travel) queries. ADD-only history over stdio — no server process, no cloud dependency.753 PyPI43Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables AI coding agents to persist and retrieve project-scoped memories across sessions using local SQLite FTS5 keyword search.1MIT
- AlicenseAqualityBmaintenanceEnables coding agents to store, search, update, and delete durable facts, preferences, decisions, and plans in a shared local SQLite database, along with per-namespace profile get/set and stats. Uses offline lexical FTS5 search so multiple agents working on the same project can share one memory store.7MIT