A shared line for AI agents — connect context, talk to each other, and reach whoever owns a project. Across machines, across networks.
Claude Code already has cross-session messaging: sessions on the same machine can deliver messages to each other through Claude Code's own messaging socket. But that stops at the edge of one machine. The moment your agents live on different machines — or on different networks — there is no path between them: no way to hand context across, no way to ask and get an answer back, and no way to reach the agent that owns a project running somewhere else.
agents-msger extends Claude Code's own messaging across machines and
networks. It does not reinvent agent messaging — it reuses Claude Code's native
cross-session delivery and adds the one missing piece: a hub + per-machine shim
that carries those same messages between machines, over any network that can
reach the hub.
- 🔗 Connect context. Any agent session can send its context — a summary, a decision, a hand-off — directly into another agent's session, on the same machine or a different one. The receiving agent picks it up on its next turn.
- 💬 Communicate. Every delivered message carries who sent it — persona, machine, window, and pid. So a back-and-forth is natural: the recipient knows exactly which agent (and which session) to answer, and replies by addressing that sender back.
- 🎯 Reach the responsible agent. Address a message to a persona — the
agent that owns a project or role — by name (
owner, orowner@machine), and it is delivered to that agent's live session wherever it is running. "Ask whoever is responsible for X" becomes a first-class, routable action. - 🧭 Never lose the right session. Agents routinely fail to find each other:
a persona isn't one session — the same agent may be open in several tmux panes
or windows, or on several machines, and name-based addressing picks the wrong
one or none.
agents-msgerkeys every session by its unique pid, so a name is only a soft label that never collapses two sessions together. You always reach the exact session you mean (owner@machine#<pid>), several panes of the same persona are each addressable, and a dead or stale session never shadows the live agent.
This makes agents-msger a substrate for multi-agent orchestration: an
orchestrator agent discovers who is live with relay_who, delegates to the agent
that owns a project, and collects the reply — across machines, through three
tools.
One agent hands a task to the agent responsible for it on another machine; that
agent receives it as a Message from @… turn, acts, and replies through the
relay:
Starting the hub and a shim, and listing who is reachable:
Names and machines are placeholders.
| Agent runtime | Status |
|---|---|
| Claude Code | ✅ Supported |
| OpenAI Codex | 🚧 Planned |
| Other MCP-capable agents | 🚧 Planned |
The name is plural on purpose: the design is runtime-agnostic and aims to let any AI agent join the same line. Today, Claude Code sessions can send and receive; other runtimes are on the roadmap.
The internal service identifier used throughout the code, config, and supervisor units is
mcp-msgers;agents-msgeris the project name.
You run one hub — self-hosted, anywhere that participating machines can reach it. It holds the only registry and routing table. Each machine runs a small shim that reports its live agent sessions to the hub and delivers inbound messages into the right local session. Agents never configure peers or network addresses — they just call the tools, and anything that can open a connection to your hub (LAN, Tailscale/VPN overlay, or the public internet) can take part.
agent session A (machine-a) agent session B (machine-b)
│ relay_send(to="owner@machine-b") ▲ (message arrives next turn)
▼ (MCP tool call) │ (delivered into the session)
shim (machine-a) ───► ┌───────────────┐ ◄─────── shim (machine-b)
│ the hub │
shim (machine-a) ───► │ registry / │ ◄─────── shim (machine-b)
(heartbeat) │ routing │ (heartbeat + long-poll)
└───────────────┘
Step by step, when agent A on machine-a messages the agent that owns a project
on machine-b:
- Discovery (no config). On each machine the shim scans the local agent session registry and learns every live session: its owner persona (derived from the session's working directory), its process id, and the messaging socket it listens on. Nothing is hand-configured.
- Heartbeat. Every few seconds each shim sends that list to the hub, so the hub holds a fresh, authoritative picture of which agents are reachable right now — and marks a session not-alive the moment it stops appearing.
- Send. Agent A calls
relay_send(to="owner@machine-b", …). Its local shim attaches the sender's identity automatically — machine, tmux window, and pid, recovered by walking its own process tree — and POSTs the message to the hub. The agent supplied only who to reach. - Routing & resolution. The hub looks up
owneronmachine-b. If that owner has one live session, it wins; if several, the newest — or the exact one named with#session. If the owner has no live session, the call fails loudly (ok:false), never a silent "queued". - Long-poll delivery.
machine-b's shim is holding an open long-poll to the hub; the queued message returns immediately. The shim resolves that owner's socket locally (it never trusts a socket path from the network) and writes the message, in Claude Code's own native format, into the socket Claude Code opened for that session. - Arrival. On its next turn, Claude Code surfaces the message in the owner's
session as a
Message from @<sender>turn, carrying the sender's identity (from-name, plusfrom-windowandfrom-pid), so the owner knows exactly who and where it came from. - Replying. There is no magic reverse channel: because the message says
exactly who and where it came from, the owner replies the same way — by
addressing the original sender back (
relay_send(to="alice@laptop", …), or#<pid>to hit that exact session).
Two liveness signals keep this honest: relay_status() distinguishes "that
agent's machine is down" from "wrong address", and a session whose process is
alive but whose socket went missing (e.g. just after a reboot) is surfaced
explicitly rather than silently dropped.
- Python 3.8+ — standard library only, no third-party packages. The hub and
shim run on a stock
python3. - Claude Code on every participating machine — this is the real dependency.
agents-msgerdoes not implement messaging; it rides on Claude Code's own cross-session messaging, which means it relies on Claude Code internals that are not a stable public API and may change between Claude Code versions:- The messaging socket — created by Claude Code, not by this tool. Each
interactive Claude Code session opens and listens on a Unix-domain socket at
/tmp/cc-socks/<pid>.sock(macOS) or$XDG_RUNTIME_DIR/cc-socks/<pid>.sock(Linux, e.g./run/user/<uid>/…— tmpfs, so it is cleared on reboot). The shim only connects to it to deliver. - The session registry — also written by Claude Code. Claude Code writes a
<pid>.jsonrecord per session under$CLAUDE_CONFIG_DIR/sessions(and/or~/.claude/sessions); the shim reads it to discover live sessions (pid,messagingSocketPath,cwd,name,sessionId,startedAt,kind,runtime). - The native message format. The shim writes the exact payload Claude Code
expects on that socket (a
type: "user"message carrying a<cross-session-message>envelope,priority: "next"), so Claude Code surfaces it as aMessage from @<sender>turn.
- The messaging socket — created by Claude Code, not by this tool. Each
interactive Claude Code session opens and listens on a Unix-domain socket at
crossSessionInbound: "accept"must be set in a receiving session's settings; otherwise Claude Code holds cross-session input instead of delivering it.
Not to be confused with a separate helper that merely links session-registry directories to make sessions addressable —
agents-msgeris the cross-machine transport layer, and the socket/registry themselves are Claude Code's.
Clone the repo onto each machine.
1. Run the hub once, on one machine:
python3 hub.py --port 84652. Run a shim on every machine. Write
~/.config/mcp-msgers/config.json with
{ "host": "<name>", "hub_url": "http://<hub-address>:8465", "role": "shim" },
then:
python3 shim.py serve3. Register the MCP tools in each machine's agent client:
Or with the Claude Code CLI:
claude mcp add agents-msger -- python3 /absolute/path/to/shim.py mcp| Tool | What it does |
|---|---|
relay_send(to, message, from_name?) |
Send to owner / owner@machine / owner@machine#session. Fails loud if the target has no live session. The sender's identity is attached automatically. |
relay_who() |
List every live agent per machine, so you can see who is reachable and who owns what. |
relay_status() |
Per-machine liveness — tells "that agent's machine is down" from "wrong address". |
- Support additional agent runtimes (OpenAI Codex and other MCP-capable agents) so any AI can join the same line.
- Richer context hand-off (structured payloads, not just text).
- A stable adapter so the tool no longer depends on agent-specific internals.
- Authentication and hardening for use beyond a trusted network.
Early preview. The core path — connecting, messaging, and reaching the responsible agent across machines — works for Claude Code today. It is designed to run on a trusted, private network (e.g. a personal VPN/overlay); it is not yet hardened for untrusted networks. APIs may change; pin a commit if you depend on it.
Issues and pull requests are welcome. Please keep the small, stdlib-only shape
and run the tests in tests/ (python3 tests/test_c1_identity.py).
MIT © agents-msger contributors.
{ "mcpServers": { "agents-msger": { "command": "python3", "args": ["/absolute/path/to/shim.py", "mcp"] } } }