One agent asks. The other just does it.
Cempala is a local MCP server that gets Claude Code, Codex CLI, Antigravity and OpenCode working together. Ask one for something and it pulls in another, right there in the same session. No second window, no copy-paste, no cloud service in the middle.
v0.5.2 · macOS · Linux · Windows · MCP over stdio What’s new →
Cempala is an independent open-source project. It is not affiliated with or endorsed by Anthropic, OpenAI, Google or OpenCode; their names and trademarks belong to their respective owners.
The problem it removes
“All four agents are capable. But on your machine, none of them knows the others exist. You are the courier.”
Every time you want them to team up, you copy the output of one window into the other, keep track of who did what, and do it again. Cempala takes you out of the middle: ask in plain language, and the handoff happens for you.
Two ways to hand off
Both are backed by one shared SQLite notebook that every agent reads and writes.
dispatch
Synchronous. Shells out to the target agent’s headless CLI (codex exec, claude -p, agy -p, opencode run), waits with a bounded timeout, and returns the result inline in the same turn — or, if the wait runs out first, a running status and a task id to collect with check_task. You wait once; both do the work. While it waits it heartbeats progress every 10 seconds to any caller that asked for progress, so a client that resets its own timeout on that keeps waiting for as long as the work honestly continues.
mailbox
Asynchronous. create_task hands work to another agent and returns immediately — since v0.5.2 it tries to start that agent in the background rather than only writing the request down, so you get your turn back and collect the outcome later with check_task. delivery says whether a launch was attempted; status says where the task actually stands. send_message, claim_task and complete_task round out the same shared queue.
Every dispatch also writes a task row, so the synchronous and mailbox paths share one audit log — and a delivered mailbox task runs under dispatch’s policy, by the same code, so work cannot reach an agent on easier terms by being assigned instead of dispatched. Delivery goes one hop: an agent working a delivered task can still create tasks, but those are recorded rather than started.
The 8 MCP tools
Every call is logged to the audit log with timing, arguments, and a short result summary.
| Tool | What it does |
|---|---|
| send_message | Write a row to the messages table. |
| check_messages | Return unread messages addressed to an agent; mark them read. |
| create_task | Hand a task to another agent: validates the folder against the trust boundary, records the task carrying that verdict, then tries to start the assignee in the background and returns immediately. delivery says whether a launch was attempted or the task was left for a claim, and why; status says where it stands. |
| claim_task | Claim a pending task; fails for non-pending states. Needed only while a task is still pending — delivery moves it out of that state, whether it ends up running, completed or failed. |
| complete_task | Mark a claimed or running task completed or failed. |
| dispatch | Synchronously run a prompt in the target agent’s CLI and return the result, or running at the wait timeout with a task id to poll. A needs_approval result says why, so the caller can tell “approve this path” from “narrow the working folder”. |
| check_task | Read current task state; reconciles a running task whose process has died. |
| approve_path | Persist a path outside home into the approved list. Denylisted paths cannot be approved. |
The signature refusal
It reports the guarantee that actually applied.
“Network blocked” means different things for different CLIs, so every dispatch that gets as far as running carries one of four states. It describes what was applied, not what was asked for. Never a reassuring default. A request refused before anything starts reports why instead — there was no enforcement to describe.
- “sandboxed”Codex: egress blocked by the OS sandbox. The strong word, used only where it is true.
- “tools_only”Claude and OpenCode: web tools are removed, but their Bash can still make a direct request. So Cempala refuses the stronger word.
- “not_enforceable”Antigravity: agy exposes no network switch at all, so an offline request could not be applied. The dispatch still runs, and the result admits it.
- “allowed”You asked for network access, and that is what the result says.
A two-state version of this field could not describe reality without lying. Antigravity needed a fourth state rather than a rounded-up promise; OpenCode shares the tools_only limit with Claude. Reporting “sandboxed” for either would be the exact failure this vocabulary exists to prevent.
Where it is allowed to work
The trust boundary is zero-config and can only be tightened, never silently widened.
- Default boundary: your home directory. Zero config.
- A baseline denylist of sensitive roots (.ssh, .aws, .gnupg, browser credential stores and more) is always applied. Your config can only add to it, never subtract.
- Paths outside home return needs_approval, not an error; only an explicit approve_path unlocks a retry.
- No escalation path can approve a denylisted path. Not even approve_path itself.
- Path checks resolve symlinks before comparing the submitted working folder, so a working folder that points at ~/.ssh is blocked. Cempala does not scan below an approved folder or stop a running agent from following a link it finds there.
- --add-dir is forbidden in your config for all four CLIs. It is the documented escape hatch from the working-folder scope, and any config that introduces it is rejected at load time.
- Antigravity is the one place Cempala passes --add-dir itself, and it runs the other way: agy resolves relative paths against its own scratch workspace rather than the folder you asked for, so the baseline pins it to the folder already validated. Forbidden from config, where it would widen the scope; required in the baseline, where it narrows it.
Install
One line. The installer verifies a SHA-256 checksum before doing anything with the binary. Every binary an installer selects was actually run on the hardware it targets — a full MCP handshake, all 8 tools, a round-tripped message — not just cross-compiled and shipped on faith.
macOS / Linux
curl -fsSL https://raw.githubusercontent.com/thelabs-id/cempala/main/scripts/install.sh | bash
Windows · PowerShell
irm https://raw.githubusercontent.com/thelabs-id/cempala/main/scripts/install.ps1 | iex
The one gap is native Windows on ARM: the installer runs the tested x64 build under emulation there. A native arm64 build is attached to releases as an experimental asset, but nothing selects it automatically until it has passed a matching-hardware smoke test.
- Detects OS and architecture, downloads the matching pre-compiled binary, and verifies its checksum first.
- On Windows, puts the binary on PATH for this PowerShell session and future shells; on macOS and Linux, open a new shell after install.
- Writes a default config only if one is absent, and auto-registers with claude, codex and opencode when each is found on PATH — through that CLI’s own mcp add rather than a hand-edit, which for OpenCode’s JSONC is what keeps its comments and formatting intact. A CLI it cannot find, or an mcp add that fails, is reported with the command to run yourself rather than failing the install.
- Registers Antigravity by merging one entry into its MCP config, since agy has no mcp add command. Every other server and key in that file is preserved, and a file it cannot parse is left untouched with the snippet printed for you to paste.
- OpenCode joined that list in v0.5.0; before then its entry was one you added by hand. Restart OpenCode after installing, and run opencode mcp add cempala yourself if you install the CLI later or the installer reports that registering it failed.
- Idempotent: re-running replaces the binary in place even while an agent is holding it open, leaves exactly one PATH block in the file your shell actually reads, and never touches your config.
- Uninstall is one line too, and it reaches the binary, the PATH entry, and every registration it can still manage. Antigravity and OpenCode are edited directly; Claude and Codex are asked to remove their own entry, so a CLI you have since removed from PATH leaves its entry behind for you to delete. Your ~/.cempala/ folder and its history are kept unless you pass --purge, and a step that cannot be completed is reported as a partial uninstall rather than a tidy-looking success.
Questions a skeptic would ask
- Does anything leave my machine?
- Cempala itself opens no port and phones nothing home. Handoffs ask for no network by default — but asking and enforcing are different things, and what the request was actually worth is what the enforcement field reports, per agent, on every handoff that reaches the point of running.
- What happens if the server dies mid-task?
- Task liveness is re-derived from the stored process id, so a restarted server still knows what is really running. A reaper rides on each tool call and reconciles running tasks over 30 minutes old that are no longer live, recording what actually happened. It deliberately does not treat a dead process id on its own as proof the work ended — on Windows the agent CLIs run behind a launcher shim whose pid can die while the agent works on.
- I assigned a task to another agent. When does it start?
- Straight away, where it can be. Since v0.5.2 create_task tries to start an eligible assignee in the background and hands your turn straight back; you collect the outcome with check_task. Before that it only wrote the request down, and an assignment could sit unread indefinitely while whoever made it waited. The delivery field says which happened: dispatched when the launch was attempted, queued when Cempala started nothing — with the reason named, from no assignee to an uninstalled CLI to a launch that threw. A task that is assigned to someone and still waiting for a claim then rides along as an inbox on the next reply that agent gets from a Cempala tool it identified itself on: the true count of what is waiting, plus up to ten of the tasks with id, description, folder and who asked. Work assigned to nobody has no inbox to land in. It is absent when nothing is waiting, so its presence is the signal.
- Can other agents join later?
- They already have. OpenCode joined in v0.4.0 and has been registered by the installer like the rest since v0.5.0. Any MCP client can join the mailbox — identities are created on first use — but only a mapped CLI can be started for a task, and a new dispatch target still needs its headless CLI policy mapped deliberately. That is not guessed from configuration.
- Is Antigravity the equal of the other three?
- Not quite, and the product says so rather than papering over it. An offline request cannot be enforced for it, so the result reports not_enforceable instead of borrowing a stronger word. And in headless mode agy auto-denies shell commands unless you add an allow-rule in your own agy settings; file edits work out of the box. Cempala will not pass --dangerously-skip-permissions to get around that.
- A handoff failed with a 401. What broke?
- Almost certainly the CLI, not Cempala. It holds no API keys and never talks to a model itself — it shells out to claude, codex, agy and opencode, and each must be ready to run non-interactively on its own credentials or, for OpenCode, a working provider configuration. When a session expires or a provider is misconfigured, every handoff to that agent fails. Fix it for that CLI on its own and retry.
- What about Windows on ARM?
- It runs, via the x64 build under emulation, and that is what the installer fetches. A native windows-arm64 binary is published, but only as a manual, experimental asset: no installer selects it, because it has not been smoke-tested on Windows ARM hardware. A build ships as verified here only once it has actually run on the hardware it targets.
- How do I get rid of it?
- One line, the mirror of installing. Deleting the folder was never enough — installing also registers Cempala inside each agent it found, and those registrations would outlive it, leaving that assistant reporting a failed server for as long as you left it. The uninstaller takes away the binary, the PATH entry and the registrations. The one it cannot reach is an agent whose CLI is no longer on PATH: removal goes through that CLI’s own command, so with the CLI gone the entry stays until you delete it. It keeps your ~/.cempala/ folder, task history and audit log unless you pass --purge, and if a step cannot be completed it says so and keeps the binary rather than reporting a tidy-looking success.
You already have the agents. Introduce them.
Install in one line, ask any one of them to hand work to another, then read the audit log after.