# A memory system for coding agents

Agent memory stays useful by gating writes, not by improving recall. The system I run on a client fintech codebase grants a memory a slot only if it passes four rules — durable, non-derivable, overrides a default, specific enough to act on — puts an adversarial five-challenge review in front of every write, and promotes a lesson into standing instructions only after repeated cross-session validation.

Published: 2026-08-17
Canonical: https://umar.codes/agent-memory-system

## Revisions

- 2026-07-30 — Created; body written from the memory system's own files.
- 2026-08-17 — Published.

---

Most coding-agent memory systems fail by remembering too much, not too
little. The one I run on a client fintech codebase works because writing is
gated: a memory earns a slot only by passing four rules and an adversarial
five-challenge review, and lessons must be re-validated across sessions
before they're promoted into standing instructions.

## Agent memory fails by writing too much

Claude Code gives each project a persistent memory directory whose index is
loaded into every session, and the default failure mode is hoarding, not
forgetting. Left to its own judgement, an agent saves session summaries,
research notes, TODO state, and the status of work that has since merged —
all of it either derivable from the repository or perishable within weeks.
Derivable facts go stale the moment the code moves, and every stale entry is
a false statement injected into every future session. The index doesn't
degrade because it's too small; it degrades because it's polluted, and the
agent has no way to know which entries to distrust.

So the design premise of this system is that the scarce resource isn't
storage or recall — it's trust in what's already written. Everything below
is machinery for protecting that trust at write time.

## Four rules a memory must pass

A memory earns a slot only if it is all four of: **durable** — still true in
six months; **non-derivable** — can't be reconstructed from the code, the
git history, or the ticket tracker; **overrides a default** — without it the
agent would confidently do the wrong thing; and **specific enough to act
on** — it names a file, a command, or a concrete pattern. The index header
restates the rules, so every session re-reads them before it can write, and
it also names the exclusions: TODOs, research summaries, merged-phase logs,
and story status don't belong — which is to say, the four things agents most
like to write down.

The entry that best shows all four rules at work: the client's Git server
enforces commit-message format with a regex anchored so strictly that it
rejects the trailing newline `git commit -m` itself appends — every
correctly formatted commit still fails to push. That fact is durable (server
hooks change rarely), non-derivable (the hook is invisible from inside the
repository), overrides a default (the standard commit command is the trap),
and the memory carries the exact newline-free commit incantation. Learning
it cost roughly nine failed pushes; re-learning it costs one line of index.

After four months the index holds 35 entries in four categories: one fact
about my own access, 7 durable preferences the client team has expressed, 22
active project constraints, and 5 pointers to where non-obvious information
lives. Each entry is one file holding one fact; the index carries only a
one-line hook per entry, so what loads into context stays under a page.

## An adversarial gatekeeper reviews every write

Rules stated in a header are advice; the gatekeeper is the enforcement. Any
proposed lesson — a correction from me, a failed verification gate, a
pattern noticed in review — is routed through a dedicated command whose job
is stated in its first paragraph: reject slop. It runs five challenges, and
the lesson must pass all five. Is it *true* — is there evidence in the
codebase now, or is this an inference from a single incident? Inference is
an automatic reject. Is it *specific* — could another engineer act on it
without asking what it means? Is it *new* — a duplicate doesn't get a second
entry; it increments the existing entry's validation counter. Is it *scoped*
— a one-off mistake that wouldn't plausibly recur is rejected rather than
memorialised. And could it *cause harm* — a rule that would produce worse
outcomes if followed everywhere gets narrowed before it's written.

What survives is classified by risk: low-risk factual entries are written
silently, pattern-level lessons get surfaced in a session-end summary, and
anything that would change standing behaviour is flagged to me immediately.
The posture throughout is refusal by default — the prompt explicitly names
rubber-stamping as the failure to avoid.

The review earns its cost on subtle entries. One lesson records that a
subagent's research summary mis-grouped a design decision under the wrong
person's sign-off, and the false attribution propagated into a committed
spec and two downstream documents before a primary source contradicted it.
The gatekeepered lesson that came out of the cleanup is a process rule —
attribution claims trace to the primary document, never to a summary, and a
contradicted summary means re-checking everything written from it.

## Lessons are validated across sessions, then promoted

The system has three tiers of confidence. Standing instructions in CLAUDE.md
load into every session and are trusted absolutely. Memories are established
facts. Lessons sit below both: provisional corrections, each carrying a
`Sessions validated` counter that increments only when a *fresh incident*
confirms the lesson — getting bitten again is treated as evidence, not
embarrassment. The clearest example: the client's "full CI" script turns out
not to run the integration suite, and the lesson recording that has been
validated by three separate incidents across three months, each one widening
its trigger conditions — first for changes touching test infrastructure,
later for any edit to DOM structure or accessibility attributes, because
those kept breaking integration tests that unit gates can't see.

Three validations makes a lesson a promotion candidate, tagged with a
suggested target section in CLAUDE.md; the promotion itself stays manual,
because standing instructions are the highest-trust tier and nothing gets in
on the agent's own say-so. Decay runs the other way: a lesson that sits 60
days without a validation is archived, and memory files untouched for 90
days get flagged for review. Right now the ledger reads: ten active lessons,
one promotion candidate, none promoted, none archived.

## Four months in: what's proven and what isn't

The write-side works, and I can see it in what's absent: four months of
heavy agent use has produced 35 memories and ten lessons, not the hundreds
of entries an ungated system accumulates, and I have yet to catch a stale
entry misleading a session. The honest limits are on the maintenance side.
The monthly audit — the process that would archive stale lessons and flag
untouched memories — has never actually run; the audit log is an empty
file, so the decay rules are designed but unexercised. Most validation
counters still sit at zero, which means one promotion candidate is too small
a sample to say whether three is the right threshold. And the whole system
is a prompt, not a mechanism: nothing physically stops a session writing to
the files directly, so the gate holds only as long as the agent routes
through it — the same unverified-instruction problem that made me build a
[trigger harness](/testing-skill-triggers) for skills.

What I'd keep even if I kept nothing else: the four write-rules. They're the
cheapest component — four tests applied in the moment before writing — and
they do most of the work, because agent memory doesn't usually fail at
recall. It fails at the junk drawer, and the fix for a junk drawer is a rule
about what goes in.
