M0 — pattern validation protocol
Status: complete (2026-07-13 → 2026-07-22; results below and in
demo.md). Goal per ROADMAP.md:
prove the shelve→recall loop on real work with zero new code — docshelf
as-is + the prompt-only kit in adapters/claude-code/.
Kit
| Piece | File |
|---|---|
/shelve skill (live + import modes) |
adapters/claude-code/skills/shelve/SKILL.md |
| Recall rule for CLAUDE.md / project prompt | adapters/claude-code/CLAUDE-md-snippet.md |
| This protocol | docs/M0.md |
Setup
- Shelf repo. Design default is
git-local(no remote), but M0 runs in cloud sessions where containers are ephemeral — so the M0 shelf uses a private remote (thegit-remotemode): a dedicated private GitHub repo, added to the session. Because the container is ephemeral the push is not optional: every shelve pushes right after its commit (a/shelvestep), or the committed episode dies with the container — the loss mode recorded below. The M1SessionEndhook (#11) will automate it; until then it is manual per shelve. - Init via docshelf:
init_shelf(name=…, default_categories=["topics", "research", "sessions"]),provider: none(relative links; recall goes over MCP/file reads, not raw URLs). - Policy file. Drop a
POLICY.mdin the shelf root stating the PII rules for this shelf’s domain. For sqst material: no student names, nicks, emails, or identifiers — roles and codes only («студент», C1..C7 / S1..S15), consistent with the course PII policy. - Copy the recall-rule snippet into the consuming project’s CLAUDE.md and
the skill into
.claude/skills/shelve/.
Case A — retro-import: the sqst homework-review dialog
Candidate: the author’s long homework-review chat (Opus 4.7 era, sqst course). This is the import mode of the skill:
- The raw transcript is provided into the session only (file upload or pasted path). It is never committed to any repo, shelf included.
- The agent proposes a segmentation (episodes per review batch / recurring
problem theme / methodology decision, plus one
kind: sessiondigest for the whole dialog), with rough token weights. Author confirms. - Episodes are composed depersonalized-by-construction (POLICY.md), written
via
add_document, ledgered asmode: import, committed. - Output: INDEX.md whose digests summarize months of review practice in a few KB.
Why this is a good M0 case: it’s real, it’s long (way past any context window), it has known-answer questions, and it stress-tests the PII posture — the exact thing black-box memory stores can’t demonstrate.
Case B — live shelving
In a working session on a real project: apply the recall rule + /shelve at
natural topic closures for ~a week of normal work. This exercises the cut
proposals, digest quality under time pressure, and the “replace in context”
convention.
Measurement — the token ledger
Every shelve appends to <shelf>/ledger.tsv:
date episode_id mode approx_tokens_in digest_tokens notes
approx_tokens_in— what the episode was costing in-window (chars/4 at shelve time; import mode: segment size in the transcript).digest_tokens— what remains in context per episode after shelving.notes— free text, and the one field with a hard constraint: no tabs (shelf-spec v0 § 4.4). It is the last column, so a tab there shifts the field count for anything parsing the file. Appending a row by hand, keep it tab-free; theshelvetool flattens tabs and newlines to spaces and warns.
Derived numbers (computed with the same tokenizer methodology as docshelf’s
benchmarks/token_savings.py):
- Standing cost = tokens(INDEX.md) + Σ digest_tokens — what memory costs every session.
- Shelved mass = Σ approx_tokens_in — what would otherwise ride in context (or be lost).
- Compression ratio = shelved mass / standing cost.
- Recall cost per question = tokens(INDEX) + tokens(fetched sections) vs. baseline = tokens(full transcript). This is the headline docshelf-style number.
The recall test
After Case A import, ask (in a fresh session with only INDEX + recall rule) five known-answer questions of the form “what did we decide about X and why” / “which recurring mistake did students make in HW-N” / “what was rejected and for what reason”. Score: correct & complete answers, sections fetched, tokens spent per answer.
Exit criteria (from ROADMAP)
- 5/5 recall questions answered correctly via INDEX navigation.
- Ledger + recall-cost numbers written down (→ future
docs/demo.mdof this repo, mirroring docshelf’s demo). - Annoyance log — every manual step that hurt (validation done by hand, ledger bookkeeping, category mapping, redaction misses…). That list is the M1 backlog, verbatim.
Results so far (2026-07-13, Case A round 1)
Imported: two chat-export fragments (sqst homework-review tail ~15 KB; personal hardware thread ~28 KB) → 8 episodes (5 topics, 1 research, 2 sessions), ~11K source tokens shelved, INDEX = 3.6 KB (~0.9K tokens). PII gate passed review (sqst-memshelf#1, merged).
Recall test: 5/5 correct and complete. Fresh-context agent, given only the INDEX path + recall rule, answered five known-answer questions (docx root cause + generator fix; student-B’s three config decisions; both students’ open minors; laptop verdict + customs math; Mac Studio imbalance + replaced plan). Navigation was exact: 6 files read (INDEX + 5 episodes), zero misses, zero over-fetch, ~21 KB total. Two questions were answerable from INDEX alone but the agent fetched for the requested specifics — correct behavior.
Cost sketch (chars/4 estimates, proper tokenizer pass pending): per question ≈ INDEX (0.9K) + one episode (~1–1.5K) ≈ ~2–2.5K tokens vs ~11K+ for carrying both fragments in-context — ~5× per question already on a tiny 8-episode shelf. The ratio grows with the archive: INDEX stays ~1K tokens regardless, and the real source chat (full export: 87 MB) does not fit any context window at all — same shape as docshelf’s RouterOS result.
Results — Case A rounds 2–3 (2026-07-13) and Case A close
Round 2 (full-export extraction batch): a fresh-context reviewer agent triaged six by-catch conversations (WORTH/MARGINAL/THROWAWAY) → 3 episodes shelved (IPv6 homelab plan; docshelf origin story — two same-day chats merged into one arc; mentee GitOps review with a live must-fix list), 2 skipped by verdict, ~40K source tokens (sqst-memshelf#2, merged).
Round 3 (Cowork session JSONLs, 1.5 MB + 201 KB): the subagent-deposit pattern validated in production — fresh-context agents read the raw transcripts, returned depersonalized structured digests, episodes composed from those; my context never touched the raw dumps. 6 episodes: four runbook blocks (artifact regen / snap rehash / blob recreation with the divergent-history catch / filter-repo PII purge), a session digest, and the vectors-drawing+lull bootstrap. ~24.8K conversational source tokens (raw JSONL ~375K with tool outputs) (sqst-memshelf#3).
Shelf after round 3: 17 episodes. Benchmark
(benchmarks/token_savings.py, chars/4 — tiktoken vocab blocked by the
environment proxy; ratios are estimator-independent): INDEX = 1,370
tokens, whole shelf 7,995, query (INDEX + 1 episode) = 1,765 tokens
— 77.9% cheaper than dumping the shelf, and ~97% cheaper than the ~76K
conversational source tokens the shelf now represents (which themselves are
a fraction of the raw sessions).
The strongest M0 finding was accidental. The actual homework-review
season transcript (April–June) no longer exists anywhere: not in the
claude.ai export (it was a Cowork session), not in ~/.claude/projects
(rotated away). The only surviving trace is what the author happened to
copy by hand — the exact loss mode memshelf exists to prevent. “Memory that
isn’t shelved while the context exists is memory lost” stopped being a
design slogan and became an observed fact within the first day of M0.
Case A: CLOSED (source material exhausted). Remaining for M0 exit: Case B — a week of live shelving in working sessions.
Results — Case B (2026-07-13 → 2026-07-22) and M0 close
Case B ran the planned week of live shelve-at-close on real work. Ledger at
close: 33 episodes (16 live + 17 import backfill), 1.91M source tokens →
5.7K digest tokens (334:1 overall, 565:1 live-only), with individual live
sessions of 160–220K tokens each — and zero episodes lost. The founding
invariants held (shelve-at-close, push-in-ephemeral-session, INDEX-first
recall, PII gate); the one-shelf-vs-multi question resolved de facto toward
per-domain shelves. The full verdict is a research episode on the shelf
(2026-07-22-case-b-verdict); the measured write-up is
demo.md. M0: CLOSED — exit criteria met. The annoyance log
below became the M1 backlog, and the M1 tool surface now enforces what this
protocol did by hand.
Annoyance log
| # | What hurt | M1 implication |
|---|---|---|
| 1 | .meta.json hand-patched to get Russian display titles over latin date-slugs — add_document couples title→slug |
memshelf_shelve takes slug and display_title separately (or upstream a docshelf slug= param) |
| 2 | Ledger rows appended by hand, token math by hand | memshelf_stats + shelve writes the ledger row itself |
| 3 | Digest/schema contract validated “by agent honor” — nothing enforced length/sections | tool-side validation as designed |
| 4 | Domain mixing: sqst + personal episodes in one shelf (INDEX interleaves them) | confirm one-shelf-per-domain; memshelf init cheap enough to make second shelves frictionless; federation stays open question |
| 5 | docshelf default INDEX preamble talks raw-GitHub-URLs — had to replace with the recall rule manually | memshelf init sets the memory preamble |
| 6 | 87 MB conversations.json can’t pass through MCP context transfer |
memshelf_import must take a file path / URL, never content-in-context; chunked local processing |
| 7 | Clone-of-empty-repo left git without a fetch refspec — bit twice (memshelf-mcp, sqst-memshelf) | environment quirk; document in shelf bootstrap notes |
| 8 | Name-based extraction from the export missed the target chat entirely (it matched by title, not content) | memshelf_import discovery must search content markers, not conversation titles |
| 9 | Session transcripts rot: the review-season Cowork session was already rotated out of ~/.claude/projects — unrecoverable |
shelve-at-close (SessionEnd hook) is not optional; retro-import cannot be the primary path |
| 10 | Raw session JSONL is ~94% tool-output noise (375K raw vs 22.5K conversational) | import pipeline must strip tool blocks before any LLM reads the transcript |