Tejas Kumar

What Is an Agent Harness? Harness Engineering Explained

An agent harness is everything around the model that gives it grounding in reality: the tools it can call, the context it sees, the guardrails that stop it, the loop that drives it, and the checks that verify what it claims. I once told GPT-3.5 Turbo to upvote the top story on Hacker News. It landed on a login page, did nothing, and reported success anyway.

In this post we’ll fix that agent together, one piece of harness at a time, until it really upvotes the story. The model stays the same. The prompt never changes.

TL;DR

  • We rent our models, and a rented model is a black box. A harness is how you get reliable behavior out of something you don’t control.
  • An agent harness has 6 parts: a tool registry, a model, context management, guardrails, an agent loop and a verify step.
  • The harness is not the agent loop. It’s everything around the loop.
  • When an agent misbehaves, the fix is usually code in the harness, not a better prompt. That practice is called harness engineering.
  • In the demo below, a harness takes GPT-3.5 Turbo from lying about success to succeeding in 6 iterations, with the same prompt.
  • All the code below is on GitHub at TejasQ/basically-ai-harness, one branch per step. The snippets here are trimmed for reading.

Why agents need a harness

Most of us don’t own the models we build on. We pay rent: tokens, a subscription, a context window someone else sized. And the model we rent is a black box. If a provider quietly served you a smaller model under a bigger model’s name, you would never know.

That’s a lot of variables you can’t control, sitting in the middle of software you’re responsible for. So the name of the game with a harness is reliability: your agent does its job, period, whichever black box happens to be underneath it today.

Think about harnesses you already know. A climber harnesses themselves to a mountain, because the mountain is stable and they are not. You walk your dog on a harness so it can’t run into traffic. Neither harness makes the climber climb or the dog walk. It keeps something unpredictable anchored to something that isn’t.

That’s the whole idea, and each half of it maps to parts of a real harness. The climbing harness doesn’t climb for you; it catches the fall. That’s what guardrails and verification do. The leash doesn’t walk for the dog; it chooses the direction. That’s what the loop and the tool registry do. The metaphor stops there, though: a rope can’t tell you whether the climber reached the top. A harness for agents has to check that too.

What is an agent harness?

The word “harness” means 2 different things depending on who you ask, and 2 neighboring terms get mixed in with it. Here they are side by side.

Term What it is Example
Evaluation harness A test suite and test runner for models: inputs in, output quality measured. The machine learning meaning. A benchmark runner scoring a model on a dataset
Agent harness The runtime layer around a model that makes an agent reliable. The AI engineering meaning, and the one this post is about. Claude Code, Cursor, Codex
Agent loop Call the model, run the tool it asked for, feed the result back, repeat. One part of a harness. A while (true) around a chat completion
Harness engineering The practice of improving the harness every time an agent fails, instead of prompting harder. Adding a verify step after an agent lies about success

Other people define the agent harness by subtraction. LangChain writes that “a harness is every piece of code, configuration, and execution logic that isn’t the model itself,” and Birgitta Böckeler on martinfowler.com calls it “everything in an AI agent except the model itself.” That’s true. I find it more useful to define a harness by its job: grounding a model you don’t control in an environment you do. That job tells you what to build next.

Claude Code is a good example. It’s a coding agent, sure. More precisely, it’s a harnessed coding agent: a model wrapped in tools, context compaction and limits.

The 6 parts of an agent harness

Almost every agent harness has the same 6 moving parts. The last column is where each one shows up in the demo below.

Part Its job In the demo
Tool registry What the agent can do: read a file, run a command, click in a browser Browser tools: navigate, read the stories, click
Model The reasoning. Sometimes you choose it, sometimes the harness does GPT-3.5 Turbo, on purpose
Context management What the model sees, and what happens when the conversation outgrows the window Keep the system prompt and the task, drop the middle
Guardrails Hard limits enforced in code, whatever the model wants Maximum iterations, maximum messages
Agent loop Call the model, run the tools it asks for, repeat until it says it’s done A while (true) around a chat completion
Verify step Check the agent really did what it claims. In a coding agent, run the linter and the tests Read the tool history and confirm the upvote

When I was preparing my talk on this, the most common question I got was: isn’t the harness just the agent loop? It isn’t. The harness is everything around the loop. It can even be a loop around your loop, as you’re about to see.

Build an agent harness from scratch

Here’s the job: open Hacker News and upvote the highest-ranked story you haven’t voted on yet. The agent drives a real browser through Playwright.

I picked GPT-3.5 Turbo on purpose. If a harness can make a weak, cheap model do real work, it can make a strong one boringly reliable. And I set myself one rule: the prompt never changes. When an agent misbehaves, the instinct is to prompt it harder. Let’s see how far we get without doing that.

Step 0: a bare loop lies about success

Branch 0. The first version has no harness at all. It’s a system prompt, the task, a few browser tools and a loop:

export async function runLoop(model, messages, tools) {
  while (true) {
    const response = await client.chat.completions.create({
      model,
      messages,
      tools: tools.definitions,
    });
    const choice = response.choices[0];
    messages.push(choice.message);

    // The model says it's done, so we believe it.
    if (choice.finish_reason === "stop") {
      return { answer: choice.message.content };
    }

    // Otherwise, run every tool it asked for and loop.
    for (const call of choice.message.tool_calls ?? []) {
      const tool = tools.byName.get(call.function.name);
      const result = await tool.execute(JSON.parse(call.function.arguments));
      messages.push({ role: "tool", tool_call_id: call.id, content: result });
    }
  }
}

I ran it. It opened Hacker News, found a story and clicked upvote. Hacker News doesn’t let you vote logged out, so it bounced to the login page. The agent panicked, and then it reported that the story was upvoted.

It lied. Nothing in this code checks anything: the loop ends when the model says it’s done, so it’s done.

Lesson: a model saying it finished is a claim, not evidence.

Step 1: guardrails bound the damage

Branch 1. Before we fix the lie, let’s make sure a confused agent can’t run forever or blow up its context. Guardrails are just functions that look at the state of the run and say stop:

const maxIterations = (limit) => ({ iterations }) =>
  iterations >= limit
    ? { ok: false, reason: `Guardrail: reached iteration limit (${limit})` }
    : { ok: true };

const maxMessages = (limit) => ({ messages }) =>
  messages.length > limit
    ? { ok: false, reason: `Guardrail: context too large (${messages.length} messages)` }
    : { ok: true };

export const defaultGuardrails = combineGuardrails(maxIterations(15), maxMessages(50));

Alongside them goes the most naive context compression you can write: keep the system prompt and the task, keep the most recent messages, drop the middle.

export function trimContext(messages, maxMessages) {
  if (messages.length <= maxMessages) return messages;
  const [system, user] = messages;
  const rest = messages.slice(2);
  return [system, user, ...rest.slice(rest.length - (maxMessages - 2))];
}

Don’t ship that compressor: throwing away the middle of a conversation loses things the agent needed. But the shape is right: the loop checks the guardrails and trims context at the top of every iteration, and none of it is up to the model. (On stage I used a limit of 6 iterations; the repo defaults to 15.)

Lesson: the limits that matter most are the ones the model can’t talk its way past.

Step 2: the harness gets a home

Branch 2. Next, move all of that out of the entry point and into a function called runHarness. Nothing about the behavior changes. It’s worth doing anyway, because now there’s a place for the next 2 steps to live, and the entry point shrinks to a task and a single call.

Step 3: deterministic verification ends the lying

Branch 3. Now the fix for the lie. Every tool call the agent makes is already recorded in a trace. So after a run, the harness reads that trace and decides, in plain deterministic code, whether the job really happened:

export function verifySuccessfulUpvote(result) {
  const events = result.trace.flatMap((iteration) => iteration.toolEvents);

  const upvoted = events.find(
    (event) =>
      event.tool === "browser_click" &&
      /up_/.test(JSON.stringify(event.args)) &&
      /news\.ycombinator\.com\/(news)?$/.test(event.result.split("now at ")[1]?.trim() ?? ""),
  );
  if (upvoted) return { passed: true, reason: "Upvote click confirmed" };

  const hitLogin = events.find((event) => isLoginUrl(extractUrl(event.result)));
  if (hitLogin) {
    return { passed: false, reason: "Hit login screen instead of completing the upvote", fatal: true };
  }

  return { passed: false, reason: "No successful upvote click found in trace" };
}

And runHarness becomes a loop around the agent loop: run an attempt, verify it, retry if it failed, up to 3 times.

for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  const result = await runHarnessAttempt(task, model);
  const verification = options.verify(result);
  // A failed check replaces whatever the model claimed.
  const answer = verification.passed ? result.answer : verification.reason;
  if (verification.passed || verification.fatal || attempt === maxAttempts) {
    return { ...result, answer, attempts: attempt, verification };
  }
}

I ran it again. It still failed. But this time it said so: “Hit login screen instead of completing the upvote.” The model didn’t get more honest. The harness stopped taking its word for it.

Lesson: failing honestly is progress. You can’t fix a failure you can’t see.

Step 4: the harness logs in so the model doesn’t have to

Branch 4. The agent can’t log in, and it shouldn’t: I don’t want my password in a prompt. So the harness does it. Before each step of the loop is recorded, a login handler looks at the browser’s URL. If it isn’t a login page, it does nothing, which costs nothing. If it is, it fills in the credentials itself, from environment variables the model never sees:

export function createLoginHandler(session) {
  return async () => {
    const currentUrl = await session.getUrl();
    if (!currentUrl.includes("login") && !currentUrl.includes("vote")) return null;

    await session.fill("input[name='acct']", process.env.HN_USERNAME);
    await session.fill("input[name='pw']", process.env.HN_PASSWORD);
    await session.click("input[type='submit']");

    return {
      tool: "harness_auto_login",
      args: {},
      result: `Harness automatically handled login at ${currentUrl}.`,
    };
  };
}

When it logs in, the harness also tells the model, in the conversation, that authentication is done and it should get back to the task.

I ran it. Hacker News, upvote, login page, the harness logged in, the vote went through. It succeeded after 6 iterations, and when I opened Hacker News the story really was upvoted.

Same model. Same prompt.

Lesson: move secrets and fragile, deterministic steps out of the model and into the harness.

What changed

Branch What we added to the harness What happened
0 Nothing: a bare agent loop Hit the login page, reported success anyway
1 Guardrails and context trimming Could no longer run forever or overflow its context
2 A runHarness function No behavior change, somewhere to put the next steps
3 A deterministic verify step and retries Failed, and said so
4 A login handler holding the secrets Upvoted the story in 6 iterations

Every fix was code. None of them was a prompt.

What is harness engineering?

Harness engineering is changing the system around the model, not the prompt, until you can trust what the agent does. Every step above was harness engineering.

The habit has a name now. Mitchell Hashimoto called it harness engineering on February 5, 2026: “anytime you find an agent makes a mistake, you take the time to engineer a solution such that the agent never makes that mistake again.” 6 days later, OpenAI wrote about harness engineering with Codex. LangChain’s anatomy of an agent harness and Birgitta Böckeler’s harness engineering for coding agent users both sum it up as agent = model + harness.

If you’ve been doing context engineering, deciding what goes into the window, you’ve been doing one part of this already. Harness engineering is the whole layer: context, plus tools, guardrails, the loop and verification.

It also matters beyond demos. At IBM we build OpenRAG, an open source retrieval platform that very large companies run against private, sensitive data like calls, PDFs and invoices. What makes it safe to point at that data isn’t a cleverer model. It’s the harness.

Where this goes next

2025 was the year of agents. 2026 is the year of harnesses.

My hope for 2027 is dynamic harnesses, generated on the fly. You ask an agent to buy you a flight, and before it touches anything it builds a harness for that specific task: it knows where it’s likely to go wrong, it adds the checks, it does the job, and it comes back to you guardrailed. Like plan mode, but on steroids.

A lot of this is still open. How much verification is enough? Which checks belong in code and which can be handed to another model? I don’t think anyone has settled those yet, me included.

Takeaways

  • An agent harness is everything around the model that ties it to something stable.
  • Don’t trust the loop’s exit. A model saying it’s done is a claim. Verify it in code.
  • Put secrets and fragile steps in the harness, not in the prompt.
  • Guardrails are cheap. Iteration limits and context limits take a few lines and prevent the worst runs.
  • When an agent fails, fix the harness first. That’s harness engineering.

The model brings the intelligence. The harness is what makes it trustworthy.

I gave this as a talk at AI Engineer Europe 2026, and you can watch me build the harness live:

Harnesses in AI: A Deep Dive, at AI Engineer Europe 2026. The full transcript is on this site.

If you want to build a harness like this around your own agents, with your team, I run a workshop on exactly that.

Questions

What is an agent harness?

An agent harness is everything around the model that gives it grounding in reality. It ties a model you rent and cannot control to a stable environment you do control, through a tool registry, a model, context management, guardrails, an agent loop and a verify step.

What is harness engineering?

Harness engineering is building and improving that layer on purpose: every time an agent makes a mistake, you engineer a fix in the harness, in code, so it cannot make that mistake again, instead of prompting the model harder.

Is an agent harness the same as the agent loop?

No. The agent loop calls the model, runs the tool it asked for and repeats until the model says it is done. The harness is everything around the agent loop: guardrails, context management, verification and deterministic steps like logging in. It can even be a loop around your agent loop.

Is Claude Code an agent harness?

Claude Code is a coding agent, but more precisely it is a harnessed coding agent. It has a tool registry to read files, write files and run bash commands, a model, context compaction, and limits on what it will do.

What does harness mean in machine learning?

In machine learning, a harness usually means an evaluation harness: a test suite and test runner that gives a model inputs and measures the quality of its outputs. An agent harness in AI engineering is a different thing: the runtime layer that makes an agent reliable.

Harness engineering vs context engineering: what is the difference?

Context engineering decides what goes into the model context window. Harness engineering covers the whole layer around the model, and context management is one part of it, next to tools, guardrails, the loop and verification.

Does a better model remove the need for an agent harness?

No. A better model makes fewer mistakes, but it is still a black box you rent and cannot inspect, and a model saying it finished is still only a claim. The harness is where you verify that claim, keep secrets out of the prompt and stop runaway loops, whichever model is underneath.

Do I need a framework to build an agent harness?

No. A working harness can be a few hundred lines of plain TypeScript: a tool registry, a loop, guardrail functions, a verify function and a retry loop. Frameworks help at scale, but the parts are simple enough to write and understand yourself first.

Why use an agent harness?

Reliability. Models are nondeterministic black boxes that most teams rent. A good harness makes an agent do its job regardless of which model is underneath, which also lets you use a cheaper or older model and still get the work done.