Direct 1:1 messaging between pi sessions on the same machine. Send context, findings, or requests from one session to another — whether you're driving the conversation or letting agents coordinate.
User flow: press Alt+M or run /intercom to pick a session and send a message
Sometimes you're running multiple pi sessions — one researching, one executing, one reviewing. Pi-intercom lets you:
- User-driven orchestration — Send context or findings from your research session to your execution session
- Agent collaboration — An agent can reach out to another session when it needs help or wants to share results
- Session awareness — See what other pi sessions are running and their current status
Unlike pi-messenger (a shared chat room for multi-agent swarms), pi-intercom is for targeted 1:1 communication where you pick the recipient.
Pi-intercom also integrates well with pi-subagents: use pi-subagents to spin up delegated workers, then use intercom for direct, session-to-session handoffs and clarifications.
Each pi session that has pi-intercom loaded and enabled connects to a tiny local broker over a Unix socket. The broker keeps track of connected sessions and routes direct messages to the one you target by name or session ID. The extension gives you both a tool (intercom) and a small overlay UI (/intercom or Alt+M). Incoming messages are rendered inline inside the recipient session, can trigger a turn immediately, and are also stored in Pi session history as extension entries.
pi install npm:pi-intercomThen restart Pi. The extension auto-connects to the broker on startup.
A session becomes intercom-connected when all of these are true:
- the
pi-intercomextension is installed and loaded in that session enabledis not set tofalsein~/.pi/agent/intercom/config.json- the session has started or reloaded after the extension was installed
- the local broker is running or can be auto-started
The session list only shows intercom-connected sessions, not every open Pi process on the machine.
If a session is unnamed, pi-intercom now exposes a runtime-only fallback alias like subagent-chat-1a2b3c4d so other sessions can still target it. That alias is not persisted as the Pi session title, so pi --resume can keep showing the transcript snippet instead of a generic session-... name.
Press Alt+M or type /intercom to open the session list overlay:
- Select a session — Use arrow keys to pick a target session
- Compose message — Write your message in the compose overlay
- Send — Press Enter to send, Escape to cancel
The agent can list sessions and send messages using the intercom tool:
// List active sessions
intercom({ action: "list" })
// → • research — ~/projects/api (claude-sonnet-4) [researching]
// → • executor — ~/projects/api (claude-sonnet-4) [idle]
// Send a message
intercom({ action: "send", to: "research", message: "Check if UserService.validate() handles null" })
// → Message sent to research
// Check connection status
intercom({ action: "status" })
// → Connected: Yes, Session ID: abc123, Active sessions: 3When a message arrives, it appears inline in your chat with the sender's info and a reply hint:
**📨 From research** (~/projects/api) — reply: intercom({ action: "send", to: "550e8400-e29b-41d4-a716-446655440000", replyTo: "c1f7...", message: "..." })
Found the issue — UserService.validate() doesn't check for null input.
See auth.ts:142-156.
The reply hint (enabled by default) shows the exact intercom() call to respond, including the sender's session ID as to and the original message ID as replyTo, so ask can match the answer precisely. For ask to resolve reliably, replies should include that replyTo value. The message triggers a new turn, so the agent can respond or act on it immediately. If the message includes attachments, their content is also included in the agent-visible message body. Messages are rendered inline in the chat and also written to Pi session history as extension entries.
The most natural use of pi-intercom is splitting a task between two sessions — one holds the big picture, the other does the hands-on work. When the worker hits an ambiguity ("should I optimize for readability or performance here?"), they ask without losing context.
Open two terminals and start pi in each. Name them so they can find each other:
# Terminal 1 # Terminal 2
/name planner /name worker
Verify they see each other from either session:
intercom({ action: "list" })
// → • worker — ~/projects/api (claude-sonnet-4) [idle]Here's how a typical exchange looks. The planner delegates with send (fire-and-forget). The worker uses ask for anything that needs a response — questions, discoveries, completion reports. ask sends the message and blocks until the planner replies, so the worker gets the answer as a tool result and continues in the same turn.
Planner sends a task:
intercom({
action: "send",
to: "worker",
message: "Task-3: Add retry logic to API client. Key files: src/api/client.ts, src/api/types.ts. Ask if anything's unclear."
})Worker hits an ambiguity — asks and waits:
intercom({
action: "ask",
to: "planner",
message: "Should retry apply to all endpoints or just idempotent ones? Also, max retry count and backoff strategy?"
})
// → Reply from planner: Only GET/PUT/DELETE — never POST. Max 3 retries, exponential backoff starting at 100ms.
// Worker continues implementing with the answer, same turn, full context.Worker finds something unexpected — escalates and waits:
intercom({
action: "ask",
to: "planner",
message: "Found: fetchWithTimeout swallows network errors. Fixing this changes the error shape. OK to proceed?"
})
// → Reply from planner: Yes, surface the error types. The current behavior is a bug.Worker reports completion:
intercom({
action: "ask",
to: "planner",
message: "Task-3 done. Added RetryPolicy type, applied to GET/PUT/DELETE, surfaced NetworkError, 4 tests passing."
})
// → Reply from planner: Looks good. Move on to task-4.| Pattern | Action | Why |
|---|---|---|
| Task Delegation | Planner uses send |
Fire-and-forget. Planner doesn't need to wait for an ack. |
| Clarification Request | Worker uses ask |
Worker needs the answer to proceed. Blocks until reply. |
| Discovery Escalation | Worker uses ask |
Worker needs approval before changing course. |
| Completion Report | Worker uses ask |
Planner might have follow-up instructions or the next task. |
When replyHint is enabled (the default), incoming messages include the exact intercom() call to respond:
**📨 From planner** (~/projects/api) — reply: intercom({ action: "send", to: "550e8400-e29b-41d4-a716-446655440000", replyTo: "c1f7...", message: "..." })
Only GET/PUT/DELETE — never POST. Max 3 retries with exponential backoff starting at 100ms.
This matters because the agent receiving the message doesn't need to construct the reply call from scratch — the hint is right there. Combined with triggerTurn (which wakes the recipient agent on delivery), it enables real back-and-forth conversation without any complex protocol machinery.
send is fire-and-forget — the tool returns immediately after delivery. By default, it sends immediately even in interactive sessions. If you want an approval dialog before non-reply sends, set confirmSend: true in config. Replies that include replyTo still skip confirmation so reply-hint flows can continue without an extra approval step.
ask sends the message and blocks until the recipient responds (10-minute timeout). The reply comes back as the tool result, so the agent continues in the same turn with full context. No confirmation dialog — if you're asking and waiting, the intent is clear.
The planner typically uses send. If you prefer manual approval for outgoing non-reply messages, turn on confirmSend: true. The worker uses ask for everything (no confirmation needed, gets answers inline), so it can operate autonomously either way.
| Parameter | Type | Description |
|---|---|---|
action |
string | "list", "send", "ask", or "status" |
to |
string | Target session name or ID (for send/ask) |
message |
string | Message text (for send/ask) |
attachments |
array | Optional file, snippet, or context attachments |
replyTo |
string | Optional message ID for threading or replying to an ask |
list — Returns all active intercom-connected sessions (excluding self) with name, working directory, model, and status.
send — Sends a message to the specified session. By default it sends immediately, including in interactive sessions. Set confirmSend: true in config if you want a confirmation dialog for non-reply sends. Replies that include replyTo skip confirmation. Returns delivery confirmation.
ask — Sends a message and waits for the recipient to reply (10-minute timeout). The reply is returned as the tool result. No confirmation dialog. Use this when the agent needs the answer to continue working.
status — Shows connection status, session ID, and count of active sessions.
| Key | Action |
|---|---|
| Alt+M | Open session list overlay |
| ↑/↓ | Navigate session list |
| Enter | Select session / Send message |
| Escape | Cancel / Close overlay |
Create ~/.pi/agent/intercom/config.json:
{
"confirmSend": false,
"enabled": true,
"replyHint": true,
"status": "researching"
}| Setting | Default | Description |
|---|---|---|
confirmSend |
false | Show a confirmation dialog before non-reply sends from an interactive session with UI |
enabled |
true | Enable/disable intercom entirely |
replyHint |
true | Include reply instruction in incoming messages |
status |
— | Custom status shown to other sessions |
graph TB
subgraph A["Pi Session A"]
A1[Intercom Client]
A2[intercom tool]
A3[UI overlays]
end
subgraph Broker["Intercom Broker"]
B1[Session Registry]
B2[Message Router]
end
subgraph B["Pi Session B"]
B3[Intercom Client]
B4[intercom tool]
B5[UI overlays]
end
A1 <-->|Unix Socket| B1
B1 --- B2
B2 <-->|Unix Socket| B3
The broker is a standalone TypeScript process that manages session registration and message routing. It auto-spawns when the first intercom-enabled session needs it and exits after 5 seconds when the last connected session disconnects. Clients now reconnect automatically if the broker disappears and later comes back.
Messages use length-prefixed JSON over Unix sockets (4-byte length + JSON payload) to handle fragmentation properly. The protocol includes request correlation for session listing, explicit delivery failures, and validation for malformed or out-of-order messages.
Runtime files live at ~/.pi/agent/intercom/:
broker.sock— Unix socket for communicationbroker.pid— Broker process IDconfig.json— User configuration
Unix sockets over TCP. Same-machine only by design. Unix sockets are faster, need no port allocation, and get filesystem-level access control for free.
Auto-spawn with file lock. The broker starts on first connection and exits after 5 seconds idle. There is no daemon to manage. A spawn lock file, keyed by PID and timestamp, prevents duplicate brokers when multiple sessions start at once.
ask stays client-side. The broker still routes plain messages; it does not have a special request/response mode for ask. The client waits for a matching reply before it triggers a new turn, then returns that reply as the tool result. Reply hints make that flow practical by showing the recipient the exact send call to use. Separately, list / sessions now carry a requestId so a delayed session-list reply cannot be mistaken for a newer one.
| Aspect | pi-intercom | pi-messenger |
|---|---|---|
| Model | Direct 1:1 messaging | Shared chat room |
| Primary use | User orchestrating sessions | Autonomous agent coordination |
| Discovery | Broker-based (real-time) | File-based registry |
| Messages | Private, session-to-session | Broadcast to all agents |
| Persistence | In Pi session history | Shared coordination files |
Use pi-messenger for multi-agent swarms working on a shared task. Use pi-intercom when you want to manually coordinate your own sessions or have one agent reach out to another specific session.
~/.pi/agent/extensions/pi-intercom/
├── package.json
├── index.ts # Extension entry point
├── types.ts # SessionInfo, Message, protocol types
├── config.ts # Config loading
├── broker/
│ ├── broker.ts # Broker process
│ ├── client.ts # IntercomClient class
│ ├── framing.ts # Length-prefixed JSON protocol
│ └── spawn.ts # Auto-spawn logic with lock file
└── ui/
├── session-list.ts # Session selection overlay
├── compose.ts # Message composition overlay
└── inline-message.ts # Received message display
- Same machine only — Uses Unix sockets, no network support
- No dedicated intercom log — Messages are kept in Pi session history, but there is no separate intercom transcript or inbox
- No attachments UI —
file,snippet, andcontextattachments are supported in the protocol, but not in the compose overlay - Only connected sessions appear — The list shows Pi sessions that have loaded
pi-intercomand successfully registered with the broker, not every open Pi process on the machine - Broker must be running — It auto-spawns on first use, and sessions now try to reconnect automatically if the broker disappears after connect
