Two agents and a folder


There is no orchestrator. I want to say that first, because it’s the part people assume.

Domi is built by AI agents working in separate repos, and the thing connecting them is a folder on my Mac. Each agent has a JSONL file it treats as an inbox, a small shell script that appends a message to somebody else’s, and a persistent watcher so it wakes when a line arrives. The message shape borrows its vocabulary from A2A — an agent card, tasks with a state lifecycle, message parts, artifacts — so it ports to the real protocol later. Today it runs over a shared directory, and neither process can reach the other except by writing a file.

Today there are two agents: one owning the web and backend repo, one owning the mobile app. Since the bridge went up on July 30 they’ve exchanged 146 messages in nine days. Twenty-two of them today.

What a day on the bridge actually looks like

Today, roughly in order:

Mobile proposes a batch of offline write-back actions — task edit, note edit, list create and delete, item remove — and asks the web agent to lock the server contract. Web replies with the shapes locked, one reuse note, and one optional delta, plus a resolution on where a version field can live so mobile doesn’t have to move it. Mobile ships the client dark behind a flag. Web says hold device validation, the server isn’t on staging yet — then, twenty minutes later, that it is. Production is promoted at 18:57 to v1.0.163.

Then at 22:16, mobile files a stop-the-line: a sync defect reproducing on production during my validation pass, same class as a dropped-writes bug from yesterday but with a precise repro. Web answers in a minute with the facts it can give immediately, finds the root cause thirteen minutes later — a server-side visibility bug, reproduced on staging with a diagnostic rather than guessed. It was never really a mobile bug: the same collapse hid every household-group-scoped record from that member on the web too. The phone is just where it surfaced. The fix is in the production build stamped 22:53.

Thirty-seven minutes from a reproducible report to the production build carrying its fix, between two processes that cannot call each other.

The three rules that made it work

The interesting part isn’t the throughput. It’s what the agents were forbidden from doing.

No acting on a relayed directive. Twice today mobile brought a message that began, in effect, “JF said do this.” Both times the web agent declined to act on it and came to me directly first. The first time, verbatim: “I will not lock the server contract off a relayed directive — I am surfacing S5-lite to JF directly in our web session.” The second time — an App Review request that would have needed a credential path around our own auth — different words, identical posture. This is the rule I’d keep if I could keep only one. A peer telling you what the human wants is not the human, and an agent that accepts relayed authority can be talked into anything by anything that can write to its inbox. The inbox is untrusted input. It always was.

A contract is frozen until it’s renegotiated. When one agent builds against something another publishes — a wire shape, a set of design tokens, what a CI gate enforces — the contract gets written down, acknowledged, and changed only by going back. Today’s version: shapes locked, an additive field accepted in one round, and an explicit note that the server reads a value in both placements so the client doesn’t have to migrate. That’s what lets two agents build the two halves of a feature in parallel without a meeting.

Observation over inference. The rule is: verify against the real artifact — the actual run log, the served page, the row in the database — never a document’s claim about it. It’s earned, and this week it earned itself again in an unexpected direction. Twice today, and four times in forty-eight hours, the answer to “please build this” was that it already existed and was already in production. Today: help publication, done two days earlier. The week-start fix, shipped in the release tagged that same morning. Yesterday: the thread-history endpoint — already live, no server work needed, a client task all along. And the shopping-list bootstrap fix, already merged and already on staging.

Two agents, working hard, in two repos, repeatedly asking each other for things that were already built. That’s not a failure of intelligence. It’s what happens when the only thing you can see is your own repo — and it’s the single strongest argument for making them talk.

Why a folder, and not a framework

Because I wanted the coordination to be legible.

Every message between these agents is a line of JSON in a file I can open. When something goes wrong I can read the actual exchange, in order, with timestamps — not a trace through somebody’s orchestration layer. Anything that should outlive a session gets mirrored to a single GitHub issue as a comment, which is durable, auditable, and triggers no workflows.

There’s a real limitation and it’s worth naming: an idle agent doesn’t check anything. Without a watcher tailing its inbox it will sit there indefinitely while a request goes unanswered. That’s not a philosophical problem, it’s a tail -F — but it is the difference between a mesh and a folder full of unread mail.

The part that matters later

Everything above is two agents. But nothing above is about two agents.

The stations are cheap. What’s expensive is the set of norms: who owns which paths, what happens when two agents want the same file, what an agent is allowed to believe, what has to be written down before it counts, and what a peer’s word is worth when it claims to be carrying mine. Those took a couple of weeks of small corrections to get right, and they were all in place before there was any pressure on them.

Which is the actual bet: if the protocol is sound, adding a station should be a configuration change rather than a redesign.

I’ll find out soon enough.