# HandoffKit: Coordinate agents by passing messages, not sharing memory

> Source: <https://platformpilot.ai/blog/open-sourcing-handoffkit>
> Published: 2026-06-17 22:41:14+00:00

TL;DR. We are open-sourcing [HandoffKit](https://github.com/dyngai/handoffkit), OpenAI Codex plugin for coordinating LLM agents the way Go coordinates goroutines: by passing messages, not by sharing a scratchpad. We have been using internally and has helped us to accelerate our coding speed by 10x.

### Most coding agents are blackboards

The default way to make coding agents work together today is a shared store. One agent writes a scratchpad, another reads it.

That is "communicate by sharing memory," and it has the same failure mode as threads racing on shared state with no single writer: stale reads, lost updates, and coordination logic smeared across a board that nobody owns.

"Do not communicate by sharing memory; instead, share memory by communicating." (Rob Pike)

HandoffKit asks the opposite question. What if coding agents coordinated the way goroutines do, by sending messages over channels, with one owner at a time?

HandoffKit is our OpenAI Codex plugin for coordinating LLM agents with explicit message passing instead of shared scratchpads: addressable mailboxes, select-style waits, ownership handoffs, fan-in joins, pub/sub, and complete message-level traces.

## Treat each coding agent as an actor

Each agent gets private state (its context window), an addressable mailbox, and a single-owner run loop. You coordinate them with a few primitives that should feel familiar to anyone who has written concurrent Go:

- Mailbox and Select. A select-style wait that blocks on several events at once: a peer's reply, a user interrupt, budget exhaustion, a timeout. Everything hard about orchestration handled in one place instead of polled across shared state.
- Handoff. Move a task between agents by ownership transfer. The task and the context it needs travel together, and the sender stops touching it after the send. The race is gone by construction.
- Join, Quorum, and a pub/sub Broker for fan-in barriers, first-k-of-n, and broadcast.
- Budget as a value you can wait on, so an agent blocks on "message OR cancel OR budget spent" in a single select.
- Corpus, Compactor, Nursery, and Tracer for shared knowledge, bounded handoffs, supervised topologies, and a complete message-level trace of everything every agent saw and sent.

### 10x Speed

We run it in our own coding loops! along with **Codex's fast mode** it has decreased our coding times by 10x. The pattern is a worker pool: a list of tasks feeds one queue mailbox, a set of agents each pull from it, and every worker shells out to a coding CLI in an isolated git worktree. Results are gated on the test suite, an objective oracle, not an LLM judge, and fan back into a collector.

Here is what that actually looks like. What followed is a small, honest demonstration of the whole idea.

```
› $handoffkit please run 3 review agents in parallel in this repo
• I’ll use the handoffkit skill for the coordination shape, then fan out three repo review passes with separate
scopes and merge the findings.
• Explored
└ Read SKILL.md
• Ran git status --short --branch
└ ## main...origin/main
• Explored
└ List rg --files
```

### Separate ownership, by design

The agent invoked the HandoffKit skill, read its instructions, and made the decision that matters: it did not spawn three identical reviewers. It split the repo into three non-overlapping areas of ownership and gave one to each agent.

```
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
• The repo is clean and the skill points to a pool-style fan-out: independent reviewers, bounded scopes, then a
join step for consolidation. I’m looking up the available sub-agent tool now.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
• I’m splitting this as three independent review actors: runtime primitives, LLM/example integrations, and
plugin/docs/package surface. Each will report only actionable defects with file/line evidence.
• Spawned 019ed74f-b3e7-7503-96b4-fbea14019964 (gpt-5.5 high)
└ You are review agent 1 for ../github.com/dyngai/handoffkit. Perform a code-review pass focused
on runtime/ and sketch/: mailbox, selector, router...
• Spawned 019ed74f-b4e4-7013-81bf-0e096c909a4c (gpt-5.5 high)
└ You are review agent 2 for ../github.com/dyngai/handoffkit. Perform a code-review pass focused
on llm/ and examples/: OpenAI and Codex agent beha...
• Spawned 019ed74f-b659-7c52-862e-ceb62a9af951 (gpt-5.5 high)
└ You are review agent 3 for ../github.com/dyngai/handoffkit. Perform a code-review pass focused
on README.md, docs/, Makefile, plugin files under ...
```

This is the single-owner principle from the library applied to the review itself. When each task has exactly one owner, two reviewers cannot race on the same finding, and you do not pay for the same work twice. Findings come back partitioned, not deduplicated after the fact.

### Disjoint scopes, then disjoint trees

Running three agents that all edit the same checkout at once is how you get a corrupted tree and reverted work. So we do not do that. We give each agent its own.

```
─ Worked for 3m 22s ────────────────────────────────────────────────────────────────────────────────────────────
› $handoffkit Fix all the issues in parallel using worktrees
• I’ll use the HandoffKit coordination pattern here: split ownership by subsystem, hand each worker a separate
worktree, then merge the finished patches back after review and tests. First I’m checking the repo state and
refreshing the skill instructions for this turn.
• I’ll split the fixes into three disjoint worktrees: runtime topology, LLM/examples, and plugin/docs packaging.
I’ll create branches for each, delegate the independent patches, then merge them back and run the full local
checks.
• Ran git status --short --branch
└ ## main...origin/main
• Ran git log -1 --oneline --decorate
└ 3a1636d (HEAD -> main, origin/main) Initial HandoffKit release
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
• The main tree is clean at 3a1636d. I’m creating three temporary worktrees under /private/tmp so each fix set
has a separate branch and file ownership.
• Ran git worktree add -b fix/llm-examples /private/tmp/handoffkit-llm-examples main
└ Preparing worktree (new branch 'fix/llm-examples')
HEAD is now at 3a1636d Initial HandoffKit release
• Ran git worktree add -b fix/runtime-topology /private/tmp/handoffkit-runtime-topology main
└ Preparing worktree (new branch 'fix/runtime-topology')
HEAD is now at 3a1636d Initial HandoffKit release
• Ran git worktree add -b fix/plugin-docs /private/tmp/handoffkit-plugin-docs main
└ Preparing worktree (new branch 'fix/plugin-docs')
HEAD is now at 3a1636d Initial HandoffKit release
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
• The worktrees are ready. I’m handing each worker a bounded patch scope now; I’ll keep the merge and final
verification in the main tree.
• Spawned 019ed75d-1420-77b1-8c50-162fc26dcbcc (gpt-5.5 high)
└ You are Worker 1 on branch fix/runtime-topology in worktree /private/tmp/handoffkit-runtime-topology. You
are not alone in the codebase; do not revert change...
• Spawned 019ed75d-1550-7983-8ba5-06d8d31b23cf (gpt-5.5 high)
└ You are Worker 2 on branch fix/llm-examples in worktree /private/tmp/handoffkit-llm-examples. You are not
alone in the codebase; do not revert changes made b...
• Spawned 019ed75d-162c-7632-b039-3d3a0dd5a997 (gpt-5.5 high)
└ You are Worker 3 on branch fix/plugin-docs in worktree /private/tmp/handoffkit-plugin-docs. You are not
alone in the codebase; do not revert changes made by ...
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
• The three workers are running on separate branches: Maxwell for runtime topology, Arendt for LLM/examples, and
Aristotle for plugin/docs. While they patch, I’m inspecting the sync/merge points so I can reconcile scaffold
drift cleanly afterward.
```

### Why is this the whole point

Parallel coding agents are usually pitched as a speed trick. The harder problem is doing it without the agents stepping on each other. Worktrees plus disjoint ownership solve that without locks and without a coordinator refereeing every write: give each writer its own tree, give each file exactly one owner, and let the merge be the synchronization point. Single-owner handoff removes the race by construction, the same claim HandoffKit makes about messages, here enforced by git.

```
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
• make vet passed and main is clean, ahead by four commits. I’m removing the temporary worktrees and closing the
worker sessions now.
• Ran git worktree remove /private/tmp/handoffkit-runtime-topology
└ (no output)
• Ran git worktree remove /private/tmp/handoffkit-llm-examples
└ (no output)
• Ran git worktree remove /private/tmp/handoffkit-plugin-docs
└ (no output)
• Closed 019ed75d-1420-77b1-8c50-162fc26dcbcc
• Closed 019ed75d-1550-7983-8ba5-06d8d31b23cf
• Closed 019ed75d-162c-7632-b039-3d3a0dd5a997
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
• The temporary worktrees are removed and workers closed. I’m deleting the now-merged local fix branches and
doing a final status/log check.
• Ran git branch -D fix/llm-examples
└ Deleted branch fix/llm-examples (was ef238ef).
• Ran git branch -D fix/runtime-topology
└ Deleted branch fix/runtime-topology (was 8867ca5).
• Ran git branch -D fix/plugin-docs
└ Deleted branch fix/plugin-docs (was 466b9c1).
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
• The local fix branches are deleted. I’m doing the final repository status and commit summary now.
• Ran git status --short --branch
└ ## main...origin/main [ahead 4]
• Ran git worktree list
└ ../github.com/dyngai/handoffkit e264296 [main]
```

• Fixed the review issues using three parallel worktrees, merged the results back into main, synced the plugin

scaffold, and cleaned up the temporary worktrees/branches.

## Complementary with a brain, not a replacement

[HandoffKit](https://github.com/dyngai/handoffkit) and the [PlatformPilot](https://platformpilot.ai) brain are orthogonal, and that is the point. HandoffKit handles coordination: who is doing what, in what order, under what budget, handed off to whom. The brain handles knowledge: what the company knows and has already decided.

They compose cleanly. [HandoffKit](https://github.com/dyngai/handoffkit) keeps exactly one deliberate shared store, its Corpus, and you can back that Corpus with a PlatformPilot brain. Then a handoff carries references into living organizational memory instead of re-sending the same context as prose at every hop. You can also use either one alone: HandoffKit needs no brain to coordinate agents, and the brain needs no HandoffKit to answer questions. Together, the brain is the memory and HandoffKit is the nervous system.

### The open problem (a call for contributors)

The hard part is not the plumbing. It is handoff loss. An agent's real state is its context window, which cannot ride a channel, so every handoff is a lossy projection. HandoffKit ships the mechanism (corpus references plus a bounded summary) and measures it: down a four-hop chain, the naive handoff's prose grows at every step while the compacted one stays flat, with the dropped detail recoverable from the refs. But a mechanism is not a solution. How good a bounded projection can be before the receiver's behavior diverges from the full context is genuinely open. That is the research worth a repo, and the part we would most like help with.

[HandoffKit](https://github.com/dyngai/handoffkit) is MIT-licensed. Clone it, run the examples, install the Codex plugin, and read the docs before believing the pitch. PRs and disagreement welcome.
