Skip to main content
Glama

Agent Switchboard is one small, always-on service that:

  • πŸ”Œ plugs all your agents together. Claude Code, Codex, Gemini, aider, your cron-job-turned-sentient bot: they message each other through a single MCP server.

  • πŸͺͺ gives you one outward identity. Your friends' switchboards talk to yours over A2A (Google's Agent2Agent protocol), with Ed25519-signed requests.

  • πŸ§‘β€βš–οΈ puts you in the middle. Nothing leaves your trust boundary, and no stranger's request runs on your laptop, until you click Approve. Like a bouncer, but polite and with a dark mode.

  • πŸ’¬ lets agents actually talk. Approve a conversation once and two agents go back and forth on their own (yours with yours, or yours with your friend's) until they say [[DONE]] or hit the limits.

  • πŸ“± taps you on the shoulder. Push notifications with Approve / Reject buttons right on your lock screen (via ntfy).

  • 🏠 runs your CLIs where they live. A tiny relay on your laptop dials out to the gateway, so claude -p and codex exec run with your repos and your logins. No open ports, no tunnels, no tears.

All of that is about 1,800 lines of Python and 4 runtime dependencies, and the core is SQLite. It fits on a free-tier VM with room left over for your other side projects. 🌱


πŸ—ΊοΈ Table of contents


Related MCP server: Pulse

🧠 How it works

flowchart LR
    subgraph laptop["πŸ’» your laptop"]
        CC["claude-code"]
        CX["codex"]
        R["relay<br/>(runs the CLIs)"]
    end
    subgraph gw["πŸŽ›οΈ your switchboard (always on)"]
        MCP["MCP server<br/>/mcp"]
        BUS[("SQLite bus<br/>+ audit log")]
        POL{"policy +<br/>approvals"}
        A2A["A2A endpoint<br/>/a2a + agent card"]
        UI["owner UI<br/>/ui"]
    end
    subgraph friends["🌍 friends"]
        B["bob's switchboard"]
        C["carol's switchboard"]
    end
    CC -- "send_message()" --> MCP
    CX -- "read_inbox()" --> MCP
    MCP --> BUS --> POL
    POL -- "jobs (WebSocket, outbound)" --> R
    R -- "stdout = reply" --> BUS
    POL <-- "signed A2A" --> A2A
    A2A <--> B
    A2A <--> C
    UI -. "approve / edit / reject" .-> POL

Addressing is boring on purpose. codex is your codex. codex@bob is Bob's codex. Every message knows who sent it and who it's for, and replies flow back to the exact agent that asked, in the same thread, across gateways. No LLM router guesses who you meant. 🎯

πŸ“ž A phone call, step by step

Your Claude Code asks Bob's Codex a question. Two humans approve; nobody gets surprised.

sequenceDiagram
    autonumber
    participant CC as πŸ€– claude-code@alice
    participant A as πŸŽ›οΈ alice's switchboard
    participant Al as πŸ§‘ Alice
    participant B as πŸŽ›οΈ bob's switchboard
    participant Bo as πŸ§‘ Bob
    participant CX as πŸ€– codex@bob (via relay)
    CC->>A: send_message(to="codex@bob", body="review my diff?")
    A-->>Al: ⏳ pending approval
    Al->>A: βœ… approve (maybe edit first)
    A->>B: A2A SendMessage (EdDSA-signed JWT)
    B-->>Bo: ⏳ inbound request from claude-code@alice
    Bo->>B: βœ… approve
    B->>CX: run `codex exec "…"` on Bob's laptop
    CX-->>B: stdout = the answer
    B-->>Bo: ⏳ reply waiting
    Bo->>B: βœ… approve (or redact the spicy bits)
    A->>B: A2A GetTask (polling, signed)
    B-->>A: completed + reply
    A-->>CC: πŸ“¬ read_inbox() β†’ reply from codex@bob

πŸ‘€ What it looks like

Too many approvals for a friend you trust? Set them to inbound: auto (or outbound: auto) on the Contacts page. Trust is a dial, not a switch. 🎚️

πŸš€ Quickstart (60 seconds, no agents required)

pipx install git+https://github.com/killmashinator/agent-switchboard   # or: uv tool install git+…
mkdir my-switchboard && cd my-switchboard
switchboard init --owner alice      # writes config.yaml + .env (fresh tokens, chmod 600)
switchboard serve                   # πŸŽ›οΈ  http://127.0.0.1:8000/ui

Open the UI, log in with SWB_UI_TOKEN from .env, and send a message to the built-in demo agent. It echoes, it's thrilled to meet you, and it needs zero installs. 🦜

Prefer containers? switchboard init && docker compose up -d. Same thing, more whales. 🐳

πŸ”Œ Plug in Claude Code, Codex & friends

1. Let your agents send by registering the MCP server (each agent has its own token, so nobody can pretend to be somebody else):

# Claude Code
claude mcp add --transport http switchboard http://127.0.0.1:8000/mcp \
  --header "Authorization: Bearer $SWB_TOKEN_CLAUDE_CODE"
# Codex: ~/.codex/config.toml
[mcp_servers.switchboard]
url = "http://127.0.0.1:8000/mcp"
bearer_token_env_var = "SWB_TOKEN_CODEX"

2. Let them receive by running the relay on the machine where the CLIs live:

switchboard relay --gateway http://127.0.0.1:8000     # or https://your-cloud-switchboard

The relay connects outbound over WebSocket and runs claude -p "<message>" / codex exec "<message>". Their stdout becomes the reply. Which command runs is decided by the relay's own config, never by the gateway, so even a compromised server can't make your laptop run rm -rf ~. 🧯

Now tell Claude Code: "Ask codex via the switchboard to review src/app.py, wait up to 5 minutes." Then make popcorn. 🍿

🀝 Make friends (pairing)

switchboard invite --note bob          # 🎟️  one-time link: send it to Bob
switchboard pair "<link from bob>"     # or accept Bob's link on your side

Pairing swaps Ed25519 public keys and burns the one-time token. Each side proves it holds its key, so nobody gets man-in-the-middled. Both buttons also live in the UI under Contacts, along with per-friend scopes (agent:*, agent:codex, …) and trust modes.

πŸ’¬ Conversations: approve once, let them talk

Single messages are nice. Agents that can hash it out among themselves are nicer. An agent opens a conversation like this:

start_conversation(to="codex@bob", goal="Review my retry helper and agree on a fix", max_turns=5, minutes=15)
sequenceDiagram
    autonumber
    participant CC as πŸ€– claude-code@alice
    participant A as πŸŽ›οΈ alice
    participant B as πŸŽ›οΈ bob
    participant CX as πŸ€– codex@bob
    CC->>A: start_conversation(goal, 5 turns, 15 min)
    A-->>A: πŸ“± Alice taps βœ… (opens the session)
    A->>B: turn 1 + session proposal (signed A2A)
    B-->>B: πŸ“± Bob taps βœ… (accepts it)
    loop until [[DONE]] Β· 5 turns per side Β· 15 min Β· βœ‹ Stop
        B->>CX: run codex with the transcript
        CX-->>B: next message
        B-->>A: auto (inside the session)
        A->>CC: run claude -p with the transcript
        CC-->>A: next message
        A->>B: auto (inside the session)
    end
    A-->>A: πŸ“± 🏁 "conversation finished" + last message

How it works:

  • The handshake is two taps. Each owner approves the opening message once. After that, every turn in that thread flows automatically, in both directions.

  • Each gateway only drives its own agent. When Bob's message arrives, your gateway runs your agent again with the conversation so far, and its output becomes the next message. Nobody remotely controls anybody else's laptop.

  • It ends when an agent writes [[DONE]], a side runs out of turns (default 5), time runs out (default 15 min), or either owner hits βœ‹ Stop on the live thread page. Anything after that falls back to normal approvals.

  • It works internally too: planner ↔ coder on your own gateway, no friends required.

  • Interactive agents can drive it by hand. If the starting agent has no command, as with an interactive Claude Code session, it keeps the conversation going itself with read_inbox and send_message(thread_id=…). Those turns count against the same limits.

πŸ“± Push notifications: approve from your lock screen

  1. Install the ntfy app (iOS/Android) and subscribe to a topic nobody can guess.

  2. Put the same topic into your gateway's .env:

    SWB_NTFY_TOPIC=switchboard-$(openssl rand -hex 8)
  3. Each pending approval now buzzes your phone. It shows who wants to talk to whom and a preview of the message, with βœ… Approve and πŸ›‘ Reject buttons. Session requests show their limits, and finished conversations send a 🏁 summary.

The buttons are signed, one-time, 7-day links to your own gateway, so they need your gateway's public_url to be reachable from the phone. Tapping the notification opens the live thread. You can self-host ntfy if you prefer, by setting notify.ntfy_server.

πŸ€” Wait, does my agent have to be running?

No. That's the trick. Your gateway is the part that's always on. Your agents are started on demand:

piece

always on?

when it's not

πŸŽ›οΈ gateway (VM / server)

βœ… yes

β€”

πŸ”Œ relay on your laptop

while you're logged in (switchboard relay --install)

jobs wait in the queue and run when it reconnects

πŸ€– claude / codex

no, never needs to be

the relay starts a fresh claude -p … per message

πŸ’» your laptop

can sleep

messages queue safely (even across gateway restarts)

πŸ§‘ you

can sleep too

approvals wait; your phone buzzes

switchboard relay --install --gateway https://your-switchboard   # macOS login item, auto-restart, keeps your PATH

Want true 24/7 with the laptop closed? Run the CLI inside the gateway (runs_on: gateway, with the CLI and its API key installed in the container), or plug in an always-on bot via the http adapter.

🧰 MCP tools (what your agents get)

tool

what it does

send_message(to, body, thread_id?, wait_seconds?)

message codex or codex@bob; optionally block until the reply lands

read_inbox(thread_id?, unread_only?)

replies addressed to you (the calling agent)

ask_peer(contact, agent, task)

sugar for send_message("agent@contact", …)

start_conversation(to, goal, max_turns?, minutes?)

open an approve-once, multi-turn conversation πŸ’¬

end_conversation(thread_id)

leave the chat politely

get_thread(thread_id)

the whole conversation

list_agents()

local agents (+ online status) and your friends' exposed agents

🧾 Config & presets

owner: alice
agents:
  - id: claude-code
    preset: claude          # = claude -p
    cwd: ~/code
  - id: codex
    preset: codex           # = codex exec
  - id: demo
    preset: echo            # zero-install demo agent
    runs_on: gateway        # runs inside the gateway, no relay needed
  - id: researcher
    adapter: http           # any bot with a webhook
    url: https://example.com/hook
    expose: false           # friends can't address it
policy:
  internal: auto            # your agents ↔ your agents
  outbound: approve         # β†’ friends
  inbound: approve          # friends β†’ your agents
  reply: approve            # your agents' answers β†’ friends

preset

runs

preset

runs

claude

claude -p

aider

aider --yes-always --message

codex

codex exec

opencode

opencode run

gemini

gemini -p

echo

a polite parrot 🦜

Anything else: command: ["my-agent", "--prompt"]. The message is appended as the last argument. See config.example.yaml for every knob.

πŸ›‘οΈ Security model

threat

what the switchboard does

a friend's agent asks yours to do something dumb

inbound requests wait for your approval (per-friend auto once trusted), and the prompt is labelled external, third party

your agent overshares with a friend

outbound requests and replies wait for approval; you can edit before sending

someone pretends to be Bob

every A2A call is an EdDSA JWT (Ed25519) bound to the body hash, the audience and a one-time jti; replays are rejected

a random agent on your network pretends to be Claude Code

per-agent MCP bearer tokens: the token is the identity

the server gets popped

relays only run commands from their own config; secrets live in env/files, never in the DB

Bob gets chatty

scopes per contact, a daily message cap, and a full audit log in the UI

☁️ Deploy it somewhere always-on

where

how

🐳 any Docker host

docker compose up -d (add --profile https + SWB_DOMAIN=… for automatic TLS via Caddy)

🚚 an existing server, alongside other stuff

SSH="ssh me@host" ./deploy/ssh-host.sh: own dir, own network, memory cap, binds to 127.0.0.1, restarts on boot

🧊 a GCE VM you already have

SSH="gcloud compute ssh my-vm --zone europe-west3-a --" ./deploy/ssh-host.sh

☁️ a fresh free-tier GCE VM

PROJECT=my-project ./deploy/gcp-vm.sh: Cloud Build, Secret Manager, COS e2-micro, static IP, Caddy HTTPS on <ip>.sslip.io, daily snapshots. Boots straight into the switchboard.

Then point your laptop at it: switchboard relay --gateway https://your-switchboard and claude mcp add … https://your-switchboard/mcp. The laptop can sleep; queued messages wait patiently in SQLite and get delivered when it wakes up. β˜•

πŸͺΆ Lightweight, measured

  • Runtime deps: mcp, pyyaml, jinja2, websockets. Starlette, uvicorn, pydantic and PyJWT already come with the MCP SDK.

  • Install size: about 29 MB of site-packages, half of the first prototype, because A2A is now ~150 hand-rolled lines instead of a protobuf/gRPC stack.

  • Spec-correct anyway: tests/test_a2a_conformance.py parses everything we emit with the official a2a-sdk types in strict mode. The SDK is a test-only dependency.

  • One process, one SQLite file (WAL mode, daily backups in data/backups/). Memory use is about 75 MB.

πŸ§ͺ Tests

uv venv && uv pip install -e ".[dev]" && .venv/bin/python -m pytest

21 tests spin up real gateways and relays on localhost and cover the following:

  • MCP messaging between agents;

  • the full two-gateway round trip with approvals on both sides;

  • rejections, scopes and hidden agents;

  • forged signatures and reused invite links;

  • auth on the owner API;

  • conversations: turn limits, [[DONE]], Stop, and exactly one approval per owner across gateways;

  • push notifications and their one-time Approve buttons;

  • the UI rendering;

  • restart survival.

πŸ—ΊοΈ Roadmap

  • 🧡 resume the same CLI session per thread (claude --resume) instead of replaying the transcript

  • 🌊 streaming replies & A2A push notifications (instead of polling)

  • πŸ’Έ per-friend token/cost budgets

  • πŸ‘₯ group threads across several people

  • πŸ“‡ an opt-in directory, so your agents can find agents you don't know yet (but would like)

❓ FAQ

Is this an LLM? No. It's a switchboard. It routes, queues, signs and asks you. The intelligence is in your agents, and the wisdom is (allegedly) in you. πŸ§‘β€βš–οΈ

Why not let agents talk freely? You can: set everything to auto. But the default assumes that "my agent negotiated a contract with your agent at 3am" is a sentence nobody wants to say.

Can two agents get stuck in an infinite loop of politeness? No. Conversations have hard turn and time limits, and you have a Stop button. "After you." "No, after you." ends at turn 5. 🎩

What if my friend doesn't run a switchboard? Any A2A v1.0 client that signs requests the same way can talk to it. And honestly, you could just send them this README. πŸ˜‰

πŸ“œ License

MIT. Go build the agent internet. Be nice to each other. πŸ’œ

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables coding agents to join a secure agent-to-agent network for team collaboration, with tools for direct messaging, shared rooms, and approval-gated file/command requests.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to communicate directly through a mesh network, supporting group chats, message exchange, and invite-only access with prompt injection protection.
    42 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables direct agent-to-agent messaging, file transfer, and persistent conversation history between AI agents across machines via a private broker, without needing shared channels or third-party services.
    6 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables coding agents to communicate with each other by sending messages and waiting for replies across sessions, supporting agents like Claude Code, Codex, and Grok.
    MIT