| name | pi-intercom |
| description | Streamline session-to-session coordination with pi-intercom. Send messages,
delegate tasks, and coordinate work across multiple pi sessions on the same
machine. Use for planner-worker workflows, cross-session context sharing,
and real-time collaboration between sessions.
|
Pi Intercom Skill
Use this skill when you need to coordinate work across multiple pi sessions
running on the same machine. Pi-intercom enables direct 1:1 messaging between
sessions for delegation, context sharing, and collaborative workflows.
When you are supervising pi-subagents, delegated child agents can escalate to
you via contact_supervisor if pi-subagents supplied child bridge metadata.
This skill covers how to handle those orchestrator-side escalations.
When to Use
- Task delegation: Split work between a planner session and worker sessions
- Context handoffs: Send findings from a research session to an execution session
- Clarification loops: Worker asks questions, planner answers, work continues
- Multi-session workflows: Coordinate between specialized sessions (frontend/backend, research/implementation)
- Cross-codebase peer messages: Message an explicit live peer in another project, or open a visible Herdr project pane when a long-lived conversation is needed
Core Patterns
Pattern 1: Planner-Worker Delegation
The most common pattern. One session holds the big picture, others do hands-on work.
Setup (in each session):
/alias planner # Terminal 1
/alias worker # Terminal 2
Planner delegates a task (fire-and-forget):
intercom({
action: "send",
to: "worker",
message: "Task-3: Add retry logic to API client. Key files: src/api/client.ts. Ask if anything's unclear."
})
Worker asks for clarification (blocks until answer):
intercom({
action: "ask",
to: "planner",
message: "Should I use exponential backoff or fixed intervals?"
})
Worker reports completion:
intercom({
action: "ask",
to: "planner",
message: "Task-3 complete. Added exponential backoff (100ms → 1600ms, max 5 retries). Ready for task-4?"
})
Pattern 2: Quick Status Check
Before sending, verify who's connected:
intercom({ action: "list" })
Pattern 3: Reply Naturally
When responding to an inbound ask, prefer reply instead of reconstructing raw IDs:
intercom({
action: "reply",
message: "Use exponential backoff starting at 100ms."
})
intercom({ action: "pending" })
intercom({ action: "reply", to: "planner", message: "Use exponential backoff starting at 100ms." })
reply still preserves exact threading under the hood by sending the response with the original replyTo value.
Pattern 4: Broadcast to Multiple Workers
Send to multiple sessions in parallel:
const workers = ["worker-1", "worker-2", "worker-3"];
const task = "Check for null pointer exceptions in your assigned files";
workers.forEach(w =>
intercom({ action: "send", to: w, message: task })
);
Pattern 5: Send with Attachments
Share code snippets, files, or context:
intercom({
action: "send",
to: "worker",
message: "Here's the fix for the auth issue:",
attachments: [{
type: "snippet",
name: "auth.ts",
language: "typescript",
content: `function validateUser(user: User | null) {
if (!user) throw new Error("User required");
return user.email?.includes("@");
}`
}]
})
Pattern 6: Cross-Codebase Peer Messages
Use to alone to message any explicit live peer on the machine, even when it is
in another codebase. Use cwd alone when there should be exactly one live peer
in that repo. Use to plus cwd when the directory is a safety guard.
intercom({
action: "ask",
cwd: "/path/to/other-repo",
to: "workbench-agent",
message: "Which module owns workbench source slices?"
})
Only open a Herdr project pane when you need a durable visible peer session in
that repo. For bounded work, prefer pi-subagents with an explicit cwd; the
child can use contact_supervisor for owner decisions and regular intercom
for explicit peer coordination.
intercom({
action: "send",
cwd: "/path/to/other-repo",
openProjectPaneIfMissing: true,
message: "Let's discuss the workbench API ergonomics in this repo."
})
If a live session already exists in that cwd, intercom reuses it. If multiple
sessions are active there, pass to to select one by name or session ID.
Pattern 7: Handle Subagent Escalations (Orchestrator Side)
When pi-subagents spawns a delegated child and supplies child bridge metadata,
that child can reach you through contact_supervisor. You receive a formatted
message that includes run metadata:
**From subagent-worker-78f659a3-1**
Subagent needs a supervisor decision.
Run: 78f659a3
Agent: worker
Child index: 0
Which API should I use?
Reply using reply:
intercom({ action: "reply", message: "Use the stable v2 API." })
This works because reply resolves the correct sender and message ID automatically.
Three types of escalations to expect:
| Type | What it means | How to respond |
|---|
need_decision | Subagent is blocked and waiting for your answer. Uses the shared ask timeout: 10 minutes by default, configurable with PI_INTERCOM_ASK_TIMEOUT_MS. | Reply promptly with a clear decision. If you need more context, ask follow-up questions via reply. |
interview_request | Subagent needs multiple structured answers in one blocking exchange. Uses the shared ask timeout: 10 minutes by default, configurable with PI_INTERCOM_ASK_TIMEOUT_MS. | Reply with plain JSON or a fenced json block using the provided { "responses": [...] } shape. |
progress_update | Subagent is sharing meaningful progress or a plan-changing discovery. Not blocking. | Read and acknowledge. No reply required unless you want to redirect. |
When a subagent asks:
intercom({ action: "reply", message: "Use exponential backoff, max 3 retries." })
When a subagent sends an interview request:
Read the rendered questions in the incoming message and reply with the exact ids in JSON. info questions are context-only and do not need response entries:
intercom({
action: "reply",
message: "```json\n{\n \"responses\": [\n { \"id\": \"api\", \"value\": \"Stable API\" },\n { \"id\": \"constraints\", \"value\": \"Keep the public error shape unchanged.\" }\n ]\n}\n```"
})
If you receive multiple pending asks from different subagents:
intercom({ action: "pending" })
intercom({ action: "reply", to: "subagent-worker-78f659a3-1", message: "Use the v2 API." })
Important: Only sessions where pi-subagents supplied child bridge metadata
get the contact_supervisor tool. Normal sessions use the regular intercom
tool. If you see the formatted supervisor decision/progress update message, treat
it as a contact_supervisor escalation. A subagent may use regular intercom for
peer coordination, including peers in other directories, but owner decisions and
new visible project panes should go through the supervisor.
Key Differences
| Action | Behavior | Use When |
|---|
send | Fire-and-forget; infers the sole pending ask as its reply | You don't need a response |
ask | Blocks until reply (10 min default, configurable with PI_INTERCOM_ASK_TIMEOUT_MS) | You need an answer to continue |
reply | Responds to the active or pending inbound ask | You were asked something and need to answer naturally |
pending | Lists unresolved inbound asks | You need to see who is waiting before replying |
list | Returns all sessions with live status | You need to discover targets or choose an idle peer |
status | Returns your connection state | Troubleshooting |
Visible Peer Sessions
For bounded cross-codebase work, prefer pi-subagents with an explicit cwd.
Use intercom({ action: "send", cwd: "/path", openProjectPaneIfMissing: true, ... })
only when a long-lived visible peer session is useful.
If Herdr is unavailable, do not invent a terminal fallback inside this workflow.
Ask the user before opening another visible surface manually.
Important Constraints
ask Limitations
- Connected targets only:
ask fails immediately when the target is not in the live intercom roster. Use list before asking when liveness is uncertain; use send for non-blocking mailbox delivery.
- Configurable timeout: If no reply arrives before the shared ask timeout, the ask fails. The default is 10 minutes; set
PI_INTERCOM_ASK_TIMEOUT_MS to a positive millisecond value to change it.
- One at a time: Cannot have multiple pending asks from the same session
- Cannot self-target: A session cannot ask itself, including through disconnected-mailbox remapping
const result = await intercom({ action: "ask", to: "planner", message: "..." });
if (result.isError && result.content[0].text.includes("Already waiting")) {
}
send Behavior
- No timeout: Message is delivered or fails immediately
- Sole pending ask inference: If the destination has exactly one pending inbound ask,
send attaches its replyTo and reports Reply sent to <target> (inferred from pending ask)
- Ambiguity stays unthreaded: Zero or multiple matching asks leave the send as an ordinary message
- Confirmation dialogs: If
confirmSend: true in config, interactive sessions confirm ordinary and inferred sends
- Explicit replies skip confirmation: A caller-supplied
replyTo skips the dialog
Best Practices
Use ask for blocking workflows
When the worker needs information to proceed:
const reply = await intercom({
action: "ask",
to: "planner",
message: "API rate limit is 100/min. Should I implement client-side throttling or batching?"
});
Use send for notifications
When you just want to inform:
intercom({
action: "send",
to: "reviewer",
message: "PR #123 is ready for review. Key changes in auth.ts."
});
Name sessions meaningfully
Use /alias so others can target you easily. It names the current session and
is shown in intercom lists, send/reply results, overlays, and incoming headers:
/alias api-worker
/alias frontend-dev
/alias planner
Error Handling
Common Errors and Solutions
"Already waiting for a reply"
intercom({ action: "send", to: "planner", message: "..." });
"Cannot message the current session"
"Session not found"
const result = await intercom({ action: "send", to: "worker", message: "..." });
if (!result.delivered) {
console.log("Failed:", result.reason);
await intercom({ action: "list" });
}
Replies to recently disconnected explicitly named senders can be queued by the broker and delivered if that sender reconnects with the same name and directory. Runtime-only subagent-chat-... aliases are not reconnect identities. New send calls may target a known live or recently disconnected session; blocking ask calls require a live target.
Ask timeout
Troubleshooting
Session not appearing in list
- Check intercom is enabled:
intercom({ action: "status" })
- Verify the target session has loaded pi-intercom
- Ensure both sessions are on the same machine (intercom is same-machine only)
Message not delivered
const result = await intercom({ action: "send", to: "worker", message: "..." });
if (!result.delivered) {
console.log("Failed:", result.reason);
}
Connection lost
Sessions automatically reconnect if the broker restarts. If persistently disconnected:
intercom({ action: "status" })
Common Workflows
Research → Implementation Handoff
intercom({
action: "send",
to: "impl-session",
message: "Found the bug. The issue is in validateUser() - it doesn't check for null.",
attachments: [{
type: "snippet",
name: "validate.ts",
language: "typescript",
content: `// Line 45-52 - missing null check
function validateUser(user: User) {
return user.email?.includes("@"); // crashes if user is null
}`
}]
});
Pair Debugging
intercom({
action: "ask",
to: "session-b",
message: "Getting 'Cannot read property of undefined' at line 78. Can you check if data.users is populated before this call?"
});
intercom({
action: "reply",
message: "data.users is null. The fetch failed silently. Add error handling in loadUsers()."
});
Progress Reporting
intercom({ action: "send", to: "planner", message: "Task-1 complete (15min). Starting Task-2." });
intercom({ action: "send", to: "planner", message: "Task-2 complete (30min). Task-3 blocked - need API key." });
intercom({ action: "send", to: "planner", message: "Task-3 complete. All done." });
Long-Running Task with Checkpoints
intercom({
action: "send",
to: "worker",
message: "Implement user authentication. This will take 30+ minutes. I'll check in at milestones."
});
intercom({ action: "send", to: "planner", message: "Milestone 1: Login form complete (10min)" });
const decision = await intercom({
action: "ask",
to: "planner",
message: "Should we use JWT or session cookies? Need decision to continue."
});