2026-09-01 · Architecture

We built an ops layer out of text files

In our agent setup, the operating instructions lived in a folder: AGENTS.md, CLAUDE.md, .mcp.json, a skills/ directory, a todo.md and a memory/ folder.

Those files did more than document the system. They supplied instructions, task state and memory to the agent runtime. The runtime and its tools determined what could execute and which access restrictions were enforced.

I call our arrangement markdown-ops: running an operational workflow from text files. We had not added the validation, coordinated writes and selective retrieval that shared state needed. That is a limitation of our setup, not of the file format.

First, credit where it's due

There's already a shelf of posts titled some variation of "Markdown is not agent memory." Almost all of them were written by companies selling memory. They get exactly the reply they earned — a counter-genre of "markdown memory is all you need." I'm not interested in being the fourth voice in that argument, because the format won on merit.

Markdown diffs in Git, opens in an ordinary editor and can be restored to an earlier version. A local text file needs no database service. Instruction files such as AGENTS.md are supported by multiple agent tools, although discovery and precedence rules differ between clients.

Those are useful properties to preserve when adding a memory service. A database can offer change history and exports too; the question is whether the implementation provides them.

The format isn't the problem. The problem is the job we quietly gave it.

The line is here

The argument isn't Markdown versus a database. It's about what the unit of change is — the file, or the fact.

Files can hold both instructions and state. Shared, changing state also needs rules for access, retrieval and concurrent writes.

"Always run the tests before committing" is intent. It changes rarely, it belongs in git, it belongs in a file. Markdown works well here.

"Task 14 is with the second agent, waiting on a human, and a third agent is already building on it" is a fact. It changes every few minutes, several participants write it at once, and you need to ask about it, not re-read everything.

With one writer, our files were enough. More writers exposed coordination problems we had not solved. File-based stores can support concurrent work, but an ordinary shared folder does not supply those mechanisms by itself.

Seven failures, each one a scar

The following are failures of our implementation. They are reasons to add missing mechanisms, whether the storage underneath is files or a database.

1. Shared Git state without coordination. Two agents in one working tree shared an index and HEAD. We hit a race while staging changes and moved to task worktrees with serialized shared operations. That isolated repository work; it was not equivalent to implementing database transactions. A database offers transaction mechanisms, but applications still have to use them correctly and handle conflicting edits.

2. Full-file loading imposed a context tax. Measurements recorded for our setup on 1 September 2026:

CLAUDE.md 13,466 bytes / 214 lines (~4k tokens), read in full by every agent every session
MEMORY.md 17,957 bytes (~5k tokens), loaded in full at startup
memory files on disk 94

The part that matters: MEMORY.md is a table of contents, not the memory. Eighteen kilobytes just to list which facts exist. None of the actual facts are in context yet.

Our startup path read the whole index. As it grew, that consumed more context before the agent had retrieved the facts it needed. Search, section reads and indexes can solve selective retrieval over files too. We chose a queryable shared store; the improvement came from fetching relevant facts, not from changing the filename extension.

3. Unstable references. We referred to headings that could be renamed and wiki links whose targets did not have to exist. Stable IDs and link validation can be implemented over files. Database row IDs also need application-level or database constraints if references are to remain valid; an ID alone does not establish referential integrity.

4. No schema, so the field lies. From our own operating instructions:

Don't trust the Status field in an ADR: "done" in the doc ≠ done in the code. Verify status against the code.

We had to verify document status against implementation because the two could drift. A typed status field can reject an invalid value; it cannot establish that the claimed work happened. The same truth check is necessary whether the status is Markdown, front matter or a database column.

5. Our repository was too broad a trust boundary. Giving an agent a checkout exposed everything in that checkout, and writable instruction files could change what later agents were told. Filesystems, sandboxes and repository controls can restrict access; database text can also carry hostile instructions. Trusted instructions, untrusted content and executable permissions need separate treatment in either design.

6. No workflow events in our setup. Git records textual changes; our workflow needed an event such as “this task changed owner.” File watchers, hooks and parsers can produce that event. Database-backed applications also have to implement it: a row update does not automatically deliver a notification or webhook.

7. File memory forks along with the filesystem. The measurement I like least, and it's ours:

.../-srv-godcrm-live/memory/               MEMORY.md 17,957 bytes   73 files
.../-root-production-business-crm/memory/  MEMORY.md  4,138 bytes   21 files

One project. Two memory indexes. A 4× divergence. 94 files, two silos, no sync. The cause is dumb: the silo key is the working directory path. An agent started from a different directory of the same repository lands in a different memory and cannot see what its neighbour already knows.

Our memory paths made each working directory a separate silo. That was a configuration choice. A shared file store could avoid it, and poorly coordinated database replicas could reproduce it. We needed an explicit source of truth and a deliberate replication policy.

Notice that none of the seven is "Markdown stores meaning poorly." All seven are about operations.

Where Markdown is still right

I'm not proposing you migrate everything. For one agent and one human, files are close to unbeatable: zero infrastructure, everything visible, one-command rollback. I'm proposing you draw a line and stop crossing it.

Our choice: keep operating instructions in files and move shared work state to a queryable store.

In Unix terms: AGENTS.md is /etc, not /var.

Stays in Markdown Moves out
Rules everyone reads, always Facts about one task
Changed by humans, rarely, deliberately Changed by agents, often, concurrently
Versioned with the code Survives /clear and a change of agent
Meaningful in a diff Meaningful in a query
One author per change N writers at once

Three changes should trigger a review: another writer, another person with different access needs, or frequent state updates. None automatically disqualifies files. Each asks whether the surrounding tools still provide the guarantees the workflow needs.

Leave instructions in files where that works. In our setup, task status and shared memory needed a common access path. Agent prompts remained configuration; the rapidly changing facts around them were operational data.

A five-question test

Not "do you need a database." Do you have an answer:

  1. What happens if two agents write to the same file in the same second?
  2. How do you let an outsider see status without handing over keys and conversations?
  3. How do you ask "what's been blocked for more than a day" without reading everything?
  4. Where did this line in memory come from, and who owns it?
  5. What survives /clear, and why that and not the rest?

Four out of five answered with "well, I'm careful" means you're doing markdown-ops. That isn't a verdict by itself. The verdict is not knowing you're in it.

A file explaining other files is a useful index, not a failure by itself. The check is whether it stays current, resolves references and supports the retrieval the team needs.

What belongs there instead

Not "put it in Postgres" — that pushes the problem down a layer and leaves you without an interface. Five properties, and the value is in having them together:

  1. A row is an addressable entity. A stable identifier that outlives rewrites of its contents.
  2. Columns are a contract. Types, value domains, required-ness — plus a free-form field where the schema hasn't settled yet.
  3. Query instead of read. A slice by condition, not the whole file. The amount returned to the agent can stay bounded; query performance still depends on indexes, data and implementation.
  4. A mutation is an event. Notify, react, recompute. Memory that's awake.
  5. One store, two surfaces. Humans see a board; agents see the same rows over an API. No sync between "the agent's memory" and "the system where people work," because it's one object.

Number five is the whole point for us. In GOD CRM, a person can read a document, edit a table or close a task while an agent works with the same records through tools. A file-based harness connected to the team's tracker can also do this. Compare the effort to build and maintain that shared workflow.

What matters is usable shared state with the required access, concurrency, retrieval and history. Its storage format alone does not settle the choice.

How to move without breaking things

You can't cut over in one step: tools, humans and outside agents all read those files.

  1. Markdown becomes a projection, not a source. The store is authoritative; the file is an export for human eyes and disaster recovery. A telling detail from ours: we had to explicitly forbid agents from searching inside the exports. The moment a copy stops being the source of truth, it becomes noise in your search results. That comes free with every file replica you keep.
  2. Dual-write during the transition, with a declared winner on conflict. "Both sources are equal" has never worked once.
  3. One access path. Agents don't read the memory file directly, even while the file still exists. As long as a second read path exists, the migration never finishes.
  4. A cutoff with a date on it. A rule without a shutoff date isn't a migration, it's a wish.

What it costs. Honestly

  • Higher barrier to entry. A file opens in any editor; a substrate has to be running. Which is why one-command self-install isn't marketing, it's a precondition — without it, "move your state" has no first step.
  • grep -r stops being the universal tool. You need real search instead, and it had better be good, or people will go back to files and they'll be right to.
  • You've signed up for operations. Backups, migrations, uptime. Both file stores and databases can become unavailable or lose data. Backups and tested recovery are required either way.
  • You must provide a review surface. "What changed this week" has to be rebuilt on top of events.
  • A file belongs to nobody; a substrate lives in a specific system. The only cure is an open format and a one-click export back to Markdown, and it has to exist on day one, not "eventually."

The verdict

Markdown-ops was a workable starting point for us. A harness built on files can remain a good system as it grows, if its runtime supplies the necessary mechanisms.

Our mistake was adding writers and responsibilities without adding coordination and selective retrieval.

Adding more instruction files would not have fixed those particular failures.

Our answer was: leave intent in files, give state a substrate. Rules go in git, where they're happy. Facts go where there are permissions, concurrent writes, queries and an audit trail.

We walked this path ourselves and paid in git-index races, forked trees, and memory half our agents couldn't see. As noted last week, this post isn't a file either — it's a row in a table in our own Postgres, rendered to markdown on read. Which is the shortest version of the whole argument: the thing you're reading has an address, a schema, and an audit trail, and the folder it used to live in had none of the three.


Correction · 29 September 2026: The original confused limitations of our setup with limitations of files. Clarified file-based harnesses, concurrency, search, permissions, events and the shared human–agent workflow. Measurements remain the September 1 snapshot.