Seven production coding agents, one Reddit manager, and xAI all landed on plain text. Here is the case, and a .NET template you can paste today.
Last month I wrote about building a four-layer agent memory system with quarantine, supersession, TTLs and a governed store in C#. I still believe every word of it, for the problems it was built for. Then this week I read a Medium piece that made me feel slightly silly about how much machinery I reach for by default, and I ended up clapping and commenting on it.
The piece is Volodymyr Vreshch’s “Why Your Agent Needs Memory, and How to Organize It.” His personal agent memory is 181 markdown files behind a 134-line flat index. No embeddings. No graph. No service to keep alive. One line in it stopped me: “If you cannot grep it, diff it, and delete a line from it, it is not your memory. It is somebody’s cache of you.”
I don’t think that is true of every agent system. Multi-tenant products with millions of users need real infrastructure. But I do think it is true of most agentic coding and ops workflows in 2026, which is where most of us actually live. So this is my opinionated case, followed by exactly how to build it for a .NET project.
A quick note on sourcing before I start. Where I quote Vreshch, I read his post directly. For the Reddit engineer and the Grok Build items I’m leaning on newsletter write-ups and xAI’s own page, and I’ll say where something is secondhand or where sources disagree.
Memory is the delta
Vreshch’s central framing is the one I’m going to steal for the rest of my career: “Agent memory is not a copy of what the agent knows. It is the delta, the part left over.”
Read that twice, because it kills about 80 percent of the bad memory files I’ve seen, including some of mine. The model already knows what dependency injection is. It already knows how EF Core migrations work, what async and await do, and that you should not commit secrets. A memory file that says "we use dependency injection to decouple services" is a tax on every future session, paid in tokens and in attention, for zero information.
His admission rule is blunt: write down only what the model got wrong, or could not have known.
Two categories fall out of that. First, things the model got wrong: it keeps generating IHttpClientFactory code with a pattern your team banned, it keeps using the EF InMemory provider for tests, it assumes Minimal APIs when your solution is controllers all the way down. Second, things it could not have known: your internal service is called gateway and it is not an API gateway, the staging database is restored from production every Sunday night, the Legacy folder is untouchable until the Q4 migration.
Everything else is noise pretending to be memory. Try auditing your own CLAUDE.md against this rule. My bet is you will find what I expect to find: a depressing amount of general advice copied from other people’s templates. It reads like a style guide. It behaves like a tax.
There is also a trust argument hiding in here. A vector store answers “what is close to this query,” and you cannot easily inspect why a given chunk came back or whether it is still true. A markdown file answers nothing on its own, but you can read all of it in a few minutes and a teammate can review every change in a pull request. For a coding agent, where a wrong memory turns into a wrong commit, I would rather have memory I can audit with my eyes than memory that scores well on a similarity metric.
What Vreshch actually runs
The numbers are what make this credible to me. 181 topic files, one flat index of 134 lines. That is less than one index line per topic file, which means the always-loaded part stays small while the total knowledge grows. The index is the only thing that has to be in context every session. Everything else is opened on demand.
He describes four tests every memory unit must pass, and three requirements for where it lives. Here they are in one place:
+-----+------------------------+--------------------------------------------------+
| # | Test | What it means in practice |
+-----+------------------------+--------------------------------------------------+
| 1 | Simple and dense | One idea, few words. If it needs a paragraph of |
| | | setup, it is probably two memories or none. |
+-----+------------------------+--------------------------------------------------+
| 2 | Non-conflicting | Adding it must not contradict an existing entry. |
| | | If it does, one of them gets edited or deleted. |
+-----+------------------------+--------------------------------------------------+
| 3 | Measurably useful | You can name a behavior it changes. If nothing |
| | | would go wrong without it, delete it. |
+-----+------------------------+--------------------------------------------------+
| 4 | Confidence-classified | Facts and hunches are labeled differently, so |
| | | a guess never gets read as a rule. |
+-----+------------------------+--------------------------------------------------+
Storage requirements:
1. Greppable (no special query language)
2. Diffable (a human can review every change in git)
3. Small always-loaded index, plus an archive opened on demand
Test 4 is the one I had never done. In every memory system I’ve built, a hunch and a confirmed fact look identical once they’re written down. The agent reads “the payments service retries three times” with exactly the same confidence whether someone verified it in the code or half-remembered it in a standup. Labeling confidence at write time is cheap, and it changes how the agent behaves when the entry turns out to be wrong.
Test 3 is the one I now think is the most important. “Measurably useful” means you should be able to point at a failure that would return if the entry were deleted. I’ll show a trick for making that literal in the template below.
Then I looked at what everyone else did this week
Vreshch also makes a claim I found more persuasive than any benchmark: that seven production coding agents independently converged on the same design, plain markdown with a small file kept in context and an archive opened on demand. In the post I read, he doesn’t name the seven, so I can’t verify that claim system by system, and you shouldn’t take it as more than his observation. But two other things landed on my desk the same week, and they point the same way.
The Reddit engineering manager
The Code newsletter (Sep 16) described an engineering manager on Reddit who keeps his agent focused with three files: projects.md, people.md and daily.md. That is the whole schema. Nothing about it is clever, and that is the point.
The clever part is the loop around it. A scheduled Codex task runs every morning at 8am. It reads the three files, ranks what actually matters that day, links every claim it makes back to a source, and writes a brief formatted so Logseq can ingest it directly.
The bit I keep coming back to is “links every claim to a source.” That is Vreshch’s confidence test enforced by process instead of by labels. The brief cannot assert something the files cannot back up, and you can click through to check. A manager’s job is largely about noticing that the thing everyone believes has quietly stopped being true, so an agent that shows its receipts is the only kind I would let near my Monday.
xAI’s Grok Build
The Simplifying AI newsletter (Sep 17) covered Grok Build’s cross-session memory, and xAI’s own page describes the shape clearly. Memory is markdown topic files in two scopes. Globally there is a MEMORY.md index and a preferences.md. Per workspace there are subject files such as testing.md, code-style.md and gateway.md. That is Vreshch's architecture again: small index, topic files, scoped by where the knowledge applies.
The commands are worth a look. According to xAI’s page, /memory opens a read-only browser over all the memory files, grouped by scope, with previews and timestamps, and /dream consolidates recent notes into the topic files, running periodically in the background. Other coverage also mentions a /remember command for saving a note on demand. One caveat: the xAI page I read lists /memory and /dream only, and the /remember and /flush mentions come from third-party coverage of an earlier release, so check the current command list before you build a habit around them.
Two design choices there match what Vreshch argues. The stated exclusions are task state, secrets and repository documentation, which is the delta rule again: if it’s already in the repo, don’t copy it. And current conversation instructions override stored notes, which is the right precedence, because memory is a prior, not a command.
Here is how the three compare:
+----------------+-----------------+-------------------+-----------------------+
| | Vreshch | Reddit EM | Grok Build |
+----------------+-----------------+-------------------+-----------------------+
| Format | Markdown | Markdown | Markdown |
| Always loaded | 134-line index | 3 small files | MEMORY.md index |
| On demand | 181 topic files | (files are small) | testing.md, etc. |
| Scope | Personal | Personal work | Global + per workspace|
| Curation | 4 tests | Ranked + sourced | /dream consolidation |
| Human review | Diff, delete | Click the link | /memory browser |
+----------------+-----------------+-------------------+-----------------------+
Three unrelated people and teams, one storage format. That is the closest thing to a convergence signal I’ve seen in this space, and it says more to me than any single benchmark.
The problem nobody talks about: the agent doesn’t read what it already wrote
Here is where markdown memory can betray you, and it is the failure I recognize most from my own workflow. Plain files only work if the agent actually reads them.
The Simplifying AI newsletter (Sep 15) put a name on a related problem: Claude Code ignoring files it already wrote. You have a folder of notes, specs and generated docs. You ask a question about them. The agent skims a file or two, then answers from a plausible-sounding reconstruction of the rest. Nothing errors. The answer just quietly mixes what is in the folder with what the model expected to be in the folder.
The workflow fix is a file called MAP.md. The agent reads every file in a folder, then builds a map in which every statement is marked either FOUND (with the file and line it came from) or GUESSED (inferred, not seen). The rule that makes it work is the last one: never present a guess as a finding.
I like this fix because it is the delta principle applied to reading instead of writing. It forces the agent to separate what the folder told it from what it brought along. Here is a prompt version I would use:
Read EVERY file in ./docs (recursive). Do not skim and do not stop early.
List each file you opened at the top so I can see coverage.
Then write ./docs/MAP.md with these sections:
1. FOUND: each statement, followed by (file:line) where you saw it.
2. GUESSED: anything you inferred, assumed, or filled in without seeing it.
3. MISSING: questions the folder does not answer.
Rules:
- Never present a guess as a finding.
- If you did not open a file, say so under MISSING.
- Keep every entry to one line.
I’d run this before any task that depends on a folder of prior notes. The MISSING section is the part I care about most. If MAP.md shows twelve files read and the folder has thirteen, you know exactly where the answer’s blind spot is, and you know before you build anything on top of it.
A starter template for a .NET project
Enough theory. Here is a structure that applies the delta rule, the four tests and the three requirements to a typical .NET solution. Put the always-loaded parts in CLAUDE.md and the memory folder next to it.
YourSolution/
CLAUDE.md # under 60 lines, always loaded, rules only
.claude/
memory/
MEMORY.md # flat index, one line per topic, always loaded
build-and-run.md # non-obvious build, SDK, and local run facts
ef-core-and-data.md # migrations, provider quirks, seed data
auth-and-tokens.md # token flow, scopes, what NOT to log
testing.md # test infra gotchas
gotchas.md # things the model keeps getting wrong
decisions.md # decisions plus the reason, one per entry
archive/ # old entries, opened only on demand
CLAUDE.md holds instructions and a pointer to the index. MEMORY.md holds one line per topic file, the way Vreshch keeps his 134-line index over 181 files.
# MEMORY.md (index, keep under 60 lines)
- build-and-run.md: SDK pin, why we build with --no-restore in CI, local Docker deps
- ef-core-and-data.md: migration ordering rule, Npgsql vs InMemory differences
- auth-and-tokens.md: token exchange flow, scopes that must never be logged
- testing.md: Testcontainers setup, flaky test list, what needs a real DB
- gotchas.md: patterns the model generates that we have banned
- decisions.md: why controllers not Minimal APIs, why no MediatR
Now a single entry, written to pass all four tests. The example below is illustrative, invented to show the format, not taken from a real repository.
## Repository tests must use a real Postgres
- rule: Do not use UseInMemoryDatabase in tests/. Use Testcontainers.PostgreSql.
- why: The InMemory provider accepted a query the Npgsql provider rejects at runtime.
- confidence: CONFIRMED (reproduced against the real provider)
- added: 2026-09-14
- check: grep -rn "UseInMemoryDatabase" tests/ (must return nothing)
The confidence line is test 4. The why line keeps it non-conflicting, because when a later entry contradicts it, you can see what reasoning to weigh. The check line is my addition and my favorite part: a command that proves the entry still matters. If the check can never fail, the entry fails test 3 and should go.
Use three labels and be strict about them: CONFIRMED for things verified in code or by running it, OBSERVED for things seen but not reproduced, and HUNCH for guesses. That three-level scheme is my own adaptation of the facts-versus-hunches idea, not a quote from Vreshch. The rule on top: an agent must never act destructively on a HUNCH, and it should tell you when it is leaning on one.
The bootstrap prompt
Paste this into Claude Code at the root of your solution. It uses the same FOUND versus GUESSED discipline as MAP.md, so the first draft of your memory is honest about what it saw.
Bootstrap agent memory for this .NET solution. Follow these rules exactly.
1. READ FIRST. Open and read: every *.sln and *.csproj, Directory.Build.props,
Directory.Packages.props, global.json, nuget.config, appsettings*.json (never
copy secret values), Dockerfile(s), docker-compose*, CI files under
.github/ or azure-pipelines*, and README*. List every file you opened.
2. DELTA ONLY. Memory is what a competent .NET developer would NOT already
know or would likely get wrong in THIS repo. Do not write general .NET
advice. Do not copy anything already documented in README or code comments.
Do not store secrets, connection strings, or task state.
3. CREATE these files:
- CLAUDE.md (max 60 lines): rules and a pointer to the index
- .claude/memory/MEMORY.md: flat index, one line per topic file
- .claude/memory/build-and-run.md, ef-core-and-data.md, auth-and-tokens.md,
testing.md, gotchas.md, decisions.md
Only create a topic file if you found at least one real delta for it.
4. ENTRY FORMAT. Every entry has: rule, why, confidence, added (today's date),
and check (a grep or command that proves the entry still applies, or
"none" if impossible). Confidence is CONFIRMED (seen in code or verified by
running), OBSERVED (seen but not verified), or HUNCH (inferred).
5. FOUND vs GUESSED. Anything you inferred and did not see goes in a section
called "Needs confirmation" in the file, marked HUNCH. Never present a
guess as a finding.
6. NON-CONFLICT. Before writing an entry, check the existing files for a
contradiction. If one exists, stop and ask me.
7. FINISH by printing: files read, files created, number of entries per
confidence level, and every HUNCH so I can confirm or delete it.
Expect to run the first pass and then delete a good chunk of what it wrote. That is not a criticism of the prompt. Deleting is the workflow. A memory file you have not pruned is a cache you have not looked at.
A small local lint, no services required
Since the whole point is that this is diffable and greppable, you can check the health of your memory with a few lines of bash. Save this as memory-lint.sh. It runs anywhere Git Bash, WSL, macOS or Linux runs.
#!/usr/bin/env bash
set -euo pipefail
DIR=".claude/memory"
echo "Index lines: $(wc -l < "$DIR/MEMORY.md") (target: under 60)"
echo "Topic files not listed in the index:"
for f in "$DIR"/*.md; do
name=$(basename "$f")
["$name" = "MEMORY.md"] && continue
grep -q "$name" "$DIR/MEMORY.md" || echo " $name"
done
echo "Entries missing a confidence line:"
grep -L "^- confidence:" "$DIR"/*.md | grep -v MEMORY.md || true
echo "HUNCH entries awaiting review:"
grep -rn "confidence: HUNCH" "$DIR" || echo " none"
If you want a local stand-in for Grok’s /dream, you can pipe your recent notes through a model running under Ollama and review the result as a normal diff before you commit it. For example, ollama run llama3.1 "Merge these notes into the existing topic file, remove duplicates, keep the rule/why/confidence/check format: $(cat notes.md)". Treat the output as a proposal. The diff is your review step, which is the whole benefit of choosing files.
“But this won’t scale”
This is the objection I hear first, and I had it too. Here is what Vreshch’s own numbers say, and I’ll be careful about what they do and do not prove.
His system has 181 topic files behind a 134-line index. The thing that scales is the index, because it stays under one line per topic file. Only the index has to be loaded every session. The 181 files are opened on demand, and a topic file is only read when the index says it is relevant. So the per-session cost is roughly the index plus the two or three topic files a task touches, not the whole archive. Growing from 181 files to 400 makes the index longer. It does not make every session pay for 400 files.
He is also honest that grep has a ceiling. In the post he says grep wins and then collapses at some size. What I could not find is a published threshold for where it collapses, so I’m not going to invent one. Anyone who quotes you an exact number here is guessing, me included.
What I will offer is a rule of mine, not his: treat the moment your index stops fitting comfortably in a short read as the signal to act. Split topics, move stale entries into archive/, and only then ask whether you need search beyond grep. A hybrid is still markdown at the source of truth. You can add a local embedding model through Ollama and index the files for fuzzy recall, while the files stay the thing you diff and review. The vectors become a derived cache you can rebuild, not your memory.
The other half of the scaling worry is that markdown loses to a database on staleness and contradiction. My experience in the earlier four-layer project was that vector similarity has no idea what is still true. A markdown file has the opposite problem: it is easy to see what is still true, because you can read it. Contradictions show up in git diff. Deleting a wrong entry is one line.
When I would still reach for a vector store
I don’t want to swap one dogma for another. Here is where I would still use something heavier.
+-------------------------------+-----------------------------------------------+
| Situation | Why markdown is not enough |
+-------------------------------+-----------------------------------------------+
| Many users, one agent | Per-user isolation, access control, and |
| | retention rules need a real store. |
| Huge recall corpus | Millions of records will not fit behind a |
| | human-readable index. |
| Automatic extraction at scale | Nobody is going to review every diff. |
+-------------------------------+-----------------------------------------------+
The Microsoft Agent Framework team’s post on native memory with Azure Cosmos DB is a good example of the other side. It targets exactly the many-user case: memory scoped by a stable user_id, conversation turns stored, durable facts extracted in the background and recalled before the agent runs. It is a sensible design for a product with real users. One practical note for .NET readers: at the time of that post it was a preview and Python only, with APIs that could change before general availability, so check where it stands before you plan around it.
But notice what that is solving. It is solving memory about your customers. Most of what I do, and most of what I suspect you do, is memory about a codebase and a team. That is a much smaller, much more reviewable problem, and it wants a much simpler tool.
What I’m changing on Monday
I’m going to run the bootstrap prompt on one repo and then delete at least half of what comes out. I’m going to add a check: line to every entry and delete any entry whose check cannot fail. I'm going to label every entry CONFIRMED, OBSERVED or HUNCH. And I'm going to put the MAP.md prompt in front of every task that depends on a folder of old notes.
None of this is exciting, and that is my favorite thing about it. The most useful agent memory I’ve seen this year is a set of text files that a person can read on a train, diff in a pull request, and fix with one keystroke. If your memory system needs a dashboard to tell you what it remembers, that is worth thinking about.
If you try the template, I’d like to know which of your entries fail the “measurably useful” test. I suspect the number will be higher than you expect.
Tags: ai-agents, agent-memory, claude-code, dotnet, markdown, llm-engineering, developer-productivity, context-engineering
Top comments (0)