@signalridge/pi-subagents1.15.1
Signalridge's managed subagent runtime with workflow-owned orchestration RPC.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
@signalridge/pi-subagents
Signalridge's managed subagent package for Pi, including protocol-v4 spawning, lifecycle isolation, Fleet UI, persistence, and recovery.
A pi extension that brings Claude Code-style autonomous sub-agents to pi. Spawn specialized agents that run in isolated sessions — each with its own tools, system prompt, model, and thinking level. Run them in foreground or background, steer them mid-run, resume completed sessions, and define your own custom agent types.
Features
- Claude Code look & feel — same tool names, calling conventions, and UI patterns (
Agent,get_subagent_result,steer_subagent) — feels native - Parallel background agents — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 4) and smart group join (consolidated notifications)
- Live FleetView UI — one navigable list below the editor with a status mark per row, live tool activity, token counts, and text status labels. Enter opens the selected agent's conversation. Toggle via
/agents → Settings → Fleet view - FleetView — Claude Code-style navigable list of
main+ every running subagent rendered below the editor (earliest-launched first). Press↓(or←) at an empty prompt to jump in,↑/↓to move the selection,Enterto open the selected agent's live, auto-updating conversation,Escto return. Finished agents linger briefly before dropping out, and a viewer stays open through completion so you can read the final output. Toggle via/agents → Settings → Fleet view - Conversation observer — select a retained agent with a session in
/agentsto open a live observer. Public fullscreen hosts use the terminal-sized view; regular hosts keep the floating bordered view. Scroll up to pause following, useHomefor the first available history andEnd/Ctrl+Endfor the latest. Bounded windows preserve older-message navigation;]pages available message text and[returns to its first text page. PressEnterto compose a steering message; native rejection retains the draft. Controls stay bound to the selected session and complete managed attempt, never a replacement that reuses its ID. - Foreground handoff —
Ctrl+Bat an empty, focused main prompt hands off one eligible foreground agent without restarting it. In its observer,barms handoff andyconfirms it;xsimilarly arms stop,yconfirms, and Esc cancels. The ID, session, transcript and worktree remain the same; managed, nested, resumed and internal generation tasks are excluded. A full background pool refuses the transfer rather than exceeding its capacity. - Custom agent types — define agents in
.pi/agents/<name>.mdor.agents/agents/<name>.md(project) or globally, with YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions - Nested subagents — opt-in, default-off delegation: a custom agent that sets
allowed_subagentsgets its own ownership-scopedAgent,get_subagent_result, andsteer_subagenttools, depth-capped from the main session (default 2). It can control only its own children, they are stopped when it finishes, and their transcripts and token spend roll up to it. The allowlist is a privilege boundary — a child runs with its own tools, so pick it as carefully astools:itself - Mid-run chat — inject messages into running or queued agents to redirect their work without restarting. From the main prompt, use
@handle message(with@autocomplete); configure this under/agents → Settings → Agent mentions. - Session resume — pick up where an agent left off, preserving full conversation context
- Graceful turn limits — agents get a "wrap up" warning before hard abort, producing clean partial results instead of cut-off output
- Case-insensitive agent types —
"explore","Explore","EXPLORE"all work. Unknown types fall back to general-purpose with a note - Fuzzy model selection — specify models by name (
"haiku","sonnet") instead of full IDs, with automatic filtering to only available/configured models - Context boundary — raw
inherit_context: trueis refused because Pi does not expose the parent's post-hook request or atomically pin the child's physical endpoint; pass a reviewed, sanitized summary in the task instead - Persistent agent memory — three scopes (project, local, user) with automatic read-only fallback for agents without write tools
- Git worktree isolation — run agents in isolated repo copies; changes auto-committed to branches on completion, with an explicit prompt guard keeping the base checkout off-limits
- Skill preloading — inject named skills into agent system prompts, discovered from
.pi/skills/,.agents/skills/, and global locations (Pi-standard<name>/SKILL.mddirectory layout supported) - Tool denylist — block specific tools via
disallowed_toolsfrontmatter - Styled completion notifications — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML. Expandable to show full output. Group completions render each agent individually
- Event bus — lifecycle events (
subagents:created,started,completed,failed,steered,compacted) emitted viapi.events, enabling other extensions to react to sub-agent activity - Cross-extension RPC — other Pi extensions can spawn and stop subagents via the
pi.eventsevent bus (subagents:rpc:ping,subagents:rpc:spawn,subagents:rpc:stop). Protocol v4 adds managed spawning, a request-level Agenttier, toolset/denylist/thread/worktree hints, owner-scoped stop/quiescence, a published Agent-tier routing policy, and standardized reply envelopes; pi-subagents remains the final policy owner. Pre-schema-v2 managed tombstones are quarantined rather than replayed. Emitssubagents:readyon session start - Schedule subagents — pass
scheduleto theAgenttool to fire on cron / interval / one-shot. Session-scoped jobs with PID-locked persistence; results land via the samesubagent-notificationfollowUp path as manual background completions; manage via/agents → Scheduled jobs - Model tiers — name a (model, thinking) pair once and let the orchestrator pick it by name; the
Agenttool exposestierand nevermodel/thinking, so which model runs stays a config decision. Manage the catalogue in/agents → Model tiers, pick the default in/agents → Settings → Default tier, or set a plaindefaultModelwhen one line beats a catalogue - Model scope enforcement — opt-in validation that subagent model choices stay within your pi
enabledModelsallowlist (sourced from/scoped-models, with both global and project-local pi settings honored). Caller-supplied out-of-scope → hard error to orchestrator; frontmatter-pinned out-of-scope → warning + runs anyway (frontmatter authoritative). Toggle via/agents → Settings → Scope models - Resilient agent files — malformed custom
.mdfiles are skipped with a path-qualified warning so one bad file cannot prevent startup; enable/agents → Settings → Strict agent fileswhen startup should fail closed instead
Install
pi install npm:@signalridge/pi-subagents
Use from this checkout
From the repository root:
pi -e ./packages/pi-subagents
Verified host baseline: Pi 1.0.4. Offline resource-ownership, child tool-policy, and native Fleet input barriers are additionally exercised on Pi 1.1.0; that focused coverage is not complete host qualification. Wildcard peer dependency ranges and retained capability fallbacks do not establish support for older host versions.
Upstream and local divergence
Derived from tintinweb/pi-subagents; the upstream MIT license is preserved. The FleetView editor-focus fix from commit 7da6bef828d7582ee33386ece9379c170e7742c4 (2026-10-09, #374; reported by @BarrMan) is selectively ported: callable getText/setText identifies non-inheriting custom editors without taking dialog or selector keys. No clone-parent-history behavior is imported; raw inherit_context: true remains refused. This fork also excludes pi-background from child resource loading until asynchronous child continuation has manager-owned lifecycle accounting.
The container-first FleetView and bounded drill-down patterns are independently adapted from tintinweb/pi-subagents at 221df02323c6b6350ea8474579e0ee0f6cc84cea and fd8d81618d80ae83a3faf40cb87dae99cca33264. Collision-only child-label discriminators are inspired by nicobailon/pi-subagents 69673c7fbf3351e54a5b0786a7e9d9fff625a749. The workflow engine and phase inspector remain owned by pi-workflows, not copied into this package. A separate optional, validated UI protocol supplies authoritative run names, statuses and observed phases and opens that owner's inspector. Without that peer, groups retain the observation-only child list. No workflow source imports, inferred DAG state or second queue/model policy are introduced. The managed protocol remains v4.
Resume-budget and shutdown parity fixes selectively adapt upstream work at d7c9ec0, ca93a52 (unreleased after the 0.22 tag), and 83be963 (released in 0.22). They retain this fork's manager-owned sessions, resource budgets, activity accounting, and protocol v4; the integrated upstream engine is not imported.
The opt-in parent clarification broker is locally implemented. It uses the existing main session, not an upstream LLM arbitrator, cloned parent history, private UI automation, or an additional Agent per question. Permission gates remain human-owned.
Lint policy
Formatter checks remain disabled for this package, while legacy noExplicitAny, control-character regex, and empty-interface rules remain off. Signalridge additions follow the package AGENTS rules and the strict --error-on-warnings lint gate.
Quick start
The parent agent spawns sub-agents using the Agent tool:
Agent({
subagent_type: "Explore",
prompt: "Find all files that handle authentication",
description: "Find auth files",
run_in_background: true,
})
Foreground agents block until complete and return results inline. Background agents return an ID immediately and notify you on completion.
At a TUI prompt, @explore find it starts one background Explore agent. In model mention mode, a hidden, throwaway turn uses only the current mention text to draft the task prompt; it does not receive the parent's conversation, system prompt, other tools, compaction summaries, or context hooks. This prevents raw parent history from reaching a separate provider before request-local privacy hooks can redact it. The resulting Agent spawn is still attributed to the parent; raw inherit_context: true is refused before the child starts (see below). If the hidden turn cannot start the agent, the extension starts it directly unless the parent runtime itself is unavailable. A pending mention is discarded if you switch sessions or branches before it starts; the off-screen provider turn is cancelled if the switch happens during streaming. An already-started agent is not started twice if a later UI or event update fails.
Pi runtime requirement: On hosts where child sessions require the parent's ModelRuntime, Pi currently exposes it to extensions only through the registry's private .runtime property. If that bridge is missing or incompatible, Agent, mentions, and managed spawns refuse before allocating an ID or making a provider request; they do not silently construct a fresh runtime that would lose custom providers, credentials, or virtual routes. Existing managed idempotency keys remain readable. An explicit child model is validated against the parent runtime after final model selection, so a stale ctx.model alone does not block a valid physical child model. The error advises updating to a host that exposes the parent runtime, or disabling dispatch until one does.
Opt-in parent clarification (background only)
By default, contact_supervisor still asks the human. Explicitly opt a fresh, non-isolated top-level background child into ordinary main-agent clarification:
Agent({
subagent_type: "Explore",
prompt: "Investigate the schema. Ask only ordinary task clarifications, never approval.",
description: "Investigate schema",
run_in_background: true,
supervisor_questions: "parent",
})
The child awaits an in-process answer promise. Only its explicit bounded question reaches the main model: sanitized request-local context contains a one-use questionId, original agentId, and numbered choice tokens. The main-only answer_subagent_question({ questionId, answer }) accepts free text, an offered option:N token, or an empty answer to decline. Display labels are separate from the original choice data returned to the child; duplicate identical answers never resume it twice. Question text is untrusted data, not new instructions or authorization.
The broker coalesces at most 16 pending questions (one per child), issues at most 64 IDs per session/branch generation, and expires unanswered questions after two minutes. Each question is displayed in hidden request-local context at most once. It requests one normal prepared extension turn only when the root is idle, has no queued messages, has no observed input/preparation in progress, and is not waiting on a dialog; otherwise questions join the next ordinary model context or wake after final agent_settled, never at retry-prone agent_end. No question means no extra model request. It does not edit genuine user text or images, transfer parent history/credentials into the child, poll a model, persist a separate question history, or automatically retry unanswered questions.
Idle wakes are factory-local authenticated control tokens, not a claim on a familiar sentence. Recognition requires input.source === "extension" and an exact namespace/nonce/MAC; an interactive/RPC literal, foreign-namespace extension literal, or invalid MAC remains ordinary input. Admission replaces only the extension-owned input with a distinct frame token. While that admission remains unambiguous, preparation adds an authenticated empty-content native custom message as prompt-local frame evidence, never persistent model guidance. Native message_start identifies the executing frame; the promptless agent_start and old branch messages cannot identify it. Superseding genuine input invalidates wake authorization without forgetting accepted control identity. Request-local context removes only proven control pairs; verbatim user quotations remain unchanged. Historical proof recognition uses a fixed-size secret and constant recognition state, not an accumulating ledger. At most 16 unresolved or ambiguous admissions are retained independently of the flight; an exhausted admission budget refuses further wakes rather than forgetting unresolved frames.
before_agent_start exposes no input source or delivery ID: token text alone cannot prove identity. If genuine INPUT copies an admitted token before its preparation reaches this package, both matching frames become ambiguous. That admission is retained inert within the same cap, neither frame receives a control proof or question context, neither is aborted or stripped by token text, and the flight's unannounced questions receive structured no-answer. The extension does not alter literal text or images. Native SDK image normalization can append a hint and resize an attachment; question suppression still requires the first text's authenticated token boundary and its retained ambiguous admission, never generic hint prose. This evidence cannot authorize a control proof or abort. The package cannot atomically cancel the unidentifiable control request before native persistence/provider dispatch. Ambiguous preparations cannot release a genuine input's readiness; idle wakes remain conservatively fenced until session retirement, but later ordinary inputs can still receive new questions normally. An already-prepared wake retains its positive native proof and cannot donate it to a later literal quotation.
An owned request-local context delivery is the ACK. Each question has one wake attempt: no ACK within 30 seconds clears only that flight and returns structured no-answer for its still-unannounced questions. Async native authentication rejection and earlier input interception cannot occupy the flight indefinitely or retry acknowledged questions. A superseding genuine prompt retains its own preparation readiness; except for the explicit copied-token ambiguity above, its questions join that prompt or expire normally. Executing-frame ownership separately captures the public ctx.signal and its matching native agent_end. A competing preparation's busy-failure agent_settled cannot clear that frame while the core operation still owns a public signal; only a proven ended and released owner permits cleanup. Native preparation can record inert control tokens and empty frame proofs in ordinary session history; question text and answers are not stored as separate broker entries.
Public-hook limits: handlers run in registration/load order. Observed genuine INPUT retires idle readiness synchronously, before later awaits. This package's mention interception releases only its own INPUT-scoped preparation on a native handled result; a thrown handler is not handled (Pi reports it and continues), and another concurrently preparing input is never released by that mention. Pi exposes neither an earlier handler's in-progress input nor a generic input-handled/auth-rejection completion notification. External handling/rejection after observation can therefore retain preparation readiness until session retirement; no timer guesses that a genuine prompt is inactive. Unresolved accepted wake preparations also retain their bounded admission slots until preparation arrives or the activation is replaced.
Pi has no cancellable post-preparation/pre-persistence boundary. Already-prepared competing prompts cannot be atomically serialized or cancelled before native persistence through public hooks. Cancellation can leave an inert native control record; a proven invalidated control frame is never reclassified as ordinary instructions or given question context. Abort is issued only after an authenticated native message boundary proves the currently executing owned frame, never from a global accepted-token guess or to stop a genuine/other run. SDK 1.0.4 coverage includes later-preparation release orders, user/proof message-start and message-end awaits, copied unprepared tokens with and without images, default AUTO resizing of real 4096x4096 PNGs in both release orders while another child's question stays pending, and guard-disabled controls. Native concurrent prompts can still reject with “already processing”; the package preserves observed frame identity but does not guarantee native concurrent-prompt acceptance or atomic pre-persistence cancellation. This is not a private SDK/renderer/history patch.
Answers are not human approval or permission grants. ask_tools, createAskGate, project trust, external permission dialogs, and human rejections are unchanged. A decision outside existing user task authorization must be declined and left to the human. Cancellation (including an aborted main turn), child stop/removal, timeout, navigation, or reload returns structured no-answer/assumption guidance, never approval. Pending IDs capture the public root session-manager identity, session ID, and complete native branch-entry prefix (including non-model metadata) at child channel construction and at every question invocation from the live root context, plus the current child attempt. A terminal child's background resume keeps its channel guard but cannot reuse that older prefix as the origin of a new question. Admission, context reads, and answer dispatch validate both live ancestries, not only generation events or model-text projections. Ordinary appends retain ownership; metadata-distinct divergent ancestry retires it even if an earlier asynchronous committed-tree handler has delayed this extension's notification. Old IDs cannot answer a replacement run.
Foreground parent-mode calls are rejected before spawn: a main model blocked waiting for a child cannot execute another answering tool. Schedules and changing question mode on resume are also unsupported. An existing parent-mode child can resume only in background with supervisor_questions omitted; it retains its mode. Do not call get_subagent_result(wait: true) while an ordinary clarification is pending: that wait blocks the answering model until cancellation or the question timeout. Nested children cannot opt in or call the root answering tool. Managed protocol-v4 requests do not carry this new mode and remain unchanged; this MVP supports direct Agent calls and in-process SpawnOptions.supervisorQuestions: "parent" only when the root manager has issued the channel. No new global configuration is added. The existing Supervisor questions setting must remain enabled; supervisor_questions: "off" withholds contact for one fresh call.
Install the changed package and reload/restart Pi to use this feature; an already running older installed package does not acquire new tools or parameters from checkout edits.
Root-owned operation approvals
When a root approval service is present at child creation, admitted canonical edit, write, and bash tools use @signalridge/pi-approval-core reviewed SDK definitions. Each operation awaits the root human service with its exact final inputs, file diff or command, working directory, and a distinct child:<agent-id> source. Ordinary parent clarification answers never authorize operations. No service means existing behavior is unchanged; malformed or duplicate services refuse before child allocation. A captured service cannot reconnect after retirement or replacement.
Tool admission, denylist, ask_tools, project trust, and isolation still apply first: approval adds no tools, including to internal zero-tool generation. The gate survives in-memory resume, steering, and nested ctx.executeTool calls. Noncanonical command/mutation-shaped tools require exactly one live cooperative adapter matching the effective schema identity. Known command or path-plus-edit/content schema properties are classified before invocation even when optional. Admission is also rechecked after each downstream public tool_call handler against the native mutable arguments and the originally captured execution name/schema; a normalizer cannot add a command or rename the event to bypass it. These post-handler checks never repeat ask_tools human prompts and retain the same shared execution deadline. Conflicting canonical extension tools are refused rather than silently replacing the reviewed binding. While root review is pending, only human waiting time is excluded from the child's default tool timeout; execution retains its remaining budget and native bash timeouts still apply after approval. Cancellation and divergent root/child branch ownership deny execution.
Inherited pi-approvals resources are excluded before factory loading when the root service is captured, including package aliases. Explicit selection is rejected with guidance to integrate approvals in the root session. Children do not load another approval extension or forward their UI to impersonate a human; no UI/model mode is enabled automatically. Known-shape admission does not interpret arbitrary extension semantics or unknown schemas; a timed-out hook can still perform its own side effects outside the executor. This is an in-process trusted extension integration, not an OS sandbox for arbitrary extension code. Managed protocol and journal formats remain v4.
Scheduling
Add a schedule field to register the agent to fire later instead of running now:
Agent({
subagent_type: "Explore",
prompt: "Look at recent commits and summarize what changed since last week",
description: "Weekly commit review",
schedule: "0 0 9 * * 1", // 9am every Monday (6-field cron)
})
Schedule formats:
- Cron — 6-field (
second minute hour day-of-month month day-of-week), e.g."0 0 9 * * 1"for 9am every Monday,"0 */15 * * * *"for every 15 minutes. - Interval —
"5m","1h","30s","2d". Fires repeatedly at that interval. - One-shot relative —
"+10m","+2h","+1d". Fires once at that future time. - One-shot absolute — full ISO timestamp, e.g.
"2026-12-25T09:00:00.000Z".
When a schedule fires, the spawn runs in background and its completion notification arrives in the conversation through the same subagent-notification followUp path as a manually-spawned background agent — your parent agent reasons about the result the same way. The fire also draws itself into the FleetView list, so a scheduled run is visible while it works and not only when it reports.
Schedules are session-scoped: they reset on /new and restore on /resume. List and cancel via /agents → Scheduled jobs (creation is the Agent tool's job — there is no parallel manual-create wizard). Storage at <cwd>/.pi/subagent-schedules/<sessionId>.json with PID-based file locking for cross-instance safety.
Disable the feature entirely: /agents → Settings → Scheduling → disabled removes schedule from the Agent tool spec (no LLM-context cost), hides the menu entry, and stops any active scheduler. The schema-level removal takes effect on the next pi session; the runtime kill is immediate. Re-enable from the same menu.
Restrictions:
schedulecannot be combined withinherit_context(no parent conversation exists at fire time) orresume(schedules create fresh agents).- Scheduled agents always run in the background; explicitly setting
run_in_background: falseis rejected rather than silently changed. - Scheduled fires bypass the
maxConcurrentqueue so a 5-minute interval cannot be deferred behind long-running manual agents. - Headless
pi -pdoesn't wait for scheduled subagents.
UI
Running agents are drawn in exactly one place: the FleetView list below the
editor (see below). Earlier versions also rendered a widget above the editor and
repeated the same summary in the footer status; a single surface under the
prompt replaces all three, and the widgetMode setting that configured the
above-editor widget is gone.
Runs that enter through the scheduler or a cross-extension spawn appear in the
list too, alongside Agent-tool runs.
Every string these surfaces take from a child run — its description, activity preview, error, the Agent tool's own result and completion notification, and the conversation overlay's messages, tool results and command output — is text the extension did not author, so it is neutralized before rendering. Fleet, conversation labels and scheduled-job selectors/confirmations/notifications use @signalridge/pi-ui display encoding (controls shown as \\uXXXX). Transcript previews drop terminal escape sequences, name remaining unsafe code points as [U+XXXX], and replace binary content with a placeholder. These are display copies, not changes to stored prompts, paths, or messages. An agent file's display_name and description get the same treatment as they are loaded, since a cloned repository supplies them. A subagent that reads a poisoned file or fetches a hostile page cannot repaint or reorder the parent's terminal.
The token field is annotated with two optional signals inside parens:
NN%— context-window utilization (color-coded: <70% dim, 70–85% warning, >=85% error). Omitted when the model has no declaredcontextWindow, or briefly right after compaction.compactions N— number of times the session has compacted, when greater than 0. Stays dim; the percent's color carries urgency.
FleetView
While subagents are running, a Claude Code-style navigable list renders below the editor:
Esc interrupt · ← agents · ↓ manage
● main · current
● general-purpose · Sleep then report 1 · running 11s · 13.1k tokens
● general-purpose · Sleep then report 2 · completed 11s · 13.1k tokens
✗ general-purpose · Sleep then report 3 · failed 4s · 2.1k tokens
3 more below
Each row leads with a single-column status mark colored by outcome — ● while a
run is live or finished cleanly, ✗ for a failure, ⊘ for one that was
stopped or aborted — so the column scans vertically without reading the labels.
The mark is a geometric shape rather than an emoji on purpose: an emoji invites
font fallback, and a fallback glyph is usually double-width, which would push
the rest of the row out of alignment on some terminals and not others.
The list is ordered earliest-launched first, and only shows agents you can actually open (pending/queued agents with no session yet appear once they start). At an empty prompt, press down or left to move focus from the prompt into the list. Up and down move the selection, Enter opens the selected agent's live conversation overlay (it auto-updates as the agent works), and Esc (or up above main) returns to the prompt. Selecting main returns to the normal view. Regular-mode observers use a complete ╭─╮ / │ / ╰─╯ border; public fullscreen hosts use a terminal-sized observer. Press Enter to chat with a running agent — type a message and Enter to send it (Esc or an empty submit returns). For a queued agent that has no session yet, type @handle message in the main prompt; the message is delivered when it starts. Press x and follow the displayed explicit confirmation to stop a running agent; holding one key must not confirm it. A viewer stays open when its agent finishes so you can read the final output, and finished agents linger in the list for a few seconds before dropping out. Typing anything at a non-empty prompt behaves normally — the list only captures navigation keys when the prompt is empty. Disable it entirely via /agents → Settings → Fleet view.
Workflow containers appear above ordinary agents. With the optional pi-workflows UI peer, a row shows the actual workflow name/status, observed current phase, call counts and available token totals; even a running workflow with no child yet can appear. Enter opens the workflow owner's inspector, where pause/resume/stop stay with that owner. Paused and interrupted childless runs remain available for explicit resume.
Without the peer, the fallback is Workflow <runId> · N retained children. That count describes retained manager records, not total workflow calls or authoritative status. Opening it gives a terminal-height-bounded child list with stable-ID selection, Up/Down, PageUp/PageDown, Home/End and Enter to view a conversation; Esc returns from the conversation to the child list, then to FleetView. Sessionless queued children remain visible with an explicit unavailable-session message. Completed owned children remain inspectable while retained, independent of the ordinary four-second linger. Duplicate descriptions receive an ID discriminator only when needed. Detached records are excluded, and controls opened on an old thread attempt cannot steer/stop a replacement attempt.
Steering rejection (including a child finishing while the composer is open) preserves the draft and shows an error; Esc explicitly discards it. Completed conversation headers fall back to the retained lifetime usage and compaction count when live activity has been removed.
Individual agent results use restrained text status labels:
| State | Example |
|---|---|
| Running | ● running · turns 3 of 30 · tools 3 · 12.4k tokens (8%) / ⠹ searching, reading 3 files... |
| Completed | ● completed · turns 8 · tools 5 · 33.8k tokens (62%) · 12.3s / Done |
| Wrapped up | ● wrapped up · turn limit · turns 50 of 50 · tools 50 · 89.1k tokens (84% · compactions 2) · 45.2s / Wrapped up at the turn limit |
| Stopped | ⊘ stopped · turns 3 · tools 3 · 12.4k tokens (8%) / Stopped before completion |
| Error | ✗ failed · turns 3 · tools 3 · 12.4k tokens (8%) / Error: timeout |
| Aborted | aborted · turns 55 of 50 · tools 55 · 102.3k tokens (95% · compactions 3) / Aborted at the turn limit |
Completed results can be expanded (ctrl+o in pi) to show the full agent output inline.
By default, foreground and background agents each stream their full conversation to a per-subagent transcript — a JSON-lines file at <os-tmpdir>/pi-subagents-<uid>/<cwd>/<session>/tasks/<agent-id>.output (owner-only 0700, cleared on reboot). Set output_transcript: false on a custom agent to write no transcript path or file for it, or set outputTranscript: false in subagents.json to make transcripts opt-in for the whole project (frontmatter overrides the project default). This governs only the transcript: it is independent of persist_session (the pi session on disk), and it does not affect isolation: worktree (which commits the agent's work to a git branch) or memory: (durable files) — set those accordingly if the goal is to keep a run off disk entirely. Background agent completion notifications render as styled text:
Find auth files · completed
turns 3 · tools 3 · 12.4k tokens · 4.1s
Found 5 files related to authentication...
transcript: .pi/output/agent-abc123.jsonl
Group completions render each agent as a separate block. The LLM receives structured <task-notification> XML for parsing, while the user sees the themed visual.
Default agent types
| Type | Tools | Model | Prompt Mode | Description |
|---|---|---|---|---|
general-purpose |
all 7 | inherit | append (parent twin) |
Inherits the parent's full system prompt — same rules, CLAUDE.md, project conventions |
Explore |
read, bash, grep, find, ls | haiku (falls back to inherit) | replace (standalone) |
Fast codebase exploration (read-only) |
Plan |
read, bash, grep, find, ls | inherit | replace (standalone) |
Software architect for implementation planning (read-only) |
The general-purpose agent is a parent twin — it receives the parent's entire system prompt plus a sub-agent context bridge, so it follows the same rules the parent does. Explore and Plan use standalone prompts tailored to their read-only roles.
Default agents can be ejected (/agents → select agent → Eject) to export them as .md files for customization, overridden by creating a .md file with the same name (e.g. .pi/agents/general-purpose.md), or disabled per-project with enabled: false frontmatter.
Custom agents
Define custom agent types by creating .md files. The filename becomes the agent type name. Any name is allowed — using a default agent's name overrides it.
Agents are discovered from three locations (higher priority wins):
| Priority | Location | Scope |
|---|---|---|
| 1 (highest) | .pi/agents/<name>.md |
Project — pi's config dir; authoritative, and where /agents writes |
| 2 | .agents/agents/<name>.md |
Project — the shared cross-tool .agents workspace (same convention as .agents/skills/) |
| 3 | $PI_CODING_AGENT_DIR/agents/<name>.md (default ~/.pi/agent/agents/<name>.md) |
Global — available everywhere |
Project-level agents override global ones with the same name, so you can customize a global agent for a specific project. If both project locations define the same name, .pi/agents/ wins — .pi stays the project authority; .agents/agents/ is an additional read location for projects that keep their agent assets in the .agents workspace. The global location follows Pi's PI_CODING_AGENT_DIR setting and can relocate all agent state.
Malformed or unreadable files are skipped with a path-qualified warning by default. If a malformed project file shadows a valid lower-priority definition with the same name, the loader warns which definition survived; set strictAgentFiles: true in subagents.json to fail closed during the first session_start instead. Strict validation and discovery use that session's ctx.cwd, not the process cwd, and a failed validation registers no root tools, manager, or RPC responder. Reloads after startup remain lenient; an accidental edit cannot terminate an active session. Unchanged warning keys are suppressed while their discovery root remains in the bounded 64-root cache; if that root is evicted, a later unchanged reload may warn again.
Interactive agent-file edits are compare-and-commit operations: replacements are written to a flushed temporary file, new files honor the process umask, and all mutations use a package-local, non-expiring atomic lock directory with bounded acquisition retries. Orphaned locks are never stolen; the error names the lock path to inspect/remove. Only writers using this protocol coordinate with these operations. Non-cooperating editors are detected when a content comparison observes their change, but portable Node has no universal filesystem CAS, so this is not a claim of an arbitrary-editor lock.
Example: .pi/agents/auditor.md
---
description: Security Code Reviewer
tools: read, grep, find, bash
model: anthropic/claude-opus-4-6
thinking: high
max_turns: 30
---
You are a security auditor. Review code for vulnerabilities including:
- Injection flaws (SQL, command, XSS)
- Authentication and authorization issues
- Sensitive data exposure
- Insecure configurations
Report findings with file paths, line numbers, severity, and remediation advice.
Then spawn it like any built-in type:
Agent({ subagent_type: "auditor", prompt: "Review the auth module", description: "Security audit" })
Frontmatter fields
All fields are optional — sensible defaults for everything.
| Field | Default | Description |
|---|---|---|
description |
filename | Agent description shown in tool listings |
display_name |
— | Display name for UI (e.g. the agent list, the conversation overlay) |
tools |
all 7 | Which tools the agent can call. Built-in names (read, grep, …), * / all (all built-ins), none, and ext:<extension> / ext:<extension>/<tool> selectors for extension tools. See Tool & extension scoping below |
extensions |
true |
Which extensions to load for the agent. true (all child-safe defaults), false (none), or an explicit list: [mcp, "/abs/path.ts", "*"]. pi-background is excluded; intentional selection is refused by name. See Tool & extension scoping below |
exclude_extensions |
— | Extension denylist applied after extensions: — exclude wins. Plain names only (case-insensitive), no paths or *. Useful with extensions: true to drop one extension (e.g. pi-notify) |
skills |
true |
true uses Pi's discovered skills, including skills supplied by loaded extensions; false exposes none; a comma-separated list preloads only those named skills into the system prompt (see Skill Preloading for discovery locations) |
memory |
— | Persistent agent memory scope: project, local, or user. Auto-detects read-only agents |
disallowed_tools |
— | Comma-separated tools to deny even if extensions provide them |
ask_tools |
— | Comma-separated tools whose first call requires human approval. Approval permits later calls to that tool across resumed turns of the current in-memory child. It is not persisted: reopening an evicted child from disk or restarting Pi asks again. Without an interactive approver, calls are blocked |
isolation |
— | Set to worktree to run in an isolated git worktree |
tier |
none | This agent's default model tier, by name, from agentTiers.profiles. A tier passed at the call site overrides it. When set, it wins over model/thinking below — see Model tiers |
model |
— | Removed. An agent no longer chooses its own model; use tier. A file that still has it loads normally, with a warning naming it — the line simply has no effect |
thinking |
— | Removed, same as model above |
max_turns |
unlimited | Max agentic turns before graceful shutdown. 0 or omit for unlimited |
persist_session |
false |
Persist this subagent as a normal pi session instead of keeping the session in memory only. The subagent's .output transcript is still written either way unless output_transcript: false |
output_transcript |
true (or subagents.json outputTranscript) |
Write this subagent's .output transcript; when set, overrides the subagents.json outputTranscript default. Set false to write no transcript file or path. Governs only the transcript — independent of persist_session, isolation: worktree, and memory: |
session_dir |
pi default | Optional session directory when persist_session: true; omitted uses pi's normal session location, and relative paths resolve from the agent cwd |
allowed_subagents |
none | Opt in to scoped nested Agent, get_subagent_result, and steer_subagent tools. Omitted / empty / none / false = no nesting; all (or "*" / true) = any enabled agent; comma-separated list = only those agent types |
prompt_mode |
replace |
replace: body is the full system prompt (no AGENTS.md / CLAUDE.md inheritance). append: body appended to parent's prompt (agent acts as a "parent twin" — inherits parent's AGENTS.md / CLAUDE.md) |
inherit_context |
false |
true is refused: the host cannot safely transfer post-hook parent context or pin the child's physical provider endpoint |
run_in_background |
false |
Run in background by default |
isolated |
false |
Hermetic specialist mode: forces extensions: false + skills: false + drops ext: selectors. Only built-in tools. Distinct from isolation: worktree (filesystem) |
enabled |
true |
Set to false to disable an agent (useful for hiding a default agent per-project) |
Frontmatter is authoritative. If an agent file sets model, thinking, max_turns, inherit_context, run_in_background, isolated, or isolation, those values are locked for that agent. Agent tool parameters only fill fields the agent config leaves unspecified.
inherit_context: true is rejected before child creation, for top-level, nested, mention-started, and managed agents. A parent's session projection is pre-hook: Pi does not expose the post-context / context_with_system result to this extension, and a child cannot atomically pin the same physical endpoint across virtual/provider routes. A raw_for confirmation would not fix that request-time auth/routing race. Pass a deliberately sanitized summary in the Agent task prompt instead; if context must be edited in the parent session, persist a context_edit first and still pass only the summary you intend to disclose. Normal calls with inherit_context: false are unchanged. Older persisted child sessions containing the inherited-history prompt cannot resume or switch models safely; start a new child session with a sanitized task rather than reopening them.
Forgiving model: resolution. A model: pin is matched against pi's model registry tolerantly, so cosmetic id variations don't silently drop the agent back to the parent's model: . and - are treated as equivalent in version numbers (claude-haiku-4.5 ≡ claude-haiku-4-5), a trailing -YYYYMMDD date stamp is optional (anthropic/claude-haiku-4-5-20251001 matches an undated registry id and vice-versa), and a provider/modelId whose named provider doesn't carry that model retries the bare id against every provider. Precedence is exact → fuzzy under the named provider → same model under any provider → unavailable, so an exact match always wins and dated snapshots aren't conflated. If nothing resolves, the pin can't run and the agent inherits the parent model — /agents → Agent types flags this case as (unavailable, fallback: inherit) and shows the resolved target (→ provider/id) when resolution lands on a different provider or version than configured. (This is distinct from Model Scope enforcement, which resolves pi's configured enabledModels patterns.)
Nested subagents
Nested delegation is default-off. Set allowed_subagents only on a non-isolated custom agent that owns a real fan-out responsibility:
---
tools: read, grep, find
extensions: false
allowed_subagents: support-file-finder, support-callsite-tracer # or `all`
---
The allowlist is a privilege boundary, not just a routing hint. A child runs with its own tools:, extensions:, and isolated: — the parent's restrictions are not inherited — so delegation grants the parent the union of what the listed agents can do. The read-only agent above can write and run commands through any listed agent that can, and all reaches every enabled agent including general-purpose. Choose the list as carefully as you would choose tools: itself; that is the main reason this is default-off.
allowed_subagents is runtime-enforced. A comma-separated list restricts nesting to those types; all (or "*" / true, matching how extensions: and skills: take booleans) allows any enabled agent; omitted, empty, none, or false means no nested tools are injected at all. Unknown, disabled, and out-of-list types are rejected rather than falling back — regardless of the project's fallback agent setting, so a configured fallback can never hand a nested caller an agent outside its allowlist — and a nested model: is validated against Model Scope exactly like a top-level spawn. Result, resume, and steering operations are ownership-scoped, so a parent can control only its own children. Nested records remain internal to that parent and do not appear in top-level tools, lifecycle events, or agent UI — so when a parent finishes, is stopped, or ends a resumed turn, its nested children are stopped with it. They do write their own .output transcript (subject to the same output_transcript gate), filed under the root session's directory alongside their ancestors', so a nested run can still be inspected after the fact. Their token usage is folded into every ancestor's totals up to the top-level agent (lifecycle events, completion notifications, /agents), so nested spend stays attributable at any depth even though the children themselves stay hidden. A nested result that ends stopped, aborted, or steered is labelled as partial, the same guarantee top-level results carry.
The hard cap is depth 2 by default: main session (0) → subagent (1) → nested child (2). Change it project-wide with maxSubagentDepth in subagents.json (or /agents → Settings → Nested depth); 0 or 1 turns nesting off everywhere. An agent already at the cap gets no nested tools at all — not even get_subagent_result, since it can never own a child. A child must independently set allowed_subagents to delegate again; isolated agents never receive nested tools.
Nested children don't occupy maxConcurrent slots — their parent already holds one, and queueing them behind it would deadlock a parent waiting on its own child. The depth cap bounds how deep nesting goes, not how wide: a parent's only limit on concurrent children is that each spawn costs it a turn. Pair allowed_subagents with a max_turns on that agent if you want a hard ceiling on its fan-out.
Because a subagent session never activates this extension (that is what keeps a child from building a second agent manager, and it is why nested tools are injected directly instead), a subagent also gets none of the extension's other surfaces: no /agents command, no cross-extension RPC handlers, no subagents:ready event.
Child project trust
Root startup and reload also gate this package's project subagents.json, .pi/agents, .agents/agents, and custom tool description on the public project-trust decision. Unknown or throwing trust checks skip project configuration before parsing; global definitions/settings and embedded defaults remain available. Global aliases into denied roots are not treated as global authority. The runner rejects a denied project-owned definition (including the actual fallback definition) before package resolution, even when it requests an extension outside the project or in a global package cache.
On Pi 1.0.4, children capture the parent's public isProjectTrusted() decision and configuration root before resolving child resources. Missing or failed trust checks mean denial; children never prompt for or grant project trust. The child passes its derived trust explicitly to SettingsManager, governing project settings merging, resource/package discovery, and project MCP configuration. Denied project settings and MCP configuration are not merged. Before the SDK reads native global settings.json or mcp.json, the child checks their lexical and canonical sources against every retained denied root. A global file or agent-directory alias into a denied root fails child admission, even with extensions disabled; genuine global files and aliases into an approved parent remain usable.
Trust belongs to the configuration root, not the tool execution directory. Execution in a worktree with configCwd pinned to the trusted parent retains that parent's resources (including MCP configuration), also for nested children. A different configuration root, including a newly copied worktree, is not implicitly trusted. Resource checks retain all denied ancestor roots, including the captured parent root whose trust was denied and the child's untrusted configCwd. Lexical and canonical path checks apply to explicit, configured, and discovered extension paths before their factories execute, and to discovered/preloaded skills before instruction injection. Changing cwd/configCwd, naming an explicit parent extension, or using a symlink/package alias cannot re-admit the denied source. Named skill preloading also checks global-root ancestor aliases against all retained denied roots.
Genuine user/global resources outside denied roots remain available subject to the child's normal policy. This is a resource-source and instruction-loading boundary, not an OS sandbox: arbitrary allowed global extension code can perform its own I/O or register MCP servers; project trust does not contain that code's behavior. isolated, extensions: false, named extension selection, and skill restrictions still narrow access independently. Project context files and appended prompts remain suppressed in child resource loading; append-mode agents receive only the parent's already-built system prompt.
Tool & extension scoping
extensions: decides which extensions load, tools: decides which tools surface to the LLM. They compose:
# Default (both omitted): child-safe extensions load, all 7 built-ins surface
tools: read, grep, find # narrow to listed built-ins; extensions still load
tools: "*" # all 7 built-ins (alias: `all`)
tools: none # zero built-ins (alias: `""`)
tools: "*, ext:mcp/search" # built-ins plus one extension tool
extensions: false # no extensions load
extensions: [mcp] # only mcp loads
extensions: ["*", "/abs/foo.ts"] # all defaults plus one path-loaded extension
exclude_extensions: pi-notify # everything except pi-notify (with extensions: true)
# Specialist: load one extension, expose only one of its tools, keep built-ins
extensions: [mcp]
tools: "*, ext:mcp/search"
isolated: true # hermetic: built-ins only, no extensions/skills/context
A few rules the examples don't make obvious:
extensions:is the loading authority, subject to child lifecycle safety.pi-backgroundis excluded before its factory loads, including under default inheritance and"*"; anextension-error:…warning names it. Its jobs can wake a retained child aftersession.prompt()finishes, when the manager has marked the record terminal, released its concurrency slot, and removed its worktree. Until manager-owned asynchronous continuation exists, explicitly selectingpi-background(by name, alias, package path, or entry path) is refused by name rather than silently dropped.ext:pi-backgroundcannot override this boundary and warns as an unloaded selector. Ordinary extensions remain available; parent sessions are unaffected. This does not add or change another extension's settings.ext:foointools:narrows what surfaces; it can't loadfooon its own. Mismatches fireextension-error:…warnings.- Any
ext:entry flips extension tools to an explicit allowlist — unnamed extensions still load (handlers fire) but expose no tools. Sotools: "*, ext:mcp/search"exposes onlysearchfrommcp, nothing from any other extension. - Extension names match case-insensitively (
[Mcp]=[mcp]); tool names inext:foo/barstay case-sensitive. - Extensions that register tools lazily work too. MCP-backed extensions typically can't enumerate their tools until their servers connect, so they register from
session_startorbefore_agent_startrather than at load. Subagent scoping is re-derived as tools appear, including underext:selectors. Scoping does not override Pi's own exposure:deferred/codemodetools anddefaultActive: falsetools are not activated for the model, and tools deliberately deactivated during a session stay inactive on later turns. Pi may still allow nestedctx.executeTool()calls to deferred/codemode tools, but the child's selector andask_tools:approval gate both model-issued and nested calls. - A configured
defaultToolTimeoutMsbounds the publictool_callpolicy and each selected extension handler using one deadline per call, including nested calls and handlers registered later withpi.on. On expiry the call is blocked; a Promise race returns control but cannot cancel side effects of a misbehaving hook that keeps running. Interactiveask_tools:confirmation is human decision time, not tool execution time: the deadline starts after approval. The separate execution timeout can still abort a tool whoseexecute()hangs. - When the SDK exports public codemode, tool-search, and MCP factories (as on the verified Pi 1.0.4 baseline), they are included as built-in extensions in children.
extensions: falseomits them;extensions: [mcp]selects MCP;exclude_extensions: mcpremoves it; andtools: ext:codemode,ext:tool-search, orext:mcp/<tool>narrows their tools like any other extension. Capability checks omit factories that the host does not export; this fallback is not an older-host support guarantee. Whenever extensions are enabled, the child uses Pi's public package resolver to select discovered paths before loading factories. Thus an excluded extension cannot register a conflicting/mcpcommand and displace a selected replaceable built-in before filtering. Resolution can perform configured package installs, as Pi's normal loader does, and a selected extension that fails to load is reported rather than silently dropped. - Explicit sources are validated separately before canonical deduplication, so a real file plus its file alias, or two package-directory aliases, load once without falsely reporting a missing source. Ownership names are unioned for admission/exclusion, but whole-package priority uses each resource's declaring package; direct entries use the canonical declaring owner. A package that aliases another package's entry does not lose its unrelated entries when the target is explicitly selected. Directory-backed modules are matched to Node's supported module entry without evaluating factories; undeclared siblings are not attributed to that package.
- An installed package extension matches by its package short name (
@scope/pi-subagents→[pi-subagents]), in addition to its path-derived name (a package whose entry issrc/index.tsalso answers to[src]). Prefer the package name — the path-derived one is incidental. - Plain
tools:typos fail loudly:tools: reed, grepfirestools-error:…instead of silently producing an under-tooled agent. exclude_extensions:wins overextensions:and overext:selectors — an excluded extension never loads and atools: ext:entry can't pull it back. Plain names only (no paths, no*); a name matching nothing fires anextension-error:…warning.exclude_extensions:is not a sandbox: preselection prevents excluded discovered factories from loading into the child, but resolving configured packages can still install code. Allowed extensions run with the Pi process's permissions. Don't rely on extension selection to contain untrusted packages.- Array and string forms are equivalent:
[a, b]=="a, b".
Tools
Agent
Launch a sub-agent.
| Parameter | Type | Required | Description |
|---|---|---|---|
prompt |
string | yes | The task for the agent |
description |
string | yes | Short 3-5 word summary (shown in UI) |
subagent_type |
string | yes | Agent type (built-in or custom) |
tier |
string | no | Model tier for this spawn, by name. Overrides the agent's own default tier. Unknown tiers are rejected, not substituted — see Model tiers |
max_turns |
number | no | Max agentic turns. Omit for unlimited (default) |
run_in_background |
boolean | no | Run without blocking |
supervisor_questions |
"human" / "parent" / "off" |
no | Default human; parent routes only ordinary clarification from fresh non-isolated background calls. No schedules or mode-changing resumes |
resume |
string | no | Agent ID to resume a previous session |
isolated |
boolean | no | No extension/MCP tools |
isolation |
"worktree" |
no | Run in an isolated git worktree |
inherit_context |
boolean | no | Defaults to false; true is refused before child dispatch. Put a sanitized summary in prompt instead |
get_subagent_result
Check status and retrieve results from a background agent.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_id |
string | yes | Agent ID to check |
wait |
boolean | no | Wait for completion |
verbose |
boolean | no | Include full conversation log |
Cancelling a wait: true call (for example, with Esc) stops only the wait. The background agent keeps running, and its completion notification still arrives normally.
steer_subagent
Send a chat message to a running or queued agent. A running agent receives it after the current tool execution; a queued agent receives it when its session starts.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_id |
string | yes | Agent ID to steer |
message |
string | yes | Message to inject into agent conversation |
answer_subagent_question (main only)
Answer a current opted-in background child's ordinary clarification: questionId is the bounded one-use ID in extension context; answer is at most 2,000 characters, an offered option:N token for choices, or empty to decline. Forged, expired, cancelled, or stale IDs and unoffered choices are rejected. The tool is never inherited or injected into children and cannot grant permission or override a human rejection.
Commands
| Command | Description |
|---|---|
/agents |
Interactive agent management menu |
The /agents command opens an interactive menu:
Agent runs (2) · 1 running · 0 queued · 1 completed · 0 wrapped up · 0 stopped · 0 aborted · 0 failed
Agent types (6) · unified list of defaults and custom agents
Model tiers (3) · the (model, thinking) catalogue
Create new agent
Settings
- Agent runs — select any retained run with a session to open its conversation viewer. Queued runs without a session remain listed but are not openable until they start; selecting one offers a stop confirmation, and
@handle messagequeues a chat message. The summary reports running, queued, completed, wrapped up, stopped, aborted, and failed buckets separately. While a run is still active, press Enter to open the chat composer, then Enter again to send a message that redirects the agent (same mechanism as thesteer_subagenttool; Esc or an empty submit returns), or pressxand follow the displayed explicit confirmation to stop/abort it — including background agents, which a global Esc can't unambiguously target (Esc still stops a blocking foregroundAgentcall). A stopped agent reports its partial output flagged as incomplete, not as a completion. - Agent types — unified list with textual source labels (
project,global, anddisabled). Each row shows the agent's model, and the highlighted agent's full description appears below the list. The model column flags(unavailable, fallback: inherit)when a configured model can't be resolved (it would silently inherit the parent model), and shows(resolved: provider/id)when it resolves to a different provider or version than configured. Select an agent to manage it:- Default agents (no override): Eject (export as
.md), Disable - Default agents (ejected/overridden): Edit, Disable, Reset to default, Delete
- Custom agents: Edit, Disable, Delete
- Disabled agents: Enable, Edit, Delete
- Default agents (no override): Eject (export as
- Eject — writes the embedded default config as a
.mdfile to project or personal location, so you can customize it - Disable/Enable — toggle agent availability. Disabled agents stay visible in the list with a
disabledlabel and can be re-enabled - Create new agent — choose project/personal location, then manual wizard (step-by-step prompts for name, tools, model, thinking, system prompt) or AI-generated (describe what the agent should do; a sub-agent writes a unique same-directory staging file, and the parent parses and compare-commits it to the target). Any name is allowed, including default agent names (overrides them)
- Model tiers — create, edit and delete the tier profiles. Each row shows what the tier resolves to on this machine (
small — claude-haiku-4-5 · thinking max), and a tier dropped as malformed is listed asblockedso it can be fixed rather than staying invisible. The model picker offers this machine's available models (narrowed to your scope when Scope models is on) plusinheritand a typed escape hatch; the thinking picker offers only the levels the chosen model actually supports, since the rest would be silently clamped at spawn. Deleting the tier thatdefaultTiernames clears the default in the same step - Settings — configure max concurrency, default max turns, grace turns, default model, default tier, and join mode at runtime
Graceful max turns
Instead of hard-aborting at the turn limit, agents get a graceful shutdown:
- At
max_turns— steering message: "Wrap up immediately — provide your final answer now." - Up to 5 grace turns to finish cleanly
- Hard abort only after the grace period
Each resumed prompt gets its own counters, not an unlimited continuation or a deduction from prior turns. This applies to foreground/background resumes, nested children, mentions, and reused managed threads. Agent frontmatter remains authoritative; without a per-resume override, the original invocation's turn limit is inherited, falling back to configured defaults. Resume overrides use the same non-negative safe-integer validation as spawning; caller zero cannot disable an inherited/configured cap. An authoritative frontmatter zero remains unlimited, even after an earlier finite invocation. Nested resumes reload the same branch-local definitions as nested spawns, never the root registry; an internal resume without scoped policy retains its admitted invocation budget. A retained session's admitted turn policy, including unlimited, survives later project-default changes; the manager supplies that same effective cap to queued and running progress trackers. The existing token/tool-call caps and tool timeout also apply to resumed prompts. Budgets are event-based: an already-running provider/tool can overshoot within one event or ignore abort; these are not process-isolation guarantees.
Confirmed shutdown retires grouped completion, batch-finalization, h