skip to content
all writeups
3 min read

Research week: how agent harnesses actually work

A week reading how agent harnesses and tool-use protocols are built before building one, and what the designs already out there get right.

otjcollegeaiprotocolsnotes

The plan was a client that drives a coding agent running on my server from any device I own, phone included – at that point the client was still going to be an Obsidian plugin. Before writing any of it, I spent the week reading how agent harnesses and tool-use protocols are actually built: what sits between an editor and an agent, which parts of these systems are the protocol, and which parts are product wrapped around it.

There is no code from this week. Writing it up after the fact makes its impact unusually easy to evidence, though: the build started the following Monday, and those first commits show exactly what the reading had settled.

What the existing designs get right

The biggest thing I took away is how much of the problem is already solved, and solved well. The Agent Client Protocol standardises communication between editors and coding agents the way LSP standardised language servers: a local agent is a subprocess speaking JSON-RPC over stdio, a remote one speaks over HTTP or WebSocket, and it deliberately reuses the JSON representations MCP defined rather than inventing parallel ones. Any agent that implements it works with any client that does.

That mattered more than it sounds, because omp – the agent I wanted to drive – already ships an ACP implementation. I was not going to design a wire protocol at all. The inherited surface is most of what a chat UI is: streaming updates carrying message chunks, thinking, tool cards and the todo plan; permission prompts as awaited round-trips (session/request_permission), which is what makes tapping an approval on a phone work by design; elicitation for questions back to the user; interrupt; session new, list, load, resume and fork; plan mode, model switches, slash commands. Reading the spec made plain that the chat surface is not the hard part of this project. Everything around it is.

The second thing the reading surfaced: a protocol carries the standard surface and drops the rest. omp’s own ACP event mapper handles message updates, tool execution and the todo plan, then falls through to a default arm that returns nothing. Compaction events, retries, notices – things the agent’s own terminal shows – never cross the wire. Full fidelity therefore needs a second channel from inside the agent process, not a bigger protocol. That became a standing rule: if omp shows it in the terminal, Atlas has to show it too.

What the week decided

The Monday commits are the research output, written in code. 27 July landed the shared envelope types, the relay broker, the subprocess supervisor and the fidelity extension, alongside a docs revision that replaced the previous plan – driving Claude Code through its Channels plugin protocol – with omp over ACP. The stated reason is straight from the reading: Channels is undocumented and tied to one tool; ACP is an open standard the agent already implements.

Two decisions from that week still carry the app. The first is that the client is conformant: all Atlas-specific framing lives in an outer envelope, and the ACP frame inside it is left untouched – deliberately untyped, so nothing in the middle is even tempted to parse it:

packages/shared-types/src/index.ts
/**
 * An ACP JSON-RPC frame. Deliberately opaque: `unknown`, not a structural
 * type. Parsing it anywhere but the two endpoints violates Decision #31, and
 * typing it here would tempt exactly that.
 */
export interface AcpFrame extends EnvelopeBase {
	kind: 'acp';
	sessionId: SessionId;
	payload: unknown;
}

The second: a browser cannot set WebSocket headers, so authentication rides the WebSocket subprotocol instead of a header. That was found by reading, days before any browser code existed – and it survived the client becoming a web app three days later, because a web app is still a browser.

Eight hours of reading replaced a wire protocol I would have designed badly, and handed me a chat UI’s worth of behaviour for free. Good trade.

Projects

what this writeup is about