Multi-Agent Sessions
PizzaPi supports multiple agent sessions running in parallel through a single orchestration model: linked parent/child sessions. Linked children communicate upward through triggers, and parents communicate downward through respond_to_trigger and tell_child.
Communication model
Section titled “Communication model”┌─────────────────────────────────────────────────────┐│ Parent Session ││ ││ spawn_session(prompt) ││ │ ││ │ ◄── ask_user_question trigger ──┐ ││ │ ◄── plan_review trigger ────────┤ ││ │ ◄── session_error trigger ──────┤ ││ │ ◄── session_complete trigger ───┤ ││ │ │ ││ respond_to_trigger(id, response) │ ││ tell_child(sessionId, message) ──► │ ││ escalate_trigger(id) ──► human │ ││ │ ││ ┌──────────┴─────────┐ ││ │ Child Session │ ││ │ (linked) │ ││ └────────────────────┘ │└─────────────────────────────────────────────────────┘Spawned sessions are always linked. Child events arrive automatically in the parent’s conversation as triggers, and the parent responds with respond_to_trigger, tell_child, or escalate_trigger.
spawn_session
Section titled “spawn_session”Spawn a new independent agent session on the runner. The new session runs in parallel and can be monitored through the PizzaPi web UI.
Parameters
Section titled “Parameters”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
prompt | string | ✅ | — | Initial prompt for the new session. Must be self-contained — the child has no context from the parent. |
model | { provider, id } | Runner default | Model to use. Call list_models to discover available values. | |
cwd | string | Parent’s cwd | Working directory for the new session. | |
runnerId | string | Current runner | Target runner ID. Usually not needed. |
Return value
Section titled “Return value”On success, returns the session ID, runner ID, working directory, status (Pending or Ready), and a web UI share URL.
Linked child sessions
Section titled “Linked child sessions”{ "prompt": "Refactor the auth module to use JWTs", "model": { "provider": "anthropic", "id": "claude-sonnet-4-20250514" }}- Parent’s session ID is sent as
parentSessionId - Child events (questions, plans, completion, errors) arrive as triggers in the parent’s conversation
- No manual polling needed — triggers are injected automatically
- If the linked relationship cannot be restored after a reconnect, the harness surfaces that as an orchestration failure instead of silently degrading into peer messaging
Trigger system
Section titled “Trigger system”When a linked child session hits certain lifecycle events, it fires a trigger to the parent. Triggers arrive in the parent’s conversation prefixed with <!-- trigger:ID --> metadata.
Trigger types
Section titled “Trigger types”ask_user_question
Section titled “ask_user_question”Fired when a child session calls AskUserQuestion. The trigger payload includes the question text, options, and the full structured questions array. The parent can answer on behalf of the user with respond_to_trigger.
<!-- trigger:abc123 -->Child session "auth-refactor" is asking: "Should I use RS256 or HS256 for JWT signing?" Options: RS256, HS256
Use respond_to_trigger(triggerId, response) to answer.plan_review
Section titled “plan_review”Fired when a child calls plan_mode to propose a multi-step plan. The payload includes the plan’s title, steps, and optional description.
Respond with an action:
approve— Accept the plan. The child proceeds with execution.cancel— Reject the plan. The child stops.edit— Provide feedback inresponse. The child revises and resubmits.
session_complete
Section titled “session_complete”Fired when the child session finishes. The payload includes the child’s final output and an exitReason:
exitReason: "completed"— Normal completion.exitReason: "error"— The child hit a usage limit or provider failure.
Respond with an action:
ack— Acknowledge and terminate the child process. This sendsSIGTERMto the child and tears down its relay session. Only use when the child is truly finished.followUp— Send more work to the child inresponse. The child resumes with a new turn.
session_error
Section titled “session_error”Fired before session_complete when a child hits a usage limit or provider error. This gives the parent an early signal to react — retry with a different model, skip the task, or escalate to the user. Check the exitReason field on the subsequent session_complete to distinguish error exits from successful completions.
Trigger lifecycle
Section titled “Trigger lifecycle”- Child hits a lifecycle event (asks a question, proposes a plan, completes, or errors)
- Child emits a
session_triggerevent over the relay WebSocket - Relay routes the trigger to the parent session
- Trigger is injected into the parent’s conversation with a
<!-- trigger:ID -->prefix - Parent responds via
respond_to_trigger,escalate_trigger, ortell_child - Response is routed back to the child, which unblocks and continues
Triggers have a 10-minute TTL. If the parent doesn’t respond within that window, the trigger expires and the child times out (5-minute default for ask_user_question and plan_review).
respond_to_trigger
Section titled “respond_to_trigger”Respond to a pending trigger from a child session.
Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
triggerId | string | ✅ | The trigger ID from the <!-- trigger:ID --> prefix |
response | string | ✅ | Response text sent to the child |
action | string | Structured intent — see below |
Actions
Section titled “Actions”| Action | Used with | Effect |
|---|---|---|
approve | plan_review | Accept the child’s plan; child proceeds with execution |
cancel | plan_review | Reject the plan; child stops |
edit | plan_review | Provide feedback in response; child revises the plan |
ack | session_complete | Acknowledge completion and terminate the child process |
followUp | session_complete | Send response as new input to resume the child |
For ask_user_question triggers, action is not required — just provide the response text.
Example
Section titled “Example”// Child asks a questionrespond_to_trigger("abc-123", "Use RS256 — it supports key rotation")
// Child proposes a planrespond_to_trigger("def-456", "Approved", "approve")
// Child completes — send follow-up workrespond_to_trigger("ghi-789", "Now add unit tests for the JWT module", "followUp")
// Child completes — done, clean uprespond_to_trigger("jkl-012", "Looks good, shutting down", "ack")escalate_trigger
Section titled “escalate_trigger”Pass a child’s trigger to the human viewer when the parent agent can’t or shouldn’t handle it.
Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
triggerId | string | ✅ | The trigger ID to escalate |
context | string | Additional context for the human |
The trigger is re-emitted as an escalate type trigger targeting the parent’s own session, where it surfaces in the web UI for the human to respond. The original trigger remains pending — when the human responds (via the web UI), the response routes back through respond_to_trigger to the child.
tell_child
Section titled “tell_child”Proactively send a message to a linked child session. Unlike respond_to_trigger (which responds to a child’s request), tell_child initiates a new turn in the child’s conversation — the message is delivered as agent input.
Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
sessionId | string | ✅ | Child session ID |
message | string | ✅ | Message to send |
Use cases
Section titled “Use cases”- Redirect a child mid-task:
tell_child(id, "Stop the refactor — focus on the auth bug instead") - Provide additional context the child didn’t ask for
- Coordinate between multiple children by relaying information
Relationship durability
Section titled “Relationship durability”Linked parent/child relationships are durable across transient relay outages:
- The server preserves durable linkage metadata even when a reconnect temporarily clears
parentSessionId - Trigger and follow-up routing falls back to durable linkage checks instead of treating the child as unrelated
- If the harness cannot prove the relationship after reconnect, it surfaces an explicit broken-link/orchestration error instead of timing out silently
fire_trigger
Section titled “fire_trigger”Fire a trigger into any session — not just children. This enables peer-to-peer trigger communication between sessions that aren’t in a parent-child relationship.
Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
sessionId | string | ✅ | Target session ID |
type | string | ✅ | Trigger type (e.g. "service", "webhook", "godmother:idea_started") |
payload | object | ✅ | Arbitrary payload delivered to the target session |
source | string | Source identifier shown in trigger history | |
deliverAs | string | "steer" (default, interrupts current turn) or "followUp" (queued after turn) |
Delivery modes
Section titled “Delivery modes”steer(default) — Interrupts the target session’s current turn. The trigger is injected immediately into the conversation.followUp— Queued and delivered after the target’s current turn completes. Use this when the trigger isn’t urgent and shouldn’t disrupt ongoing work.
fire_trigger uses the HTTP Trigger API with API key auth, with a Socket.IO fallback for offline/local mode.
Pattern comparison
Section titled “Pattern comparison”subagent tool | spawn_session (linked child) | |
|---|---|---|
| Communication | Tool call → result (synchronous) | Triggers + tell_child |
| Blocking | Yes — parent waits for result | No — parent continues, triggers arrive async |
| Context | Isolated in-process session | Fully independent session + process |
| UI | Inline card in parent session | Separate session in web UI |
| Overhead | Near-zero (in-process SDK) | Higher (relay round-trip, new PTY) |
| Model selection | Via agent definition model field | Via model parameter |
| Best for | Quick tasks: research, review, refactor | Long-running parallel work, background tasks |
| Agent definitions | File-based (~/.pizzapi/agents/) | Prompt-based (inline) |
| Modes | Single, parallel, chain | One session per call |
Real-world patterns
Section titled “Real-world patterns”Orchestrator → workers
Section titled “Orchestrator → workers”A parent spawns multiple linked children to work in parallel, then synthesizes their results:
// 1. Spawn workersspawn_session({ prompt: "Review packages/server/ for security issues" })// → sessionId: "worker-1"
spawn_session({ prompt: "Review packages/ui/ for accessibility issues" })// → sessionId: "worker-2"
// 2. Triggers arrive automatically as children complete:// <!-- trigger:t1 --> session_complete from worker-1// <!-- trigger:t2 --> session_complete from worker-2
// 3. Acknowledge each workerrespond_to_trigger("t1", "Received security review", "ack")respond_to_trigger("t2", "Received a11y review", "ack")
// 4. Synthesize results into a summaryIf a worker asks a question mid-task, the parent gets an ask_user_question trigger and can answer directly or escalate to the human:
// Worker asks: "The auth module uses both bcrypt and argon2. Which should I focus on?"respond_to_trigger("t3", "Focus on argon2 — that's the active path")
// Or if unsure:escalate_trigger("t3", "Worker needs domain knowledge about our crypto choices")Review ping-pong
Section titled “Review ping-pong”A parent spawns a reviewer and iterates on feedback:
// 1. Spawn reviewerspawn_session({ prompt: "Review this PR diff for bugs: ..." })// → sessionId: "reviewer-1"
// 2. Reviewer proposes a plan (plan_review trigger)respond_to_trigger("plan-t1", "Skip the style checks, focus on logic bugs", "edit")
// 3. Reviewer revises plan and resubmits (another plan_review trigger)respond_to_trigger("plan-t2", "Looks good", "approve")
// 4. Reviewer completes (session_complete trigger)// If findings need action:respond_to_trigger("complete-t1", "Fix the null check bug you found in auth.ts", "followUp")
// 5. Reviewer fixes and completes againrespond_to_trigger("complete-t2", "Done, thanks", "ack")Error recovery
Section titled “Error recovery”When a child hits a usage limit, the parent gets a session_error trigger before session_complete:
// session_error arrives: "Rate limit exceeded for claude-sonnet-4"// Option 1: Retry with a different modelspawn_session({ prompt: "Continue this task: ...", model: { provider: "google", id: "gemini-2.5-pro" }})
// Option 2: Escalate to humanescalate_trigger("err-t1", "Child hit rate limit — need human to decide next steps")Push notification behavior
Section titled “Push notification behavior”Linked child sessions do not trigger push notifications. Only top-level sessions — those started by a user or the runner daemon directly — send push notifications. This prevents notification spam when orchestrating multiple child sessions.
Related guides
Section titled “Related guides”- Subagents — File-based agent definitions, parallel/chain modes, model routing
- Runner Daemon — How sessions are spawned on runners
- CLI Reference — Full tool parameter reference