Will ChenWill Chen
← Writingsystem design

Writing a book to decide what to build

A language model is a stateless function from text to text. Everything that looks like intelligence around it is code somebody wrote. I wrote a book to find out where each piece goes.

Will ChenWill Chen11 min

Motivation

A language model is a function that takes text and returns text. It holds nothing between calls. It has no record of your last message, no sense of who you are, and no way to affect anything outside the string it hands back. Send it the same input twice under the same settings and it draws from the same distribution both times, because there is nothing else in there to draw on.

Almost everything people find remarkable about an agent lives outside that function. When an assistant remembers your name, some code put your name in the prompt. When it looks something up, some code parsed a piece of its output, matched it against a list of permitted functions, called one, and pasted the result into the next prompt. When it works overnight while you sleep, a scheduler is calling it on a timer and rebuilding its instructions from a database each time. The model did what it always does, which is read text and write text.

That gap between what a system appears to do and what its code actually does is the whole subject. It is also a practical problem rather than a philosophical one, and the practical version goes like this: when you want to make an agent smarter, you have to decide where the improvement goes. Is it a prompt problem, a storage problem, or a scheduling problem? Without a map of the parts, every design decision feels arbitrary, and you end up inventing the same pattern twice under two names or putting a feature in a place it does not belong.

The book as a spike

At the end of 2025 I was building an agent runtime and could not decide what belonged in it. So I started writing a book about agentic systems instead, and I was explicit at the time about why. On the last day of December I wrote that the book itself was a meta-spike.

A spike, in the ordinary engineering sense, is code you write to answer a question and then throw away. You are not trying to keep it. You are trying to find out whether something is possible, or how hard it is, before you commit to a design. I was doing that with prose. Each section of the book would need working examples to be honest, and building those examples would tell me what the runtime had to support. Writing the explanation was the cheapest way to discover the requirements.

Deriving the ten elements

The question I opened that work with, at the start of January, was what a developer actually needs in order to design intelligence rather than plumbing. My claim at the time was that the resolution at which intelligence design happens is not streaming, or retrieval pipelines, or caching, or chunking. It is how information flows, how context gets loaded and transformed, how things are put into memory and taken back out, and how an agent makes a decision and routes text to effects. Intelligence design is code design.

The premise that fell out of working through that is the one everything after it rests on, and I wrote it down compactly a few hours later. You have a stateless text-to-text function and you wish to give it some intelligent behavior, so you break human-level language down into mechanism, and then describe what each mechanism is and the code structures that enable it.

The direction of that derivation is the part that matters. You could organize a book about agents by technique, with a chapter on retrieval, a chapter on function calling, a chapter on planning. That produces a catalogue, and a catalogue does not help you when something is broken, because you cannot look up a symptom in it. Starting from the behavior instead gives you a diagnostic. A person says the agent forgot what they told it. That is an ordinary sentence about an experience, and it traces to exactly two possible places in the code: what got assembled into the prompt for that call, or what got stored and retrieved beforehand.

The reference table I ended up keeping is just that translation, done once for every behavior people describe:

"It seems to..."Actually is...
Remember what I saidConversation history array in prompt
Have long-term memoryDatabase plus retrieval into context
Do things in the worldStructured output, parser, function dispatch
Think step by stepMultiple calls with state passed between
Plan before actingplan = llm(task), then for step in plan: execute(step)
Check its own workGenerate, separate verify call, conditional retry
Have multiple expertsDifferent system prompts routed by classifier
Work while I sleepCron job triggers agent
Learn from experienceOutcomes extracted, stored, retrieved into future contexts

Nothing in the right column is exotic. That is the point of putting it next to the left column, where everything sounds like a mind.

Ten elements came out of that, each named for the behavior its code produces rather than for the code itself. The second column is what the element is, and the third is the question I actually cared about, which is where in your codebase the capability lives.

#ElementWhat it isWhere capability lives
1ContextInformation available to the model for a single callToken budget and context construction
2MemoryExternal storage for selective retrieval into contextStorage structures and retrieval mechanisms
3AgencyTranslation layer from text to effectsExecution boundary and policy enforcement
4ReasoningGrammar of call composition, meaning chaining, looping and branchingCall structure and interstitial computation
5CoordinationCommunication and sequencing between reasoning structuresExecution flow and data flow
6ArtifactsShared persistent state for coordinationTyped objects, operations and lifecycle
7AutonomyWhat triggers execution and who owns the main loopTrigger infrastructure and context reconstruction
8EvaluationDetermining whether the system succeededQuality signals and measurement functions
9FeedbackGradient signals that steer behaviorSignal sources and injection points
10LearningFeedback that persists to change future behaviorLearnable parameters and extraction pipeline

Read the third column on its own and the list stops being a taxonomy. Every row names a place in a repository where a change would go.

Try it against a real complaint. Someone says their research agent keeps going off and reading things nobody asked about. That is one sentence of ordinary annoyance, and the table turns it into four candidate diagnoses that live in four different files. It could be Context, if the task description going into each call is vague enough that wandering is a reasonable reading of it. It could be Reasoning, if the loop has no step that asks whether the current sub-question still serves the original one. It could be Agency, if the search tool is registered with no scoping parameter, so the model has no way to express a narrow search even when it wants one. Or it could be Evaluation, if nothing anywhere measures whether a retrieved document was used, in which case the system has no signal that wandering is bad and you are relying on the model to know.

Those are four different afternoons of work, in four different parts of the codebase, and three of them are wasted if you pick wrong. Before the list I would have started by rewriting the prompt, because rewriting the prompt is what is nearest to hand. The list does not tell you which of the four it is. It tells you that there are four, which is enough to stop you from spending a week on the first one.

Three of those are the same move applied at different scopes, and noticing that was the moment the list stopped feeling arbitrary. Memory is externalized context, which is storage for one agent across time. Artifacts are externalized coordination, which is shared state across several agents. Learning is externalized feedback, which is a signal stored so it can steer a task that has not happened yet. In each case something that only existed inside one call gets written down so it can survive outside one call. That is the pattern the whole design space is made of.

The execution boundary

In February I spent a long session redoing the diagrams, and the rule I set for them was more useful than any single picture. I wanted the visual punchline for each one, and the theme I was after was locating the chain of causality that creates what you experience. A normal diagram would show how tool calls work. The one I wanted showed a text zone, then your code translating, then an effect zone.

Draw the vertical line where the translation happens and the whole thing becomes obvious. On the left, the model has produced a string. That string is inert. It can say delete_all_records as easily as it can say hello, and neither one does anything. On the right, effects happen: a file is written, an email goes out, a row disappears. The line between them is code you wrote, and it is doing two jobs at once. It is parsing the string into a call, and it is deciding whether that call is allowed.

Which means capability is a property of your registry, not of the model. If the model emits a request to delete everything and your dispatch table has no entry for it, nothing happens. Not because the model was well behaved, and not because a filter caught it, but because there was no path from that string to that effect. Most of the anxiety about what an agent might do is really a question about what you wired up.

Context reconstruction

The diagram I redid most was the one about identity, and it is the idea I would keep if I could only keep one. In the note asking for the redraw I wrote that continuity is an illusion, that agent identity and behavior are determined by context reconstruction on each call, and that the unified identity you experience is captured in persisted state rather than in the model.

Every turn, your system assembles a fresh context out of stored pieces and sends it. The model then acts as though it has whatever history you included, because that history is simply text in front of it. Nothing carried over. The sense that you are talking to a continuous someone is produced fresh, each time, by an assembly step you control.

Once you believe that, a class of bug reclassifies itself. The agent that forgot a fact did not forget it, because forgetting requires having held it. The fact was not in the context for that call. The agent that contradicted itself was not being inconsistent, because consistency requires a self to be consistent with. Two different contexts were assembled and each was reasoned over correctly. The model behaved the same way it always does. The assembly was different.

You can test this directly, which is what makes it a claim rather than a metaphor. Keep the history and swap the model underneath, and the character mostly survives, because the character was in the transcript. Keep the model and change what you inject, and the character changes.

The Ralph loop counterexample

On February 8th I ran into a result that cut against the framework, and what I did with it is the part worth reporting. There is a technique people call the Ralph loop, which in its plainest form is running the same coding agent on the same task over and over in a loop rather than building elaborate scaffolding around it. It was outperforming much more sophisticated designs.

The easy reading is that simple beats complex, and I wrote down that I did not think dumb versus sophisticated was the right division, because the loop is doing something structural in terms of compute that applies a forcing function to bend probabilities. Repetition is not an absence of design. Running the same task many times and keeping what passes is a search, and search converts compute into quality on problems where checking an answer is cheaper than producing one. The loop was not winning by being unsophisticated. It was winning by spending compute in a shape the sophisticated designs were not.

Then the prediction, which is the useful part and which I have not tested: a Ralph loop with better context construction, better error feedback signals, and better learning should work as well or better. If that turns out to be true, the loop is not a counterexample to the framework at all, it is an unusually clean instance of a few elements doing all the work. If it turns out to be false, then something about repetition is doing a job that the elements do not describe, and the framework has a hole in it.

Renaming to harness engineering

In March, other people started publishing on the same thing under a different name. A LangChain post on harness engineering is what made me look, and the term had been circulating for a while before that in work coming out of HumanLayer. What I had been calling agentic system design was what people were calling harness engineering, and I wrote it down that plainly: what I have called agentic system design equals what people are calling harness engineering, and I just need to use their words.

I rewrote it as a design space map under the field's term. Giving up your own name for a thing feels like a loss and is almost never one, because the point of a vocabulary is to be understood in, not to be owned. A framework with a private name has to teach the name before it can teach anything, and every reader who already has a word for the concept has to translate. Adopting the common term deletes that work.

What I keep from the whole exercise is smaller than the book and more useful. The model is a stateless function from text to text. Every intelligent-seeming behavior around it is a loop, a query, a scheduler, or a policy check that somebody wrote, and once you can name which one you are looking at, an agent stops being a personality you negotiate with and becomes a program you can inspect.