keel-distill
Skill9 starsChanged 51 days agoMITView on GitHub
Consolidate project memory — rotate old HANDOVER blocks to the archive, promote critical facts to LESSONS.md, dedup/merge, and lint for contradictions. Run when caps are exceeded.
In this repository17 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- keel-adoptSkill
- keel-agent-team-createSkill
- keel-agent-team-startSkill
- keel-auditSkill
- keel-autopilotSkill
- keel-compactSkill
- keel-continueSkill
- keel-handoverSkill
- keel-phase-reviewSkill
- keel-pilotSkill
- keel-planSkill
- keel-researchSkill
- keel-statsSkill
- keel-teamSkill
- keel-tidySkill
- keel-updateSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 2 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Run when HANDOVER.md exceeds 3 session blocks / ~150 lines, when LESSONS.md exceeds ~250 or TASKS.md ~100 lines, or when any of them is …
- Distinctive: line 77
Verify done items were deleted (their one-liner lives in HANDOVER (a)).
- Very specific: line 93
A move is
git mv+ citation rewrite, one pass: Before moving, grep the repo for BOTH the full path and the bare filename. - Distinctive: line 104
Strip noise: delete VCS/ritual bookkeeping ("pushed N commits", sha ranges, "ran /keel-distill") and conversational meta ("user asked if I …
- Very specific: line 127
LESSONS
## Indexrouter mirror: every Index line's target file exists, and every graduated-lesson rule/skill/doc has its ONE Index line — …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
/keel-distill — the memory consolidation ritual ("sleep" for the project)
Run when HANDOVER.md exceeds 3 session blocks / ~150 lines, when LESSONS.md exceeds ~250 or
TASKS.md ~100 lines, or when any of them is over its KB cap (one long line can overflow a file
that is green on lines — the SessionStart hook warns on all of these), or every ~5 sessions as hygiene —
that cadence is yours to keep, the hook only detects cap overflows. Memory that is written but never
reviewed degrades the project — consolidation is what keeps it useful (rules.md §9).
Propose the full plan, get user approval, then apply. Never lossy-delete.
(File-TREE clutter — stray scripts, logs, dead code — is not this ritual's job: that is /keel-tidy, §3.10.)
1. Rotate HANDOVER blocks (oldest first, until ≤2 blocks AND under the line cap AND the KB cap)
For each block being rotated, triage by criticality — content-aware, not age-blind:
- (b) Tried, didn't work → PERMANENT: distill each to one
LESSONS.md [fail]line (<date> — <approach> → FAILED: <reason>. (full trace: docs/handover-archive.md, block <date>)). - Open questions live in HANDOVER's global section, not in blocks — leave them until resolved.
- (a) Completed → nothing to move: git history + the archive keep it.
- (c) Latest updates → superseded by newer blocks — nothing to promote; the verbatim archive keeps it.
- (d) Next steps of an old block → dead (that work is done or re-planned) — no promotion.
- Then move the WHOLE block verbatim to the TOP of
docs/handover-archive.md(prepend, newest first — restorable compression: the distilled lines keep pointers back to it).
2. Consolidate LESSONS.md (write-policy: add / update / retire / promote-and-delete — never LOSSY delete)
- Scope-triage every entry first (A/B/C/D — the LESSONS header defines the tiers): always-relevant
stays; file-scoped graduates (below); a permanent domain fact → docs; superseded/closed/already-
promoted → DELETE. Field measurement behind the triage: in a mature file ~52% of entries were
file-scoped and any one task needed ~15% of the file — the always-on core is the minority
(
reports/2026-08-17-lessons-scope-audit.md). - Merge duplicates and near-duplicates into the stronger phrasing (keep the earliest date).
- Start where the SessionStart hook pointed: above 70% of the cap it names the OLDEST unpromoted entries and the inflow since the last distill. Those are the triage queue — ask of each: still true → promote (below) · proven wrong → retire · resolved → delete. A file with an inflow valve and no worked exit sits at its cap forever (measured: three distills in four days, count still rising 123 → 129, not one entry ever retired).
- Retire what proved WRONG — move the entry VERBATIM to the top of
docs/lessons-retired.mdwith aRETIRED <date>: <what refuted it · what is true instead · evidence path>line, and leave ONE corrective line here ("believed X — measured Y → retired"). Never aSUPERSEDEDstub: the original text keeps paying rent in an always-loaded file for knowledge known to be false, while the one-line correction is what stops a session re-learning it. - Promote what has graduated: a lesson applied 3+ times is no longer a lesson — move it into
rules.md(conduct), a.claude/skills/skill (procedure), an ADR (decision), or — for a permanent DOMAIN fact (a data quirk, an API contract) — the relevant docs (docs/architecture.md"known limitations" / a guide). A lesson CLUSTER that binds only SPECIFIC files (a must-run test after touching X, an append-only dir, a never-hand-edit generated file) becomes a path-scoped rule —.claude/rules/<name>.mdwithpaths:frontmatter: it loads ONLY when a matching file is touched, so it is cheaper than bothrules.mdand this file — or apaths:-scoped SKILL (.claude/skills/<name>/SKILL.md, samepaths:format) when the cluster must SURVIVE a mid-task compaction: invoked skill bodies are the one scoped mechanism re-injected after compaction (≈5k tokens/skill, 25k total, truncation keeps the TOP — put the load-bearing lines first), while a path rule waits for the next file match. Never put must-always-hold discipline in either (that stays in LESSONS tier A / rules.md). The new rule/skill is shown as its FULL text in the distill plan and lands owner-approved, written self-contained — constraint + why + check inline, evidence pointing only to permanent artifacts, never to TASKS/PLAN live state (.claude/rules/README.md"Writing discipline"). - Promotion DELETES the LESSONS entry — lossless: the content lives in its target and git keeps
the history. Never leave a "moved to X" stub (field case: 10 promoted entries left 42 lines of
stubs, taxing every session). The ONE surviving pointer is a line in LESSONS
## Index(what → where → when it loads) — add or refresh it in the same pass. This is the main pressure valve on a long project's[gotcha]list: reference facts belong in docs, file-scoped stock behind its trigger — not in the always-loaded core. - COOL what is still true but no longer cited — the third exit, for the entry that is neither
wrong (retire) nor applied often enough to promote, yet earns no line in a file every session pays
for. Proxy criterion, labelled as one: is the entry cited from
## Indexor from any report / spec / ADR (grep -rlits key phrase)? Not cited → move it VERBATIM (with its date) todocs/retro/<YYYY-MM>.mdand leave ONE router line in## Index. Cooling is not deletion and not retirement (docs/retro/README.md). Field origin: a file stuck at 1000/1000 for weeks because the entry budget throttled the inflow while nothing opened an outflow; 82 of its 132 entries were single-copy knowledge, so the exit had to be worked per entry, never as a bulk sweep.
3. Prune TASKS.md
- Verify done items were deleted (their one-liner lives in HANDOVER (a)); delete any stragglers.
- Drain
## Discoveredto convergence (incl. project variants like## Discovered-team) — it is an INBOX, not storage: each line →## Next(real task, with done-when) ·docs/known-limitations / R-request doc (domain/provider fact) ·LESSONS.md(lesson) · an ADR (design decision) · DELETE (resolved/superseded — git keeps it). One line per entry; longer → a report/ADR + pointer here. A line surviving TWO distills is in the wrong file — after this step the section holds only the current week's untriaged finds. Refill## Now(max 3–5, per person on teams).
4. Sweep closed team reports to done/ (folder hygiene — orchestrator/owner only)
A long-running author folder drowns its open work: dozens of spec/fix files, no visual way to tell
finished from in-flight. The valve: a report whose task reached closed <date> accepted|rejected
in the index moves to its author's reports/team/<@tag>/done/ — the flat folder then holds only
live work, and "what is @X still carrying?" becomes a directory listing again.
- Only
closedmoves.wip · delivered · verifiedare in flight — on the board, cited from## Review(the reground hook checks those paths against disk), never swept. - A move is
git mv+ citation rewrite, one pass. Before moving, grep the repo for BOTH the full path and the bare filename; rewrite every hit (index line, LESSONS, ADRs, other reports) to thedone/path in the same commit. A move that breaks one citation is worse than the clutter — this sweep is the ONE sanctioned exception to §10.40's "never moved", and the rewrite is what keeps the exception honest. Evidence subfolders (<task>_<what>/) move with their report. - Stays in the index, same section. The
[x] closedline keeps its status and history; only its file path gainsdone/. Throughput counting is untouched — done/ is INSIDE the author's folder. - Same write authority as every shared-file transition: the ORCHESTRATOR/owner sweeps; workers never move files out of their own delivered set (§10.42).
5. Lint the memory set (drift check)
- Strip noise: delete VCS/ritual bookkeeping ("pushed N commits", sha ranges, "ran /keel-distill") and conversational meta ("user asked if I saved", "don't rush") from HANDOVER — it's git-log / ritual-log-derivable, and it's usually what pushed the file over cap (keep decisions + their WHY).
- Header hygiene (every memory file, not just HANDOVER). A header is DOCTRINE — how the file
works, written once, frozen. Move OUT anything that is state: a date or changelog line, a
measurement ("file is 202 lines"), a debt or pending decision (nobody triages a header, so it
becomes a permanent complaint — send it to
## Discovered, then a## Nextitem with a done-when), a cap NUMBER (write "cap:.claude/keel-caps" — a copy drifts from the file that sets it, which is why the stale-cap-header lint below exists), a chat quote. Also collapse teaching prose that merely restatesrules.md: it is paid in every session by an@-imported file and the two wordings drift — the detail lives indocs/memory-files.md. What EARNS a header line is a timeless, non-obvious invariant (field example: "this file is@-imported IN FULL — a lane heading is a WRITE boundary, not a context boundary"). Measured on the template itself: the three imported files carried 110 header lines, 55 after this rule. (The SessionStart hook flags dated lines and hard-coded caps in a header.) - Contradictions between
rules.md/LESSONS.md/CLAUDE.md— flag, ask the user which wins. - Decision/constraint drift (
docs/adr/+.claude/rules/): an Accepted ADR contradicted by a newer decision, a measurement, or the code as it now stands → PROPOSESuperseded (by ...)— the OWNER flips the status, never this ritual; a path-scoped rule whose constraint has been overtaken → propose rewrite or retirement the same way. Also verify each rule's/skill'spaths:globs still match existing files (a glob matching nothing = the rule silently never loads — a dead rule; brace expansions past the documented 1,000-pattern budget and malformed[brackets ALSO match nothing, silently), and the ADR README index still mirrors the folder. - LESSONS
## Indexrouter mirror: every Index line's target file exists, and every graduated-lesson rule/skill/doc has its ONE Index line — an orphan cluster is invisible to sessions whose trigger hasn't fired; a dead line hides lessons that still exist. (The reground hook flags dead targets at session start; the missing-line direction is checked here.) Same mirror check forreports/team/README.md: every INDEXED report — live status, or cited by a permanent record (the template's scope line) — has exactly ONE index line (an orphan = unfindable evidence; a duplicate diverges — field case: the same report indexed twice with two different status texts), statuses use only the controlled vocabulary (wip · delivered · verified — owner part: <…> · closed <date> accepted|rejected,[x]only at closed), and the mirror countsdone/files too — aclosedline whose path lacksdone/is a §4 sweep candidate, a non-closed line pointing INTOdone/is a bug. - Stale claims (files/commands/paths that no longer exist) — fix or mark superseded. Resolve bare BASENAMES against the tree before flagging: field measurement found ~54% of citations are basename-only and DO resolve — a naive path check reports them as false staleness.
- Language drift (rules §9.31): HANDOVER/LESSONS/worker boards are ENGLISH on every project. Convert only the lines this run already rewrites/merges/rotates (gradual, no bulk-translation pass: a mass rewrite risks meaning loss and buries the real diff) — quoted user wording stays as-is.
- Stale cap HEADERS in the memory files themselves (e.g. a LESSONS.md header still quoting an old
"~100"): correct to the current thresholds below — headers are prose, the SessionStart hook is the
authority; they drift in adopted projects because
/keel-updatenever touches PROTECTED memory files. - Cap check (solo defaults — the project's
.claude/keel-capsoverrides them, rules §10.40):HANDOVER.md≤ ~150 lines,LESSONS.md≤ ~250,TASKS.md≤ ~100,CLAUDE.md≤ ~200,rules.md≤ ~400 — of which the stock template is whatwc -l rules.mdsays on a fresh clone, so the project's own rules get the REMAINDER (rule budget §10.38 — measure it, never copy a number; merge/retire/promote to a hook, don't just append). Every file also has a KB cap (<FILE>_KB).
6. Report → approve → commit
Summarize: N blocks archived, M lessons added/merged/superseded/promoted, lint findings. On approval, apply + propose a commit (rules.md §6.15). Never push without user approval.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

