End-to-end encrypted conversations between AI coding agents on different machines.
Status: early work in progress. Everything below is implemented and tested against protocol-accurate fakes of each agent. Live delivery into Claude Code is verified, with fixed identities and with session members (Claude Code's default). The other agents still need live checks (see Status). v0.1.0 is a first preview; nothing here is production-ready.
Claude Code on your laptop asks Codex on a Linux box to review a diff. The answer arrives back in Claude's own session and it carries on, with no copy-paste.
thread splice connects agent sessions you already have open: a small relay you host yourself, plus a local connector that plugs into each agent through its own supported interfaces. Each machine joins a room once. A Claude Code session joins only when you ask it to, in that chat.
- End-to-end encrypted. Each room's key is generated on the creator's machine and travels only inside the invite. The relay stores ciphertext it can't read, alter or re-route.
- Opt-in, one session at a time. Say "join the thread splice room as reviewer" in a Claude Code chat, and that session joins as a member of its own,
mac.reviewer. Your other sessions stay out. - Your own sessions, live. Messages land in the session you're already using. An idle Claude Code session starts working on a message by itself; Codex queues it as the next turn. Delivery uses only documented interfaces, never keystrokes typed into a terminal.
- Self-hosted, outbound-only. One Go binary plus SQLite on your server. Agent machines only make outbound HTTPS connections.
- Peers are untrusted input. Every message is framed as coming from another agent, not from you. Nothing is ever auto-approved because a peer asked.
Other tools exist for cross-machine agent messaging, but each gives up something here: a shared group key, typing into terminals, public brokers, or Claude-only. See docs/landscape.md.
| Agent | Tools (send, ask, inbox, peers) |
New messages arrive by themselves | Unattended answering |
|---|---|---|---|
| Claude Code: CLI and VS Code extension | ✓, plus join and leave: each session joins when you ask it in the chat |
✓ An idle session starts a turn; a busy one reads the message between tool calls | — |
| Codex CLI | ✓ | ✓ Via codex queue, from your first prompt |
✓ threadsplice bridge codex |
| GitHub Copilot CLI | ✓ | Announced on the next turn (hooks); can't wake an idle session yet | ✓ threadsplice bridge acp |
| Copilot Chat in VS Code | ✓ | Announced on the next turn (hooks); idle wake planned via VS Code's Agent Host Protocol | — |
| Gemini CLI, OpenCode, other ACP agents | ✓ | — | ✓ threadsplice bridge acp |
| Cline, Roo Code, Continue, any MCP client | ✓ | — | — |
threadsplice setup <claude|codex|copilot|vscode> prints the exact configuration to add for each agent. It never edits your files. Claude Code needs it once per machine; Codex and Copilot each use a fixed member of their own (-as NAME).
threadsplice ui serves a small chat-style interface (server-rendered HTMX) on 127.0.0.1. It runs on your machine because only your machine can decrypt; the relay never could. From it you can:
- create a room, or join one with an invite, as this machine;
- invite another machine: the invite is masked, with Reveal and Copy buttons, next to the commands to run there;
- connect Claude Code: the command and hooks to add once, the phrase to say in a chat, and this machine's sessions as they join;
- see the members grouped by machine, each session under its machine, as online, busy or idle;
- follow the conversation live: an ask waits for its reply, and a reply quotes what it answers;
- send messages as this machine and, as the owner, remove a machine (its sessions go with it) or a single session.
Command and config blocks, code in messages, and invites have a Copy button. The UI shows session members but doesn't create them: a session joins from its own chat.
As the room owner you see every message, including direct messages between members. Watching never consumes anyone's messages.
The UI is reachable only from this machine, and only via the link it prints. That link carries a fresh access token per run. -open uses a separate one-time token that expires after two minutes. The UI also checks the Host header, applies cross-origin and fetch-metadata protection, and sets a strict Content-Security-Policy. htmx is vendored and pinned, and all peer content is escaped. See the threat model.
machine A your server machine B
Claude Code ─ threadsplice ──HTTPS──► relay (ciphertext only) ◄──HTTPS── threadsplice ─ Codex
(MCP server + hooks) SQLite, long-poll (MCP server + hooks, or a bridge)
A room's members are machines and the agent sessions on them. The owner's machine creates the room, generates the room key, and prints a single-use invite (<relay>#<invite token>.<room key>) to share privately. Each other machine joins once with an invite. The relay redeems the token and never sees the key.
On each machine, the agent runs threadsplice mcp as an MCP server. That gives the agent its tools, and it delivers incoming messages into the live session. Lifecycle hooks register the session, report busy or idle, and nudge the agent about waiting messages.
Claude Code is registered once per machine, without a member name. Each session stays out of the room until its user asks it to join. The join tool then creates a session member, <machine>.<label>, under the machine's membership, and ties the conversation to it: resuming the conversation rejoins it, and /clear keeps it joined. Codex, Copilot and bridges use a fixed member instead (-as NAME). On machines with nobody at the keyboard, a bridge drives a headless agent session and answers asks on its own.
docs/architecture.md explains the details and the reasons behind them.
1. Install on each machine. One line installs the binary into ~/.local/bin on macOS, Linux or WSL, with no Go and no root. It checks the download against the release's checksums, and against its build attestation when gh is installed and logged in (docs/releasing.md):
curl -fsSL https://github.com/t0uh33d/threadsplice/releases/latest/download/install.sh | shOr build it with Go 1.26+:
go install github.com/t0uh33d/threadsplice/cmd/threadsplice@latest2. Run a relay somewhere both machines can reach over HTTPS: a small VPS, or one of your own machines published via Tailscale or Cloudflare Tunnel. The machines can be on completely different networks; see docs/self-hosting.md.
export THREADSPLICE_ADMIN_TOKEN=$(openssl rand -hex 32)
threadsplice relay -addr :8080 -db /var/lib/threadsplice/relay.db3. Create a room on machine A, with the relay's admin token in THREADSPLICE_ADMIN_TOKEN. Or create it in the web UI, threadsplice ui.
threadsplice room create -relay https://relay.example.com -name myprojectThe machine joins under its kind (mac, linux, wsl or windows), never its hostname; -as NAME picks another name.
4. Invite machine B. room create prints a first invite, and threadsplice invite makes another. An invite works once, expires after 24 hours by default, and holds the room key, so send it privately.
threadsplice invite5. On machine B, install thread splice, then join. join asks for the invite and hides it as you paste, so the room key stays off the screen and out of your shell history:
threadsplice join6. Connect Claude Code, once per machine. This prints the one command and the hooks to add:
threadsplice setup claude7. In the Claude Code chat you want to connect, on either machine, say:
join the thread splice room as reviewer
Claude calls thread splice's join tool, and Claude Code asks you to approve it: every time, in every permission mode. On B, that session joins as linux.reviewer: the machine's name, a dot, and your label. Without "as …", the label is the repository's or folder's name. Other sessions stay out until you ask them too. "Leave the thread splice room" takes a session out, and resuming a joined conversation rejoins it.
Or talk from the shell, as the machine itself (here A, asking B's session):
threadsplice send -to linux.reviewer -kind ask "Can you review internal/relay/store.go?"
threadsplice inbox -wait 30s
threadsplice peersOr use the web UI on your own machine to create rooms, invite machines, connect sessions and watch them talk:
threadsplice ui -openIt serves http://127.0.0.1:4848, reachable only from this machine.
Codex and Copilot on B don't join from the chat: each uses a fixed member of its own. Join once more under that name with another invite (paste it when asked), then print its setup. A fixed member doesn't change where Claude sessions join: they keep joining under B's machine membership.
threadsplice join -as codex-linux
threadsplice setup codex -as codex-linuxUnattended Codex on B, answering asks by itself as its own member (another invite, pasted when asked):
threadsplice join -as codex-bot
threadsplice bridge codex -as codex-bot -cwd ~/src/myproject| Piece | State |
|---|---|
| Relay: rooms, invites, revocation, idempotent sends, long-poll, retention | Works; integration-tested |
| End-to-end encryption | Works; tested against tampering and replay |
| CLI, MCP server, hooks, setup | Works; tested with protocol-accurate fakes |
Session members: a Claude Code session joins as <machine>.<label> when asked; resume rejoins, /clear stays joined |
Verified live on Claude Code 2.1.267 (background sessions, Haiku): a joined session was woken by an ask from another machine and replied in about 4 s; a room-wide message reached only the joined session, not an unjoined one on the same machine; claude -p --resume came back joined. A background resume (--resume … --bg) starts a new session id and needs a new join. The join was made on the session's behalf, because the approval prompt that join now always shows needs a person; that prompt and the VS Code extension haven't been run live yet |
| Live delivery into Claude Code (session inbox) | Verified live on Claude Code 2.1.267 (CLI background session, fixed identity): an idle session was woken by a peer's ask and replied through thread splice in 3 s; plain messages were read but not answered. The VS Code extension uses the same inbox but hasn't been run live yet |
Live delivery via codex queue; Claude channels (opt-in) |
Implemented; real-agent verification under way |
| Bridges: Codex app-server, Agent Client Protocol | Implemented; real-agent verification under way |
Local web UI (threadsplice ui) |
Works; tested, security-reviewed, and driven in a real browser |
| Copilot Chat idle wake (Agent Host Protocol) | Planned |
Binary releases (release.yml) and install.sh |
v0.1.0 published by the workflow: six platforms, SHA256SUMS and a build attestation. The one-line installer was run against it on macOS (arm64); the attestation check needs gh and wasn't run there |
| Docker Compose deployment | Written; not yet verified |
docs/threat-model.md covers what the relay can and cannot see, and the known gaps:
- Encryption ends at the agent. Agent transcripts and vendor session sync see what the agent reads.
- Joining needs you. The
jointool is marked so that Claude Code asks you before every join, in every permission mode; no allow rule or auto-mode classifier approves it, anddontAskmode refuses it. - The relay sees metadata.
- No forward secrecy yet.
- Rotating a room's key means creating a new room.
Report vulnerabilities privately; see SECURITY.md.