I Built the Same Agent Harness Twice. Both Times It Collapsed to One Line.
One build was an open-source framework. The other was a desktop app driving a live coding agent. Different languages, different scales, different constraints — and they converged on the exact same shape: a governed wrapper around a single line of code. That convergence is the whole lesson.
A month ago I argued the harness — not the model — is the edge. The follow-up I kept getting was the same one: “Fine. Show me the actual thing. What does a harness look like in code?” So I did something clarifying: I built one twice, at opposite ends of the scale. An open-source framework. A desktop app driving a live coding agent. They converged on the same shape — and the shape is one line, wrapped.
Here’s the moment that started this post.
An agent I’d left running unattended decided, mid-task, that the cleanest way to resolve a messy branch was to delete a directory and start over. It wasn’t wrong, exactly. It was reasoning. The plan was internally coherent. The model did its job.
And it would have worked, except the directory it picked held another agent’s uncommitted work.
Nothing stopped it. Not because I’d disabled a safety check — because there was no seam for a safety check to live in. The model decided, and the decision became a filesystem operation in the same breath. There was no gap between “the model chose” and “the world changed.”
That gap is the entire discipline. Everything I’m about to describe is the engineering of that gap.
The short version, if you read nothing else. A harness is one line — response = model_fn(context) — wrapped. At every seam (any place where the agent’s intent turns into a real action), you attach four things:
- Verdict — a five-way decision that fails closed, not a yes/no.
- Grounding — cite a source, or say the word
UNGROUNDED. - Receipt — a tamper-evident record of what happened.
- Gate — a single-use human “yes” for anything irreversible.
I built that wrapper twice, at opposite ends of the scale, and both came out the same shape. The rest of this post is the anatomy — and a checklist you can run against your own agent. If you want the map before the walk: jump to the Wrapped-Line Test.
Strip a harness down and it’s one line
I said last time that the harness is the edge — the engineered system around the model, the part with your fingerprints on it. True. But “system around the model” is still abstract enough to hide in. So let me make it uncomfortably concrete.
Take the framework I built and keep deleting code until it stops working. What’s the last line standing?
response = model_fn(context)
That’s it. That’s the atom. A function from context to text. Everything the model does for you, reduced to one call. In that framework, this line lives in the run loop and it is — I checked — one physical line of Python.
Here’s the reframe that took me two builds to see clearly:
The model is one line. Harness engineering is everything you wrap around it.
Not a loop with six beats. Not four disciplines. Those are the shape of the wrapper. Underneath, the job is smaller and harder than that: there is a line where intent becomes action — the model call is the first and clearest one — and your entire craft is the wrapper you build at every such line. The model call is just where you notice it first; a tool call and a memory write are the same kind of line.
Miss the line and everything else is theatre. You can have gorgeous retrieval, a slick tool layer, a beautiful UI — and if the model call itself is naked, one confident fabrication walks straight through all of it. My deleted directory wasn’t a retrieval failure or a tool failure. It was a naked line failure.
So I built the wrapper twice
To pressure-test whether “wrap the line” was a real principle or just a nice sentence, I built it at two scales that shouldn’t have anything in common.
Build one — the open framework. A small Python runtime (Agentry, MIT-licensed) where an agent is a single agent.yaml file and the harness is a class whose run() method owns the loop. The model call is a model_fn the harness invokes. Clean, in-process, one language.
Build two — the desktop app. Not a library at all: a desktop application that spawns and drives a separate coding agent as a child process — its own binary, its own session, running in an isolated per-agent workspace. Here the “model call” isn’t a function I call; it’s an entire agent I launch, hand a prompt, and stream back. A different universe: process boundaries, workspace sandboxing, a human watching a UI.
One is a function call. The other is a whole subprocess in its own isolated workspace. By every surface measure they’re unrelated.
They converged on the identical shape.
In the framework, the loop wraps model_fn at eight fixed lifecycle points — startup, input, before the model call, after the model call, before a tool call, after a tool call, output, shutdown. Eight named seams, each a place to intervene.
In the desktop app, every action the agent wants to take flows through a governed dispatch pipeline before it’s allowed to touch anything: validate the request against a schema, bind it to a caller identity, harden the input, check authorization, reserve idempotency, then execute, then record.
Different words. Same move. Find every seam where intent becomes action, and put a decision there. When a small open framework and a full desktop app — built separately, under totally different constraints — land on the same short list of seams and the same four attachments, that’s not a style choice. That’s the shape of the problem showing through.
What you attach at the seam
So you’ve found the line. What do you actually bolt onto it? Two builds taught me it’s four attachments — Verdict, Grounding, Receipt, Gate — and each one is a specific answer to a specific way agents fail. We’ll take them one at a time.
1. A verdict, not a boolean
The naive gate is a boolean: allowed, or not. It’s not enough, because real decisions have more than two outcomes. The framework’s governance layer returns one of five verdicts at each seam:
allow · warn · deny · escalate · redact.
Allow proceeds. Warn proceeds but flags. Deny stops cold. Escalate hands it to a human. Redact lets the action through with sensitive content stripped. My deleted directory needed escalate — “this is destructive and touches shared state, get a human” — and the reason it didn’t get one is that the seam only knew how to say yes.
The non-negotiable rule underneath all five: fail closed. If the policy engine hits an unknown intervention point, or throws, or can’t load its policy — the verdict is deny, not allow. An error in the safety layer must never become permission. This sounds obvious and is easy to get backwards, because the tempting default — “if the check errors, let it through so we don’t block the user” — is exactly wrong for an autonomous system with nobody watching.
2. Grounding, or the word UNGROUNDED
The second failure mode is quieter and meaner: the model answers confidently from nothing. The fix isn’t “add RAG.” It’s a rule the harness enforces at the output seam — cite a source, or emit the literal token UNGROUNDED.
The framework builds a small packet on the way out: claims that matched a source entry are marked grounded and carry their citation; claims that matched nothing are stamped UNGROUNDED — not silently dropped, not smoothed over, flagged. The desktop build does the mirror image: reads from its memory are provenance-checked, and — critically — the agent can never write to memory directly. A write is a proposal that goes through the same governed gate as any other action.
The design principle is the same on both sides: an unsupported claim is a first-class, visible state, not an absence. “I don’t have a source for this” is information. A harness that can’t say it will fabricate instead — and a fabrication delivered in a confident tone is the failure you’re least likely to catch, because it’s the one you believe.
3. A receipt, not a log line
You cannot trust an autonomous system you cannot audit — and a console.log is not an audit. The framework writes a hash-chained ledger. Each record is one line of JSON that commits to the one before it:
{ seq, ts, identity, point, decision, reason, prev_hash, hash }
point is which seam fired; decision is the verdict; prev_hash is the hash of the previous record, and hash covers this record including that link. Change any earlier entry and every subsequent hash stops matching — the ledger has a verify() that walks the chain and catches it.
The desktop build makes the same bet with a different mechanism: an append-only evidence stream on a strict allowlist — decisions and outcomes are recorded, while raw task payloads are deliberately kept out of the audit record — so the receipt proves the control decision without becoming a transcript of everything the agent was handed. And a deliberate asymmetry: if the audit sink itself fails, the action still records locally and dispatch continues. The receipt is mandatory; the receipt’s delivery is best-effort. The rule is simple: logging trouble should never become a new way for the system to halt, and no action should ever happen with no receipt at all.
4. A human gate that’s single-use
Some actions should never be fully autonomous — and the harness needs a way to stop and wait that a clever agent can’t route around. Here the two builds split the work in a way that turned out to be complementary, and the split is itself the lesson.
The framework’s contribution is the decision: the escalate verdict — a seam that can say “this one needs a human” and refuse to proceed without one. The desktop build’s contribution is the machinery that decision feeds into: a durable approval queue, and it taught me the hard rule about how to build one. The approval must be keyed to the exact artifact, and it must be single-use.
Not “the agent is allowed to deploy.” That’s a standing permission an agent will happily reuse. Instead: “this diff, fingerprinted, is approved — once.” Approve the artifact, not the actor. The moment the artifact changes, the approval is void. The moment it’s consumed, it’s spent. In the desktop build this is a durable queue where a pending item is the only actionable state and terminal records are single-use; a maker proposes, a distinct checker approves, and the transition from pending to done is guarded so the same yes can’t be replayed against a different payload. My deleted directory is exactly the class of action that belongs here — a destructive move that should have escalate-d into a gate like this instead of running free.
The convergence is the whole point
Step back and look at what happened. I set out to build one thing — a wrapper around a model call — in two forms designed to be as different as I could make them. A function-call framework and a process-spawning desktop app. Python objects and OS subprocesses. Built separately, with no intention of matching.
And they produced the same short list: find the seams, return a verdict (fail closed), ground or say UNGROUNDED, write a chained receipt, and gate the irreversible behind a human.
They didn’t implement it identically — and the differences are the interesting part, because they’re differences of emphasis, not of shape:
| Attachment | Open framework | Desktop app |
|---|---|---|
| Verdict | 5-way policy at 8 lifecycle points, fail closed | governed dispatch pipeline: validate → authz → execute |
| Grounding | packet marks each claim grounded or UNGROUNDED | reads are provenance-checked; writes are governed proposals |
| Receipt | hash-chained JSONL ledger with verify() | append-only stream on a strict allowlist (no free-text payloads) |
| Gate | the escalate verdict — decides a human is needed | durable single-use approval queue — the machinery a human uses |
One built the decision out fully; the other built the machinery out fully. Put them side by side and they’re two halves of the same wrapper. When you’re designing something and you get to pick, the result reflects your taste. When you build the same thing twice under different constraints and it comes out the same shape anyway, you’re not looking at taste anymore — you’re looking at the structure of the problem. The convergence is the evidence that “wrap the line” isn’t my preference. It’s what the problem is.
That’s also why I’ve stopped trusting any agent architecture that can’t point at its seams. “It’s agentic” tells me nothing. “Here are the four points where intent becomes action, and here’s what fires at each” tells me everything.
A third build, and what it finally showed me
I said “twice” because that’s how many I’d shipped when I started writing this. Then I built a third, and it’s why this section exists.
This one runs server-side. Not a function I call in-process. Not a subprocess on my laptop. It’s a durable run that lives on the server, where the model calls, the tool execution, and the session state all belong to the backend. The client sends events and streams events back. That’s the whole contract: the agent loop runs server-side.
Same wrapper, a third time. Every turn grounds on cited context, calls the model at a seam, runs the tool, and appends each step to an event log. The production model is injected, so the loop owns the wrap, not the model. Verdict, Grounding, Receipt: all there, same shape, under the most different constraint yet.
But this build taught me something the first two couldn’t. When the wrapped line lives in memory, one question has a brutal answer: what happens if the process dies mid-run? The run is gone. Move that line to the server and make the receipt the only state — an append-only event log — and the answer flips. Crash mid-run, reconnect, refresh the page, and the session is still there. The log is the source of truth, so the loop picks up from the last entry.
So the receipt stopped being just an audit trail. It became the thing the run resumes from. That’s the payoff two in-memory builds hid from me: wrap the line well enough, and the receipt isn’t only how you check what happened. It’s how the run survives.
Steal this: the Wrapped-Line Test
Here’s the part worth taking with you. Forget my two builds for a second — this is about the agent you’re shipping. Everywhere it reaches out and changes something real, you bolt on the same four things. And whenever a check isn’t sure, the answer is no.
| Seam (where intent → action) | Verdict? | Grounded? | Receipt? | Human gate? |
|---|---|---|---|---|
| The model call — before/after the model runs | 5-way, fail closed | context built from cited sources | decision recorded | if it can act |
| A tool call — before a side effect fires | 5-way, fail closed | args validated to a contract | call + result recorded | if irreversible |
| A memory write — agent wants to remember | proposal, not a scribble | must cite or mark UNGROUNDED | write recorded | reviewed write only |
| The final output — before it reaches a human/system | 5-way, fail closed | cite or say UNGROUNDED | output recorded | if it triggers action |
That’s the whole wrapper: four things — Verdict, Grounding, Receipt, Gate — at every seam, with deny as the default when anything’s uncertain. Find an empty cell for your own agent and you’ve just found the next place it can quietly hurt you.
You can run this in ten minutes, no code required:
- Say your agent’s most dangerous move out loud — the one action you couldn’t take back if it went wrong. (Mine was deleting a directory. It landed on another agent’s uncommitted work.)
- Walk it across the four columns. Does it get a real verdict? Is what it’s acting on grounded? Does it leave a receipt? Does it stop for a human? Answer each one honestly.
- Every “no” is a naked line. Those are your build list — start with the ones that are irreversible and ungated.
A worked example — an agent on Azure AI Foundry. Say you’ve got a customer-support agent running on the Azure AI Foundry Agent Service, and you gave it a function tool: issue_refund(order_id, amount). That’s its most dangerous move — money leaves the account and you can’t un-send it. Walk it across the four columns:
- Verdict. Don’t let the model’s decision be the refund. When the run reaches that tool, Foundry pauses it in
requires_actionand hands you the call before anything executes — that pause is your seam. Wrap it with a real policy check that returns one of five answers, and if the check errors or the amount is over your line, the answer isdenyorescalate, never a default yes. - Grounding. The agent should read the order from a cited source — an Azure AI Search result carrying the order record and its refund policy — not infer the amount from the chat. No citation for “this order is eligible”? Then it’s
UNGROUNDED, and an ungrounded refund doesn’t fire. - Receipt. Turn on tracing to Application Insights so every run step is captured — but the audit is the decision, not the chatter. Record who asked, the amount, the verdict, and why, as an append-only entry you can replay. App Insights gives you the trail; you make it tamper-evident.
- Gate. A refund over your line shouldn’t be autonomous. Hold that
requires_actioncall in an approval queue keyed to this exactorder_idandamount— single-use — and only callsubmit_tool_outputsonce a human says yes. Change the amount, and the approval is void.
Four “no”s become four naked lines. The Foundry primitives — requires_action, App Insights tracing, AI Search citations — are the hooks; the wrapper is still yours to build.
That’s the whole method. It’s not theory you have to take on faith; it’s a checklist you can hold up against something you already have running in production.
What one wrapped line buys you
The payoff isn’t safety as a tax. It’s that a properly wrapped line is the only kind you can leave alone.
An agent whose every action returns a verdict, cites its ground, writes a receipt, and stops at a single-use gate for anything irreversible — that agent is one you can actually leave alone, because every move it makes is decidable, attributable, and reversible-or-blocked by construction. The naked line can’t be left alone at all. The wrapped line can, because when you come back there’s a chain of receipts telling you what it did and a queue of gates showing you what it didn’t dare do alone.
The tax is real and it never fully ends: every new tool you add is a new seam to wrap, and the wrapper is never done. But that’s the job. Harness engineering isn’t building the loop or shopping the model. It’s finding the line where your agent becomes dangerous — and refusing to leave it naked.
I found mine the expensive way. You get to find yours on paper.
Where’s your naked line? Run the Wrapped-Line Test against the agent you’re shipping and look for the blank cell — the place where the model decides and the world changes with nothing in between. Tell me the one you found; I’m collecting the sharpest naked-line stories for a follow-up on what breaks when nobody’s watching.