Mechanism 4 of 4 · Memory & the learning loop

A model that repeats itself is not a model that is right.

The absence this makes loud: a lesson nobody vouched for.

Agent-written memory is telemetry, not truth. Left alone it becomes a pile of confident assertions that the next agent will read as established fact — and the cheapest way to get a wrong one promoted is simply to say it twice.

Ingest

It reads what already happened.

You don't write these lessons. The loop reads harness transcripts — Claude Code's JSONL today, behind an adapter protocol so other harnesses plug in — scrubs them at extraction time, and turns episodes into candidate memory shards.

More of the work here than you'd expect is refusing to learn from noise. Harness-injected pseudo-user records — system reminders, hook output, compaction summaries — look exactly like things a person said; in the real corpus they were 19 of the 42 surviving messages. A malformed line is a warning and a skip, never a dead run, and the watermark that tracks progress is owned by the adapter and opaque to everything else.

The unit is the episode, not the event. What's worth remembering is almost never a single message — it's a thing that was tried, went wrong, and got corrected.

Promotion

The reasons to say no are counted before the reasons to say yes.

A candidate shard climbs a ladder to become a published lesson. The ordering of that ladder is the mechanism, not an implementation detail: vetoes are evaluated first, and a veto is not outvoted by evidence.

Candidate shards pass two vetoes before any acceptance condition is considered, then need either recurrence across distinct candidates or corroboration from an already-vetted shard. transcript (JSONL) scrub candidate shard vetoes — evaluated first resembles a shard you already rejected ≥ 0.85 → rejected near-duplicate of a published shard ≥ 0.95 → rejected a veto is not outvoted by evidence accept if either holds recurs across ≥ 2 candidates from distinct sources — or — corroborated ≥ 0.88 by an already-VETTED shard published lesson
The cheap way to get a lie promoted is to say it twice — so the ladder counts the reasons to say no before it counts the reasons to say yes. The corroboration pool deliberately excludes unvetted shards, or two candidates from the same bad run vouch for each other.
publish_memory(shard_id) submits a candidate for independent adjudication — it does not publish it. The judge decides, and kept: false is a normal outcome. reject_memory needs no judge at all — removing your own candidate takes nothing out of the trusted pool.

The distinct-source rule

Saying it three times in one session is saying it once.

Recurrence is the strongest signal available for whether a lesson is real. It is also trivially faked by a single confused run repeating itself — so the counter counts sources, not occurrences.

Three identical shards from one session count as one distinct source and are refused; three shards from three sessions count as three and pass. one session same lesson same lesson same lesson distinct_sources = 1 not evidence three sessions distinct_sources = 3 ✓ corroboration pool — vetted only unvetted — outside the pool otherwise months of unreviewed shards become the pool that new junk corroborates against.
Repetition is not independence; the system counts sources, not occurrences. Two recurrences across too few distinct sources drop back to review rather than promoting.

Artifacts

The recommendation is the file, not a description of the file.

A published lesson that stays in a memory store is a lesson somebody has to remember to search for. The loop's last step turns recurring lessons into the actual infrastructure that would shape the next run — and it drafts the real file, rendered, ready to read as a diff.

A published lesson is classified into one of eight artifact tiers, coloured by whether it creates a new file or edits a shared one. published lesson classify fact hook rule skill allowlist agent update delete blue — edits a shared file purple — creates a new one
.claude/skills/two-engine-check/SKILL.md
---
name: two-engine-check
description: Run the suite on SQLite AND Postgres
  before calling a change done.
---

SQLite and Postgres have separate vector-search
implementations. Only the Postgres run executes the
real <=> SQL and the migration chain from empty.

A change is not done until both pass.

Classification is batched, and scope is normalised before anything is created — so "migration guard" and "the migration guards" don't become two competing recommendations. Drafting is keyed on a hash of the lesson text, so re-running the loop is free. A scheduled job that bills you for re-deriving yesterday's answers is a scheduled job somebody turns off.

The boundary

Approving a shared-file recommendation writes nothing.

This is the one place in the whole loop where a person is not optional. Everything to the left of the line is machinery. Nothing crosses it.

The machine pipeline ends at a boundary; approving a recommendation terminates at that boundary writing zero bytes, and only a human carries the drafted change into the repository. the human boundary review_recommendation transcripts candidate shards classify draft the real file approve → wrote: 0 bytes AGENTS.md .claude/settings.json CLAUDE.md you, with the drafted diff in hand "a machine editing AGENTS.md is the move that loses trust once and keeps it lost."
Approval here means "this is worth doing", never "do it" — and the proof is that the successful path writes zero bytes. The arrow ends in a flat bar, not an arrowhead; the repo files have no inbound edge at all.

Recommendations that create a new private file — a skill, a hook, a subagent definition — can be installed additively, because nothing of yours is being overwritten. Anything that would edit a file you own is returned as contents for you to apply. And before recommending anything, the loop inventories the artifacts it didn't write, so it resolves against what you already have rather than proposing a fourth version of it.

Telemetry

uses is NULL, never 0.

Artifacts are tracked after they land, and retired when they stop earning their place. Which raises the question this whole site is about: what do you render for a thing whose use you cannot observe?

TierUsesWhat the number means
hook14Observed firing fourteen times. It is doing something.
rule0A real zero, and it is information — you shipped a rule nothing has hit.
factNULLThis tier's use cannot be observed. 0 would be a claim we can't make.

Zero is a result. Unknown is not. Only one of them is safe to render as a number — and a dashboard that prints 0 for both is a dashboard that will eventually get an artifact retired for being unused when nobody ever checked.

The same rule now applies to lessons. Observe has a catalog; reach and outcomes are computed. Publishing something the next agent never found is a miss, not a silent success that looks like the lesson landed.

← previousThe living graph All four mechanisms ↑ next → the loop closesSpec → done

What's new

Every entry is work that merged. The full history is on GitHub.