Will ChenWill Chen
← Writingsystem design

Defining an agent in a markdown file

A markdown document is addressable, which makes it a filesystem. Following that out produced an interpreter that turns a document into a running agent.

Will ChenWill Chen6 min

Motivation

Agent behavior normally lives in code. You write a class or a config file, you register some tools, you wire up a loop, and the thing that describes what the agent does is a program that only a programmer can read and only a runtime can execute. The description and the machinery are the same artifact, which means you cannot hand someone the description without handing them the machinery.

In February I ended up somewhere else, which is that the description is a markdown file and the machinery is a small interpreter that reads it. Getting there took a detour through filesystems.

The filesystem derivation

At the start of the month I was stuck. I had been building on Cloudflare's Durable Objects, which give you a single addressable instance that stays alive next to its own storage and streams results to a browser, and the trouble was that this solves the delivery half of the problem. It is a streaming runtime for the web, and an unusually good one. What it needed from me was complex agent code, and the demo it invites you to give is a lot of agents streaming at once, which had stopped being interesting. I was trying to hold the compiler for that platform, a product built on it, and a use case worth showing, all at once, and the state space was too large.

Local agents were what I actually wanted, because that is where the use cases expand and where you can make progress in an order that builds on itself. I also wrote down the problem with that direction in the same breath, which is that an open-source framework for building local agents is not obviously a business. Choosing it did not make that go away.

A few days later I asked for an explanation of filesystems from first principles: a Socratic derivation in discrete steps, starting from the fundamental problem you have once arbitrary binary storage exists, and moving through problem, insight, solution, new problem. I was not doing background reading. Working out how a familiar abstraction was forced into existence is how I find out which parts of it are essential.

The derivation goes roughly like this. You have storage that is addressable, meaning you can write bytes at a position and read bytes from a position, and that is all you have: a flat plain of numbered slots. The first problem is that nothing tells you where anything ends, so you need extents. Then nothing tells you what a region is for, so you need names, and a table of names pointing at regions. Then names collide, so the table becomes a tree. Then several programs want the same region, so you need permissions on nodes of that tree.

Then I read that back and noticed the premise applies to something else. A markdown document is addressable too. You can point at a heading, a section, a line. So I asked to trace the same evolution with markdown as the storage medium, and the correspondence holds further than it has any right to.

ProblemFilesystem answerMarkdown already has
Nothing says where a region endsExtentsThe next heading of equal or higher level
Nothing says what a region is forNames, and a table pointing at regionsThe heading text
Names collideThe table becomes a treeHeading nesting
Several writers want one regionPermissions on nodes of the treeNothing

Three of the four steps were already done, by a format nobody built for this. The document you are writing in turns out to have the shape of a small filesystem that nobody bothered to give the rest of the machinery to.

The missing piece is the last step of the derivation. Permissions on addressable writable sections would be very good for AI. If a section of a document is a node with an owner and a write bit, then a document stops being a blob an agent reads and becomes a surface an agent operates through, with parts it may change and parts it may not.

The interpreter

That is the thinking mdagent came out of. It is a minimal agent interpreter: it takes a markdown file and spins up an agent you can chat and interact with. Instead of code it parses special markdown directives that implement primitives such as cron heartbeats, event listeners, and parallel subagent delegation.

The shape of the instruction set is the part that generalises, and it is the part worth describing. The particular syntax stayed private; mdagent was listed on the lab site a fortnight later at the stage marked exploring, with no packages or repositories against it.

Those three primitives are worth looking at, because they are the ones that usually require a runtime rather than a prompt. A cron heartbeat means the agent gets to act on a timer, which is what makes it something other than a function you call. An event listener means it reacts to something happening elsewhere. Parallel subagent delegation means it can fan work out and collect it back. In a normal framework each of those is a subsystem with an API. Here each is a directive in a document.

The obvious objection to a minimal interpreter is that it will not stay minimal. Every primitive anyone wants becomes a new directive, every new directive is a change to the parser, and in six months you have a large interpreter with a private language and no way to extend it without touching the core.

The answer was four words: the directive set is plugins.

That is the whole extensibility story and it is the reason a minimal interpreter is viable rather than merely small. The interpreter parses markdown and dispatches on directives it does not itself define. A primitive is a plugin that registers a directive. Adding a capability is writing a plugin, not editing the language, which means the instruction set is open while the thing executing it stays fixed and comprehensible. It is the same relationship a shell has to the programs on your path: the shell does not know what grep does, and it does not have to.

What the shape settled

There is a version of the last year I could tell where the work was mostly about constraint. Typed runtimes, safe execution boundaries, schemas that make invalid states unrepresentable. That instinct is not wrong and I still hold most of it, and applied to agents specifically it kept producing systems that were rigorous and joyless to use.

The thing I noticed on the day mdagent worked is that the plain approach and the sophisticated approach tend to agree, and it is the middle that overcomplicates. Handing an agent a document and letting it read the document is the naive move. It is also, once you have followed the filesystem derivation, the sophisticated one, because the document is already the tree and already addressable and already the thing a human wanted to edit.

The elaborate typed runtime sits between those two, and it is worth being specific about what it trades. It costs the property that made the document worth using, which is that a person can open it and change it without installing anything. What it buys is a checker that reads your definition before it runs and refuses the ones that are malformed. That is a real thing to want. But look at where an agent definition actually goes wrong. The structure around the instructions is a few fields and rarely the problem; the instructions themselves are English, and a checker cannot tell a good instruction from a bad one. So the checking lands on the part that was already fine, and you have paid for it by making the document something only a programmer can open.

It felt like the first time I had shipped something aligned with the right way to think about agents rather than with the instinct to constrain them.

The permissions layer is the piece of the derivation I would build next, and it is worth naming precisely. Addressable writable sections with an owner and a write bit are what turn a document from something an agent reads into an interface an agent operates through. mdagent parses and dispatches directives today, and enforcing who may write where is the step after that.