This release makes subagents much cheaper to run from the parent. The tool definitions the parent sends on every request are about 80% smaller, long child reports arrive as a short preview plus a file path, and upgrading pi-subagents no longer costs resumed sessions their prompt cache. Delegating also takes fewer turns: you launch agents by name, and a wrong name comes back with suggestions instead of a required lookup first. Background children can now run inside a sandbox, terminals can show each run's state, and a long list of reliability fixes covers schedules, async results, wakes, and untrusted projects.
Highlights
- Far fewer tokens. The
subagenttool's schema and description dropped from about 18,700 to 3,300 characters per request,bg_waitfrom about 4,500 to 1,100, and the bundled role prompts are about 20% shorter. Long child reports and guide pages now show a short preview plus a file path. - Prompt cache survives upgrades. Resumed sessions keep the tool definitions they already have, and upgrading under a running Pi no longer breaks every child launch.
- Fewer wasted parent turns. Wrong agent or model names come back with suggestions, the new built-in
parallelworkflow runs a batch of children without a script, and a parent woken by a result handles it instead of going quiet. - Sandboxes and terminal status.
launcher:runs background children inside a sandbox with steering and results intact, Ghostty and similar terminals show each run's state, and tmux users get a bundled inspector pane. - Untrusted projects stay untrusted. A session that declined project trust no longer takes models, agents, or settings from that project.
Need to know
| Who | What changes |
|---|---|
| pi-intercom users | The fix for "Agent is already processing a prompt" when both extensions wake the parent at once needs the matching pi-intercom release. |
| Sessions that declined project trust | Project agents, chains and subagent settings are ignored, and agentScope: "project" is rejected.
|
| Missions created through a symlinked path | The mission store is now keyed by the project's real path. Missions created earlier through another path stay on disk but are no longer found through that path. |
Changelog
Highlights
- Subagents cost the parent far fewer tokens. The
subagenttool's schema and description dropped from about 18,700 to 3,300 characters per request,bg_waitfrom about 4,500 to 1,100, and the bundled role prompts are about 20% shorter. Long child reports and guide pages now arrive as a short preview plus a file path instead of the full text. - Upgrading pi-subagents no longer costs resumed sessions their prompt cache, and upgrading under a running Pi no longer breaks every child launch.
- Fewer wasted parent turns: launch an agent by name and a wrong agent or model name comes back with suggestions, the new built-in
parallelworkflow runs a batch of children without a script, and a parent woken by a result handles it instead of going quiet. - Background children can run inside a sandbox with
launcher:, terminals such as Ghostty show each run's state, and tmux users get a bundled inspector pane. - A session that declined project trust no longer takes models, agents, or settings from that project.
Added
- Running independent subagents in parallel needed a JavaScript workflow script that the model had to write in its reply, and some parents had to save it to a file first. The built-in
parallelworkflow takes the children as data:subagent({ workflow: "parallel", args: { tasks: [{ agent, task }, ...] } })runs them in one batch and returns each child's output in order. The tool schema is unchanged. (#2773) - Background children could run in a sandbox only through an external CLI runner, which loses native steering and results. An agent can now set
launcher:to a command from the user config'srunnerLaunchers, such as a sandbox, and that command wraps its background runner. Steering, supervisor questions, stop, resume and results keep working. Thanks to @aaronkyriesenbach for the idea, design and nono testing. (#2577) - A terminal could not tell which background subagent runs were working, waiting on you, finished, or failed without reading the screen. Each run's state is now reported with OSC 7501, the Program Status Protocol, which terminals such as Ghostty can show next to Pi's own status. Set
programStatus: falseorPI_PROGRAM_STATUS=0to turn it off. (#2724) - Fleet's
Enter/Hinspector needed Herdr or Ghostty, so tmux users had to write their own provider. A bundledtmuxinspector now opens the read-only inspector in a split pane of the current tmux window, with status and close support. Thanks to @tobymao for the plugin. (#2719) - The async widget under the editor gives each active background run two or more lines while the terminal has room, and
asyncWidgetCollapsed: truefolds it to a count that names no run. SetasyncWidgetLayout: "rows"to show a header and one line per run; clicking the header still folds the widget, and Pi's expand key still shows the details. Thanks to @pwguler for #2738. - Session browsers and usage tools could link a subagent run to the session that launched it only by parsing the directory layout. A new child session's header now records the launching session's file as
parentSession, the same field Pi writes for forks, and the run's_meta.jsonrecordsparentSessionId. Thanks to @yingzhi0808 for the request. (#2763) - A host that shows subagent activity could not tell which run a
subagent-notifycompletion notice belonged to without parsing its text. Each entry in the notice'sdetails.runsnow carries the run id (runId, plusworkflowRunIdand per-childchildRunsfor workflows), status, duration and saved paths. The text the model sees is unchanged. Thanks to @kushaldotdev for the request. (#2788)
Changed
- The
subagenttool sent about 13,000 characters of parameter schema on every model request, mostly for management fields a typical delegation never uses. The schema now listsagent,task,workflow,args,async,model,cwd,worktree,output,action,idandmessageat the top level and every other field inside oneoptionsobject, which cuts it to about 2,100 characters. Management fields and therunId,maxRuntimeMsandisolationaliases still work at the top level, so existing calls and resumed sessions are unaffected. An unknownoptionskey gets the closest valid name, and a field given in both places with different values is rejected. Docs, guides, skills and the run hints in tool results now showoptions. (#2767) - The default
subagenttool description took about 5,700 characters on every model request, and the rule about when delegation is allowed appeared three times. The description is now about 1,200 characters and states each rule once: how to call the tool, that async runs wake the parent on their own, one writer per worktree, and not to silently switch to another runner after a failure. It points to the guide for workflow scripts, resume and external CLIs. The tool no longer registers a prompt guideline, and its prompt snippet no longer repeats the delegation rule.toolDescriptionMode: "full"keeps the old detail inline. (#2768) - The
bg_waittool's description and parameters took about 4,500 characters on every model request, and each of its five parameters repeated that ordinary async runs already wake the session. They now take about 1,100 characters: the description says it once and lists the call shapes, and each parameter has a short label. (#2769) - The
subagenttool description told the parent to call{action:"list"}before delegating and{action:"models"}before choosing a model, which cost an extra parent turn each time plus 1,300–2,000 tokens that stayed in history. It now says to launch by name with an exactprovider/id. An unknown or disabled agent fails with a "did you mean" and the available agents with one-line descriptions (at most 30). An unknown or ambiguous model fails with up to 5 of the closest model ids.listandmodelsstill work on demand. (#2771) - The catalog of agents marked
advertise: truealso told the parent to call{action:"list"}before every delegation. Now that a launch with an unknown agent returns the available agents, new sessions no longer get that instruction. A resumed session keeps the catalog text it already has, so the upgrade does not change its prompt or cost it the provider's prompt cache. (#2791) - Changing a pi-subagents tool description, schema, prompt snippet, or guideline made every resumed or reloaded session re-declare that tool before its next request, so the provider re-read the whole conversation without its prompt cache. The
subagent,bg_waitandsubagents_enabletools, and a child session'sbg_waitand fanoutsubagent, now keep the definition and prompt text the session already declared. Only sessions that never declared a tool get its current definition. (#2778) - Every turn of a child launched with a bundled role resent its full role prompt, and those prompts repeated their own rules and the supervisor instructions that are already appended. The
oracle,reviewer,worker,researcherandscoutprompts drop the repeats and keep every rule, going from 21,283 to 17,139 characters in total (oracle 6,059 → 4,789, reviewer 5,211 → 4,443, worker 4,296 → 3,083). (#2775) - Async completion notices, completed-run
statusand foreground results put a child's whole output into the parent's context (about 38,000 characters for one long report), and every async launch receipt repeated about 870 characters of waiting guidance. They now show the first 2,000 characters of a longer output followed by the path, size and line count of the file that holds all of it; output with no such file stays whole. Workflow child previews are capped at 2,000 characters, structured-output previews instatusat 500 when a path line follows, failure notices now include anError:line even when the child produced output, and the receipt guidance is two short lines. (#2774) subagent({action:"guide",topic})returned a whole document, up to 62 KB fortool-reference, and that text stayed in the parent's history for every later turn. A docs-backed topic now returns its intro and a list of sections, andtopic: "<topic>/<section>"returns one section of at most 8,000 characters.overviewandcouncilare unchanged. (#2772)- Each message pi-subagents put in the main chat had its own look: bordered cards for supervisor requests, replies and attention notices,
✓/✗lines for completions, plain colored lines for steering, and Pi's default block with raw labels such as[subagent-wait-subscription]for reminders and wakes. They now all use Pi's own message block, the one behind[skill]: a single[subagent]line whose label color names the kind (supervisor question or attention, reply, result, or watchdog) and whose status word uses the theme's status color. A click opens one block and Pi's expand key opens them all, showing the text the main agent read. Thanks to @pwguler for #2753. - Subagent model displays (Fleet, the async widget, run status, nested renders and result cards) showed only the model id, so two providers or logins serving the same model looked identical. They now show
provider/model. Thanks to @IdrisGit for #2734.
Fixed
- A session that declined Pi project trust still took subagent models,
modelScopeand agent definitions from the project, although Pi loads nothing from an untrusted project's.pidirectory. Such a session now ignores project subagent settings, project agents and chains, and agents from packages installed for the project or declared in itspackage.json. The default scope behaves asuser, and an explicitagentScope: "project"is rejected. Public preflight takesprojectTrustedto match. Thanks to @Saturnoon for the report. (#2764) - In a session that declined project trust, a malformed project
.pi/settings.jsonstill made subagent discovery and launch checks fail with "Failed to parse settings file", and a project'sagentExcludeDirscould still hide user agents. An untrusted lookup now reads nothing from the project's settings; trusted lookups still report a broken file. (#2804) - After pi-subagents was upgraded under a running Pi session, every child launch could fail at once with "Extension path does not exist" or MODULE_NOT_FOUND, because the paths of the child runtime extensions and the async runner kept the file extension (
.tsor.js) of the copy loaded at startup. They are now found on disk at each launch, preferring.js. Thanks to @barjatiyasaurabh for the fix (#2761) and @gitwyy for the report. (#2780) - A parent could wake for a supervisor decision or a finished workflow, reply with nothing, and leave the delegated work unhandled until you sent another message. The wake message now asks the parent to handle the result within the authority it already has. If it still replies with nothing, it gets one more turn and then a short notice that the work is unhandled, never an automatic approval. A visible reply, including a question to you, does not force another turn. Thanks to @CamAnNguyen for #2716.
- When a subagent notice and an intercom message reached an idle parent at the same moment, both extensions started a turn, and the second failed with
Extension "<runtime>" error: Agent is already processing a prompt. pi-subagents and pi-intercom now share one wake per session, so the second message joins the turn the first one starts. This needs the matching pi-intercom release. - A message you typed while a blocking
bg_waitwas open sat in Pi's queue until the wait ended, which could take the whole wait window (30 minutes by default). A steer or follow-up from you now ends the wait with a non-erroruser_inputresult that lists the work still running; the work keeps going. Messages sent by extensions do not end the wait. Thanks to @nuzayets for #2736. - A scheduled run whose async runner died before writing a final status kept its schedule's claim, so every later fire was recorded as
skipped_overlapand the schedule did nothing until someone deleted itsactive.lock. A fire that overlaps an active run, and a session that restores the schedule, now check whether that run is still alive: when its process is gone, or its status has not changed for 24 hours, the run is recorded asfailed_runwith the reason and the due fire launches. A run whose runner is alive is still skipped. Thanks to @kmatzen for the report and the first fix. (#2806) - Starting Pi in the home directory failed with "Project schedule root ... resolves outside the real project" when
~/.piwas a symlink to a directory outside home, such as a mounted drive. When the project is the home directory, the real user config directory is now accepted as a schedule root; a project.pithat links outside its project is still rejected. Thanks to @mecha for the report. (#2737) - A host still handling a background run's paused result could delete or overwrite the final result written after it under the same run id, or remove that run's session, run and mission indexes. When the final result could not replace the paused file (EPERM/EACCES), it was delivered over and over without completing. Results are now written and consumed under a per-run lock, and a consumer leaves alone any result written after it read the run, along with its indexes. (#2744)
- When a workflow awaited an async child, it could read the child's result before the child had written its index entries, so those entries were written after the workflow cleaned up and stayed behind in the child's run directory. A child now writes all index entries before its result becomes readable. (#2741)
- A foreground child that detached to ask its supervisor kept running after a reload or session switch. Status and resume then said "Async run not found", and its workflow showed as stopped. Reloading pi-subagents now stops each detached foreground child and records it as stopped, so status shows the stop and its cause, resume works from its session, and its pending supervisor question is closed. Thanks to @Saturnoon for the report. (#2762)
- Interrupting a run id that could not be resolved interrupted the newest unrelated running job instead, and a nested async child whose owner had no foreground route to it could not be interrupted at all. An explicit interrupt id now fails when it does not resolve (a bare interrupt still picks the newest run), and an interrupt the owner cannot route now goes to the nested child directly. Thanks to @leopepe for the fix (#2760).
- RPC stop refused queued and paused async runs, which the
subagenttool's own stop action already handles. RPC stop now follows the tool's stop rules, with the same session and process checks. Thanks to @alexei-led for the fix (split from #2717). - An RPC client that lost the spawn reply for a direct async run could not find the run by its tool-call ID once the result was delivered. Direct runs now keep the tool-call ID for status lookup after delivery, and an ID shared by a live run and a finished run is reported as ambiguous. Thanks to @alexei-led for #2717.
- Over Pi's RPC mode, the
/subagentsadmin screen's "Change model" and "Change thinking level",/subagents-fleetand/subagents-stopclosed with no screen and no message, because RPC mode has no custom UI. These screens now use their component only in the TUI; elsewhere they use a select prompt or text instead. Thanks to @gelubodrug for the report. (#2766) - A mission created through one path to a project was "not found" by
mission.show,mission.listor amissionIdlaunch through another path to the same directory, such as/tmp/xand its real path/private/tmp/xon macOS. The default mission store is now keyed by the project's real path, so symlinks, junctions and, on case-insensitive filesystems, letter case no longer split it; a configureddirectoryorglobalIndexDirstill expands against the path as given. Missions created before this change through a path other than the real one stay on disk but are no longer found through that path. Thanks to @hughy for the report and the fix (#2785). (#2783) - When TypeBox was installed only in Pi's own dependencies, every
structured_outputcall failed with "Cannot load typebox/compile for structured output validation" unlessPI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOTwas set. Validation now also looks for TypeBox in the running Pi's package. Thanks to @Saturnoon for the report. (#2765) - Pruned fork context failed with
400 MissingSessionIDwhenforkContext.modelwas an OpenCode or OpenCode Go model, because the summary request carried no session id. It now sends the parent session id and thex-opencode-sessionheader, as the watchdog and prompt-audit requests do. (#2759) - The
subagenttool schema marked thereviewedvalue ofacceptancewithdeprecated: true, which strict tool-schema validators reject with HTTP 400, so the tool failed on those providers. The schema no longer includesdeprecated. Thanks to @kingofdies for the report. (#2713) - Background children failed within a second with "Request timed out" on networks where connecting to the provider takes longer than 250 ms. The background runner used Node's 250 ms per-address connection limit, while Pi uses 2 s; the runner now uses 2 s too. Thanks to @robinchm for the report and the fix. (#2758)
- A foreground child whose model comes from a provider that a parent extension registered gets the provider but not that extension's session hooks, so a provider that needs those hooks failed the first request with an error only the provider understood. That failure now also says the child ran in the foreground without the extension and names the fixes:
async: true, or loading the extension for that agent. Thanks to @tharanee-bit for the report. (#2730) - Prompt sections that other extensions add in
before_agent_startreached a child's transcript but not its provider requests, because the child prompt was finished before those extensions ran. The child's prompt now keeps their sections and adds its own boundary after every other extension. Thanks to @adameq for the report. (#2745) - Every workflow script failed before its first line with "Cannot redefine property: then" (or "Attempting to change value of a readonly property") when the host Pi build froze
Promise.prototypeeverywhere, including the workflow sandbox. The runner now works with a frozenPromise.prototypeand still catches children that were never awaited. Thanks to @albertgwo for #2793. - A workflow script failed with "emit. must be a JSON value; received undefined" when it emitted an object with an undefined field, such as a child result's missing
outputPathMapping, althoughreturnaccepted the same object.emitnow drops undefined object fields and turns undefined array entries intonull, asreturndoes, and workflow script errors report the script's own line numbers instead of one line later. Thanks to @vip0351 for #2751. - A workflow stage that resumed an earlier stage without its own
outputreused the earlier stage's report path, so it failed before launch when the earlier stage ran in the background, and overwrote the earlier report otherwise. It now writes to its own default report path. (#2711) - A background workflow's child rows showed no token usage or turn count, although each child's own status had them and the workflow total was their sum. Each child row now shows the usage and turn count from that child's result. Thanks to @moxuun for the report. (#2715)
- A long-running foreground child re-formatted every past tool call on each progress update, although progress shows only the latest 64, so each update got slower as the child's history grew. Progress updates now format only those 64 calls. (#2704)
- Fleet reread and re-rendered the selected transcript every 750 ms even when it had not changed, which took hundreds of milliseconds per refresh for a long transcript. It now reuses the rendered transcript until the file, selection, width or tool view changes. (#2705)
- A Herdr-placed Pi child's bridge kept every frame it received, so a healthy run failed and started reconnecting after its 1,024th frame. Frames are now released once the session has taken them. (#2706)
- Two inspector opens for the same run or child at once, for example from Fleet and a tool call or from two Pi processes, could each open a pane and save a binding, so one pane could no longer be reached by
inspector.statusorinspector.close, and a close racing an open could miss the pane the open created. Open and close for the same run or child now wait for each other, across processes too. (#2727)