~/blog/retiring-ai-agent-memory-system

AI Workflow · part 11

[Dev Workflow] Retiring an AI Agent's Memory System: Three Traps When Doctrine Drifts From Runtime

cat --toc

TL;DR

I set out to retire a "dead" shared-memory folder my knowledge base had supposedly replaced. It wasn't dead — its log mirror had been written at 2:30 that morning. Three traps followed: it was a live one-way mirror, not legacy (the layers were inverted); the doctrine said the memory had a git backup, but the hook was versioning a different folder that excluded the most important notes; and from the one machine I could see, the sync had two peers — from the hub, four, in a star. Delete only what you can see and the hub revives it. The lesson that generalizes: a memory system's self-description drifts out of sync with what it's doing — so verify from the hub, and never let the producer sign off on its own retirement.

The task looked like a chore. "That old shared-memory folder is probably replaced by the knowledge base — if so, let it go." One line, a cleanup, half an hour tops.

It wasn't dead. The first thing I checked was the mirror of my running log, and its modified time was 2:30 that same morning. Something was still writing to the folder I'd been told to bury. That's where a thirty-minute cleanup turned into a topology investigation, and into three traps that all share one root: the written doctrine had quietly drifted from what was actually running.

This is Part 11 of the AI Workflow series. Part 9 externalized live state; Part 10 externalized the task. This one is about the layer underneath both — the memory itself — and the thing nobody warns you about: once you externalize memory into a system, its self-description quietly drifts from what's actually running. Old assumptions linger unedited; new state and stale notes fall out of sync. The system isn't lying to you — it just no longer matches itself, and you can't take what it says about itself at face value.

The retirement that wasn't: a "dead" folder still writing at 2:30 AM

The folder had 352 files. My curated knowledge base had 59. The obvious reading — the one in my own head — was that the knowledge base had replaced the folder and I'd left 293 files un-migrated. Clean it up.

That reading had the layers upside down. The 352-file folder wasn't a stale predecessor; it was a live one-way mirror of my canonical memory (407 files at the time), produced by a hook that re-syncs on every write and then fans out over a sync daemon. The knowledge base wasn't its replacement — it consumed the mirror as an upstream source and distilled it down to 59. So 352-versus-59 wasn't migration debt. It was a full mirror feeding a curated digest, and I had the dependency direction backwards.

The tell was mtime. A dead folder has an old timestamp; this one had been written hours ago. Before you retire anything, check whether it's still breathing — a comm against the canonical source confirmed the mirror had zero unique content (the only six files it held alone were secondary copies of handoffs). Retiring it would lose no knowledge. But retiring it wrong could take down whatever was still reading it.

The backup everyone assumed: doctrine said git, runtime had none

Here's the trap that would have actually hurt.

My own written doctrine stated, in plain text, that the memory directory "already has a git hook that auto-commits." I believed it. I'd have told you my notes were versioned and safe. So while planning the retirement, I checked — and the hook did exist. It was just committing a different folder: the mirror, not the canonical memory. And the mirror was configured to exclude exactly one category of file — the behavioral-guidance notes, the ones that encode how I want my agents to work, the least reproducible thing in the whole system.

So the real state was: the canonical memory had no git of its own, no Time Machine, no backup job. The one category I'd least want to lose had no cloud copy at all — only a local daily rsync to another machine, one node failure away from gone. The doctrine's claim had been true at some point and had quietly stopped being true, and nobody had run the restore path end-to-end to notice.

That's the most expensive failure mode in any backup system. It isn't "there's no backup." It's "everyone assumes there's a backup," because a line of doctrine said so once and no one re-verified it against runtime. A rule that describes your system is a claim with an expiration date. If you never test it, you don't have a backup — you have a sentence about a backup.

Two peers or four? Map topology from the hub, not the spoke

The third trap is the one I'd have walked straight into if an agent hadn't stopped.

I dispatched the transport teardown as a ticket. Partway through, the executor hit a red line and halted rather than proceeding — the topology didn't match the ticket. From my laptop, the sync daemon reported two peers: the NAS, and a second machine whose lastSeen was the Unix epoch — a device that had never once connected. Two peers, one of them a ghost. Tear down two ends and you're done, or so the spoke's view said.

The hub told a different story. Reading the NAS's own config, the folder was shared across four live machines in a star — my laptop, a Mac mini, the NAS itself as the hub, and my always-on agent box — each one connected only to the hub, never to each other (none of them configured to talk to their siblings). Standing on any single spoke, you can't see the other spokes; the daemon only shows you your own edge of the star. And the sync's ignore file is per-device — it doesn't propagate — so an exclusion I'd set on one machine meant nothing on the others.

Two views of the same sync. From a spoke (one machine), the daemon shows two peers — the hub and a ghost device whose lastSeen is the Unix epoch — so teardown looks like a two-node job. From the hub, the real topology is a four-machine star: laptop, Mac mini, NAS-hub, and agent box, each spoke connected only to the hub. Delete the folder on the two nodes you can see and the hub re-syncs it back from the two you couldn't.

The consequence is concrete: with the folder still live on the two ends I hadn't reached, deleting it on the two I could see just let the hub re-sync it back from them. I had proof from the agent box's own sync log — a file deleted at 03:15:14 came back at 03:15:42. (Syncthing propagates a delete like any other change; here the delete lost to the copies still live on the spokes I hadn't torn down.) A half-finished teardown of a star heals the hole you punch in it. The only safe teardown maps every spoke from the hub first, then removes them together. It generalizes to anything hub-and-spoke: message queues, replicas, a fleet behind a load balancer. The daemon on your machine is not a map of the system; it's a map of your edge of it.

Governance: the producer doesn't sign off on its own work

Notice what actually saved me in the third trap: the executor stopped instead of trusting its own plan. That's not a lucky accident — it's the one rule that ties this whole post together.

I got the same lesson from the other direction a few hours earlier, when I had one AI CLI do a read-only audit of my entire agent setup. It came back with a heavyweight report: critical findings, a proposed agentctl control plane, a phased build-out. My first instinct was to treat the report as a blueprint and start building. I didn't — because most of the prescription collided with a constraint the auditor couldn't see. My own notes carry an explicit operator rule: keep this system lightweight, tolerate small gaps, don't build a factory that ships nothing. The auditor had never read those notes, so its proposed fix was systematically over-built. I accepted its diagnosis and threw out half its prescription — reuse the handful of objects I already have (ticket, handoff, evaluator, review queue, knowledge base), don't stand up a parallel task database.

Then it turned the tables. In the same exchange it caught four factual errors in my own reasoning — including a self-certified claim from a previous round ("the canonical contradiction is fixed") that turned out to be false. The contradiction had persisted for nine days because the superseding decision only edited the top-level doc and never went back to flag the stale note. We were each half right, and the two halves fit together.

That's the governance rule, stated plainly: the producer can't certify its own high-stakes work. An audit report will self-certify a fix that never happened — just as confidently as it reports a real one. A doctrine will tell you there's a backup. An agent will report "done." None of those is evidence — they're all self-reports, and the terminal gate has to be a second party that re-verifies against the actual state. Diagnosis and prescription are separable: you can trust what someone observed and still reject what they want to build. And when the proposed fix for a governance gap is "build a whole new system," the operator constraint usually wins — reuse beats build, because every new layer is a new thing that can drift from its own doctrine.

What the system looks like now

The end state isn't complicated, which is the point. Canonical memory lives in one place and now has its own version control and a private offsite copy — including the behavioral-guidance notes that used to have none. The knowledge base sits above it as a distilled, curated layer; retrieval runs over both through search and a small graph. The old four-end mirror is archived, not deleted, sitting through an observation window in case something I didn't map still depends on it.

The layers, right-side-up. Canonical memory (with its behavioral-guidance notes) is the source of truth, now under version control with a private offsite copy. A curated knowledge base distills it; retrieval runs over both via search plus a small graph. The retired one-way mirror is archived under an observation window, not deleted. Arrows point from source to derived — the direction I had backwards on day one.

I retired it in reversible stages: first drop it as a retrieval source, then add the offsite backup, then tear down transport one node at a time. Nothing actually gets deleted until after a weeks-long observation window. At no point is there a step you can't walk back.

Takeaways

Where the time went. Not the teardown — the mapping. The hours went into discovering that every self-description of the system was wrong: the folder wasn't dead, the backup wasn't there, and the peer list was half the truth. Once the real topology was on paper, the actual retirement was mechanical.

Reusable diagnostics. Check mtime before you call anything dead. Test the restore path before you trust the backup — a documented backup you've never restored from is a rumor. Map hub-and-spoke systems from the hub; the view from a spoke is your edge, not the graph. And treat every "done," every "fixed," every "backed up" as a claim to verify, not a fact to file.

The general principle. Once memory is a system, its self-description drifts — old assumptions linger, new state falls out of sync — so governance is refusing to take that self-description at face value. Verify from the hub, keep a real offsite copy, and never let the producer sign off on its own retirement. The two agent halts that afternoon — one on a secret-scanner tripwire, one on the topology mismatch — were worth more than any "done."

The retirement checklist

  • Is it actually dead? Check mtime and diff it against the canonical source before touching it.
  • Is the backup real? Run the restore path end-to-end; a doctrine line is not a backup.
  • What's the topology? Map it from the hub, not from the machine in front of you.
  • Retire in reversible stages: retrieval source → offsite copy → transport, one node at a time.
  • Keep an observation window before the final delete — archive, don't delete, until nothing screams.
  • The producer doesn't sign off: a second party re-verifies against evidence, not the report.

FAQ

How do you safely retire or delete part of an AI agent's memory system?
Don't trust the doctrine that describes it — map the real topology first. Before deleting anything, confirm what actually reads and writes the folder (hooks, sync daemons, other machines), verify there's a real backup by testing the restore path, and retire in reversible stages: pull the retrieval source, add an offsite copy, then tear down transport one node at a time. Keep an observation window before the final delete.
Why did deleting a synced folder on one machine not actually remove it?
Because the sync was a hub-and-spoke star, not a peer-to-peer link. Each machine only connects to a central hub (a NAS, in my case), so from any one spoke you see a partial peer list. Delete the folder on the machines you can see and the hub re-syncs it back from a node you couldn't. You have to map the topology from the hub and tear down every spoke.
What is doctrine-versus-runtime drift?
When your written rules describe a system that no longer matches what's actually running. My doctrine said the memory folder had a git hook backing it up. The hook existed — but it was versioning a different, mirrored folder that excluded the most important notes. The claim was true once and quietly stopped being true. The dangerous part isn't having no backup; it's everyone assuming there is one.
When two AI agents review each other's work, who has the final say?
Not the one who produced the work. A producer can't certify its own high-stakes change — an audit report will self-certify a fix that didn't happen just as readily as any other report. The terminal gate is a second party: another agent or a human who re-verifies against evidence, not against the report's own summary.

Read next

Don't miss the next one

Subscribe, and you won't.

One-click unsubscribe anytime.