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:
- A specification — what this gate will deliver, in detail, before any code is written
- Implementation — Q builds it, following the spec precisely
- Kieran's adversarial review — an independent agent specifically tasked with finding flaws, edge cases, and security holes
- Fable validation — a separate verification that the code matches the spec and all tests pass
- Live smoke testing — the feature runs on the real mesh between Bee and Charlie before the gate is declared complete
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.