
Most AI agents that fail in production don't fail because the model is weak. They fail because nothing around the model checks its work, limits what it can touch, or remembers what it already did. That surrounding layer is the harness, and building it well is now most of the job.
Key points
- An agent is a model plus a harness: the loop, tools, context, state, guardrails and verification around it.
- When an agent is unreliable, the fix is usually in the harness, not in a bigger model.
- Guardrails and verification only work when they live in code the model can't talk its way past.
What is harness engineering?
A language model does one thing. Text goes in, text comes out. It can't run code, call your API, keep state between steps or enforce a permission.
An agent can do all of those. The difference between the two is the harness. The shorthand people use is simple: Agent = Model + Harness.
Harness engineering is the work of designing that layer so the agent is reliable for a specific job. The term took off in February 2026, when OpenAI wrote about harness engineering with its Codex coding agent. Nine months on, the lessons are clear enough to write down.
Think of a sharp new hire on day one. They're smart, but they don't know where anything lives, they have no logins, and nobody lets them deploy to production alone. So you give them a laptop, access to some systems but not all, a notebook, a checklist and a reviewer. That setup is the harness. The hire is the model.
Why a better model won't fix an unreliable agent
When an agent misbehaves, the first instinct is to swap in a stronger model. In production I've seen that move the needle far less than people expect.
Take a support chatbot that tells customers their refund went through. The model is fluent and confident. The real bug is that nothing checked whether the refund API actually returned success. A smarter model just lies more convincingly.
The same pattern shows up everywhere:
- A coding agent says "all tests pass" without running them.
- An agent repeats the same failed step because it doesn't track what it tried.
- An agent runs a destructive command because nothing said no.
None of these are model problems. They're missing pieces around the model.
How an AI agent harness works
At its core, a harness is a loop. You ask the model what to do, check the request, run it, and feed the result back. When the model says it's done, you verify before you believe it.
Two details matter more than they look. Every rejection goes back to the model with a reason, so it can correct itself. And the loop has hard stop conditions: max steps, a cost budget, or a hand-off to a human.
The parts of a production agent harness
| Part | What it does | What breaks without it |
|---|---|---|
| Agent loop | Repeats model, action, result until done | No multi-step work, or infinite loops |
| Tools | Functions the model can call, with clear schemas | The model guesses instead of checking |
| Context | Picks what the model sees each step | It gets lost, or costs balloon |
| State and memory | Tracks steps, results, progress | Repeated mistakes, no resume after a crash |
| Guardrails and sandbox | Limits actions, isolates execution | Destructive actions reach real systems |
| Verification | Tests, schema checks, rules on the output | Confident wrong answers ship |
Context deserves a note. The context window is the amount of text a model can see at once. Too little and it guesses. Too much and it loses focus. Picking what goes in is a design decision, not an afterthought.
A minimal agent harness in Python
Here's the skeleton. The model function is a stand-in for any LLM API with tool calling.
TOOLS = {"add": lambda a, b: a + b, "multiply": lambda a, b: a * b}
BLOCKED = {"delete_file"}
def run(task, model, max_steps=8):
messages, last = [{"role": "user", "content": task}], None
for _ in range(max_steps):
reply = model(messages)
if "final" in reply:
if reply["final"] == str(last):
return reply["final"]
feedback = f"Rejected: tool results say {last}"
elif reply["tool"] in BLOCKED or reply["tool"] not in TOOLS:
feedback = f"Blocked: {reply['tool']} is not allowed"
else:
last = TOOLS[reply["tool"]](**reply["args"])
feedback = f"Result: {last}"
messages.append({"role": "tool", "content": feedback})
return None # stopped: hit max_steps
It's small, but every core idea is there: a step limit, an allow-list, state, feedback and a verifier that trusts tool evidence over the model's claim. Swap the model and nothing else changes. That's what makes the harness the durable part of the system.
Guardrails belong in code, not prompts
"Please don't delete files" in a system prompt is a request. A blocked tool in code is a rule. Only one of those holds when the model gets creative.
What I check first in any agent design:
- Is there an allow-list of tools, not a deny-list?
- Do risky actions like deletes, payments or deploys need human approval?
- Does generated code run in a sandbox, such as a Docker container, away from production?
- Is every action logged so it can be audited later?
Build your own harness or use one?
| Approach | Best when | Trade-off |
|---|---|---|
| Your own loop on a model API | Narrow agents, full control | You own every edge case |
| Agent framework such as LangGraph | Branching workflows, checkpoints, resume | Framework concepts to learn |
| Existing coding agent such as Claude Code or Codex | Software tasks | You shape rules, docs and tools, not the loop |
In practice teams often mix them. The loop comes from a framework or an existing agent. The tools, guardrails and verifiers are custom, because they encode your domain.
What to monitor once it's live
I watch five numbers: steps per task, tool error rate, verification pass rate, cost per task, and how often a human has to step in. A rising tool error rate usually means a tool's name or description is unclear. A rising step count usually means the context is wrong.
When the same failure keeps recurring, treat it as a signal. Something is missing from the harness: a doc, a tool or a rule. Add it, and the whole class of failure goes away. Research like the Reflexion paper (2023) showed agents improve when failures are fed back in words. The harness is what makes that feedback happen.
What to do
- Pick one agent you run today and list every action it can take. Move anything risky behind an allow-list or human approval.
- Add one verifier that checks real evidence, such as test results or an API response, before the agent can say "done".
- Log steps, tool errors and cost per task, and review the top recurring failure every week.