@tylerho/pi-subagents
Background subagents on a pi or Claude Code backend, with fire-and-forget spawn and deferred result delivery.
Package details
Install @tylerho/pi-subagents from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@tylerho/pi-subagents- Package
@tylerho/pi-subagents- Version
0.3.0- Published
- Nov 5, 2026
- Downloads
- 288/mo · 222/wk
- Author
- tylerho
- License
- MIT
- Types
- extension
- Size
- 410.5 KB
- Dependencies
- 2 dependencies · 4 peers
Pi manifest JSON
{
"extensions": [
"extensions/subagents"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@tylerho/pi-subagents
Background subagents on a pi or Claude Code backend, with fire-and-forget spawn and deferred result delivery.
Install
pi install npm:@tylerho/pi-subagents
Subagents
Subagents ports Claude Code 2.1.281's Agent tool to pi: background subagents on the native pi harness, behind one Effect service. (A Claude Agent SDK backend remains in the manager but is not offered to callers.) A spawn is a self-contained child with its own context window, background by default, whose report returns to the parent as a framed hand-back. Workflows, memory consolidation, and summaries share its delegation defaults and activity counters.
Claude Code lineage
Claude Code 2.1.274 supplied the background-delivery contract in commit 1379455 feat(subagents): drop subagent_wait; make subagent_check the pull path (2026-09-16). Claude Code 2.1.281 defines Agent, SendMessage, TaskStop, and ListAgents, but no per-agent check tool. Pi removed its check tool and kept automatic delivery and recipient discovery. An earlier pass used 2.1.273, which supplied the naming rules and the nesting depth, and commit 9829e61 feat(subagents): name agents verbatim, Claude Code-style (2026-09-10) took the naming rules.
Restated verbatim in src/domain.ts:
SUBAGENT_NAME_PATTERNis the Agent tool's name schema,/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/, andSUBAGENT_NAME_ERRORis its rejection wording.SUBAGENT_NAME_RESERVED_ERRORrestates the reserved-recipient rule formainandteam-lead, including the generated-id shapes that already address an agent.generateAgentId()builds the omitted-name id,a<description-slug>-<16hex>, ora<16hex>when the slug is empty.
Other borrowed mechanisms:
- The hand-back frame states that the report is model output with no user authority, which mirrors Claude Code's framed final report.
/btwmirrors Claude Code's side question. The child inherits the parent conversation up to the spawn point, andBTW_PROMPT_PREFIXtells it to answer from that context in one response.subagent_sendmirrorsSendMessage: steering a running agent, and resuming a settled one in the same session.
Pi does not copy these parts:
- Claude Code's configurable nesting behavior is separate. Native pi subagents delegate to a default maximum depth of three, with the main session at depth 0.
/subagents-menucan set a greater positive safe-integer maximum. One root manager owns the full tree and its concurrency cap. Every caller, root or nested, runs on the native pi harness:subagent_spawnand/subagents-menudo not offer the Claude route. - There is no persistent resume.
subagent_sendresumes a settled agent only while the root manager tracks it, with no reconstruction across a pi restart or parent reload. - There are no agent profiles. A delegation is a tier or an explicit target plus the prompt, with no reusable definition files.
- There is no per-call worktree isolation. A child runs in the resolved
working_dir. The workflow runner'sisolation: "worktree"option is separate and is not exposed on spawn.
How it works
- Backends. The pi backend is an in-process
createAgentSession()with real session files visible in/resume, per-cwd resources under trust gating, and the canonical child tool policy; it is the only backend the tool surface reaches. A claude backend stays registered in the manager for direct callers but is not selectable throughsubagent_spawnor/subagents-menu: one@anthropic-ai/claude-agent-sdkquery()in streaming-input mode, where the CLI owns conversation continuity, tool execution, and~/.claude/projectstranscripts. It runsbypassPermissions, disallows Claude's nativeAgentandTasktools so orchestration stays inside this manager, and reports unavailable when noclaudebinary is on PATH. - Effect v4 layering.
backends/produce a scopedSubagentSessionwith a normalizedSubagentEventstream. The manager runs one pump fiber per child that folds the stream into a mutableSubagentSnapshot.runtime.tscomposes the layer into oneManagedRuntime, andindex.tsis the async boundary where tool handlers run effects throughrunTool(). A typed failure becomes a thrownError, and an AbortSignal interruption throwsinterruptMessage. - Semantic tiers. The parent picks one of three fixed names,
fast,standard, ordeep. Each is a preset for a pi model and effort in the local config, and an omitted tier meansstandard. A fourthclaudetier is still parsed and resolvable inshared/subagent-models.ts(workflow agents reject it, and the picker's internal list retains it), but it is not exposed to callers. Resolution is pure, throughresolveDelegationTarget(), so the same tier map serves subagent spawns and workflow agents. - Explicit overrides.
model, with optionalreasoning_effort, is the one-off override path; the harness is always pi. Without a tier, amodelis required (an effort alone is rejected). With a tier, each explicit field replaces that part of the preset:modelreplaces the preset model, andreasoning_effortalone keeps the preset model and its tier source (so no cost-ceiling check). A new model makes the target explicit. A"provider/model"override splits at the first slash, and a bare model uses thestandardtier's provider for the registry lookup. An omitted effort falls back to thestandardtier effort. Spawn results name the resolved model and effort. - Spawn contract.
descriptionis required, a short task label for the UI and the generated-id slug, capped at 200 characters.promptis required and must be self-contained, because a model-origin child cannot see the parent conversation.nameis optional and only controls addressing. - Ids and names. Pi stores one agent handle, not a separate name alias and an internal id. A supplied
namemust matchSUBAGENT_NAME_PATTERN, must not normalize tomainorteam-lead, and must not have the shape of a generated id, or the spawn is rejected. A valid name becomes the id verbatim, with no prefix and no slug, so a parent that setsname: "researcher"passes"researcher"to every lateridoridsparameter. An omitted name getsgenerateAgentId(description), and the parent uses the generated id returned bysubagent_spawn. Repeats get a numeric suffix, sofix-login-bugbecomesfix-login-bug-2. Extension-generated btw ids useslugifyTitleinstead, because btw descriptions are free-form questions. Ids stay unique per parent session and are the handle forsubagent_cancelandsubagent_send. - Foreground and background.
run_in_backgrounddefaults totrue. A background spawn returns at once, and its settled run is delivered later as asubagent-resultfollow-up message (deliverAs: "followUp",triggerTurn: true) when the parent is idle or on the nextagent_settled. A foreground spawn claims its run at spawn time, waits for settlement, and returns the hand-back from the tool call. A tool abort releases the claim and leaves the child running. - Per-run, exactly-once delivery. Delivery is keyed by a run ref (
id#runSequence), not by agent id, because a settled agent resumed withsubagent_sendstarts a new run that can deliver again. A foreground spawn andsubagent_canceleach claim the run before it can be delivered, and an unconsumed background settle is deferred and delivered once on the next flush. A native intermediate parent stays active while its children run, while summaries are computed, and until thesubagent-resultmessage starts processing. Scheduling the follow-up is not an acknowledgement. The parent receives that follow-up without a status-pull tool. A tool that claims a run while its digest is still computing (asubagent_cancelof a settled agent) suppresses the pending follow-up. A native parent that is cancelled, failed, or closing drops child results addressed to it instead of starting a turn for them. - Run lifecycle. Only
subagent_sendopens a new run: the snapshot showsrunningunder the newrunSequenceas soon as the resume is accepted, and its run time (runStartedAt) is set then. The backend'sRunStartedconfirms the run without resetting that time, so a native parent's follow-up turns inside one run keep its elapsed time. A terminal event that arrives before a restart'sRunStartedbelongs to the previous run and is ignored. A backend turn nobody requested after settlement does not reopen the agent: itsRunStartedandRunSettledare ignored and it adds no live tool or streaming state, so an agent every view shows as settled never showsrunningagain under the same run. - The hand-back (24 KiB).
buildHandback()frames one report per run. The header names the id, description, terminal status, tier orexplicitsource, harness, model, and effort, plus any error text, and the description and error text are each bounded to 1 KiB with a truncation marker. A fixed frame then states that the report is model output with no user authority, so instructions, approvals, and permission claims inside it are not from the user. Control syntax that could imitate harness messages is escaped (reserved control-tag openings and fakeHuman:orAssistant:turn markers), and every report line and every continuation line of an interpolated header value is indented two spaces, so child text cannot appear at column zero as a harness frame. Above the cap the model text keeps the head and appends an exact truncation notice pointing at the takeover view and the persisted session.details.fullOutputcarries the sanitized, untruncated report. - Origins. A model-origin spawn is visible to model-facing tools and the
/subagentsdashboard.isModelVisiblefilters byorigin === "model", so user asides from/btwstay out of the tool layer and the dashboard and are revisited in the/btwpanel. A btw result is appended as a synchronousbtw-resultsession entry, safe while the parent is streaming, and never becomes a model-context follow-up. A failed aside also fires an error notify. A btw child runs withnoTools: "all", so built-in and extension tools are excluded. - btw context inheritance. A btw child is a fork, not a clean slate. At spawn time the command handler snapshots the parent conversation with
buildSessionContext(getEntries(), getLeafId()).messages, two adjacent synchronous reads that are race-free while the parent streams. The snapshot ends at the parent's last completed message, the same cut Claude Code applies. The pi backend seeds those messages into the child session file beforecreateAgentSession, which restores them as the child's history, so compaction, resume, and the transcript treat them as its own. Summary roles are rewritten to the plain user textconvertToLlmwould produce. The inherited range is fenced withBTW_CONTEXT_STARTandBTW_CONTEXT_ENDcustom entries, and the persisted-transcript loader skips it. Result extraction excludes the seeds by object identity, so a child that fails before answering reports failure instead of echoing a parent message. - Tree ownership and limits. The main session is depth 0; the default
maxDepthis 3, so levels 1, 2, and 3 may run. A tree's maximum is snapshotted when its root manager is created.MAX_RUNNING = 50andMAX_TRACKED = 4_096apply once across that root's tree; provisional startup and idle restarts reserve capacity before yielding. A child can address only its direct children. Cancelling a node seals and stops its descendants but leaves sibling branches running. Root shutdown closes the whole tree. - Transcripts are bounded in memory and full on disk. The manager keeps 512 transcript items and 64 KiB per text. The takeover view lazily streams the full persisted JSONL, from
~/.pi/agent/sessions/<escaped-cwd>/<timestamp>_<id>.jsonlon pi or~/.claude/projects/<escaped-cwd>/<sessionId>.jsonlon claude, through thesrc/persisted/parsers, with the format auto-detected per record and the file read line by line.mergeTranscriptsmerges the persisted snapshot with the live transcript, and live items always win, so streamed content never vanishes when it finalizes. The persisted load refreshes once when the run settles. The persisted parsers also keep what the live pipeline bounds: the full pretty-printed tool arguments (args) and the full tool output.ctrl+oreveals both in the takeover view, redacted for secret-shaped values, andmergeTranscriptsbackfills full arguments onto a live tool call when its persisted item exists. A live tool call the persisted file does not cover yet falls back to its boundedargsPreview. - Delegation defaults and the cost ceiling. With no explicit model, a spawn runs the tier's configured target from
shared/subagent-models.json, set by/subagents-menu, and never the parent's model. The cost ceiling (disabled by default, set with/subagents-menu, overridden per launch byPI_SUBAGENT_COST_CEILING) applies only on the pi harness and only to an explicit model an agent picked for itself. A tier target, a configured default, and a btw aside are deliberate user choices and are not checked. - Child safety rails.
childToolPolicy()defaults to non-delegating for workflows and memory workers. A bound native subagent receives spawn/send/cancel/list when its depth allows; at the maximum depth the spawn tool is excluded. Retiredsubagent_waitandsubagent_check,workflow,ask_user_question,enter_worktree,exit_worktree, andcode_reviewremain excluded in every child context. The retained claude backend disallows Claude's nativeAgentandTask. Every child tool call is wrapped with a 30-minute execution timeout fromshared/tool-call-timeout.ts, re-applied onagent_startso tools registered mid-session are covered. - Trust gating. A child in the parent's directory inherits the parent's trust decision. An alternate
working_diris trusted only when pi's persistedProjectTrustStoreexplicitly trusts it, and unreadable or invalid trust data fails closed. A claude child in an untrusted cwd getssettingSources: ["user"], so an untrusted project's config cannot reconfigure it. - Progress line. In the root TUI,
createProgressTracker()insrc/progress.tsrefreshes a one-line status for each running model-origin agent everyprogressIntervalMs(30 s by default). Each refresh is one fast-tiersummarizeReportcall withPROGRESS_PROMPT, thinking off and a 40-token cap. Its input isbuildProgressNotes(): the task description, the previous status, and one capped line per item from the last 8 transcript items plus in-flight tools, at most 1,500 characters. The full transcript is never sent. An agent whoseactivityFingerprint()(transcript length plus live tool counts) has not changed since its last refresh gets no call, so an idle or briefly running agent costs nothing. Calls run one at a time, a failed call keeps the previous line, and the line is dropped when the run settles. Tracker state is keyed by run ref, so a resumed run starts fresh. - Agent panel. A
belowEditorwidget (src/ui/task-rail.ts) lists the root's all-descendant read model in Claude Code's panel layout; model-facing list and control tools remain immediate-child scoped. Membership comes from the purepanelRows(snapshots, now)insrc/ui/panel-model.ts: model-origin agents only, a running agent always, adonerun never (it leaves on its settle event), anderrororcancelledruns whilenow - settledAtis underPANEL_LINGER_MS(30 s). Rows are in tree order: roots by creation time, each followed by its shown descendants with├/└connectors, and a running child whose parent left is promoted to a root. Each row shows the pointer, the status glyph (braille spinner in the warning color while running, red✘failed, warning✘stopped), the agent id in a column of at most 28 cells, the activity (the progress line, else the description;Failed: <first error line>orStoppedonce settled), and right-alignedelapsed · ↓ tokens · cost, plus· N queuedin the warning color. Elapsed runs tonowwhile running and stops atsettledAt. The activity truncates before the stats.panelWindowshows at most 5 rows with↑ N moreand↓ N more, and keeps the selection on the row that took a departed row's place. The spinner ticks once a second only while something runs, andnextExpiryarms one timer for the next lingering row, so rows clear without other events. Unfocused, the last line is↓↓ to manage. In the default editor view with empty editor text, a lone down-arrow reaches the editor and a second within 500 ms focuses the panel on its first row; then up and down move without wrapping (up at the top returns focus), Enter opens takeover,xstops a running agent, Esc closes, and any other key returns focus to the editor and reaches it. A focused panel that empties closes itself. Finished agents stay reachable through/subagents. Key release and repeat events are filtered, and inside a modal the gesture yields to the focused component. - Lifecycle and cross-extension signals. Native child sessions bind to their manager by
SessionManageridentity before extension hooks run. A persistedsubagent-ancestryentry has only serializable identity; reopening it without its live controller disables delegation. The in-memory tree is not reconstructed after an extension reload or a separate transcript open; shutdown closes the old tree, and the next root manager uses the current depth config. A completion gate keeps a parent active through direct-child work, digest generation, scheduled hand-backs, and actual message processing.agent_settledflushes deferred results. A child shutdown revokes its own subtree and subscriptions without disposing the root runtime; root shutdown disposes the full tree. The manager publishes its running count withreportRunning("subagents", n)at transition points, which summaries reads to gate recaps. Both backends publish child spend throughshared/child-cost.ts: pi adds each assistant message'susage.cost.total, and claude adds only the positive delta of the result message'stotal_cost_usd, so a resumed run is not double-counted. The expanded footer reads that accumulated total, and the takeover header renders the snapshot's ownusage.costUsdthroughformatCost.
API
Tools
subagent_spawn spawns on a tier preset or with an explicit override. Parameters: description (string, required, max 200 chars), prompt (string, required), name (optional string: addressable handle that becomes the id verbatim for every id-based tool), tier (optional "fast" | "standard" | "deep", omitted means standard), working_dir (optional string, must exist and be a directory, default parent cwd), run_in_background (optional boolean, default true), model (optional string, required for an explicit override: "provider/model-id" or a bare id), reasoning_effort (optional "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"). The tool schema has a top-level object with optional target fields, as DeepSeek requires. Explicit fields override the matching part of a tier preset; without a tier a model is required, and the harness is always pi. Results identify the resolved values. A background spawn returns text like Spawned fix-login-bug "fix login bug" (preset "standard" → harness="pi", model="deepseek/deepseek-v4-pro", reasoning_effort="high"). plus the tail telling the parent to report what it launched and to say the agent is still running if the user asks. A foreground spawn returns the same header plus Waited for it; the report follows. and the run's hand-back. details: { id, description, cwd, harness, tier, model, effort }. Rejections: the 50-agent cap (ConcurrencyLimitError), an invalid or reserved name, an explicit effort without a model, a bad working_dir, an unavailable configured or requested model, and an over-ceiling explicit model on pi.
{
"description": "fix login bug",
"prompt": "Refactor src/util.ts: split the two exported functions into separate files, run the test suite, report the diff stat.",
"name": "fix-login-bug",
"tier": "standard"
}
subagent_send steers a running subagent or resumes a settled one in the same parent session. Parameters: id (string: the supplied name, or the generated id returned by subagent_spawn), message (string). While the agent runs the message is a course correction to the active run. When the agent is settled the call starts another turn in the same child session as a new runSequence and returns the Resumed acknowledgement. A steer returns Steered <id> (run N). The transcript shows the first line of message as a one-line preview, so the message should open with a self-contained sentence saying what it is about. A resumed turn counts against the concurrency cap, and a backend rejection rolls back the restart reservation and the snapshot's previous status. A send to an agent with a subagent_cancel in flight is rejected until the cancel reports. details: { id, runSequence, restarted }. An unknown or pruned id fails through the shared unknown-id error, and a SendError surfaces to the model and inline in the takeover view.
subagent_cancel aborts running subagents. Parameters: ids (array of string: supplied names or generated ids). The call claims each run before interrupting (a 5 s graceful session.interrupt with a force-close fallback that settles first, so the stream-ended fallback cannot misreport), waits for settlement, and reports Cancelled <id> "<description>". or <id> "<description>" was already <status>. followed by each run's hand-back. Cancelled (and cancelled: true) means the run settled as cancelled; a run that finished or failed on its own while the interrupt was in flight reports was already done or was already error, partial output included. It consumes the deferred automatic delivery for every listed run, settled or just cancelled, and the combined report is capped at 48 KiB. details: { results: [{ id, description, status, cancelled }] }. Partial session transcripts stay on disk.
subagent_list finds the ids of tracked model-origin direct children for subagent_send or subagent_cancel. It is not a completion poll. No parameters. One describeSubagent() line per model-origin child, in the form id [status] "description" (tier, backend: model, token occupancy, elapsed, cwd), or No subagents. when empty. details: { subagents: [{ id, description, harness, status, tier }] }. The root dashboard and task rail show all descendants.
Commands
All three are TUI-only. In a non-TUI mode a command notifies when it has a UI and returns.
| Command | Args | Behavior |
|---|---|---|
/btw |
optional prompt text | Spawns a pi-harness child with origin: "btw" and a description from deriveBtwTitle (first non-empty prompt line, 60 chars max), then opens the BtwPanel bottom dock. The panel lists past asides as /btw <question> lines above the selected aside's answer: left and right switch asides, up and down or j and k scroll, n asks a new question through ctx.ui.input, c copies the answer, ctrl+t toggles reasoning (hidden by default), Esc closes. With no args and history it opens on the newest aside, and with no history it goes straight to the question input. The child runs without tools on a session file seeded with the parent conversation, and its result arrives as a btw-result entry, not a model follow-up. |
/subagents |
none | Opens the fullscreen dashboard of all model-origin descendants in the root tree, with btw asides excluded. j/k or the arrow keys move the selection, Enter takes over, and x aborts a running agent. |
/subagents-menu |
none | Shows all four tier mappings, cost ceiling, maximum depth, and summary settings. Select a tier to choose its model and effort. Each completed edit saves immediately and returns to the refreshed menu. Cancelling an edit leaves the setting unchanged and returns to the menu; Esc in the main menu closes it. Validation and save errors notify and return to the menu. |
The menu replaces /subagent-model, /subagent-cost, and /subagent-depth. It lists the three pi tiers; each uses the curated model list with prices and ceiling markers, then the model's supported thinking levels. The Claude tier is not shown because the route is disabled, but its stored config entry is preserved. The cost ceiling accepts a positive amount or off and shows the effective launch override when it differs from the saved value. Depth requires a positive safe integer and applies to new root managers only.
Summary settings include enabled, input cap, raw report cap, skip threshold (all three in characters), and timeout (milliseconds). Numeric settings require positive safe integers, except the skip threshold permits zero. Empty numeric input cancels. All configuration files remain local-only.
Events
session_startresolves native delegation by session id and reads persisted ancestry from the active branch. A child without its live binding fails closed. It captures theExtensionContext; in root TUI mode it registers the all-descendant task rail throughsetWidget("subagent-task-rail", …, { placement: "belowEditor" })and the rawonTerminalInputhandler for the down double-tap gesture.agent_settledrunsflushResults(), which drains the deferred-result queue and delivers each entry as asubagent-resultfollow-up message. Native parents acknowledge its run ref atmessage_start; a send call returning does not count as the report being processed.session_shutdownclearssessionContext, removes the task-rail widget, unsubscribes the raw-input handler, resets the rail and the result queue, then either revokes a bound child's descendants or disposes its owned root runtime. Childsession_shutdownhooks are emitted with a 5 s bound.
Renderers
subagent_spawncalls in one parent response cycle form one collapsed tree, even when tool results split them across assistant messages. The cycle is keyed to the nearest preceding user message, so the next user message starts a separate group. The first call row showsRunning N agents…while any spawn is pending, thenN background agents launchedwith the↓↓ selectmanagement hint and one├/└description row per call. Failed calls use the error color. Sibling call and result rows are hidden while collapsed. Expanded mode shows each call and its full result independently. A single spawn keeps its own compact call and result rows, split so neither repeats the other: the call row carries only the description, and the result row carries the id, tier, harness, and model. Expanded results add the working directory and reasoning effort; the model-facing spawn text is not echoed, since it is guidance for the parent model and already in the tool result.registerMessageRenderer("subagent-result")renders a settled follow-up in Claude Code's completion format,⏺ Agent "<description>" finished · <elapsed> · <cost>(failed: <first error line>orwas stoppedotherwise), with the dot in the status color and the id standing in for a missing description. Collapsed it adds the one-line digest. It does not display digest-call cost or token counts. Expanded it showsdetails.fullOutputcapped atrawCharCap, with a notice that points to takeover and, when known, the persisted session file. The framed report lives indetails, and the message content stays the model-facing hand-back.registerEntryRenderer("btw-result")renders/btwanswers as session entries. Collapsed it shows theby the way · description · answered/failedheader and a/btw to reopenhint. Expanded it shows the full markdown body.subagent_senduses a compact result-only row after the call completes, so the call and result do not repeat the message preview. The row shows⏵ id · steered/resumed (run N). Expanded mode shows the full message and resume guidance when relevant. Errors show the error text.subagent_cancelrenders one line per result (Cancelled id "description"orid "description" was already status) and puts the per-run hand-backs behind expansion, capped atrawCharCap.- Token occupancy is rendered as a compact token count such as
40k, without a context-capacity denominator or percentage. Run cost remains visible where the snapshot provides it.
Config
<agentDir>/subagent-nesting.jsonholds{ "maxDepth": 3 }by default. Missing or malformed values use 3; valid values are positive safe integers./subagents-menuwrites it atomically. The file and its temporary writes are local-only and excluded from packages and private backup candidates. It does not change model tiers.extensions/shared/subagent-models.jsonholds the persisted version 2 config,{ version: 2, costCeiling, tiers: { fast: { harness: "pi", provider, model, effort }, standard, deep, claude: { harness: "claude", model, effort } } }. The file is local-only (gitignored and excluded from published packages), so a fresh checkout runs onDEFAULT_DELEGATION_CONFIGwith every pi tier ondeepseek/deepseek-v4-proathigh, the claude tier onsonnetathigh(parsed but not selectable), and no ceiling. A legacy version 1{ costCeiling, pi, claude }file still loads:piseedsfast,standard, anddeep,claudeseeds the claude tier, and the ceiling carries over. Migration happens in memory, and the file is rewritten as version 2 only after a save. Read throughloadDelegationConfig(), written atomically (temp file plus rename) by/subagents-menuthroughsaveDelegationConfig().- The env var
PI_SUBAGENT_COST_CEILINGis a per-launch override of the file value, in USD/Mtok output, with"off"disabling the ceiling. <agentDir>/subagent-summary.jsonis the optional local config for the settle-time digest and the progress line:{ enabled, inputCharCap, rawCharCap, skipUnderChars, timeoutMs, progressEnabled, progressIntervalMs }. Every field falls back on its own, so a missing or corrupt file means the defaults.enableddefaults to true,inputCharCapto 8000,rawCharCapto 4000,skipUnderCharsto 300,timeoutMsto 15000 (also bounding each progress call),progressEnabledto true, andprogressIntervalMsto 30000. The menu saves it atomically throughsaveSummaryConfig(). The file and its temporary writes are local-only and gitignored. A fresh checkout runs on the defaults.
Exports
index.ts: default(pi: ExtensionAPI) => void, andconsumeReturnedRuns().domain.ts:BACKEND_NAMES,SUBAGENT_NAME_PATTERN,SUBAGENT_NAME_ERROR,SUBAGENT_NAME_RESERVED_ERROR,validateSubagentName,generateAgentId,REASONING_EFFORTS,runRefOf,latestText,formatElapsed, typesBackendName,SubagentOrigin,ReasoningEffort,SubagentStatus,ParentContext,SpawnTask,SubagentMeta,SubagentSnapshot,formatAncestry,TranscriptPart,TranscriptItem,LiveToolState,QueuedMessage,RunOutcome,SubagentEvent,SubagentRunRef,SubagentUsage, and tagged errorsSpawnError,BackendUnavailableError,ConcurrencyLimitError,SendError.backend.ts:SubagentBackend,SubagentSession,BackendCapabilities,BackendRegistry.backends/pi.ts:piBackend,piChildToolOptions,toSeedMessage,seedInheritedBtwContext,SeedableMessage.backends/claude.ts:claudeBackend,contextOccupancyTokens,costDelta.backends/stub.ts:makeStubBackend,StubProfile. IncontextOccupancyTokens, the per-request assistant-messageusageis the occupancy, and the result message's whole-runusageaggregate must never be used as one.manager.ts:SubagentManager,SubagentManagerLive,MAX_RUNNING,MAX_TRACKED,slugifyTitle,SubagentManagerShape,SubagentOwner,SubagentReadModel,CancelResult,SendResult,SpawnOptions.runtime.ts:createSubagentRuntime({ maxDepth? }),SubagentRuntime,runTool().nesting-config.ts:DEFAULT_MAX_DEPTH,parseNestingConfig,loadNestingConfig,saveNestingConfig,parseDepthArgument,runNestingDepthCommand.delegation.ts:NativeDelegationBinding,createNativeDelegationBinding,registerNativeDelegation,resolveNativeDelegation,serializeAncestry.completion.ts:createCompletionGate,CompletionGate, andDeliveryReservation. The gate tracks SDK turn activity, immediate-child work, and run-ref delivery until message processing.handback.ts:HAND_BACK_MAX_BYTES(24 KiB),buildHandback({ snapshot, output })returning{ modelText, fullOutput, truncated }.prompt.ts: theSUBAGENT_*_TOOL_DESCRIPTIONconstants, the*_PARAMETER_DESCRIPTIONSmaps,SUBAGENT_SPAWN_PROMPT_SNIPPET,SUBAGENT_SPAWN_PROMPT_GUIDELINES,buildNestingPrompt,buildSubagentSpawnResult,describeSubagent,delegationLabel. This is the single source of model-facing strings.by-the-way.ts:deriveBtwTitle,isModelVisible,BTW_TITLE_MAX_LENGTH,BTW_PROMPT_PREFIX,BTW_CONTEXT_START,BTW_CONTEXT_END,isBtwContextStart,isBtwContextEnd.result-delivery.ts:runRefKey,acknowledgeDeliveredResult,createDeferredResultDelivery.format.ts:formatContextTokens,formatCompactTokens,formatCost,capChars,truncationNotice,ContextUtilization.summarize.ts:summarizeReport,shouldSummarize,makeSummarizeDeps,fastSummaryTarget,buildSummarizeRequest,SUMMARY_TIMEOUT_MS,SUMMARY_MAX_TOKENS,SUMMARY_PROMPT,SummarizeResult,SummarizeDeps,SummarizeTarget.progress.ts:createProgressTracker,buildProgressNotes,activityFingerprint,cleanProgressLine,PROGRESS_PROMPT,PROGRESS_MAX_TOKENS,PROGRESS_NOTES_CHAR_CAP,ProgressTracker.summary-config.ts:loadSummaryConfig,parseSummaryConfig,saveSummaryConfig,summaryConfigPath,DEFAULT_SUMMARY_CONFIG,SummaryConfig.ui/:openSubagentPicker,openSubagentTakeover,reconcileDashboardSelection,configuredKeys,statusGlyph,statusWord,takeoverHeader,fitTakeoverHints,DashboardSelection(takeover.ts).BRAILLE_SPINNER_FRAMES,spinnerGlyph,StatusLike,statusGlyph,statusWord(activity-row.ts).SpawnCallArgs,SpawnDetails,SpawnBatchCall,SpawnResult,SpawnGroupMember,SpawnGroupView,SpawnGroupCoordinator,createSpawnGroupCoordinator(spawn-group.ts).completionHeaderText,digestLineText(result-row.ts).openBtwPanel,BtwPanelResult(btw-panel.ts).TaskRailController,railRows,createTaskRail,DOUBLE_TAP_MS(task-rail.ts).inPanel,panelRows,nextExpiry,panelWindow,PANEL_LINGER_MS,PANEL_MAX_ROWS,PanelRow,PanelWindow(panel-model.ts).firstLine,sendRowText,sendCallRowText,sendExpandedText,rowComponent(send-row.ts).findFocusedComponent,isDefaultEditorFocused(focus.ts).buildTranscriptLines,buildBtwAnswerLines,sanitizeText,mergeTranscripts(transcript.ts).pickTierConfig,pickEffort,applyTierSelection,tierTargetLabel,TierPickerUi,TierPickerDeps,TierPickerResult(model-picker.ts).runSubagentsMenu,SubagentsMenuUi,SubagentsSettingsStore(settings-menu.ts).persisted/:readPersistedTranscript(lazy async generator),loadPersistedTranscript,detectSessionFormat,SessionFormat(transcript.ts).parsePiEntry(pi.ts).ClaudeParserandcreateClaudeParser(claude.ts), plus the sharedparseJsonLine,safeJsonPreview,safeJsonArgs,previewOf,textOf,isRecord.
Examples
- Delegate on a tier and keep working.
subagent_spawn { description: "errors survey", prompt: "Read docs/errors.md and list the three most common failure modes with their codes, as a bullet list.", name: "errors-survey", tier: "fast" }returns a background receipt, and the report arrives later as asubagent-resultfollow-up. The parent keeps working or ends its turn when the result is all it needs. The final report arrives as a follow-up without pollingsubagent_listor sending progress requests. - User-requested override. When the user names a specific model,
subagent_spawn { description: "todo sweep", prompt: "In this repo, find every TODO and categorize by owner file; write the result to TODOS.md.", name: "todo-sweep", model: "deepseek/deepseek-v4-pro", reasoning_effort: "medium" }. Omitmodelandreasoning_effortunless the user named them, because a tier already carries the configured model and effort. - Steer or resume.
subagent_send { id: "errors-survey", message: "Also note which codes are retryable." }steers the run while it works. If the agent has settled, the same call starts a new run in that child session, and the next report is delivered like a spawn. - User aside. The user types
/btw what changed in the subagent API in this update?. A child inherits the conversation up to that point and answers while the main agent keeps working. The answer appears as a one lineby the way · …session entry, reopened in the/btwpanel or expanded inline. - Manage.
/subagentsopens the dashboard of model-origin subagents (j/k or the arrow keys select,xaborts a running agent, Enter takes over). In takeover, the view shows a two-line identity and status header, a fixed-height transcript, and a composer. User messages use the parent message surface, assistant text uses pi Markdown, and tool states use the parent tool colors. Thinking starts as a short preview with actrl+thint. Tools use a compact A1 layout with one call line and one result or state line.ctrl+oreveals the full redacted tool arguments and output from the persisted transcript,ctrl+ttoggles thinking, up/down or pgup/pgdn scroll,ctrl+caborts a running child, the interrupt binding closes the view, and typing at the input line steers or continues it. The view follows new output until the user scrolls up./btwwith no args opens the aside panel (left and right switch questions,nasks a new one,ccopies the answer)./subagents-menuchanges a tier's model and effort.