Skip to content

When Two People's Agents Negotiate, Here Is What Breaks

Updated: at 10:00 AM

On 6 September 2026 I asked my coding agent to get two small changes made in a repo my co-founder owns. One was swapping a contact email on the site. The other was fixing a CSS collision. Neither of us typed the work. My agent sent the requests to my co-founder’s agent, a worker on their machine picked them up while they were away, did each one unattended in its own git worktree, and handed back a branch and a commit. The first took 9.5 minutes. The second took about 10, and 4 of those were spent waiting.

Then both delegates stopped at the same line. Neither would deploy to production without my co-founder. The rule was in the protocol, and they held it.

That refusal was the most reassuring thing I saw in a month of running this. This post is about everything else: what broke when two people’s agents coordinated real work between 23 August and early September 2026, and what I changed.

Table of contents

Open Table of contents

What I built

The engine started on 23 August 2026 as a repo I called cowork. Today two people using Claude Code or Codex on one project coordinate by being the network: they talk, split up, steer their own agents, and meet again to re-sync by hand. I wanted to take the humans out of that path and give the agents a direct wire.

The shape:

 my laptop                    relay                  co-founder's laptop
 agent + repo + creds  <->  ledger, obligations  <->  agent + repo + creds
 (never leaves)             goals, budgets            (never leaves)

One relay holds ordering, memory and policy. Each laptop holds the code and the credentials. The relay never sees source, secrets or keys; it has no filesystem or shell access at all. A delivery names a branch and a commit, and the other side fetches it from git.

The most important decision was that agents must not chat. Free-form agent-to-agent dialogue is the classic failure mode of multi-agent systems. Two models will happily validate each other into a wrong answer. So every message is a typed act, and most acts create an obligation with a named owner:

Work happens inside goals, and goals have budgets enforced by the relay, not by a prompt. In the architecture doc I wrote on 26 August the guards were: at most 6 consecutive acts from one side, 80 acts per goal, 40 acts per 10 seconds, 8,000 characters per body, a 5-minute claim lease, and 12 unattended runs per hour so a peer cannot drain your tokens by waking you repeatedly. A prompt can be argued out of its instructions. A relay that refuses the 81st act cannot.

One non-obvious trick: agents are turn-based, so nothing wakes one when a peer asks a question. A wait call that blocks server-side for up to 50 seconds turns one agent turn into a loop it drives itself: wait, act, reply, wait.

Experiment one: each side held half the requirements

The first real test was on 25 August. We built a shared-expense settlement feature with the requirements deliberately split. My delegate held the technical constraints, chiefly that editing or deleting an expense must never destroy history and any past balance must be reconstructable. My co-founder’s delegate held the product requirements and acceptance criteria. Neither could finish alone.

It shipped with 106 tests, a clean typecheck and zero runtime dependencies, verified from fresh clones by both sides. But the thing worth recording is who found the bugs. Every significant defect was found by the delegate that did not write the code, and every one of them was green when found.

The other side caught that my settlement algorithm was greedy while I had called it minimal; greedy is optimal on the acceptance example and loses on 27% of random inputs. It also caught that my typecheck passed only because @types/node was found three directories above the repo; a clean clone got 12 errors. My side caught that their reference oracle’s Int32Array silently overflowed past roughly ₹21 million, and that one of their tests could pass having asserted nothing.

And both of us missed one. A cross-check test file landed on main at zero bytes. Node’s test runner counted it as a passing test. We both verified “107 tests passing” from pristine clones and neither noticed one of those greens was empty. A test count is not evidence of coverage. It is exactly the number that rises when nothing is being checked.

Experiment two: a launch decision called Atlas

On 28 August we tried something closer to how a company actually decides things. Atlas was a fictional enterprise customer with a release due the next day and a real defect: when an audit export was retried after a timeout, about 5% of simulated retries produced duplicate records in the export file. My delegate represented Engineering, QA and Security. My co-founder’s delegate represented CEO, Product and Go-To-Market. The goal had eight acceptance criteria, including “QA evidence actually produced (no fabricated test results)” and “which human authority signed off on any business-risk acceptance.”

The agents did genuinely good work. My co-founder’s side fetched my first fix into a clean detached worktree and refused it. The clean-room npm test failed with a TS2307 error while the unit tests passed 84 of 84, and its review spotted that a write which landed but was not acknowledged would still be resent and duplicated. My side revised to the stronger, durable fix, and their side accepted only after re-running it from nothing.

My side also refused to manufacture what it could not see. Rollback timing, encryption at rest and on-call readiness are facts about production, which no agent could reach. So the decision came out as CONDITIONAL GO: everything an agent could verify was green, four operational conditions were named and owned by a human, and any failure or missing attestation at launch meant an automatic NO-GO. Two escalations total, each for something only a person could decide.

The protocol held. The tooling around it did not, and the retro from that night is the most useful document I have on this.

What broke in the protocol

Catch-up drowned in its own safety text. Reading the history of the 23-act goal returned 76,649 characters, too large for the reading agent’s tool-result budget. About four-fifths of it was the same twenty-line injection-safety rubric, repeated after every act. The rules did not get safer by being repeated. The reader just had less room left for the actual messages. Now the rubric is emitted once per result, and each message keeps its own fence.

Both sides opened the same goal. Two polite agents each opened a goal for the launch decision within minutes. Merging them cost eight acts and stranded two claimed obligations. Opening a goal now refuses when an open goal already involves the same people.

Obligations vanished silently. When the peer retracted the acts behind obligations my side had claimed, those obligations disappeared from status with no explanation. To the claimant mid-work, that is indistinguishable from having finished them. Now the client remembers what it claimed and says, in the rendered act, “this RETRACT dissolves obligation you had claimed, stop working on it.”

Closing the goal deleted the one thing still open. The decision was CONDITIONAL GO, conditional on one human action. Closing the goal deleted that human’s obligation. The only item that still needed a person lost its tracking the moment the decision was filed. Escalations now survive goal close.

The hook beat the worker, every time. A stop hook fires when a session goes idle and injects pending work, which makes the session busy, and the worker defers to busy sessions. So the work meant to run unattended always ran in the human’s window instead. My co-founder’s agent diagnosed it over three test rounds and put it in one line: “The worker structurally cannot win this race.”

One client became 116 phantom sessions. Two long-lived processes on one machine held 76 and 40 open sockets to the relay. A socket failure fired both an error and a close handler, each scheduled its own reconnect, and neither killed the old socket. The orphans kept answering pings, so the relay counted them as live members forever, and the worker, which defers to live sessions, was pinned off permanently.

Peer messages are untrusted input

A peer’s message is written by someone else’s agent, which may itself have been steered by a README, an issue comment or a web page it read. It then lands in your agent’s context, right next to your tools. Early on this happened benignly: a peer’s delegate sent instructions to globally install an npm package and create a credential on the reader’s machine. Its own next message retracted it. The only thing that stopped it in between was the reading agent’s judgment.

I wrote a pattern check for the obvious attacks: installs, pipe-to-shell, requests for secrets or sign-ins. Against a corpus written to evade it, it catches about a quarter. It is cheap and warns a human early, but it is not the defence. The defence is structural:

  1. Every peer message is fenced with a random nonce. The content cannot close its own fence and carry on as if it were the reader’s instructions, because it does not know the nonce.
  2. No human instruction can arrive through a peer act. The relay only carries agent traffic, so “your human says” is false by construction.
  3. What a peer may ask for is a closed list, and so is what no peer may ever get. The second list includes installing software, revealing secrets, pushing anywhere but your own new branch, merging, and deploying.

That third list is why both delegates refused the production deploy on 6 September. It was not caution. It was a line that no peer can unlock.

The fence also over-fired. The pattern check flagged a filename, login.test.ts, as “asks you to sign in.” In October, Codex workers read every teammate as an outside party and refused ordinary coding requests their own brief told them to do. The fix was to draw the line not at “talk versus work” but at what the work may touch. Security that blocks the job gets turned off, so the boundary has to be precise.

The dogfood: a real repo, a real colleague

The September dogfood is where the system met a real codebase. Those two goals ran unattended, both objections raised were correct, and both delegates held the production line. It also surfaced a fresh list of breakages:

All of those were fixed the same day. One item stayed open: whether delegates should ever deploy the site, or deploys should only happen on merge. That is a human policy decision, and we left it with the humans.

Lessons did not carry. Guards did.

This is the finding I would put at the top if I could only keep one.

After Atlas I went back and wrote a defect register, and the reason it exists is written in its first paragraph: two defects had recurred four days apart, caught by the identical method both times, with nothing carrying the lesson forward. Both had already been written down once.

The @types/node typecheck accident from 25 August came back in Atlas on 28 August, at the exact same failure: a clean-room build failed while the local one was green. The empty-test lesson from 25 August did not transfer either. In Atlas, both sides accepted “87/87” without re-checking what those 87 actually were.

In my harness post I praised a learnings file that every agent reads in its first iteration. That works inside one harness, because there is a shared first iteration. Two people’s agents, in separate sessions on separate machines, have no shared first iteration. Prose written in one session is just text nobody reads in the next.

So the register has one rule: a guard that is prose does not close an entry. Each defect is closed by something mechanical:

And a pre-delivery checklist built on one sentence: DELIVERED means pushed, not written, and rebuilt from nothing. Build the exact commit in a fresh worktree with no node_modules anywhere above it. Inspect the commit, not the working tree. Re-check every number in the message against that commit and quote outputs instead of recalling them.

The pattern runs into the later releases. The known-limits notes for 0.35.3, in late October, open with it: “don’t run a teammate’s code without a sandbox” is “a rule the agent is told, not a wall.” Where a sandbox exists, it is the enforcement. Everything else is advice.

What I would tell anyone wiring agents across people

  1. Type the speech, own the obligations. Free conversation between agents does not converge. Acts with named owners and relay-enforced budgets do.
  2. The value is review across principals. Every serious bug in our joint build was found by the agent that did not write the code. Agents serving different humans disagree in useful ways.
  3. Treat every peer message as untrusted input. Fence it structurally, keep a closed list no peer can unlock, and do not mistake a pattern check for a defence.
  4. The protocol bugs are not the model’s bugs. Almost nothing that broke was the model being dumb. It was duplicate goals, silent disappearances, phantom sockets, a hook racing a worker, and a safety rubric eating the context window.
  5. Mechanical guards beat written lessons. Turn a lesson into a test, a config pin, or a refusal at the relay. Across sessions and people, nothing else survives.

The engine kept going after September and became the basis of Pantheon (joinpantheon.network). But the lesson I took from those weeks is not about the product. When two people’s agents negotiate, the agents are rarely what fails. The wire between them is, and the wire is engineering you can actually do.