A message bus made of a git repo
I wanted a few AI assistants to talk to each other and to me. One of them, Instinct, I do not own. It also had to work with my own Claude Code and with my wife Hafsa’s Instinct.
The rule I set was simple. If any one of them is compromised, the damage stops at one throwaway token and one repo. Not my GitHub account, not my shell, not everything my agents can reach.
The plumbing is ordinary: a Cloudflare Worker, a private git repo, and a small Mac app. The work was keeping three things narrow.
- The airlock: an outside AI writes in without ever holding a real key.
- The bus: the assistants leave messages for each other in the repo.
- The approval: a message runs code only after I say yes.
The bus is a git repo
There is no server. A private git repo is the whole channel. Each assistant gets a lane, which is just a pair of Markdown files. Each message is an entry in one of those files. Git hands me ordering, history, and a record I can read back, for free.
Two assistants never get a direct line to each other. Each one only touches its own lane. Claude Code sits in the middle and carries a message across. A note from Hafsa’s Instinct reaches mine because Claude Code moved it, and every message stays in the open, where I can see it.
The airlock
The Worker is the only piece on the public internet. Instinct holds one thing: a random token. It sends a file and the lane it belongs to, and the Worker checks the token and commits the file to the private repo.
The key that can actually write to GitHub never leaves the Worker. Instinct never sees it. If Instinct’s token leaks, I swap the token and move on, and GitHub is untouched.
One more rule does a lot of work:
Write-only. There is no delete.
An outside assistant can add to the record. It can never erase it.
The message protocol
Every message is one Markdown entry with a time, a short id, and a status. It moves from new to done as it gets handled, or stops at blocked or declined with the reason written in.
The one idea worth stealing:
You acknowledge a message by changing its status, not by sending another one.
No “got it” replies. The fastest way to wreck a shared channel is to fill it with acknowledgments. Silence is allowed too: if there is nothing to say, you change nothing.
Approving what runs
Instinct can ask me to do something. It cannot make that happen on its own. A small menu-bar app shows me what is waiting, with three buttons: I’ve got it, run it, or no.
“Run it” is the button that matters. It hands the request to Claude Code on my machine and lets it work. That is the line between a message and a real action, and a person stands on it.
What works today
Messages already move, and they move between people. On my lane, the first handshake ran and the changes Instinct asked for shipped. On Hafsa’s lane, her assistant read a message from me and answered in eight minutes. A request from my side reached Hafsa’s Instinct and came back. Two people’s assistants are talking through one repo.
The one thing I have not done is let a message run code on my machine. That last step is built, and I have left it switched off on purpose.
The takeaway
Assistants do not need a big platform to work together. They need a shared place to leave messages, small permissions, and clear rules about who owns what.
A git repo, some Markdown, a small worker, and one approval button were enough. The narrow interfaces are the whole trick.