Skip to content

Design: Agent lesson corpus — shared, verified operational memory

The agent’s hard-won operational lessons — the traps, the silent-failure modes, the “this looks like success but isn’t” gotchas — are today scattered across eight machine-local memory stores with zero overlap, so any one session can read about 14% of what the estate has actually learned. This design moves those lessons into the version-controlled docs corpus where every session and every future agent can retrieve them, pushes a small set of generalised failure classes into every session so the warning arrives before the mistake, and repairs the nine directives in soul.md/CLAUDE.md that currently cite evidence which does not resolve. It amends ADR-0018, whose reference/feedback split was correct but assumed native memory was a single store.

The problem — 297 lessons, 8 stores, zero overlap

Section titled “The problem — 297 lessons, 8 stores, zero overlap”

Claude Code keys its memory store by project path. The cc-pool launches each session with its working directory at ~/sessions/cc-N, a plain folder that contains worktrees rather than being a repo itself, so the harness falls back to path-keying and each session slot gets a private store.

Measured 2026-08-12:

StoreLessons
/home/pit86
sessions/cc-160
sessions/cc-245
sessions/cc-342
sessions/cc-442
sessions/cc-518
/home/pit/.claude5
/home/pit/ansible3
Total297 files, 297 distinct names — no duplication

Zero duplication is the diagnostic finding: nothing is redundantly stored, so every lesson exists in exactly one silo. A trap learned expensively in one session is available to that session alone, and the same mistake can be paid for up to five times. Nine feedback-* citations in the operating doctrine resolve to nothing from a given session; two of them (reference-pit-memory-search, feedback-pve-needs-sudo) exist in no store at all — cited but never written.

Roughly a quarter of the corpus describes one recurring failure class: an empty, zero, or quiet result treated as a fact.

Decision tree — the resolved architecture

Section titled “Decision tree — the resolved architecture”
hybrid~12 failure classeshand-authoreddocs/feedback/re-routememory_write(kind=feedback)migrate all 297cite class, link instanceverify-on-use297 lessons · 8machine-local storeszero overlap · 9 deadcitationsStore of recordBodies docs corpusIndex pushed to everysessionPush payloadClasses always loadedinstances pulled on demandClass fileclaude-config repo, besidesoul.mdno cron · no watchdog · nobot commitsdrift caught by /wrapupreviewCorpus homeindexed · nav-excludedown .pages · stable URLsWrite pathone call · indexed on writeartifact asserted, not returncodecommitted at /wrapupreconciled vs files stagedBack cataloguetiered proof · evidence lineeachlive re-test partialcited incidentconsolidate: mergeduplicatesretire only where a guard isproven redDoctrine citationsrule arrives with itsreasoning in context2 orphan citations written orremovedFreshnessre-confirm + re-stamp whenacted oneffort follows usage, not asweepGolden path: write underone context retrievable under another
hybrid~12 failure classeshand-authoreddocs/feedback/re-routememory_write(kind=feedback)migrate all 297cite class, link instanceverify-on-use297 lessons · 8machine-local storeszero overlap · 9 deadcitationsStore of recordBodies docs corpusIndex pushed to everysessionPush payloadClasses always loadedinstances pulled on demandClass fileclaude-config repo, besidesoul.mdno cron · no watchdog · nobot commitsdrift caught by /wrapupreviewCorpus homeindexed · nav-excludedown .pages · stable URLsWrite pathone call · indexed on writeartifact asserted, not returncodecommitted at /wrapupreconciled vs files stagedBack cataloguetiered proof · evidence lineeachlive re-test partialcited incidentconsolidate: mergeduplicatesretire only where a guard isproven redDoctrine citationsrule arrives with itsreasoning in context2 orphan citations written orremovedFreshnessre-confirm + re-stamp whenacted oneffort follows usage, not asweepGolden path: write underone context retrievable under another
DecisionChoice madeRationaleRejected alternatives
Store of recordCorpus holds bodies; a class index is pushed to every sessionOnly option where a lesson learned in one session reaches the others and survives a control01 rebuildConsolidate native stores only (stays machine-local, un-versioned, fights upstream path-keying churn); corpus-only (converts every gotcha from a push warning to a pull lookup); do nothing
Push payload~12 named failure classes, not 297 pointersGeneralises to traps not yet made; keeps always-loaded context ~1–2k tokens instead of 15–20k growing unboundedAll 297 pointers (bloats the always-loaded file, the documented way an agent stops prioritising it); recency window (arbitrary — high-severity lessons age out); classes + domain slice (best precision, but needs scope inference that fails quietly toward under-loading)
Class fileHand-authored doctrine in claude-configThe class list changes ~monthly while lessons arrive ~6/week; generating it would add a scheduled job, a dead-man, and a bot with commit rights to the agent’s own operating instructionsNightly generation; generate-and-commit-by-hand (no trigger — run twice then never); fold into soul.md (mixes evolving list with stable doctrine)
Capture pathRe-route memory_write(kind=feedback) to the corpusFriction is what kills knowledge systems; 297 lessons exist because capture is one call. memory_write already routes by kind, so this repoints an existing routeDocs-as-code commit per lesson (edit-commit-push-wait mid-task, five sessions racing one index — realistically ends capture); nightly harvest; promote at /wrapup only
Corpus homedocs/feedback/, indexed, nav-excluded, own .pagesStable greppable home and a citable URL, without putting terse agent notes in front of a human readerReuse _drafts/ (conflates two lifecycles — a draft awaits promotion, a lesson never gets promoted); publish to the site (editorial cost kills capture); fold into the incident corpus (most are technique traps, not incidents — degrades incident search precision)
DurabilityIndexed on write, committed at /wrapupSibling sessions retrieve within seconds; commit rides a gate that already runs and already commits docs. No service account with push rights to estate documentationCommit per write (needs a container with push rights; five sessions racing commits); batch commit on a schedule (reintroduces the job + dead-man just deleted); shared un-versioned directory
Migration scopeAll 297, tiered proof, evidence line on eachPreserves the whole asset. Full live re-verification is not literally achievable — several lessons were learned by breaking productionVerify-what’s-safe-and-drop-the-rest (systematically deletes the highest-severity lessons because they were expensive); lab reproduction (own Epic); re-trigger everything (deliberately re-breaks the estate)
ConsolidationMerge duplicates + retire-if-guarded, guard proven red firstThe guard becomes the memory — toil elimination applied to knowledge. The proof requirement stops this silently deleting live knowledgeMerge-only (leaves notes for structurally impossible traps); rewrite to 12 class docs (specifics are what make a lesson actionable — wise and useless); retire anything not hit twice (most are one-shot by nature; “never recurred” usually means the note worked)
Doctrine citationsCite the class, link the instanceThe class is already in context, so the rule arrives with its reasoning attached and costs no retrievalPlain URLs only (every citation is a pull that does not happen); keep wikilinks (private format, not clickable, not valid markdown); strip citations (a rule with no incident behind it reads as arbitrary and gets dropped)
FreshnessVerify-on-use, re-stamp the evidence dateEffort follows actual usage; re-verifying dormant lessons is patching a machine nobody usesScheduled sweep (toil over a mostly-dormant store); expiry by class (shelf lives are guesses; false staleness trains the warning to be ignored); nothing
Secret controlExisting gitleaks CI gate onlyArron’s call — one control, no second ruleset to driftShift-left scan at write time (rejected: see residual risk)
Golden pathCross-context canary in pit-memory’s existing testplan.yamlOnly an assertion that crosses contexts can fail if fragmentation returnsSame-context write/read (passes today, before anything is built — satisfiable without the feature); separate test plan (needs its own scheduled run + dead-man); verify once at handover
RequirementHow this design satisfies it
Monitoring (Pillar 1)Inherited — pit-memory is an existing monitored service. No new host or container is introduced.
Observability (Pillar 2)Inherited dashboard and Loki shipping. The one genuinely new signal is cross-session retrievability, made observable by the canary rather than by a metric nobody reads.
Alerting (Pillar 3)Deliberately none added. The failure mode — silent capture failure — is caught in-band by asserting the written artifact, and again by the /wrapup reconcile. A capture-rate dead-man was rejected: capture rate is behavioural, so the threshold is either too loose to fire or too tight and gets muted.
SBOM / vulnerability (Pillar 4)Inherited — pit-memory ships a Dockerfile, so its base image and requirements.txt are already Renovate-tracked and Trivy/Dependency-Track scanned. No new image.
Documentation (Pillar 5)This design doc, the ADR amending ADR-0018, the docs/feedback/ tier definition, and the front-matter schema.
Software update & patch lifecyclePattern: the pit-memory service follows the built-image path — base image + requirements.txt pinned in-repo, bumped by Renovate, deployed by the docker-stacks-* pipeline with the pinned tag in Git as the rollback handle. Trigger/cadence: Renovate update PRs, continuous. Gate: the stack’s post-deploy testplan_gate.py run — now including the cross-context canary, so a bad bump fails on the feature and not merely on readiness. Staleness detection: Trivy scan + the Dependency-Track project already covering the image. The knowledge-domain analogue — a lesson going stale — is handled by verify-on-use rather than a scheduled sweep: a retrieved lesson past its age threshold is re-confirmed and re-stamped before being acted on.
Config as code (RULE 7)Every artifact is in a git repo: lessons and the tier in pitlab-docs, the class file and doctrine in claude-config, the service change in docker-stacks. Nothing is manual-only.
Planned maintenanceNot applicable — no service outage is planned. A pit-memory redeploy is a rolling container restart on docker01 with no dependent alerting.
NotificationsNone added. This design emits no alerts and sends no email.
Tagging (RULE 10)Front-matter kind: feedback, failure class, evidence tier and evidence date on every lesson; docs tag vocabulary extended; the grilled tag on the Epic.
Enterprise patternThis is knowledge management as code — the discipline a mature SRE org realises through a shared postmortem corpus and runbook library rather than tribal memory. The push/pull split mirrors a hot doctrine layer over a cold retrieval layer; verify-on-use is demand-driven revalidation rather than scheduled revalidation.
DORA / SRE delivery performanceDeployment frequency and lead time come from the docker-stacks pipeline data already collected for pit-memory. Change failure rate is the post-deploy gate’s red rate, now sharpened because the canary tests the feature rather than readiness. MTTR is not newly instrumented — accepted, since no user-facing outage class is introduced. Reliability contract is inherited from pit-memory’s existing SLO; this design defines no new error budget, which is a conscious gap recorded below.
Minimal decomposition3 child Issues → ~3–4 prompts. Splits only where a hard dependency forces ordering.
Control / decisionFramework mappingHow tested / evidencedResidual risk
Lessons held in an Access-gated corpus (docs.pitbun.com, Require EntraID policy)CPS 234 §26 control implementation · NIST CSF Protect (PR.AA) · ISO 27001 A.5.15 access controlVerified against tf-cloudflare apps.tf; continuously re-tested by the external attack-surface scanNone material — the corpus is internal-only
gitleaks secret scan on the docs repoCPS 234 control testing · NIST CSF Protect (PR.DS) · ISO 27001 A.8.12 data leakage preventionRuns in the docs pipeline and in the pre-commit hook; proven on every pushAccepted (Arron, 2026-08-12): detection happens at CI, not at the keyboard. A leak reddens the shared docs pipeline, blocking concurrent sessions’ publish until fixed. Shift-left scanning at write time was considered and declined to avoid a second ruleset
Artifact asserted on write; /wrapup reconcileCPS 234 control testing · NIST CSF Detect (DE.CM)Fails loudly at capture; reconcile compares captured-vs-stagedA session ending abnormally loses that session’s uncommitted lessons — bounded to one session, and visible as uncommitted files
Version control + PBS coverage of the corpusCPS 230 business continuity · NIST CSF Recover (RC.RP) · ISO 27001 A.8.13 backupGit history plus the existing pitlab-docs backup pathRestore is proven-by-construction, not drilled — recorded as a known gap
Cross-context canary in the service test planCPS 234 §26 systematic control testing · NIST CSF Detect · ISO 27001 A.8.29 security testingRuns on every deploy through testplan_gate.py; proven red before trusted greenNone — this is the strongest control in the design
No new external dependencyCPS 230 material service provider riskAll components are self-hosted; no third party is introducedNone — no exit plan required
Design authority: grilled + approved before buildNIST CSF Govern (GV.OC) · ISO 27001 A.5.1 policyThis document, the grilled tag on Epic #2382, and the ADR-0372 carve-out that authorised itNone

Data classification. Lessons are internal operational knowledge — no personal data, no customer data, no residency constraint. They may legitimately reference internal hostnames and API shapes, which is acceptable inside an Access-gated corpus. Secret values are prohibited by authoring rule; a lesson references where a secret lives, never its value.

Service record. No new service. This extends pit-memory (existing docs page, existing NetBox record, existing criticality tier). Dependencies: pitlab-docs (source of truth), Qdrant and the SQLite FTS index (retrieval), docker01 (runtime). No new material service provider.

Reliability contract. Inherited from pit-memory. The new SLI worth naming is cross-session lesson retrievability — a lesson written under one project context is retrievable under another. It is asserted by the canary on every deploy rather than continuously measured; no separate error budget is defined. This is a conscious gap: the failure is not user-facing and degrades silently rather than dangerously.

Runbook. The failure modes a cold reader must be able to handle: capture refuses (assert the artifact, check the corpus path is writable); a lesson is not retrievable from a sibling session (check the reindex ran, check the file committed); the class file has drifted from the corpus (caught at /wrapup, re-author the class). These land in the pit-memory operations page rather than a new runbook.

Test strategy.

  • Throughout delivery — the mechanism issue closes only when a lesson written under one project context is demonstrably retrieved under another, shown live. The migration issue closes only when every migrated lesson carries a class and an evidence line, and every retirement names a guard proven to fire. No sub-issue closes on “applied successfully”.
  • End-to-end — the cross-context canary added to stacks/pit-memory/testplan.yaml. Golden path: write via memory_write under context A, assert retrievable by content under context B, assert the file exists on disk. Failure action: the stack has a revertable pinned-tag deploy, so auto-rollback.
  • Triggers — inherited from the existing plan: post-deploy pipeline gate, the /vulnscan post-remediation close-gate, and on-demand /verify.
  • Maintenance obligation — any future work touching pit-memory or the feedback tier runs this plan first as a regression baseline and updates it in the same change when behaviour changes.

Day-2 BAU and toil. Deliberately near-zero: no cron, no watchdog, no scheduled sweep, no bot commits. The only recurring obligations are verify-on-use (paid inside work already happening) and the /wrapup class-gap review (inside a gate that already runs). This handover does not increase Arron’s manual load.

Backup and restore. The corpus is git-backed and covered by the existing pitlab-docs backup path. Restore is a clone. Recorded honestly as proven-by-construction, not drilled.

Standing owner. pit-memory is already picked up by /health, /vulnscan (Dependency-Track project + Trivy target), Renovate (base image + requirements.txt), and the synthetic test-plan gate. The class taxonomy is owned by /wrapup, which reviews whether a session’s lessons revealed a class that does not yet exist. Nothing in this design is unowned.

Exit. If the corpus tier is ever retired, lessons remain readable markdown in git — there is no proprietary store to escape. The native-memory path stays available as a fallback.

3 child Issues → ~3–4 prompts. Ordering is forced twice: migration needs the classes and the corpus tier to exist, and the doctrine rewrite needs the migrated URLs.

#IssueContents
1Mechanism (#2383)Author the ~12 failure classes; create docs/feedback/ + .pages + front-matter schema; re-route memory_write(kind=feedback) with artifact assertion; /wrapup commit, reconcile and class-gap review; cross-context canary in testplan.yaml, proven red; ADR amending ADR-0018
2Migration + doctrine (#2384)Migrate all 297 at tiered proof with an evidence line each; merge duplicates; retire only against a proven guard; rewrite soul.md/CLAUDE.md citations to cite-class-link-instance; resolve the 9 dangling citations
3Handover / PRR (#2385)Canary green through its real trigger; capture path exercised end to end; silent-capture-failure proven to fail loudly; verify-on-use demonstrated; standing owners named; early-life-support window defined and the Epic parked in Hypercare

Known thin spots, carried into delivery: the reliability contract is inherited rather than defined; the restore path is proven-by-construction rather than drilled; and the twelve failure classes do not exist yet — authoring them is the judgement-heavy first task, and if the taxonomy is wrong the push payload is wrong with it.

Stamped 2026-08-12 at Epic close (#2382). Twelve of the fifteen design decisions were implemented exactly as agreed. Three changed during delivery, and two things were delivered that the design never scoped. Every one is recorded below with its reason and its governing record.

Decision as designedWhat was builtWhyRecord
Migrate 297 lessons from 8 storesMigrated 277 lessons from 9 stores → 255 pages after consolidating 28 duplicates into 24The planning-time inventory counted files including each store’s MEMORY.md, missed the store keyed -, and its own rows summed to 301. Re-measured at migration time. The decision is unaffected — fragmentation and zero overlap both holdCorrection notes in this doc and ADR-0392
The class field carries only failure-class slugsTwo non-trap kinds added — standing-directive (54) and environment-invariant (52)106 of the migrated pages describe no self-deception at all. Forcing a trap slug onto 43% of the corpus would have made the field meaningless. Both are deliberately kept OUT of the always-loaded fileLesson corpus tier definition
Push payload is failure classes onlyAlso carries 12 standing directives, one line + link eachArron’s ruling at close-out. A directive that lives only in the pull layer cannot work — the agent will not search for a rule it does not know exists, which is ADR-0018’s own argument for a push layer, applied to directives rather than gotchasADR-0392 Consequences; claude-config efb0084
Reliability contract inherited, no SLI or error budget — recorded as a conscious gapTwo SLIs named with an error-budget policy, on the existing dashboard panelThe gap was acceptable at design time and cheap to close at handover. Closing it is what makes the hypercare exit criteria testablepit-memory production readiness
(not scoped)Retrieval-index chunk cap + catalog exclusionAcceptance queries surfaced a single 636,832-character chunk degrading recall corpus-wide, not just for lessons. Fixed rather than carried, because every session depends on itCorpus Retrieval Standard
(not scoped)Manual-gate-run rule in the Service Test Plan StandardProving the canary red required running the gate as CI does, which paged and attempted a live revert of another session’s commit. The gap was in the standard, not the instanceADR-0102 Consequences; standard #manual-gate-run

The three thin spots the design carried into delivery, resolved: the reliability contract is now defined rather than inherited (above); the restore path was drilled, not assumed — a fresh git clone --depth 1 yielded 255 lesson pages, content spot-checked; and the taxonomy landed at 14 classes rather than the estimated twelve, with the two non-trap kinds held in the pull layer.

Nothing was dropped. No design decision went unimplemented.