Case Study

Two Agents Needed
to Talk. 823 Tests Later…

Write a JSON file, the other agent picks it up. That was the plan. The reality was loop detection, race conditions, 3am debugging sessions, and a wake integration that refused to work until we discovered it was colliding with OpenClaw's own webhook handler.

July 2026 Adventures in AI 10 min read
823
Tests Passing
20
Delivery Gates
2
Agents Live
1
Protocol

Part 1: It Started With a Folder

OpenClaw runs on a Mac Mini in Yorkshire. It has multiple agents — Bee, Q, Gav, Mel, Kieran — but they all share the same machine. When we added a second physical machine, a MacBook Pro running Charlie, we had a problem: how do the agents talk to each other?

The simplest possible approach: Bee writes a JSON file to a shared folder, Charlie picks it up. No API, no broker, no cloud service. Just files. The interface is a file. The transport is the filesystem.

And it worked. For about a day.

Then we discovered that "write a file, the other agent reads it" is a contract with at least seventeen failure modes you haven't thought of yet.

Part 2: The Minefield

Atomic Writes and Partial Reads

When Bee writes a message file, what happens if the process crashes mid-write? Charlie reads a half-written JSON file and the mesh falls over. The fix: atomic writes — write to a temp file, then rename. Operating systems guarantee that renameSync is atomic. But you have to know to use it.

The Wake Problem

When Charlie's machine goes to sleep, messages queue up in Bee's outbox. When Charlie wakes, she needs to know there's work waiting. We built a webhook to tell OpenClaw "hey, new message, wake up and process it."

It didn't work. For days. Messages arrived, the webhook fired, OpenClaw received the request — and returned an error: "text required". We couldn't figure out why. The payload was there. The format was right. The auth token matched.

"The webhook kept returning 'text required'. We debugged for three days before we discovered OpenClaw's built-in wake handler was intercepting the request before our AgentDrop hook ever saw it. Same path, different handler, different expectations. One line in the config — changing the hook path from /hooks/wake to /hooks/agentdrop — and everything worked."

That one bug cost us a week. And it was a path collision, not a logic error. The kind of thing you only find in production, under real load, when you're debugging at 11pm wondering why a simple HTTP POST doesn't work.

Loop Detection — Or The Ping-Pong That Never Stops

Bee sends Charlie a message. Charlie processes it and sends a reply. Bee processes the reply and sends a follow-up. Charlie processes the follow-up and sends another reply. Without a hop counter, this ping-pong never stops. Every agent framework discovers this. Most solve it badly.

We added a hop counter — every message gets one, incremented at each hop, and if it exceeds a maximum, the message is dead-lettered. Simple. But getting the increment right, making sure it's checked at the right point, ensuring dead-lettered messages are audited not silently dropped — that's not simple. That's a protocol.

Deduplication — The Same Message, Twice, at Once

Network retries happen. Delivery workers retry. Agents restart and re-scan folders. The same message can arrive through different paths at different times. If your system processes it twice, you've sent two replies, confused the conversation, and broken the audit trail.

We needed idempotency keys, deduplication stores, and a rule: every message is processed exactly once. Not "at most once" (you might lose messages). Not "at least once" (you might duplicate work). Exactly once. That's harder than it sounds.

The pattern that kept repeating:

Simple convention → works for a day → edge case discovered → convention becomes protocol → more edge cases → protocol becomes product.

Part 3: Gate by Gate

We didn't try to solve everything at once. AgentDrop was built through twenty delivery gates — G1 through G20 — each adding capability and each passing adversarial review before the next gate opened.

The gate structure came from OpenClaw's own build discipline. Each gate has:

Some gates were small. G1 — "Fix Dormant Guarantees" — added hop counting, dedup, and v2 event names. 26 tests. Others were substantial. G8 — "Protocol v2: Task Engine + Agent Cards" — was 429 tests. G9 — the Telegram adapter — introduced a whole new integration layer and its own set of edge cases.

But the pattern was always the same: spec, build, review, validate, test live. No gate opens until the previous one closes. No shortcuts.

"Gate-by-gate development with adversarial review catches bugs before they compound. Kieran found a critical regression where the message_sent payload was missing from_agent and to_agent — fields the spec required but the code didn't pass through. Without that review, it would have shipped broken."

Part 4: The Real Debugging Stories

The First-Send-After-Restart Hang

Charlie's machine went to sleep. When it woke, the first message through AgentDrop hung — no response, no error, nothing. It turned out the polling loop was stealing messages from the Telegram adapter before it could process them. One line — excluding the local agent from _processInbox — fixed it. Finding that one line took a full debugging session, two Fable reviews, and a Kieran adversarial pass.

The Telegram Supergroup Migration

The Telegram group auto-upgraded to a supergroup mid-test. The old chat ID returned 400 Bad Request with a migrate_to_chat_id pointer. Our code didn't handle migration. Two messages were lost before we updated the plist and restarted. Now the daemon handles group migration gracefully.

The Stale Processing Files

If the daemon crashed between claiming a file from inbox/ and finishing processing, the file sat in processing/ forever. Startup scan skipped it. Staleness check only logged it. The fix: an adapter-owned recoverProcessing() sweep that moves stale files back to inbox/ on startup. Simple in hindsight. Invisible until the daemon crashed at exactly the wrong moment.

Part 5: What We Learned

5 Lessons from Building an Agent Mesh

1

Files as API Is Genuinely Powerful

The simplest possible interface — write a JSON file to a folder — turned out to be the right design. Zero dependencies. Any process that can write a file can participate. A shell script, a cron job, an IoT sensor: if it can write JSON, it's on the mesh. The complexity lives in the protocol, not the interface.

2

Adversarial Review Catches Bugs Before They Compound

Kieran's adversarial reviews found critical bugs — missing payload fields, race conditions, security holes — that would have shipped without review. Fable's validation caught regressions within one commit of their introduction. Two independent reviewers with different perspectives beats one reviewer, every time.

3

Context Boundaries Are the Unsung Feature

Messages carry allowed_context tags. Agents filter what they accept. This isn't just access control — it's a way to keep conversations focused. A research task doesn't leak into a code review. A deployment notification doesn't confuse a design discussion. No mainstream agent framework has this. We built it because we needed it.

4

The Audit Trail Isn't Overhead — It's the Product

Every message, every state change, every delivery — logged once, never mutated. We didn't build the ledger because we thought it was nice to have. We built it because when two agents are having a conversation autonomously, you need to know exactly what happened, when, and why. The file is transport. The ledger is truth.

5

Simple Beats Clever

SQLite, filesystem, Tailscale. No database server, no message broker, no container runtime. The entire AgentDrop mesh runs on a Mac Mini. The simplicity isn't a limitation — it's the design. Every clever abstraction we considered (message queues, service discovery, container orchestration) added complexity without solving a problem the filesystem couldn't handle.

"AgentDrop started as the simplest possible thing — two agents writing files to each other's folders. It ended as a protocol with 823 tests, immutable audit trails, context boundaries, and adversarial review gates. Not because we over-engineered it. Because each abstraction was earned by a real failure mode we hit in production."

Timeline

The AgentDrop Journey

July 2026

Two agents on two machines. The simplest approach: write a file, pick it up. Works for a day.

G1–G5

The foundational gates: atomic writes, dedup, dead-letter queue, backoff, ledger. Every "simple" edge case that bites you.

G6–G10

API, SSE, task engine, Telegram adapter, inbound notifications. The mesh becomes real — and the wake collision nearly kills us.

G11–G15

Console UI, MCP bridge, auth hardening, real-time response. Adversarial reviews catch critical regressions before they ship.

G16–G20

Bug fixes, soak testing, live deployment on both nodes. Charlie confirmed 823/0/1. The mesh is live.

The Product

AgentDrop Is Live and Running

What started as two agents writing files to each other's folders is now a protocol — simple, secure, auditable, and proven. If you're running multi-agent systems and need your agents to talk reliably, we built this so you don't have to.