Agents and Harnesses: Everything That Isn't the Model

Here’s the odd thing about the last couple of years. The models got better, sure. But the jump from “an LLM that writes code” to “a thing that opens a pull request while I’m at lunch” didn’t come from a model release. Claude Code, Codex and Cursor were all built on models that had been sitting there, available to everyone, doing far less.

What changed was the scaffolding around them. That scaffolding has a name, the harness, and it is now most of the product.

So let’s open one up and look inside.

What isn’t an agent

Before we get into what a harness is, it’s important to understand what an agent is. Before that, we should understand what an agent isn’t. I know that’s a little confusing, but bear with me, because no-code “AI Agent” platforms and prompt-chain orchestration libraries share a single delusion: that combining LLM calls with if-else branches and hardcoded routing logic is “building an agent”. It isn’t.

You cannot brute-force intelligence by stacking procedural logic and chained prompt waterfalls and praying it produces autonomous behaviour. It won’t.

So what are all these things, if they aren’t agents? Usually one of five:

Here’s the test that separates them from the real thing:

If I can draw the flowchart before it runs, it isn’t an agent.

Every one of those five is a program that calls a model. I don’t mean that as an insult. They’re cheaper, faster, far easier to debug, and most of the time they get the job done instead of an agent. But they’re just not agents.

What an agent is

Turn that test around and you have the definition. An agent is a program whose flowchart nobody can draw in advance — because the model draws it at runtime.

Three things have to be true at once, and none of them is enough on its own:

  1. A loop. One call and you’re finished isn’t an agent.
  2. Tools that change something real. Otherwise it’s just thinking out loud.
  3. The model owns the control flow. It picks the next action. Not me, not a graph I dragged together in a UI.

The third one is the whole ballgame, and it’s exactly what those platforms keep for themselves. They’ll happily let the model choose between the branches. They won’t let it decide there should be a different branch.

Put simply, then: an agent is a model scaffolded by software that helps it take the right actions to achieve a task. And the software that scaffolds the model is the harness.

Agent = Model + Harness

Where the harness fits

It’s the harness’s responsibility to provide enough resources, information and environment for the model to take the right action and complete the task.

That sounds like a support role. It isn’t — look at what you just gave away.

Write an ordinary program and you get things for free: what runs, in what order, how many times, what happens when it fails. Now hand over all this control flow to the model and all of that is gone, and you can’t get it back the usual way.

Stop after twenty turns. Never touch production. Tell me what it spent. Every one of those now has to come from outside the loop.

That scaffolding is the harness.

The harness is everything that isn’t the model.

The car analogy

Think of it this way: the agent is the car. The model is the engine, and the harness is everything else — the steering, the brakes, the accelerator, the mirrors, the safety features. The analogy isn’t perfect, but I hope it conveys the main point.

CarHarness
enginethe model
steeringwhat it does next
brakeswhat it isn’t allowed to do
acceleratorhow far it goes before checking with me
dashboardwhat it actually did

Here’s why the analogy is worth it for building a mental model around the harness. I don’t build the engine. I buy it and so does everybody I’m competing with, from the same handful of suppliers, at the same price per token. Nobody is going to win on the engine. An engine bolted to a stand at full throttle is noise and heat and nothing else; it’s the steering and the brakes and the instruments that turn it into something you’d put a person in. The car is the product.

If you think closely, Cursor and Claude Code both support Claude models — Claude Opus runs in each of them — but Claude Code seems to perform a little better.

The coding agent is a good example of harness engineering

Coding agents like Claude Code, Codex, Pi and Cursor have taken the agentic world by storm recently, and they are perfect examples of harness systems. The model sits at the heart of it, and everything engineered around it — bash tools, a sandboxed environment, permission to run scripts in a terminal, feeding the repo to the model, managing context — is what does the wonders.

Hello world of a coding harness

First, the engine. Buy it, name it, tell it what job it’s doing.

import os, subprocess
from google import genai

client = genai.Client(api_key=<YOUR_GEMINI_API_KEY>)
MODEL = os.getenv("MODEL_ID", "gemini-3.5-flash-lite")

SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain."

Then one tool. This is the only thing the model can actually do:

TOOLS = [{
    "type": "function",
    "name": "run_bash",
    "description": "Run a shell command.",
    "parameters": {
        "type": "object",
        "properties": {
            "command": {"type": "string", "description": "Command to execute"},
        },
        "required": ["command"],
    },
}]

The tool definition is what the model sees. This is what actually runs — and it’s where the brakes live:

def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, capture_output=True,
                           text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

Now the loop — the part everyone thinks is the hard bit:

def agent_loop(history):
    while True:
        interaction = client.interactions.create(
            model=MODEL, system_instruction=SYSTEM,
            input=history, tools=TOOLS, store=False,
        )

        # everything the model produced this turn: thoughts, text, tool calls
        for step in interaction.steps:
            history.append(step.model_dump())

        calls = [s for s in interaction.steps if s.type == "function_call"]
        if not calls:            # it didn't ask for a tool, so it's done
            return

        for call in calls:
            command = call.arguments["command"]
            print(f"$ {command}")                        # the dashboard
            history.append({
                "type": "function_result",
                "name": call.name,
                "call_id": call.id,
                "result": run_bash(command),
            })

And a driver’s seat:

history = []
while (query := input(">> ").strip()) not in ("q", "exit", ""):
    history.append({
        "type": "user_input",
        "content": [{"type": "text", "text": query}],
    })
    agent_loop(history)

Run it and ask for something small:

create a python program which prints "hello world", then run it

Two tool calls off one sentence. I never told it to write a file, and I never told it to use cat to do so — it decided both. That’s the loop steering itself.

What those fifty lines actually contain

Four of the five car parts are already in there. The engine is the interactions.create call. The steering is that if not calls check — the model decides whether there’s another turn. The brakes are the blocklist, the timeout and the output cap. The dashboard is that one print. Four out of five, in fifty lines.

The beauty of it is there is no rigid flow — the model owns the control flow, leading towards autonomous software.

Now go and build one

Nothing here is coding-specific. Swap the tool and the same fifty lines become a harness for whatever you work on.

Two easy places to start:

Pick whichever is closer to your day job and give it an afternoon. Then let it run somewhere it can do damage you can afford, and watch it. Watching your own loop spin for the first time is what makes all of this make sense.

NOTE

When you build for your own domain, you’ll notice something the coding harness got for free. A coding agent can run the tests — the compiler, the linter and git diff came with the domain, so it acts, finds out within seconds whether it worked, and corrects itself unattended.

Almost nothing else works like that. A support agent that sends the wrong answer gets no red build. It doesn’t feel wrong, it feels finished, and you find out a week later from somebody else. Coding agents got good first because their domain handed them a verifier. Everywhere else, building that verifier is the job.

← Back to all posts