M1.1
What a harness is, and what it is not
984 words · about 6 min
Concept
An agent harness is the program that turns a stateless token predictor into a durable, tool-using, interruptible process that acts on real systems. It owns four things the model cannot own: what the model sees, what happens when the model asks for something, what is allowed, and what happens when something goes wrong.
The clearest way to fix the term is by contrast with four neighbours it is routinely confused with.
A harness is not a model. The model contributes exactly one capability:
given tokens, produce plausible next tokens, including tokens that look like a
request to call a tool. It has no ability to execute that request. When a model
"reads a file", what actually happened is: the model emitted a structured tool
call, the harness parsed it, the harness decided it was permitted, the harness
called readFile, and the harness appended the bytes to the conversation as a
new message. Four of those five steps are yours. Confusing model capability with
harness capability is the single most common category error in this field, and it
produces a specific failure: teams try to fix loop bugs — runaway retries,
context overflow, tools called with stale paths — by editing the system prompt,
because they have misattributed a control-flow problem to a persuasion problem.
A harness is not an SDK. An SDK (@anthropic-ai/sdk, openai, a raw
fetch wrapper) marshals one request and unmarshals one response. It is
transport. It has no opinion about what happens between requests, which is where
all the difficulty lives. An SDK will happily let you send a 400,000-token
conversation to a 128,000-token model and surface the resulting 400; a harness is
the thing that was supposed to prevent that.
A harness is not a chatbot. A chatbot renders a conversation. Its effects
are confined to a text box. A harness produces side effects in the world —
files written, processes spawned, network calls made, money spent — which means
it inherits an obligation a chatbot never has: every action must be attributable,
reversible where possible, and refusable. The moment your program can run
rm -rf, you are writing a harness, and the permission model is no longer
optional.
A harness is not an agent framework. This distinction is the subtlest and
the most contested. A framework (LangChain, LlamaIndex, and their successors)
gives you abstractions for composing agent-shaped programs — chains, graphs,
memory objects. A harness is the runtime underneath: the thing holding the
loop, the process boundaries, the cancellation, the log. You can build a harness
using a framework, and you can build a harness with no framework at all. The
production coding tools people actually use — Claude Code, Codex CLI, Cursor's
agent mode, and dsh — are overwhelmingly the second kind. That is not a
coincidence: frameworks optimise for expressing many topologies, while a harness
optimises for making one topology reliable under interrupt, failure, and
adversarial input. Be honest that this is a live disagreement in the field rather
than settled fact; the framework camp would argue that harness authors are
rebuilding the same abstractions badly, and sometimes they are right.
Mechanism
Operationally, a harness is a loop with four collaborators wrapped around a stateless call:
┌─────────────── harness ───────────────┐
user input ──> │ compose context │
│ ↓ │
│ call model (stateless) ───────────────┼──> provider
│ ↓ │
│ parse response │
│ ↓ │
│ policy check ──> execute effects │──> filesystem,
│ ↓ │ shell, network
│ append observations ──┐ │
└───────────────────────┼───────────────┘
└─> back to composeThe loop terminates when the model stops asking for tools, when the user interrupts, when a budget is exhausted, or when a guard fires. Module 2 makes each of those exits precise.
Worked example
Ask "what does the harness own?" of one concrete request: the user types
fix the failing test. The model never sees that string alone. It sees a system
prompt the harness assembled, a project-instructions block the harness read off
disk, a description of every available tool the harness registered, the prior
conversation the harness replayed, and only then the user's sentence. The model
replies with a tool call — say, run the test suite. The harness decides whether
running a subprocess is permitted here, runs it, truncates 40,000 lines of output
down to something affordable, appends it as an observation, and calls the model
again. The model's contribution is two acts of text generation. Everything else
is the product.
Tradeoffs
Drawing the harness boundary wide (the harness owns permissions, context assembly, and persistence) buys you a model-agnostic product and a testable core, and costs you a large amount of code that has nothing to do with AI. Drawing it narrow (leaning on provider-side agent APIs that manage state for you) buys you speed to first demo and costs you portability, offline replay, and the ability to enforce your own policy — the provider's idea of "allowed" becomes yours.
How `dsh` does it
How dsh does itdsh states the boundary as an architectural rule: "DeepSeek Harness is a
plugin-based agent harness on vendored Cordis: everything is a plugin"
(AGENTS.md). The model adapter, the tool registry, the session log, and even
the agent loop itself are plugins registered into a context. The loop lives in
packages/core/agent-loop; the model-facing vocabulary lives in
packages/llm/llm; the two are connected only through a service key, ctx.llm.
docs/architecture.md describes the consequence: extension packages depend on
dsh-agent events and services, not on the concrete loop package.
How others differ
Claude Code ships one engine that is simultaneously CLI, permission authority, and tool host, with an IDE extension acting as a thin client onto the same process. Codex CLI takes a similar local-execution stance. Frameworks like LangGraph invert it: the graph is the artefact, and the runtime is generic. The tradeoff is consistent — the harnesses buy reliability in one shape, the frameworks buy expressiveness across many.
Check yourself
2 questions on M1.1