intercom · git:20260904.4608daf · 2026-09-04 · sha256 0ed2f2a58d5af4e8

intercom git:20260904.4608dafA

Immutable. This exact content is served forever at /api/v1/blob/0ed2f2a58d5af4e8.

---
name: intercom
description: |
  Streamline session-to-session coordination with the intercom extension. Send
  messages, delegate tasks, and coordinate work across multiple atomic sessions on
  the same machine. Use for planner-worker workflows, cross-session context
  sharing, and real-time collaboration between sessions.
---

# Intercom Skill

Use this skill when you need to coordinate work across multiple atomic sessions
running on the same machine. Intercom enables direct 1:1 messaging between
sessions for delegation, context sharing, and collaborative workflows.

When you are supervising with the `subagent` skill, delegated child agents can
escalate to you via `contact_supervisor` if the subagent runtime 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)

## 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):
```
/name planner    # Terminal 1
/name worker     # Terminal 2
```

**Planner delegates a task** (fire-and-forget):
```typescript
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):
```typescript
intercom({
  action: "ask",
  to: "planner",
  message: "Should I use exponential backoff or fixed intervals?"
})
// → Returns the planner's reply as the result
```

**Worker reports completion**:
```typescript
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. The full session ID printed by `list` is directly usable by `send`, `ask`, and targeted `reply`:

```typescript
intercom({ action: "list" })
// → • planner (6332faab-1111-4222-8333-123456789abc) — /workspace (model) [idle]
intercom({ action: "ask", to: "6332faab-1111-4222-8333-123456789abc", message: "Which option should I use?" })
```

Live sessions accept an exact full Intercom session ID or exact case-insensitive name. For workflow stages, first join `workflow:<rootRunId>` and use `intercom({ action: "list" })`: materialized stages appear as `PENDING` or `RUNNING` with canonical `workflow:<rootRunId>/<segment>[/<segment>...]` targets and actual groups, followed by possible future targets with queued counts. The invocation context can control owned isolated subgroups by exact target, while sibling subgroups and other runs remain isolated. Use queued `send` for `PENDING` or future targets; `ask` is supported only for `RUNNING`, where an exact correlated reply returns to the invocation asker.

### Deliver to workflow stages that have not started

Send material updates through Intercom to every affected workflow stage, including stages that have not started. Before steering, join the invocation group `workflow:<rootRunId>` (discover it with the Intercom `groups` action), then use `intercom list` there to see live, pending, and possible future targets.

```typescript
intercom({
  action: "send",
  to: "workflow:<rootRunId>/reviewer",
  message: "Scope changed: preserve raw amendment text in the verification oracle."
})
// → queued, distinct from live-session delivered, with the FIFO position
```

Each path segment may be a stage name, a run id, or a glob: `*` matches one segment and may be embedded (`reviewer-*`), while `**` matches any depth. When shared scope or acceptance criteria change, broadcast one authoritative update to `workflow:<rootRunId>/**` (or a narrower pattern) rather than enumerating stages. The broadcast reaches every live stage immediately and remains sticky for every future matching stage, including nested children, until the root run terminates. Other name or pattern sends have the same every-future-match behavior.

A syntactically valid target outside the persisted possible-stage set is accepted speculatively: the queued acknowledgment includes `notInKnownSet`. At terminal settlement, an entry that never delivered produces the correlated undeliverable notification; an entry delivered at least once does not. A stage receives queued messages through the ordinary inbound path before its first model turn under **Messages received before you started**, with real sender identity and a `Sent:` timestamp. Only same-workflow-group sessions may queue messages. Each target holds at most 50 queued messages; the next send is refused rather than evicting one. Resume/replay, broker restart, and stage-attempt restart preserve exactly-once delivery per message and materialized stage. Use `ask` only for a live target: pending, future, and pattern asks return `pending_stage_ask_unsupported`.

### Runtime named groups

Plain chat sessions can add and remove group memberships without restarting:

```typescript
// Add a membership. Existing memberships remain active.
intercom({ action: "join", group: "api-review" })

// Discover every available group and see membership markers.
intercom({ action: "groups" })

// Remove one membership, or reset to home by omitting group.
intercom({ action: "leave", group: "api-review" })
intercom({ action: "leave" })
```

`list` still lists sessions: without a filter it returns every peer sharing at least one membership. `default` remains shared, while `true` and `auto` remain reserved. Later subagents inherit the most recently joined membership. Ordinary `send`/`ask` requires a shared membership; only authorized `contact_supervisor` traffic crosses group boundaries.

### Pattern 3: Reply Naturally

When responding to an inbound ask, prefer `reply` instead of reconstructing raw IDs:

```typescript
// In the turn triggered by the ask:
intercom({
  action: "reply",
  message: "Use exponential backoff starting at 100ms."
})

// If replying later and there might be more than one pending ask:
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:

```typescript
const workers = ["worker-1", "worker-2", "worker-3"];
const task = "Check for null pointer exceptions in your assigned files";

// Fire-and-forget to all workers
workers.forEach(w => 
  intercom({ action: "send", to: w, message: task })
);
```

### Pattern 5: Send with Attachments

Share code snippets, files, or context:

```typescript
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: Handle Subagent Escalations (Orchestrator Side)

When the `subagent` runtime 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`:**

```typescript
// The reply hint in the incoming message will show the exact call:
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. Has a 10-minute timeout. | 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. Has a 10-minute timeout. | 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:**

```typescript
// In the turn triggered by the incoming ask:
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:

```typescript
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:**

```typescript
intercom({ action: "pending" })
// → Shows every unresolved ask with sender, exact message ID, age, and preview

intercom({ action: "reply", to: "subagent-worker-78f659a3-1", message: "Use the v2 API." })
// If that sender has multiple asks, select the exact listed thread:
intercom({ action: "reply", replyTo: "message-id", message: "Use the v2 API." })
```

**Important:** Only sessions where the `subagent` runtime 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.

### Pattern 7: Constructive Quorum

Use constructive quorum when several fresh-context reviewers judge the same artifact and a tally could hide a defect one reviewer found or preserve another reviewer's misreading.

1. Each reviewer inspects independently and records a preliminary verdict before reading sibling findings or verdicts.
2. Run exactly one bounded evidence-exchange round: share concrete findings and evidence, challenge blocking claims, surface missed defects, and correct objective/acceptance-criteria misreadings. Do not continue into a second round.
3. Change a verdict only through evidence, never deference. Each reviewer emits its own final structured verdict and records whether the round changed it and which evidence caused the change.
4. Let the deterministic reducer count final votes; this pattern does not change quorum counts or the `stop_review_loop` contract.

In Atomic workflows, each invocation has its own Intercom group, and parallel stages and delegated subagents inherit it when Intercom is available. Sibling reviewers can therefore coordinate without custom group wiring. See the [constructive quorum workflow pattern](../../../coding-agent/docs/workflows/reliable-design.md#common-workflow-patterns).

## Key Differences

| Action | Behavior | Use When |
|--------|----------|----------|
| `join` | Adds or creates a named membership in place | Sessions need another shared routing group |
| `leave` | Removes one named membership, or resets to home when omitted | Stop sharing one group or restore startup membership |
| `groups` | Lists every available group with counts and membership markers | Discover a group instead of guessing its name |
| `send` | Fire-and-forget to a live session, or durable sticky delivery to `workflow:<rootRunId>/<segment>[/<segment>...]`; globs and `**` broadcasts cover live and future matches | You don't need a response |
| `ask` | Blocks until a live recipient replies (10 min timeout); refused for an unstarted stage | You need an answer to continue |
| `reply` | Responds to the active or pending inbound ask; `to` accepts an exact full session ID or exact session name | 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 sharing any membership, with full IDs and live status | Discover targets or choose an idle peer |
| `status` | Returns connection state and every current membership | Troubleshooting |

Inside workflows, `ask` may target a sibling stage that has already completed. If that stage retains a valid conversation, Atomic automatically schedules a post-mortem turn there and preserves the exact child-to-child reply thread; do not send a separate workflow follow-up. Missing, deleted, non-resumable, or failed-to-reopen completed targets return an actionable error. A parent or unrelated session cannot satisfy the pending ask.

## Optional: Visible Peer Sessions via cmux, tmux, or psmux (Windows rewrite of tmux that has fully parity with tmux)

If no suitable intercom-connected peer session already exists and the task benefits from a long-lived visible conversation, you may spawn a new `atomic` session.

Prefer `cmux new-split right` over new surfaces or workspaces so both sessions are visible side by side.

If `cmux` is unavailable, `tmux` is an optional fallback when it is installed and relevant. Use it with a private socket so the session is isolated and observable.

Use spawned peer sessions only for:
- same-codebase worker/planner splits
- reference-codebase scouting
- long-lived visible conversations where the user benefits from watching both sides

Do not use this for unrelated repos, trivial questions, or work you can finish cleanly in the current session.

### Preferred: cmux Worker or Scout Session

Same codebase:

```bash
cmux new-split right
sleep 0.5
cmux send --surface right 'cd /path/to/current/repo && pi\n'
```

Reference codebase:

```bash
cmux new-split right
sleep 0.5
cmux send --surface right 'cd /path/to/reference/repo && pi\n'
```

### Optional Fallback: tmux Worker or Scout Session

Same codebase:

```bash
SOCKET_DIR=${TMPDIR:-/tmp}/pi-tmux-sockets
mkdir -p "$SOCKET_DIR"
SOCKET="$SOCKET_DIR/pi.sock"
SESSION=pi-worker
tmux -S "$SOCKET" new -d -s "$SESSION" -c "/path/to/current/repo" 'pi'
```

Reference codebase:

```bash
SOCKET_DIR=${TMPDIR:-/tmp}/pi-tmux-sockets
mkdir -p "$SOCKET_DIR"
SOCKET="$SOCKET_DIR/pi.sock"
SESSION=pi-reference-auth
tmux -S "$SOCKET" new -d -s "$SESSION" -c "/path/to/reference/repo" 'pi'
```

When you use `tmux`, tell the user how to watch it:

```bash
tmux -S "$SOCKET" attach -t "$SESSION"
```

After launch, name the new session clearly so it is easy to target:

```text
/name worker
/name reference-auth
```

Then coordinate from the current session:

```typescript
intercom({
  action: "send",
  to: "worker",
  message: "Take task X. Ask if blocked."
})

intercom({
  action: "ask",
  to: "reference-auth",
  message: "How does this repo structure token refresh retries?"
})
```

### Spawn Decision Rule

Spawn a visible peer session only when all of these are true:
- no existing intercom-connected session already fits the need
- the work benefits from a long-lived visible peer session
- the peer session is either in the same codebase or in an intentional reference codebase
- `cmux` is available, or `tmux` is available as an intentional fallback

If neither `cmux` nor `tmux` is available, skip this path and use normal `intercom` workflows.

## Important Constraints

### `ask` Limitations

- **10-minute timeout**: If no reply comes within 10 minutes, the ask fails
- **Bounded concurrency**: Up to `maxPendingAsks` asks (default: 6) may wait concurrently; additional calls receive a structured capacity error
- **Exact correlation**: Same-target and mixed-target asks may run together; out-of-order replies and peer disconnects settle only the matching sender/message pair
- **Cannot self-target**: A session cannot ask itself
- **Supervisor exclusivity**: One blocking `contact_supervisor` decision/interview may coexist with peer asks, but a second supervisor wait is refused with `Already waiting for a supervisor reply`

```typescript
// Parallel fan-out is supported within the configured capacity.
const [design, tests] = await Promise.all([
  intercom({ action: "ask", to: "architect", message: "Review the design" }),
  intercom({ action: "ask", to: "qa", message: "Review the tests" }),
]);
```

### `send` Behavior

- **No timeout**: Message is delivered or fails immediately
- **Confirmation dialogs**: If `confirmSend: true` in config, interactive sessions show a confirmation dialog
- **Replies skip confirmation**: Messages with `replyTo` never show confirmation dialogs

## Best Practices

### Use `ask` for blocking workflows

When the worker needs information to proceed:

```typescript
// GOOD: Worker blocks until planner responds
const reply = await intercom({
  action: "ask",
  to: "planner",
  message: "API rate limit is 100/min. Should I implement client-side throttling or batching?"
});
// Continue with the answer...
```

### Use `send` for notifications

When you just want to inform:

```typescript
// GOOD: Fire-and-forget notification
intercom({
  action: "send",
  to: "reviewer",
  message: "PR #123 is ready for review. Key changes in auth.ts."
});
// Continue immediately, don't wait
```

### Include reply hints in messages

Make it easy for recipients to respond:

```typescript
// GOOD: Recipient sees exact command to reply
intercom({
  action: "send",
  to: "worker",
  message: `Found the issue in auth.ts:142. Use getUserById() instead of getUser().

Reply with: intercom({ action: "reply", message: "..." })`
});
```

### Name sessions meaningfully

Use `/name` so others can target you easily:

```
/name api-worker
/name frontend-dev
/name planner
```

## Error Handling

### Common Errors and Solutions

**"Too many pending asks"**
```typescript
// This session reached maxPendingAsks. Wait for an existing ask to settle,
// or use fire-and-forget send when the answer need not be this tool result.
intercom({ action: "send", to: "planner", message: "..." });
```

**"Already waiting for a supervisor reply"**
```typescript
// Supervisor decisions/interviews are exclusive per child. Ordinary peer
// asks may continue concurrently; wait for the current supervisor call.
```

**"Cannot message the current session"**
```typescript
// You cannot target yourself
// This usually means you confused session names - double-check the target
```

**"Session not found"**
```typescript
const result = await intercom({ action: "send", to: "worker", message: "..." });
if (!result.delivered) {
  console.log("Failed:", result.reason);
  // → "Session not found" - check the name and list available sessions
  await intercom({ action: "list" });
}
```

**Ask timeout (after 10 minutes)**
```typescript
// The ask will reject with a timeout error
// Design your workflow so answers come within 10 minutes
// For longer tasks, use send + follow-up ask pattern
```

## Troubleshooting

### Session not appearing in list

1. Check Intercom connection status: `intercom({ action: "status" })`
2. Verify the target session has loaded pi-intercom
3. Ensure both sessions are on the same machine (intercom is same-machine only)

### Message not delivered

```typescript
const result = await intercom({ action: "send", to: "worker", message: "..." });
if (!result.delivered) {
  console.log("Failed:", result.reason);
  // → "Session not found" or delivery failure reason
}
```

### Connection lost

Sessions automatically reconnect if the broker restarts. If an explicit `send`, `ask`, or `reply` fails with the typed `Client disconnected` error, copy the opaque `retryToken` from the tool result and repeat the exact action and arguments with it. Make at most three claimed attempts. Never attach that token to a different action, target, message, attachment sequence, or reply thread, and omit it for every fresh operation—even a deliberately byte-identical one. Invalid, expired, foreign-session, mismatched, concurrent, exhausted, and settled claims fail without sending.

The token keeps the original message ID and deadline (11 minutes from the initial attempt: the 10-minute ask window plus one minute). A claimed retry that returns `delivered: false`, `Session not found`, durable-authority uncertainty/capacity refusal, or another typed disconnect returns the same token for the next bounded claim; each claim consumes one attempt and never extends the deadline. Delivered or queued success settles it. Initial tokenless nondelivery and unrelated/non-recoverable failures do not create retry state.

Broker acceptance is retained for 12 minutes, so acknowledgement loss does not duplicate delivery and a deduplicated ask remains replyable. Implicit public reply prefers the recorded exact sender ID while live and falls back to authorized reconnect resolution only after departure. Durable SQLite stores fixed keyed HMAC digests rather than message/attachment text; its paired key and database artifacts are owner-only on POSIX. Missing, malformed, uncertain, or capacity-bound authority fails closed rather than risking a duplicate.

Retry safety is bounded: the client allows at most 1,000 retained identities, while the broker allows 10,000 live records and 64 MiB of digest/routing authority. At either limit Intercom refuses new work rather than evicting a still-valid identity.

If the session remains disconnected after those retries:

```typescript
intercom({ action: "status" })
// Check if broker is running and restart if needed
```

## Common Workflows

### Research → Implementation Handoff

```typescript
// Research session finds relevant code
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

```typescript
// Session A encounters error
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?"
});

// Session B investigates and replies
intercom({
  action: "reply",
  message: "data.users is null. The fetch failed silently. Add error handling in loadUsers()."
});
```

### Progress Reporting

```typescript
// Worker sends periodic updates
intercom({ action: "send", to: "planner", message: "Task-1 complete (15min). Starting Task-2." });
// ... work ...
intercom({ action: "send", to: "planner", message: "Task-2 complete (30min). Task-3 blocked - need API key." });
// ... get unblocked ...
intercom({ action: "send", to: "planner", message: "Task-3 complete. All done." });
```

### Long-Running Task with Checkpoints

```typescript
// For tasks that might exceed 10 minutes, use send + periodic asks

// 1. Initial send with full context
intercom({
  action: "send",
  to: "worker",
  message: "Implement user authentication. This will take 30+ minutes. I'll check in at milestones."
});

// 2. Worker sends progress via send (no timeout)
intercom({ action: "send", to: "planner", message: "Milestone 1: Login form complete (10min)" });

// 3. Worker asks for specific decision when needed
const decision = await intercom({
  action: "ask",
  to: "planner",
  message: "Should we use JWT or session cookies? Need decision to continue."
});
// Continue with decision...
```