Harness Engineering vs. Loop Engineering
Why reliable AI agents need both, and why confusing them leads to brittle autonomous systems
Everyone is talking about AI agents. Harnesses. Loops. MCP. Planner agents. Multi-agent systems.
Somewhere along the way, two completely different ideas started getting blurred together. People say they’re “building a loop”. In reality, they’re usually building a better harness. Those are not the same thing.
One makes a single agent invocation reliable. The other makes an AI system reliable when nobody is watching. Understanding that distinction changes how you design autonomous systems because the two layers fail in different ways, and no amount of effort spent on one will fix a problem that actually lives in the other.
Think about an airline
The harness is the cockpit. It gives a pilot everything needed to safely fly one flight: instruments, controls, navigation, checklists, safeguards.
The loop is the airline itself. It schedules flights, dispatches crews, performs maintenance, remembers yesterday’s incidents, and decides when the next plane takes off.
You can’t build an airline by making the cockpit better. Likewise, you can’t build an autonomous AI system by continuously improving a single agent invocation. These are two different layers, solving two different problems.
Layer 1: Harness Engineering
A language model is just a probabilistic function. It has no built-in concept of tools, retries, verification, memory, or stopping conditions. A harness is everything wrapped around the model that turns one LLM call into something you can actually trust, which is the system prompt, tool schemas, tool execution, conversation history, retry policies, guardrails, iteration limits, grounding requirements, failure detection.
Its responsibility is simple: make one execution trustworthy.
Every harness runs the same loop
No matter which framework you’re using, be it LangGraph, the OpenAI Agents SDK, Claude Code, Cursor, or your own implementation, the core execution model looks remarkably similar: ask the model what it wants to do, check whether it requested a tool, execute the tool, feed the result back, repeat until it finishes.
def run_agent(user_input: str):
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_input},
]
for _ in range(MAX_ITERATIONS):
assistant = call_model(messages, tools=TOOL_SCHEMAS)
messages.append(assistant)
tool_calls = assistant.get("tool_calls")
if not tool_calls:
return assistant["content"] # final answer, loop ends
for call in tool_calls:
result = TOOL_IMPLEMENTATIONS[call["name"]](**call["arguments"])
messages.append({"role": "tool", "content": str(result)})That’s the execution loop. It is not, on its own, a harness. A harness starts where this code ends, because this loop faithfully executes whatever the model asks, and that’s fine right up until the model starts behaving like a model: answering from memory instead of using a search tool, repeatedly calling the same failing tool, declaring success without checking anything, hallucinating that it already verified a fact.
None of those failures get fixed with a better prompt. They get fixed with code.
A good harness doesn’t ask the model for permission
One of the first failures I hit was with a small local model that had a working search tool available and simply didn’t use it. It answered a factual question straight from memory, confidently, and got it wrong. The system prompt said, in plain language, always search before answering. The model ignored it.
That’s the whole lesson in one sentence: prompts are instructions, and harnesses are enforcement. So instead of trusting the model’s own judgment about whether it had done enough research, the harness rejected the premature answer outright:
if name == "finish_answer" and not used_grounding_tool:
messages.append({
"role": "tool",
"content": "Rejected. You haven't gathered evidence yet.",
})
continueThe model isn’t deciding whether it should use tools here. The harness is. That’s the subtle but total shift between prompt engineering and harness engineering. One persuades, the other constrains. A model can ignore a prompt. It cannot ignore your Python.
Guardrails are just software
The other classic failure mode is the infinite tool loop. The model calls the same tool with identical arguments, gets the same result, and calls it again anyway. Rather than hoping it eventually stops on its own, the harness recognizes the pattern and ends it:
call_counts[key] = call_counts.get(key, 0) + 1
if call_counts[key] >= MAX_REPEATS:
return "stuck", "Model repeated an identical tool call -- stopping."None of this is AI magic. It’s ordinary software engineering. Iteration limits, timeouts, retry policies, circuit breakers, grounding requirements. None of it makes the model smarter. It makes the system safer, which is a different and, for production purposes, more important property.
A perfect harness still isn’t autonomous
Suppose you’ve built the perfect one. It always uses tools, never loops forever, grounds every answer, detects failures, retries intelligently. Congratulations! You still don’t have an autonomous system, because every morning, you’re still the scheduler. You’re the one deciding when it runs, the one reading every output, the one deciding what happens next. The harness is reliable. The system still depends entirely on a human standing next to it.
That’s where loop engineering begins.
Layer 2: Loop Engineering
Addy Osmani recently put a name to the layer above the harness: loop engineering. Instead of asking how do I make one agent invocation reliable, it asks a different question entirely: how do I make an AI system operate reliably without me?
That’s not a bigger version of the same problem. Instead of improving one execution, you’re orchestrating many executions over days, weeks, or months, and the unit of design changes. You’re no longer writing prompts. You’re designing behavior over time.
The harness waits. The loop decides when it runs.
A harness doesn’t wake itself up. Something else has to decide it’s time to investigate an incident, review a pull request, or summarize the week’s emails. An event occurs, and the loop triggers the harness:
@app.post("/pagerduty-webhook")
async def webhook(request: Request, background_tasks: BackgroundTasks):
payload = await request.json()
background_tasks.add_task(investigate_incident, payload)
return {"accepted": True} # ack fast; the harness runs after this returnsNobody clicked a button. The system decided.
The harness solves. The loop verifies.
Most agent systems stop the moment a model reaches a conclusion. Loop engineering assumes something stricter: every conclusion that’s going to be acted on deserves an independent reviewer; not another prompt, not self-reflection, but a genuinely separate execution with its own instructions.
maker_result = maker.run(incident)
approved, reason = checker.verify(maker_result, raw_tool_trace)Here’s the detail that makes or breaks this pattern: raw_tool_trace has to actually be the raw trace, not the maker’s summary of it. It’s tempting to just pass the maker’s own evidence bullets to the checker. But that isn’t independent verification, it’s the model reviewing its own paraphrase of its own work. Building the real trail means walking the actual conversation and reconstructing what the tools returned, not what the maker claimed they returned:
def extract_tool_trail(messages: list) -> str:
"""Rebuild the real tool-call/result sequence from the conversation,
so the checker sees what actually happened, not a summary of it."""
trail, pending = [], []
for msg in messages:
if msg.get("role") == "assistant" and msg.get("tool_calls"):
pending.extend(
(c["function"]["name"], c["function"]["arguments"])
for c in msg["tool_calls"]
)
elif msg.get("role") == "tool":
name, args = pending.pop(0) if pending else ("unknown", {})
trail.append(f"{name}({args}) -> {msg['content']}")
return "\n".join(trail)Skip this and “independent review” is a claim your architecture diagram makes that your code doesn’t actually back up.
The harness forgets. The loop remembers.
Every harness invocation starts from an empty conversation. Without persistent state, today’s investigation disappears the moment it’s done. Loop engineering keeps it around:
incident_store.record(incident)
similar = incident_store.search("database failover")Now tomorrow’s investigation can learn from yesterday’s. Memory doesn’t belong inside the prompt. It belongs inside the system, outside the lifetime of any single run.
The harness uses tools. The loop connects systems.
A harness knows how to use a tool. A loop decides which tools exist in the first place. This is where something like MCP earns its keep, wiring GitHub, Jira, PagerDuty, Slack, Datadog, or internal APIs into the system once, so any harness invocation can draw on them. The harness consumes those capabilities. The loop is what assembled them into a coherent, autonomous workflow to begin with.
Where the boundary actually is
The harness is the box in the middle. Everything around it; the trigger, the checker, the memory feeding back in; is the loop.
Which problem are you actually solving?
If your agent answers before gathering evidence, hallucinates tool results, loops forever, or ignores the tools it has; that’s a harness problem, and no amount of scheduling or sub-agents will fix it.
If your agent works perfectly task-by-task, but still needs you to wake it up, review every answer, approve every action, and remember what happened last time; your harness is probably fine. You’re missing the loop.
Final thought
As AI systems get more autonomous, I think we’ll talk less about prompts and more about architecture, because prompts don’t build reliable systems, software engineering does. The future isn’t a smarter prompt or a bigger model. It’s the software wrapped around them, at both layers. Harnesses make individual executions trustworthy. Loops make entire systems trustworthy. The best AI systems won’t choose between the two; they’ll be built on both, deliberately, as separate design problems rather than one blurred into the other.
Loop Engineering: Addy Osmani frames it as the layer directly above what he separately calls “agent harness engineering”. A single harness runs on a schedule, spawns helper agents, and feeds itself, rather than waiting on a human to invoke it each time.




