IntermediateAnalysisAgent Engineering

Agent harnesses

An agent is a model plus a harness. What the harness is, the seven subsystems every one of them implements, and why the hardest parts are context, verification and the credential boundary.

Updated Reviewed

A language model, on its own, does one thing: given a sequence of tokens, it produces a probable continuation. It holds no state between calls, executes nothing, and has no way to find out whether anything it produced was correct. Everything else that a working agent appears to do is built around it.

That surrounding software has a name. The formulation that spread through 2026 is deliberately blunt:

An agent is a model plus a harness.

The harness is the runtime: the loop that decides when to call the model again, the code that executes the tools the model asks for, the logic that decides what stays in the context window, the rules about what is allowed to run without asking, and the record of what happened. A 2026 source-code study of eleven production coding agents defines it as the runtime that couples a model to the world through a loop, tools, context management, safety controls, orchestration and extension surfaces, and names harness engineering as the design and evolution of that runtime.

This page is written against the state of the field through the first three quarters of 2026. Harness design is moving quickly, and the specific vendor behaviour cited below is dated where it appears, deliberately: one of the lessons is that such behaviour expires.

The practical consequence is the reason this page exists. If you are not training the model, then almost everything you control is the harness. Which model you choose is a procurement decision that takes an afternoon. The harness is the system you will spend the next year maintaining, and it is where the cost, the failure modes and the security boundary live.

What a harness is not

The word is used loosely, and four confusions account for most of it. The source-code study separates them, and the distinctions are worth keeping because each one points at a different piece of software.

It is not an agentic framework. A framework such as LangChain, AutoGen or CrewAI is a library you import in order to build an agent. A harness is a runtime you work inside. The distinction was clean in 2025 and is eroding from both directions, but the direction of dependency is still the test: do you call it, or does it call you?

It is not a scaffold, quite. The two are near-synonyms in practice. Where the distinction earns its keep, scaffold names the structural code, the loop and the registries, while harness names the shipped runtime that embeds it. One research baseline in the study is a scaffold of roughly a hundred lines of Python. Claude Code is a harness: a product with a terminal interface, a permission system and a plugin ecosystem.

It is not an evaluation harness. The SWE-bench harness wraps an agent in order to run it against a benchmark. An agent harness wraps a model in order to make it act. The study puts it neatly: same word, opposite direction of wrapping.

It is not an orchestrator. An orchestrator, or meta-harness, coordinates one or more harnesses from above and implements no editing loop of its own.

The seven subsystems

The most useful result in the source-code study is structural. Every system examined, from the hundred-line research baseline to the large production command-line tools, has to take a position on the same seven subsystems. A system can decline one, but declining is itself a position, and the study found exactly that: one single-agent tool implements no orchestration at all, deliberately.

Subsystem What it is responsible for
Agent loop Alternating inference with action execution; owning stop conditions and failure recovery
LLM integration Speaking provider protocols, assembling the prompt, managing caching and routing
Tools and actions Defining and executing what the agent can do, file editing above all
Memory and context Rationing the context window, persisting knowledge across turns and sessions
Safety and permissions Deciding what runs, what asks, what is forbidden, and isolating execution
Orchestration Spawning and coordinating sub-agents, connecting to other agents
Extensibility Letting users and ecosystems add capability through config, hooks, skills, plugins and MCP

Two further surfaces cut across all seven: the interface layer through which humans and programs drive the harness, and the session substrate that records what happened and supports resuming or forking it.

The range within each subsystem is enormous, and seeing both ends is what makes the framework useful. The study records the minimal and maximal implementation observed for each. For the agent loop, minimal is a linear while over a single shell tool; maximal is an event-sourced conversation over a persistent event log with parallel action batches. For tools, minimal is shell access alone; maximal is dozens of typed tools loaded on demand. For safety, minimal is a cost limit and a step limit; maximal is a policy engine plus a model-based approval reviewer plus an operating-system sandbox across three platforms.

Neither end is wrong. A cost limit and a step limit is a real safety subsystem, and for a single-purpose internal tool it may be the correct amount of one. The value of the list is that it makes omission deliberate. Most teams building their first agent implement the loop, the model call and the tools, and discover the other four subsystems by being harmed by their absence.

The loop, and its stop condition

The loop is the part people picture when they imagine an agent: call the model, see whether it asked for a tool, run the tool, feed the result back, repeat. That much is straightforward to write in an afternoon, which is part of why the field has settled where it has. Across roughly four million lines of Python, TypeScript and Rust in the study’s corpus, no agent runtime imported a general-purpose agentic framework. They are hand-rolled async loops.

The interesting part is not the loop but the condition that ends it. A loop with no stop condition is not an agent, it is an unbounded bill. The failure has a shape worth recognising: an agent attempts a task, a verifier rejects the result, the agent tries again with substantially the same approach, and the cycle repeats until something external intervenes. Every iteration is a full inference call over a context window that is getting longer.

Stop conditions in production harnesses are layered, and the layers are not interchangeable:

  • Success, which requires something capable of recognising success. This is the hard one, and it is the subject of the verification section below.
  • A machine-checkable condition, such as the test suite passing or the build succeeding. This is the strongest available signal and is the reason coding agents are further along than agents in domains without a compiler.
  • A budget, in tokens, in currency, or in wall-clock time. This is a backstop, not a strategy.
  • A step limit, which bounds the worst case even when the budget accounting is wrong.
  • Escalation to a human, which is a stop condition and should be designed as one rather than arrived at by exhaustion.

A harness that has only the budget and the step limit will work, and will occasionally spend the entire budget producing nothing. That is not a malfunction; it is what the design specified.

Context is the subsystem that decides how long a task can be

For any task that outlives a single context window, the memory and context subsystem determines whether the agent can finish at all. Three strategies are in production use, and they differ in what they throw away.

Compaction summarises earlier conversation in place, so the same agent continues on a shortened history. It preserves continuity. Anthropic’s engineering team notes the cost plainly: the transformation is irreversible unless the original is stored separately, and it is difficult to know in advance which tokens later turns will need.

Context resets clear the window entirely and start a fresh agent, with a structured handoff artefact carrying the state and the next steps. This gives a clean slate at the price of the handoff needing to be good enough to work from.

Externalising the context keeps the full record outside the window in a durable log, and lets the loop pull back the slices it needs. Anthropic’s Managed Agents design does this: the session is an append-only event log, and the harness can request positional slices of it, rewinding to before a particular moment or rereading the lead-up to an action. The point of the design is that no irreversible decision is made about what to keep. The phrasing in their post is the one to remember: the session is not the model’s context window.

A cautionary detail worth the whole section

While testing long-running coding agents, Anthropic observed that Claude Sonnet 4.5 would wrap up work prematurely as it sensed its context limit approaching, a behaviour they call context anxiety. Compaction alone did not fix it, because compaction leaves the agent in the same session with the same sense of an approaching limit. Context resets did fix it, and so resets became part of the harness.

Then they ran the same harness on Claude Opus 4.5, and the behaviour was gone. In their words, the resets had become dead weight.

This is the single most useful thing to understand about harness engineering, and it generalises past this example. Every component in a harness encodes an assumption about something the model cannot do on its own. Models change. The assumptions do not expire on their own, and nothing in a passing test suite will tell you that a workaround is now pure overhead. A harness accumulates scar tissue from model versions that are no longer in service, and re-examining those assumptions has to be scheduled, because nothing else will prompt it.

Verification, and why the agent cannot do it itself

The natural way to close the loop is to ask the model whether its own work is correct. This does not work, and the reason is not a prompt that needs improving.

The research is specific about the shape of the failure. Huang and colleagues examined intrinsic self-correction, where a model revises its answer using only its own judgement and no external feedback, and found that models struggle to do it for reasoning tasks, with performance sometimes degrading after the attempt. Earlier results that appeared more positive turned out to depend on oracle labels telling the model when to stop.

A companion result sharpens it usefully. Tyen and colleagues separated finding an error from fixing one, and found that the weakness is in finding: models are poor at locating logical mistakes, but their ability to correct a mistake is robust once they are told where it is. That distinction has a direct architectural consequence. You do not need a smarter model to correct the work. You need something other than the model to say where the problem is.

Anthropic’s practitioners report the same thing from the other direction: agents asked to evaluate their own output tend to praise it confidently, and the effect is worst on subjective tasks where no binary check exists. Their finding is that separating the agent doing the work from the agent judging it is a strong lever, with an honest caveat that the separation is not magic, since the evaluator is still a model inclined to be generous about model-generated output. What makes it work is that tuning a standalone evaluator to be sceptical is far more tractable than making a generator critical of its own work. Once that external feedback exists, the generator has something concrete to iterate against.

The design conclusion is the same from the research and from practice: decouple generation from verification. In descending order of strength, verification comes from a machine-checkable signal such as a compiler, a test suite or a schema validator; then from a separate model instance with its own criteria and no stake in the original answer; and only then from the generating model itself, which is close to no verification at all.

Safety is a structural property, not a prompt

The permissions subsystem is where the difference between a demonstration and a production system is most visible, and Anthropic’s account of rebuilding theirs contains the clearest worked example of why.

In their original design, everything lived in one container: the session, the harness, and the sandbox where the model’s generated code ran. Credentials were therefore in the same environment as untrusted generated code. As they put it, a prompt injection only had to convince the model to read its own environment. With those tokens an attacker could spawn fresh, unrestricted sessions.

The instructive part is their assessment of the obvious mitigation. Narrowly scoping the token is the standard advice, and they reject it as insufficient on the grounds that it encodes an assumption about what the model cannot do with a limited token, and models keep getting better. The structural fix was to make the credentials unreachable from the sandbox at all:

  • For repository access, the token is used to clone during sandbox initialisation and wired into the local Git remote. Push and pull work from inside the sandbox without the agent ever handling the token.
  • For other tools, credentials sit in a vault outside the sandbox and calls go through a proxy that exchanges a session-scoped token for the real credential. The harness is never made aware of the credential either.

The general principle transfers to any harness: a control that depends on the model choosing not to do something is not a control. Isolation, credential separation and an allowlist of permitted actions are controls, because they hold whether or not the model cooperates.

Decoupling, and why it matters operationally

The same Anthropic post describes an architectural lesson in familiar infrastructure terms. Putting the session, harness and sandbox in one container turned the container into a pet: a named individual that could not be lost and had to be nursed back to health when it misbehaved. If it died, the session died with it.

Worse, it destroyed observability. Their only window into a running agent was the event stream, which could not distinguish where a failure came from, so a bug in the harness, a dropped packet and a dead container all presented identically. Diagnosing anything meant opening a shell inside a container that also held user data.

The fix was to separate three things that had been fused, each behind an interface that assumes little about the others:

Component Responsibility
Session The append-only log of everything that happened
Harness The loop that calls the model and routes its tool calls to infrastructure
Sandbox The execution environment where generated code runs and files are edited

With the harness outside the container, the container becomes replaceable: if it dies, the harness receives an ordinary tool-call error and can provision a new one. With the session outside the harness, the harness becomes replaceable too, because nothing in it needs to survive a crash. A fresh harness can wake, read the event log, and resume from the last event.

For a team building something smaller, the transferable point is not the hosted service. It is that these three concerns fail differently and should be able to fail separately, and that if your session lives inside the process running your loop, a crash costs you the work.

The shape of a minimal loop

The following is a skeleton, not a library. It exists to show where the seven subsystems attach, and every one of the interesting decisions is a comment rather than an implementation.

def run(task: str, *, max_steps: int = 40, token_budget: int = 200_000) -> str:
    session = Session()                      # subsystem: session substrate, durable and external
    session.append({"role": "user", "content": task})
    spent = 0

    for step in range(max_steps):            # subsystem: safety, the backstop bound
        window = build_context(session)      # subsystem: memory and context, the real decision
        reply = call_model(window)           # subsystem: LLM integration
        spent += reply.usage.total_tokens
        session.append(reply.message)

        if spent > token_budget:             # subsystem: safety, the budget bound
            return escalate(session, reason="token budget exhausted")

        if not reply.tool_calls:             # the model believes it is finished
            verdict = verify(reply, session) # subsystem: verification, external to the generator
            if verdict.ok:
                return reply.text            # stop condition: checked success
            session.append(verdict.as_feedback())
            continue                         # the generator now has something concrete to act on

        for call in reply.tool_calls:        # subsystem: tools and actions
            if not permitted(call):          # subsystem: safety, before execution and not after
                session.append(refusal(call))
                continue
            session.append(execute(call))    # runs in the sandbox, never with credentials in reach

    return escalate(session, reason="step limit reached")

Three things in that skeleton are worth stating explicitly, because they are the ones most often got wrong.

build_context is called fresh on every iteration rather than appending to a growing array. That signature is what allows compaction, resets or slice-based retrieval to be swapped without rewriting the loop.

verify takes the session and is not the model that produced the reply. If it were, the loop would inherit the finding from the self-correction literature and terminate on the generator’s own opinion of its work.

permitted is consulted before execute, and returns a refusal into the transcript rather than raising. The agent needs to see that it was refused, or it will try the same call again and burn the budget discovering nothing.

Both ends of the loop return through escalate rather than returning a result. An agent that has run out of budget or steps has not produced an answer, and returning its last output as though it had is how unbounded work becomes a wrong answer delivered confidently.

What the field actually does

Two findings from the eleven-system study are worth holding onto, because both contradict what the surrounding discourse suggests.

No agent runtime in the corpus imported a general-purpose agentic framework. Across all eleven production systems, the loops are hand-written. This is not an argument against frameworks, which are useful for assembling an application quickly. It is evidence that the teams building the runtimes themselves have consistently found the abstraction unhelpful at that layer.

None retrieved code with vector embeddings. In a period when retrieval was widely assumed to mean semantic search over an embedded corpus, the coding harnesses used deterministic retrieval: searching, listing, and reading files the way a developer would. Worth weighing before an embedding pipeline is treated as the default for a code-adjacent agent.

Where this fits

This page is the anatomy. Four subsystems have enough depth to warrant their own treatment, and three of them already have pages: the tool interface in tool calling, the memory and context subsystem in agent memory, and the extensibility surface in Model Context Protocol. Where a framework helps and where it stops helping is covered in agent frameworks.

What this page does not tell you

It does not tell you which harness to adopt. The eleven systems in the study are all defensible designs serving different constraints, and the study is an anatomy rather than a ranking.

It also does not give you numbers. There is no reliable published figure for what a given harness design costs to run against a given workload, because the answer depends on the task distribution, the model, the context strategy and the stop conditions interacting. Anyone quoting a general figure is quoting their own workload. Measure yours, which is an evaluation problem before it is an architecture one.