By Bogdan Baciu · August 8, 2026 · 19 min read

The Harness Is Everything You Can Swap Without Retraining the Model.

Anthropic and OpenAI use “agent harness” for different things. A substitution test that draws the boundary and localizes agent failures against it.

The loop · steps 2–8 Evaluation harness contains all of it
Foundation model — held fixed Agent harness — the substitutable layer Tools / MCP Sandbox / execution Application — outside Evaluation harness — contains everything

The problem

If you debug agents for a living, you have had this argument. Someone says the model is bad at long tasks. Someone else says the scaffold is wrong. A third says it is tooling. Nobody settles it, because nobody agrees where the model stops and the system around it starts.

The word for that system is harness. It currently means two different things.

Anthropic, in January 2026, defined an agent harness, or scaffold, as "the system that enables a model to act as an agent: it processes inputs, orchestrates tool calls, and returns results." That is a runtime. It sits between the caller and the weights. Its job is the loop.

OpenAI's harness engineering post, published February 11, 2026, contains almost none of that. It is about a docs/ directory treated as the system of record. A roughly 100-line AGENTS.md used as a table of contents rather than an encyclopedia. Linters that enforce dependency direction between layers. Per-worktree app instances, with an observability stack the agent queries in LogQL and PromQL. Their harness is the repository and the environment around it.

Both are first-party. Both are right about their own system. They are not describing the same object. That is the tell: if two of the best-resourced agent teams alive use one word for two things, your own boundary is probably wrong too.

A definition you can test

I use a behavioral definition rather than a structural one.

The agent harness is everything that determines what the model sees, what it is permitted to do, and when it stops: every component you could change to alter the outcome while the weights stay fixed.

The operational form is a substitution test. Hold the weights constant. Change one thing. See whether the result moves.

That settles the disagreement rather than papering over it. OpenAI's repository is harness here, because making it legible changed what Codex produced from the same model. Anthropic's loop is harness for the same reason. Both worked one surface at different points.

The substitution test, in miniature Three lanes, each showing a different harness driving the same fixed-weight model to a different result — context strategy, retry policy, and tool surface each changed independently in one lane. HARNESS A minimal context, no retry MODEL same weights RESULT A baseline HARNESS B + retry policy, verification MODEL same weights RESULT B moved HARNESS C + repo-aware context, wider tools MODEL same weights RESULT C moved again
The weights never move down the middle column. Everything else in the row is fair game.

What a harness is not

Not the model. The weights are what you hold fixed. Not the product: billing and accounts change no agent outcome. And not a synonym for "orchestrator," which answers sequencing and stays silent on context, policy, and stop conditions.

Where the boundary falls

Eight layers, one boundary. The stack below runs in the same order as the legend above — trace a color from the hero animation straight down into this list.

Foundation model

Token generation, tool intent

Held constant — not the variable

Prompt

One input to context assembly

Inside the harness

Workflow

A fixed sequence of steps

Inside; a harness can run many

Agent harness

Context, loop, policy, retries, execution, state, stop

— the substitutable layer itself

Tools / MCP

Capability surface and schemas

Inside; the harness chooses what to expose

Sandbox

Where effects land

Usually called by the loop, not containing it

Application

UI, accounts, billing, analytics

Outside; swapping it changes no outcome

Evaluation harness

Tasks, traces, grading, aggregation

Outside; it contains the system under test

The layer stack as a flow graph A request enters from the user, passes through the application layer, then into the agent harness, which contains the prompt, workflow, and tools/MCP components and owns context, policy, retries, execution, state and stop conditions. The harness calls out to the foundation model, held fixed, and to the sandbox, where effects land. An evaluation harness, drawn as a dashed outer boundary, contains the harness, model and sandbox but not the calling user or application. USER / CALLER APPLICATION UI · accounts · billing — outside; swap changes no outcome EVALUATION HARNESS — contains the system under test, not the caller AGENT HARNESS — the substitutable layer Prompt Workflow Tools / MCP Policy / State owns: context assembly · retries · execution dispatch · stop conditions — everything you could change to alter the outcome while the weights stay fixed substitution test: swap one of the boxes above, hold the model, see if the result moves FOUNDATION MODEL held fixed — not the variable token generation, tool intent SANDBOX where effects land called by the loop, not containing it
Same eight layers as the table above — read top to bottom instead of row by row.

Two of those rows get collapsed constantly. Anthropic's line on workflows is clean: they are "orchestrated through predefined code paths," while agents "dynamically direct their own processes and tool usage." A workflow is something a harness executes, not a rival to it. "Runtime" is worse. It gets used for the process executing tool calls and, loosely, for the whole harness. Say which you mean.

None of this implies a minimum size. A short loop that exposes two tools, gates one permission, and records state is still a harness; the stack above lists responsibilities, not a parts count. Complexity earns its place only when a real failure mode requires it — the same argument the depreciation section below makes about pruning, not just extending.

MCP is a protocol, not a harness

The specification is explicit about its scope: base protocol, lifecycle, authorization, server and client features. No agent loop. No context budget. No stop condition. More telling still, the server-primitives page splits control three ways: prompts user-controlled, resources application-controlled, tools model-controlled. The tools page then warns that "there SHOULD always be a human in the loop with the ability to deny tool invocations." MCP delegates policy to the host, and that host is the harness.

Where the agent-harness boundary falls A loop of nine steps sits inside the harness boundary. It dispatches outward to the foundation model, tools, and execution environment. An evaluation harness, drawn as a dashed outer boundary, contains the entire system and grades its output. AGENT HARNESS — the substitutable layer User / caller 1 · Ingest normalize intent · budget · run id 2–4 · Assemble context → invoke model → parse output instructions · retrieval · compaction · tool-intent parsing 5–7 · Policy gate → dispatch → render result allow / ask / deny · side-effect class · result as data 8–9 · Stop test → verify outcome goal · budget · error, then step 9 probes real state loop FOUNDATION MODEL weights, held fixed TOOLS registry · schemas · MCP servers EXECUTION ENVIRONMENT worktree · container · browser EVALUATION HARNESS — separate system; contains all of the above task set → run and record trace → grade outcome → aggregate
Steps 1–9 sit inside the boundary. The model, tools and environment are called across it.

The loop, as code

The diagram above compresses to about nine lines. Only one of them touches the model.

harness.py
# everything here is harness — except the one call to model.generate()
while not done:
    context  = assemble_context(state, budget)        # 2 · context
    response = model.generate(context, tools=exposed) # 3 · the model — held fixed
    action   = parse(response)                         # 4 · parse

    if not policy.allows(action):                    # 5 · policy gate
        action = policy.escalate(action)               #   allow / ask / deny

    result = dispatch(action)                          # 6 · dispatch
    state  = render(state, result)                     # 7 · rendered as data, not instructions

    if stop.reached(state, budget):                   # 8 · stop test
        done = verifier.check(state)                   # 9 · probes real state, not the model's claim

The model produces response. Every other line — assemble_context, policy, dispatch, render, stop, verifier — is a component you can swap without retraining anything, and the substitution test from above applies to each one individually: change how policy.allows() decides, or what verifier.check() actually probes, and the agent's behavior moves while model.generate() stays byte-identical. That is the whole definition, in a form you can set a breakpoint in.

A benchmark number is a joint measurement

Anthropic reported 49% on SWE-bench Verified for the upgraded Claude 3.5 Sonnet, against a 45% previous state of the art. The same post described the scaffold: a prompt, a Bash tool, an Edit tool, deliberately minimal, "to give as much control as possible to the language model itself." Then came the sentence that matters:

The performance of an agent on SWE-bench can vary significantly based on this scaffolding, even when using the same underlying AI model.Anthropic, Raising the bar on SWE-bench Verified

A vendor conceding its own headline number is scaffold-sensitive is unusually clean evidence.

SWE-agent made the same point eight months earlier, from the other direction. It introduced the agent-computer interface and reported 12.5% pass@1, "far exceeding the previous state-of-the-art achieved with non-interactive LMs." The variable was the interface, not the model.

In SWE-bench's own repo, harness means the grader

Sharper evidence sits in the benchmark's own source tree. Look at what the directory named harness contains — and notice which color it belongs to on the legend: this is the evaluation harness, not the agent harness, even though it shares the name.

swebench/
# the grader — evaluation harness, colored like the diagram above
harness/
├── docker_build.py
├── test_spec/
├── log_parsers/
├── grading.py        # reasons about FAIL_TO_PASS / PASS_TO_PASS
└── reporting.py

# the model-calling code lives one level away — agent harness territory
inference/          # sibling directory, not inside harness/

The grading module reasons about FAIL_TO_PASS and PASS_TO_PASS statuses. No agent loop appears in it. The model-calling code lives in a sibling directory, swebench/inference/ — a split visible directly in the source, and confirmed by the original SWE-bench paper, which describes the benchmark's 2,294 task instances as a grading harness built around real GitHub issues and their test suites, independent of whichever agent later attempts them.

So in the most-cited agent benchmark, harness already means the grader. Three systems produce any agent number: the model, the harness that drove it, the evaluation harness that graded it. Crediting the first alone is a category error.

A benchmark number is a joint measurement An observed score sits at the top, fed by three sources below it: the foundation model held fixed, the agent harness that drove it, and the evaluator that graded it. Each source is annotated with the figures cited in the text above. OBSERVED SCORE e.g. "49% on SWE-bench Verified" MODEL weights held fixed across every run below same checkpoint, every column HARNESS Anthropic's scaffold: 45%→49% SWE-agent's interface: 12.5% pass@1 same class of model, different scaffold EVALUATOR grades FAIL_TO_PASS / PASS_TO_PASS per instance lives in swebench/harness/ Crediting the model alone is a category error. Two of these three can move the number while the weights never change — which is what "scaffold-sensitive" means in the source material above.
Three systems, one number. Only the left box holds still.

Verification is not part of the definition

A common formulation has the harness returning a verified result. That does not survive the sources. Anthropic's own definition says "returns results." No verification in it. And in the November 2025 write-up on long-running agents, verification is something the team had to build: the coding agent was required to run tests before marking a feature complete, because otherwise it did not.

If verification is definitional, the many production loops returning unverified output are not harnesses. That is false, and it hides the gap you most want to find. Verification is step 9 and a maturity property, not a membership test. Calling it optional is what lets you notice it missing.

Localizing a failure

Every symptom below has at least two plausible causes on opposite sides of the boundary — the tag column marks which layer, colored to match the legend.

SymptomPossible causeLayerEvidence needed
"Done" but state unchangedFalse verbal completion or no outcome verifiermodel / harnessExternal state probe, not the final message
Behavior changed after tool outputFollowed injected text; result rendered as trusted contextharnessByte diff of the rendered result
Agent stops earlyWeak long-horizon behavior or stop rule / budget ceilingmodel / harnessToken state at exit, stop event
Tool call failsMalformed arguments; schema drift, missing credential, outagetoolRaw request, tool version, probe
Benchmark regressedModel behavior changed; or different scaffold, tool, prompt, graderany layerVersion-pinned comparison, ablation

Row two is the one most tables omit. Prompt injection looks like a model failure, but the mechanism is a harness one: untrusted bytes reached the instruction channel because the harness rendered tool output as trusted context rather than as data. That is a claim about mechanism, not frequency — I have no published rate for how often injection incidents trace to that specific rendering choice — but it is the assumption baked into MCP's own tools spec, which insists on a human able to deny invocations: the protocol expects the host, not the model, to enforce policy.

One failure, five layers

The agent reports a migration applied; the column is not there. Walk the boundary outward from the model, cheapest check first:

  1. EvaluatorGraded the final message rather than the schema — the cheapest thing to rule out first.
  2. EnvironmentThe container may have torn down before the write flushed.
  3. ToolThe tool may have returned exit 0 on a partial write.
  4. HarnessNo post-condition probe existed, so it believed the model's claim.
  5. ModelMay have planned correctly and claimed falsely — the most expensive layer to confirm.
Localizing a failure — diagnostic flowchart The agent reports a migration applied but the column is not there. Freeze the model, replay the trace, then check five layers in order of cost: evaluator first, then environment, tool, harness, and model last, since the model is the most expensive layer to confirm. TASK "FAILED" Freeze model · replay the trace Where did the trace diverge? 1 · EVALUATOR graded the final message, not the schema cheapest to rule out 2 · ENVIRONMENT container may have torn down before the write flushed 3 · TOOL may have returned exit 0 on a partial write 4 · HARNESS no post-condition probe — it believed the model's claim 5 · MODEL planned right, claimed falsely — costliest to confirm CHEAPEST CHECK → MOST EXPENSIVE CHECK, LEFT TO RIGHT
One symptom, five candidates on opposite sides of the boundary. Only a state probe plus the trace tells you which.

One symptom, five candidates. Only a state probe plus the trace separates them.

One task, five failures

The walkthrough above manufactures one symptom and asks which of five layers caused it. Run it the other way: one task, five separate attempts, a different failure independently injected at each layer. Same request every time — fix the failing authentication tests and open a pull request — so what changes is only where the loop breaks, not what it was asked to do.

task
# same task, run five times, one layer broken per run
Fix the failing authentication tests and open a pull request.

# the correct run maps straight onto the pseudocode above:
assemble_context()  → repo state, failing test's stack trace, prior commits
model.generate()    → proposes a patch to the clock-skew check
dispatch()          → writes the patch, reruns the suite
render()             → suite output fed back as data
stop / verifier      → tests pass, PR opened against the real diff
1 · MODELWrong fix, right informationbreaks inside model.generate()

The context has everything needed: the failing assertion, the stack trace pointing straight at the clock-skew check. The model patches the token-refresh handler instead — a plausible, adjacent fix that leaves the real test failing.

Confirm: replay the exact context the model saw. A correct action was available in it and wasn't taken — that isolates the failure to reasoning, not information.

2 · HARNESSRight model, missing contextbreaks inside assemble_context()

The failing test's stack trace points three files away, into a schema-migration mismatch. Context assembly only pulls files named in the test's own imports, so the migration file never enters the window. The model patches what it can see and never sees the actual break.

Confirm: diff what was in the context window against what the trace referenced. The gap belongs to the harness, not the model.

3 · TOOLA lying exit codebreaks inside dispatch()

A fixture teardown swallows an exception; the assertion inside it fails silently, but the test runner still returns exit 0. The harness reads the exit code, not the log, and reports success.

Confirm: rerun the identical tool call directly, outside the harness, and read the raw output instead of the exit code.

4 · ENVIRONMENTCorrect run, discarded resultbreaks after render(), before persistence

The patch is right, the suite passes inside the container — and the container tears down before the working tree is written back to the branch. The PR opens against the unmodified file. Every step inside the loop succeeded.

Confirm: diff the PR's actual file contents against what the transcript claims was written. The loop's own trace looks clean; the environment discarded the result after the trace ended.

5 · EVALUATORA real success, scored as failureoutside the harness entirely

The agent harness does everything right — patch, passing suite, real PR. The separate evaluation harness scoring the run reads a stale CI log from a previous attempt instead of triggering a fresh check, and marks the task failed.

Confirm: check which CI run the evaluator's report actually points to. This is why the evaluation harness gets its own box in every diagram above — it can be wrong about a system that worked.

Five failures, one visible symptom each time: "the task didn't work." The five-layer table earlier in this piece exists because that symptom alone never tells you which of these five you're looking at — and the confirmation step is different in every case, which is the actual argument for treating the layers as distinct in the first place.

A harness is a depreciating asset

Every component encodes an assumption about what the model cannot do alone. Anthropic's March 2026 harness-design write-up says so: "every component in a harness encodes an assumption about what the model can't do on its own, and those assumptions are worth stress testing." The method was ablation: "removing one component at a time and reviewing what impact it had on the final result." A sprint-decomposition construct was deleted outright: a stronger model could decompose unaided.

A harness is a depreciating asset Three successive harness versions, each with fewer components than the last, as model capability increases along the horizontal axis. The sprint-decomposition component is pruned between version one and two, because a stronger model no longer needed it. MODEL CAPABILITY, ACROSS RELEASES → HARNESS v1 Context compaction Retry policy Sprint decomposition Verification each block is a bet on what the model can't do alone HARNESS v2 Context compaction Retry policy Sprint decomposition Verification deleted — stronger model decomposes unaided HARNESS v3 Context compaction Retry policy Verification the components that survive are the ones nothing yet replaces
One documented deletion (Anthropic, March 2026) shown as the general pattern — the survivors get re-tested at every model release.

Both extremes are wrong, and the same vendor refutes each: its number moves with scaffolding at fixed weights, and deleting components improved the result. Neither publishes a clean scaffold-only delta, so I will not quote one.

Harness components are hypotheses with an expiry date.

Running your own ablation

Neither vendor publishes a clean scaffold-only delta, so treat their write-ups as evidence that the method works, not a number to cite. The method itself is cheap enough to run on your own system:

Hold constant: model version, task set, repository state, evaluator. Change exactly one harness component per run — system prompt, tool surface, retry policy, context-assembly strategy, verification step, budget. Everything else stays pinned, including the grader.

Per run, record what the checklist above already asks you to track: success rate, tool calls per task, retries before success or abandonment, and which of the five layers absorbed each failure. A component earns its place if removing it drops success rate or pushes failures onto a layer with no recovery path; it is a candidate for deletion if the number does not move. Run it again after your next model upgrade — the March 2026 write-up's sprint-decomposition deletion is exactly this test, repeated, catching a component that used to matter and stopped.

In my own setup they are inspectable before they run rather than reconstructed after the fact from a transcript. The manifest is declarative: each entry binds a trigger, a model route, source-trust rules, side-effect classes, and closeout verifiers. One entry treats an unauthenticated social-media post as untrusted by policy — it checks the post's own linked sources before using anything from it, and blocks every social-platform side effect regardless of what the model concludes. The point isn't the specific rule; it's that the rule is readable in one place before the agent runs, not inferred afterward from what it happened to do.

What to ask

Tick the ones you can currently answer about your own system.

Answer 1, 2, 5 and 9 first — everything else builds on them.

Limitations

What the evidence doesn't establish

The strongest case against this framing is that "harness" is a transitional word. If models keep absorbing what scaffolding supplies, the layer thins until the distinction stops earning its keep, and this taxonomy describes a 2026 bottleneck, not an architecture. Anthropic's own ablation results point that way: a component built for a weaker model became dead weight once the model improved.

The substitution test is not clean either. You cannot hold everything else constant, and components interact: removing a planner may look harmless only because a stronger model quietly compensates for its absence. Treat the test as a discipline for organizing an inspection, not a controlled experiment.

Three more caveats. The Anthropic and OpenAI write-ups are first-party reports on their own products, and OpenAI's warns its results should not be assumed to generalize without similar investment. The 49% and 12.5% figures are snapshots from different SWE-bench configurations, roughly a year apart, and support the general claim that scaffolding moves outcomes, not a specific magnitude. And the prompt-injection mechanism above reasons from the protocol's own design assumption, not a measured incident rate — treat it as a hypothesis to check against your own traces, not a settled statistic.

None of this makes harness a good word. It probably will not survive better models. The question underneath it will: when the result changed, what did you change, and were the weights any part of it?

Source register

SourceDateUsed for
Anthropic, Demystifying evals for AI agents9 Jan 2026Runtime definition of agent harness; separate evaluation-harness definition
OpenAI, Harness engineering11 Feb 2026Competing meaning: repository as system of record, AGENTS.md, layer linters, observability
Anthropic, Effective harnesses for long-running agents26 Nov 2025Verification as something built, not inherent; handoff mechanics
Anthropic, Harness design for long-running apps24 Mar 2026Ablation as method; ownership-assumption framing
MCP specification, rev. 2025-06-18Declared protocol scope; absence of agent loop or stop condition
MCP server primitivesControl hierarchy: user / application / model
MCP toolsHuman-in-the-loop requirement for tool invocation
Anthropic, Raising the bar on SWE-bench Verified6 Jan 2025Minimal bash-and-edit scaffold; 49/45% figures; scaffold-sensitivity admission
SWE-agent, arXiv:2405.157936 May 2024Agent-computer interface concept; 12.5% pass@1
SWE-bench source, swebench/harness/inspectedGrader-only contents of the harness directory; sibling inference/ split
SWE-bench, arXiv:2310.0677010 Oct 2023What the benchmark measures; how its 2,294 instances were built
Anthropic, Building effective agents19 Dec 2024Workflow-vs-agent distinction; "reduce abstraction layers" guidance
Claude Code settings referenceofficial docsManaged settings as policy outside model's writable reach

← All thoughts