Skip to content

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.


┌─────────────────────────────────────────────────────┐
│ 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 a new independent agent session on the runner. The new session runs in parallel and can be monitored through the PizzaPi web UI.

ParameterTypeRequiredDefaultDescription
promptstringInitial prompt for the new session. Must be self-contained — the child has no context from the parent.
model{ provider, id }Runner defaultModel to use. Call list_models to discover available values.
cwdstringParent’s cwdWorking directory for the new session.
runnerIdstringCurrent runnerTarget runner ID. Usually not needed.

On success, returns the session ID, runner ID, working directory, status (Pending or Ready), and a web UI share URL.

{
"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

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.

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.

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 in response. The child revises and resubmits.

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 sends SIGTERM to the child and tears down its relay session. Only use when the child is truly finished.
  • followUp — Send more work to the child in response. The child resumes with a new turn.

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.

  1. Child hits a lifecycle event (asks a question, proposes a plan, completes, or errors)
  2. Child emits a session_trigger event over the relay WebSocket
  3. Relay routes the trigger to the parent session
  4. Trigger is injected into the parent’s conversation with a <!-- trigger:ID --> prefix
  5. Parent responds via respond_to_trigger, escalate_trigger, or tell_child
  6. 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 a pending trigger from a child session.

ParameterTypeRequiredDescription
triggerIdstringThe trigger ID from the <!-- trigger:ID --> prefix
responsestringResponse text sent to the child
actionstringStructured intent — see below
ActionUsed withEffect
approveplan_reviewAccept the child’s plan; child proceeds with execution
cancelplan_reviewReject the plan; child stops
editplan_reviewProvide feedback in response; child revises the plan
acksession_completeAcknowledge completion and terminate the child process
followUpsession_completeSend response as new input to resume the child

For ask_user_question triggers, action is not required — just provide the response text.

// Child asks a question
respond_to_trigger("abc-123", "Use RS256 — it supports key rotation")
// Child proposes a plan
respond_to_trigger("def-456", "Approved", "approve")
// Child completes — send follow-up work
respond_to_trigger("ghi-789", "Now add unit tests for the JWT module", "followUp")
// Child completes — done, clean up
respond_to_trigger("jkl-012", "Looks good, shutting down", "ack")

Pass a child’s trigger to the human viewer when the parent agent can’t or shouldn’t handle it.

ParameterTypeRequiredDescription
triggerIdstringThe trigger ID to escalate
contextstringAdditional 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.


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.

ParameterTypeRequiredDescription
sessionIdstringChild session ID
messagestringMessage to send
  • 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

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 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.

ParameterTypeRequiredDescription
sessionIdstringTarget session ID
typestringTrigger type (e.g. "service", "webhook", "godmother:idea_started")
payloadobjectArbitrary payload delivered to the target session
sourcestringSource identifier shown in trigger history
deliverAsstring"steer" (default, interrupts current turn) or "followUp" (queued after turn)
  • 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.


subagent toolspawn_session (linked child)
CommunicationTool call → result (synchronous)Triggers + tell_child
BlockingYes — parent waits for resultNo — parent continues, triggers arrive async
ContextIsolated in-process sessionFully independent session + process
UIInline card in parent sessionSeparate session in web UI
OverheadNear-zero (in-process SDK)Higher (relay round-trip, new PTY)
Model selectionVia agent definition model fieldVia model parameter
Best forQuick tasks: research, review, refactorLong-running parallel work, background tasks
Agent definitionsFile-based (~/.pizzapi/agents/)Prompt-based (inline)
ModesSingle, parallel, chainOne session per call

A parent spawns multiple linked children to work in parallel, then synthesizes their results:

// 1. Spawn workers
spawn_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 worker
respond_to_trigger("t1", "Received security review", "ack")
respond_to_trigger("t2", "Received a11y review", "ack")
// 4. Synthesize results into a summary

If 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")

A parent spawns a reviewer and iterates on feedback:

// 1. Spawn reviewer
spawn_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 again
respond_to_trigger("complete-t2", "Done, thanks", "ack")

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 model
spawn_session({
prompt: "Continue this task: ...",
model: { provider: "google", id: "gemini-2.5-pro" }
})
// Option 2: Escalate to human
escalate_trigger("err-t1", "Child hit rate limit — need human to decide next steps")

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.


  • Subagents — File-based agent definitions, parallel/chain modes, model routing
  • Runner Daemon — How sessions are spawned on runners
  • CLI Reference — Full tool parameter reference