What is a harness?
A visual guide to the infrastructure that turns language models into agents.
An LLM, on its own, is a function. You hand it text. It hands you back text.
That's not what people mean when they say "AI agent." An agent books flights. It reads files, runs tests, opens pull requests, drives a browser. It does things.
So how do you get from the first to the second? That's what this post is about.
Part 1: building the harness
The model, alone
Start by handing the task straight to the model.
The model knows which airlines fly the route and can ballpark prices. What it can't do is find specific flights or book anything. There's no airline API on the other side of the text box. There's just more text coming back. The request arrives, a reply goes out, and nothing in the real world moves.
For that to change, the model needs a way to trigger code outside the text box.
Giving it tools
To go further, the model needs tools. We describe the actions it can request, such as searching for and booking flights, then pass those descriptions in with the user's message. Each tool has a name, an explanation, and an argument schema. From the model's point of view, they are options it can pick.
There's no function-calling magic here. The model still generates a sequence of tokens. What changes is how the harness interprets them. We tell the model to use an exact format when it wants a tool.
<search_flights>
{
from: "CPH",
to: "AMS",
date: "2026-05-24",
}
</search_flights>
As the reply streams back, we watch for those tags. The moment we see a <search_flights> opening, we stop treating the output as conversation and start treating it as a request. We parse the JSON, call the real search_flights function, and carry on. The model wrote text in a particular shape. Our code noticed the shape and did something with it. That's all tool use really is.Note: In a real system you wouldn't use a format this simple. A tag like <search_flights> can collide with what the model is actually trying to write. A coding agent might emit <div> or <table> as part of its real answer, and a naive parser would grab it. Production systems use formats that can't plausibly appear in normal output. OpenAI's Harmony is a good example. It defines special tokens like <|start|>, <|message|>, and <|channel|> that are trained into the model and never show up in regular text, so the harness can scan for them without false positives.
The model picked the right tool, filled in the arguments, and got flight data back. Open the Code tab to see the implementation.
Then it stops. The search ran, but nobody passed the result back to the model, so it never got to react to what it found. The return flight never got looked up. Nothing got booked.
The agent loop
What's missing is a way to keep going. Once a tool runs, the model has to see the result and decide what to do next. And it has to be able to do that as many times as the task takes.Note: In real systems there is usually a cap on how many turns the agent can run, to prevent runaway loops.
Wrap the whole thing in a loop. Call the model. If it asks for a tool, run it, append the result to the conversation, and call the model again. Exit when it gives a normal reply instead of requesting another tool.
Same model, same tools. The only thing we added was a while and an append. That loop is the agent.
You could stop here and ship something useful. The rest of this post is about what goes on inside that growing conversation. Once the loop works, most of the hard problems in agent design are about the list of messages you keep handing back to the model.
Part 2: looking inside the harness
What the model sees on every call
The model is stateless. Each call starts from nothing. If you want it to know what happened on the previous turn, you include that turn in the next call. Same for the turn before, and the one before that. What looks like memory is just us re-sending the whole history every time.
What we've been loosely calling "the conversation" is just a messages array we keep in a variable and replay. Below is the full transcript of the flight-booking task. Send a follow-up to see how it grows when the loop keeps going.
The system prompt is the first message. It tells the model what it's for, which tools it has, and how to behave. The model treats it as higher-priority instruction than user messages, although that does not make it a security boundary. It stays at the top of every call. Below it, user messages, assistant replies, tool calls, and tool results pile up unless the harness removes or compresses them.
That pile is the messages array from the loop we wrote in Part 1. It's the only memory the agent has. Whatever is in there is what the model can reason over on the next call.
The cost of remembering
Because the full transcript goes out on every call, the input the model has to read grows with every turn.
Every tool result sticks around. Every assistant reply sticks around. By the sixth call, the model is re-reading everything it has already done. That is why context management becomes the engineering problem.
Every noisy tool call keeps costing you. A test runner might dump 200 lines. A file read might return 4,000 lines. That output stays in the transcript and gets read again on every later call. One careless tool call at step 2 can double the input at step 10.
That's where most of the design work happens. Once the loop works, you have to decide what enters the transcript and what stays out. Compaction, sub-agents, skills, and memory hierarchies all answer the same question: what belongs in the context?
Where this leads
That's the whole thing. An agent is:
- A model
- A set of tools the model can call
- A loop that keeps calling the model with the growing transcript
Everything else refines one of those pieces. That includes system prompts, sub-agents, context compaction, skills, permission gates, verification loops, and memory hierarchies.
Tool design starts with which tools you give the agent and how well each one works. Too few and the agent can't do the job. Too many and it gets lost picking between them. One tool can wreck a run by dumping too much output, requiring too many arguments, or doing the wrong thing. The rest of the harness can't save it.
Changing the loop usually means adding a step between model calls, such as verification, compaction, or a handoff to a sub-agent.
When you read about a new agent framework or prompt technique, ask which part it changes: the tools, the loop, or the transcript?
That's the map. Most of what follows in this series is just working out the details.
This is the first in a series on harness design. Next, I'll test progressive disclosure: how agents navigate large contexts and how that context should be structured. Reach out at noah@schenktechnology.com if you're building agents and want to compare notes.