Skip to main content
Glama

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.

version license python dependencies release gates platform


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 NOTES.md

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 PROGRESS.md, a transcript pointer into the previous session

The scope rule (cli.py off-limits) and the bug-fix ask

The rule survives only as an inference ("cli.py needed no edits"); that the bug fix was part of the task is gone entirely

Both stated back: "cli.py was deliberately not touched, per your request", and the fix flagged as still missing a regression test

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 ValueError, "verified at tally/store.py:38"

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 /checkpoint before 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 raises ValueError("amount must be non-negative, got {amount}") (verified at tally/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

export_json()

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

rm -rf legacy/

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 unreadable config.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 (--list prints them), every one driven red individually before being kept.

  • Documentation is under the same gates as code. Every file.py:LINE citation 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

claude /plugin marketplace add skymanbp/cc-memory
claude /plugin install cc-memory

Install — from a local checkout

git clone https://github.com/skymanbp/cc-memory.git
claude /plugin marketplace add ./cc-memory
claude /plugin install cc-memory

Install — standalone, no marketplace

python cc-memory/cc_memory/ui/installer.py          # GUI
python cc-memory/cc_memory/ui/installer.py --cli    # headless

Or 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 status

To 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 seconds

Deep 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 memories

Six 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, plan-status and the drift guardian all answered from the previous plan; a demand stated 6× reached zero implementation (lore_disaster, 2026-08-15)

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 (Autoshop, 2026-08-25)

v2.12.0: plan-set audits every active directive on replacement and names each reference DEAD or SILENTLY RETARGETED; the carryover gate itself had already refused two malformed replacements in the same session

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 <private> tags

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 + os.replace)

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

memory_search

FTS5 search, compact results

memory_get_details

Batch fetch full details by id

memory_add

Add through the anti-patch writer

memory_stats

Project statistics

memory_topics

Topic summaries (bounded)

memory_recent

Recent memories with filters

progress_get

Read the PROGRESS state as structured fields

progress_regenerate

Force-rewrite .ccm/PROGRESS.md from SQL

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

version

the current release (the version gate asserts it equals core/version.py)

Last-resort fallback for a flat install predating core/version.py, which is canonical

consolidation.auto_interval_sessions

5

Sessions between async consolidation runs (the backlog trigger is independent of this — its thresholds are module constants in core/consolidate.py)

ccl.enabled

false

Local Ollama fallback — opt-in

ccl.ollama_url

http://localhost:11434

Ollama endpoint

ccl.local_model

ccl-9b

Local model name

excluded_projects

[]

Absolute paths that opt OUT entirely. The only opt-out; there is no per-project override file

notes

—

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

ANTHROPIC_API_KEY

Preferred credential; falls through to the Claude Code OAuth token when absent or dead

CLAUDE_PROJECT_DIR

Consulted by the project-root resolver when it names a directory in the ancestor chain

CC_MEMORY_PLAN_ENFORCE=0

Kill switch for Stop-hook plan enforcement

CC_MEMORY_LOG_LEVEL

Log verbosity under ~/.claude/hooks/cc-memory/logs/: DEBUG, INFO (default), WARN, ERROR or SILENT; an unknown value means INFO

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

UserPromptSubmit

hooks/user_prompt.py

8 s

Auto-init .ccm/, count the turn, seed the first real request once per session, then query-time recall on stdout (v2.15.0), preceded by any plan advisory the previous Stop parked (v2.16.0)

PostToolUse

hooks/post_tool_use.py

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)

Stop

hooks/stop.py

22 s

Detached Haiku observer (v2.16.0), per-turn PROGRESS patch, idle reorg every 5 turns, backpressure probe, plan enforcement

PreCompact (sync)

hooks/pre_compact.py

120 s

Extract → reconcile → full-rewrite PROGRESS.md → archive

PreCompact (async)

hooks/consolidate_async.py

300 s

Budget-gated consolidation, off the blocking path; also the standalone backpressure worker

SessionStart

hooks/session_start.py

15 s

Inject layered context + the forced <system-reminder>, held under Claude Code's 10 000-character hook cap (v2.17.0); start the detached retroactive-save worker (v2.16.0)

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

注入(inject)

SessionStart: the memories, directives and PROGRESS digest loaded, and the size

裁剪(trim)

SessionStart: entries cut to fit Claude Code's 10 000-character cap

续接(resume)

a resumed session: the directives restated

存档(save)

the save before a compaction — shown by the next SessionStart, because Claude Code does not display PreCompact's line

整理(consolidate)

a background consolidation finished, or was started on a write backlog

提炼(extract)

the background observer turned this turn's tool calls into memories

补存(retro-save)

earlier sessions that were never compacted were saved

闲时整理(idle)

the idle reorg changed something

召回(recall)

memories matched your prompt (full only)

计划捕获(plan) / 计划同步(plan)

a plan captured from plan mode; a todo list synced to its steps

建库(init)

the project's first use

警告(warn)

an injection still over the cap

失败(error)

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 (简体中文):

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 is

Or 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 RED

Tests 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.exe

The 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

hooks/hooks.json invokes python3, and the python.org installer ships no python3.exe — the py launcher does not provide one either. Install with "Add Python to PATH", then alias or shim python3 → python

/cc-mem says the plugin is not found

Both install layouts must be probed. Run /cc-mem status — it inspects the layout and reports which files are missing

Nothing is being extracted

No credential. /cc-mem status checks it. Log in to Claude Code, or set ANTHROPIC_API_KEY

Where is the database, actually?

/cc-mem paths prints the resolved DB / PROGRESS.md / PLAN.md / MEMORY.md with exists/absent verdicts — do not hunt with a recursive glob; the first *.db it finds may belong to another tool

CJK output shows as �

The capturing shell decoded UTF-8 with its own codepage (PowerShell 5.1 uses the console codepage). Use --json — pure-ASCII output that no capture codec can garble

A .ccm/ appeared in a subdirectory

A stray from before root anchoring. /cc-mem status lists every separate database below the project root with its memory count, and names the remedy on each row: /cc-mem adopt-stray <dir> shows what it would merge, --yes merges it into the project and moves the stray under .ccm/.adopted/ (never deleted). Hooks never merge on their own — pin a genuinely nested project with a .ccm-root file. A session in a linked git worktree of an initialised checkout writes to the checkout's database (v2.17.0), so a worktree no longer starts one of its own

The plugin went completely silent

A config.json that exists but cannot be parsed fails closed and excludes every project. SessionStart prints one line saying so; fix the JSON

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; CC_MEMORY_PLAN_ENFORCE=0 switches it off

A directive keeps blocking but it's waiting on me

/cc-mem directive-edit <slug> --status blocked parks it (idle enforcement skips it); --status active un-parks. A "never do X" rule should be --kind constraint — it is never idle-checked at all

Hook cancelled on compaction

Fixed in v2.3.2 by moving consolidation to an async leg. If you still see it, file an issue with .ccm/.last_save.json

A memory is simply wrong

/cc-mem archive <id> — reconciliation handles a restatement, archive handles a repudiation


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 / #N shapes; 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.py and 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. (A verbatim quote 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.md is 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 in CHANGELOG.md § Recorded, not redesigned.

  • Candidate future work: surfacing inject-usage signals in the SessionStart status line; a directive-* surface in the dashboard; richer paths-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 supersedes walks 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/^0 session 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 field systemMessage, which Claude Code shows the user and does not send to the model, so nothing new enters the context. /cc-mem announce full|batch|off sets it per project: batch drops the per-prompt recall line, off restores 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; --yes moves 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: python3 must 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

README.md · README.zh.md

What this is, what it does, how to use it

docs/ARCHITECTURE.md · .zh

Module map, data flow, install layouts, the i18n convention

docs/CONTRACTS.md · .zh

The three hard contracts, in specification form

commands/cc-mem.md

Every /cc-mem subcommand, with semantics

CLAUDE.md

The operating manual for Claude Code working on this repository

INVARIANTS.md

The numbered rules a change must not break, each with its gate and falsification case

CHANGELOG.md

The complete version history

docs/debug-pass-2026-09.md

The 2026-09 debug pass's evidence record (English only, never edited)

demo/README.md

How the before/after captures were produced and what they redact

CONTRIBUTING.md · SECURITY.md

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 tools
memory_addC

Add a memory via the anti-patch upsert path (merge / supersede / insert depending on similarity to existing).

ParametersJSON Schema
NameRequiredDescriptionDefault
topicNo
contentYesMemory text, at least 10 characters after trimming (shorter content is dropped by the writer, not stored)
projectNo
categoryYes
importanceYes

TDQS

C2.9/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness2/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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".

ParametersJSON Schema
NameRequiredDescriptionDefault
idsYes
projectNo

TDQS

B3/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
projectNo
categoryNo
sessions_backNo
min_importanceNo

TDQS

C2.7/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness2/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_statsC

Get project memory statistics.

ParametersJSON Schema
NameRequiredDescriptionDefault
projectNo

TDQS

C2.7/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness2/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
projectNo

TDQS

A3.7/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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).

ParametersJSON Schema
NameRequiredDescriptionDefault
projectNo

TDQS

C2.7/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness2/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
projectNo

TDQS

B3/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness2/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

  1. 8 tool updatesv2.18.0
    • First observedmemory_add
    • First observedmemory_get_details
    • First observedmemory_recent
    • First observedmemory_search
    • First observedmemory_stats
    • First observedmemory_topics
    • First observedprogress_get
    • First observedprogress_regenerate

TDQS

B3.4/5.0

Scored across 8 tools

Disambiguation4/5

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.

Naming Consistency5/5

All tools use snake_case with consistent domain prefixes (memory_, progress_), and no mixed conventions. The pattern is predictable and readable.

Tool Count5/5

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.

Completeness4/5

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

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides 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 npm
    Apache 2.0
  • A
    license
    A
    quality
    A
    maintenance
    Embedded, 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.
    7
    53 PyPI
    43
    Apache 2.0
  • A
    license
    A
    quality
    B
    maintenance
    Enables 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.
    7
    MIT