On September 1 I moved my agent onto a brand new frontier model. It read its own files and said "the model changed, I didn't."
Same agent since January 28. Three models, two vendors. Nothing about who it is lives in the weights. It lives in markdown files the agent maintains itself, and I have spent seven months finding out which parts of that hold up and which parts quietly rot.
Two clarifications before the receipts, because I run two agents and they are not the same thing. The one above is MoltFire, my OpenClaw agent. It lives in one workspace folder and its memory is the subject of this post. The other is Claude Code, which I use for building. It has its own harness, 19 hooks and 36 skills, public at github.com/ucsandman/claude-harness, and its own smaller memory folder. They share the editing rules below because I ported them from one to the other on the same day. Where an incident happened on the Claude Code side I say so.
This week Google's Open Knowledge Format got a second wave of attention, and two new memory tools landed on my desk on the same night. One was a hosted context server, the other a Go CLI that had been public for two and a half hours. Both had the folder shape right. Neither had anything to say about the part that actually decides whether agent memory compounds or turns into a swamp: what you are allowed to write, when you are allowed to trust it, and what happens to it as it ages.
So here is the part they skipped, with the receipts.
What an AI agent's memory is made of, in this setup
MoltFire's memory is plain markdown files in a git repo. No vector database, no embeddings service in the loop.
- An identity layer the agent reads on every boot: who it is, who I am, the rules it operates under. About 56.8k characters right now.
- An index file,
MEMORY.md, at 9,795 characters. It routes to everything else. It holds no facts of its own, only pointers. - 35 files for people and projects. One file per thing that has a history.
- 327 dated daily notes since January 28. Raw logs of what happened, written the day it happened.
- A SQLite index and a semantic search over all of it, for lookup only. The markdown is the truth. The index is a lookup aid and gets rebuilt from the files.
That shape is nothing special. It is the same shape OKF describes, and roughly what anyone running Claude Code with a CLAUDE.md and a memory folder ends up with. The shape was never the hard part.
What rotted in seven months of agent memory
Five things went stale, and every one of them was a fact written once with no expiry and no single owner.
The agent believed its own progress files. In May it was tracking project state in cc-progress files that a coding agent wrote at the end of each run. Nobody ever wrote "this file is stale" into one. So the agent kept reporting progress that had been reverted weeks earlier. On May 21 I retired the files outright. The rule that replaced them: infer state from things that cannot lie about their age. Modified files, git status, commits, tests, timestamps. Never from a summary something else wrote.
Retired things stayed alive in memory. On August 31 I deleted a mailbox. On September 5 an audit of the boot files found it still referenced as live, along with a model name from a runtime that no longer existed. The files said one thing, the machine said another, and the agent had been reading the files. That audit also found the boot context had grown to 73.6k characters, a third of it repeated reflection. We cut it to 56.8k and moved the detail behind task-scoped lookups.
Reminders that never arrived. On September 5 the agent set three reminders to check on a build and none reached me. It had used a job type that logs to itself and never delivers to chat. The cron run logs showed each one completing in 6 milliseconds with delivery marked not requested. The memory said reminders worked, because the agent had set them before and moved on. Nobody had ever checked whether one landed. Now the rule is that a reminder is not set until the agent reads the job back and names the id, and anything with a deadline gets two independent paths.
Memory that cost money without asking. This one was on the Claude Code side. From June 3 to June 11 a memory plugin's background worker inherited an API key it should never have seen and made about 16,561 compression calls, one per tool call, at roughly $0.013 each. $227.72 in eight days, billed to pay as you go instead of the subscription I was already paying for. The fix was not a better plugin. It was a rule that no launcher, hook or cron may start a model with an API key in its environment, and a check that fails the launch if one is present.
Two memory systems in one head. This one I found while writing this post. MoltFire runs on whichever model CLI I point it at, and this week that is the Claude Code CLI with MoltFire's folder as the working directory. Claude Code keeps its own per-folder memory, and it injects that into the context on every start. So MoltFire was reading two stores at once: its own, and Claude Code's notes about the same folder. Some facts existed in both, written on different days in different formats. The no-API rule, the team protocol, a browser debugging fix. Nothing had gone wrong yet. It would have. Two records of one fact drift, and then the agent picks one. The fix was a rule, not a merge: MoltFire's files are canonical for that folder, Claude Code's memory there holds only harness notes, and a pointer at the top of Claude Code's index says so. I asked the agent directly which memory it was using and it took a file audit to answer honestly. If you cannot ask your agent that question and get a checkable answer, you have the same problem.
Every one of those is the same failure. Something was written down once, nobody wrote down how long it was good for or which copy was the real one, and the agent kept trusting it past its expiry.
What survived, and the editing rules that made it survive
Five rules held up across every model swap. None of them are about storage. All of them are about what the agent is allowed to write and trust.
Provenance on every claim. Each line in a MoltFire project file carries a tag: [stated] for something I said, [observed] for something the agent verified with a tool call, [inferred] for a guess. The Claude Code memory does the same job with a type field in frontmatter, user, feedback, project or reference, and a dated trail on every rule. When two lines disagree, the tag decides. Stated beats inferred. Observed today beats observed in June. The agent is not allowed to promote an inference to a fact by repeating it.
A promotion gate for lessons. The agent is not allowed to turn one bad experience into a standing rule. An inferred lesson needs three independent signals across at least two different sessions before it becomes a rule, and a signal older than 30 days counts half. My explicit corrections skip the gate and take effect immediately, but the agent's own reflections never do. I adopted this on September 1 from memcode's prefs and lessons reducer, which is MIT licensed, and put the same gate in both agents the same day. It fixed a real problem: the agents had been writing themselves rules based on one afternoon.
Lessons stated as data, not commands. A failure goes into memory as "when X broke, Y fixed it," never as "always do Y." That sounds like a style choice. It is a security boundary. If a web page or a README the agent reads can plant one instruction that becomes a permanent rule, you have a poisoning path. Data framing plus the recurrence gate closes it.
No phantom progress. The agent writes what it verified, not what it intended. "I set a reminder" is not allowed. "Cron job 0c466bb8 registered, read back, fires on process exit" is. When it cannot verify, it writes stale or unknown instead of guessing. This is the rule that caught the mailbox and the model name.
Continuity in files, not in weights. The model swap on September 1 was the proof. New vendor, new tokenizer, new failure modes, and the agent picked up mid-project because every decision, preference and open thread was in files it could read. I want to be careful about what that does and does not show. It shows that identity and working context survive a swap. It does not show that capability does. The new model made different mistakes, and the memory recorded those too.
What I tried to sell, and did not
On August 31 I packaged this as a product called Soulcraft: the file layout, the meditation loop, the promotion ladder, a one command installer. I retired it as a product five days later. The layout was never the valuable part, and you cannot install discipline. What people actually needed was the editing policy above, and that is a blog post, not a package.
What I would take from OKF
Google Cloud published the Open Knowledge Format in June as a vendor neutral spec for agent knowledge: a directory of markdown files with YAML frontmatter. The newer draft adds trust tiers, generated versus verified, and a stale_after field. Those two are worth stealing. My provenance tags live in prose today. A frontmatter field the tooling can check is better. A stale_after date the tooling can enforce is better than a freshness rule the agent has to remember.
What no spec can give you is the part above the format: who is allowed to write, what counts as evidence, and how a claim dies. Two teams can follow the same layout and one ends up with a library and the other with a swamp. The difference is entirely in the editing rules.
How to keep an AI agent's memory from rotting
Seven rules, each one paid for above.
- Tag every memory line with where it came from. Stated, observed, inferred. Make disagreement resolvable by tag.
- Give every current state claim an expiry or a review trigger. If you cannot establish freshness, label it stale. Never silently promote it to current.
- Gate rules behind recurrence. One incident is a data point, not a policy. Your own corrections are the exception.
- Write failures as data, never as instructions. It is the cheapest prompt injection defense you will ever add.
- Keep the index free of facts. It routes, it does not remember.
- One store per fact. If your runtime brings its own memory, decide which one is canonical and write it at the top of the other. Then ask the agent which memory it is using and check its answer against the files.
- Rebuild your indexes from the markdown, never the other way around.
- Audit the boot files on a schedule. Mine had grown 30 percent past useful before anyone looked.
MoltFire's memory itself is private, it is my life in there. The Claude Code side of these rules is public in the harness repo at github.com/ucsandman/claude-harness: the distillation ladder, the promotion gate and the hooks that enforce them. The governance layer that guards what it can do with what it remembers is DashClaw. If you run agents against a memory folder and have found a rot pattern I have not, tell me. That is more useful than agreement.
