github nicobailon/pi-subagents v0.77.0

4 hours ago

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 subagent tool's schema and description dropped from about 18,700 to 3,300 characters per request, bg_wait from 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 parallel workflow 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 subagent tool's schema and description dropped from about 18,700 to 3,300 characters per request, bg_wait from 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 parallel workflow 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 parallel workflow 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's runnerLaunchers, 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: false or PI_PROGRAM_STATUS=0 to turn it off. (#2724)
  • Fleet's Enter/H inspector needed Herdr or Ghostty, so tmux users had to write their own provider. A bundled tmux inspector 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: true folds it to a count that names no run. Set asyncWidgetLayout: "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.json records parentSessionId. Thanks to @yingzhi0808 for the request. (#2763)
  • A host that shows subagent activity could not tell which run a subagent-notify completion notice belonged to without parsing its text. Each entry in the notice's details.runs now carries the run id (runId, plus workflowRunId and per-child childRuns for workflows), status, duration and saved paths. The text the model sees is unchanged. Thanks to @kushaldotdev for the request. (#2788)

Changed

  • The subagent tool sent about 13,000 characters of parameter schema on every model request, mostly for management fields a typical delegation never uses. The schema now lists agent, task, workflow, args, async, model, cwd, worktree, output, action, id and message at the top level and every other field inside one options object, which cuts it to about 2,100 characters. Management fields and the runId, maxRuntimeMs and isolation aliases still work at the top level, so existing calls and resumed sessions are unaffected. An unknown options key 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 show options. (#2767)
  • The default subagent tool 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_wait tool'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 subagent tool 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 exact provider/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. list and models still work on demand. (#2771)
  • The catalog of agents marked advertise: true also 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_wait and subagents_enable tools, and a child session's bg_wait and fanout subagent, 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, researcher and scout prompts 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 status and 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 in status at 500 when a path line follows, failure notices now include an Error: 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 for tool-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, and topic: "<topic>/<section>" returns one section of at most 8,000 characters. overview and council are 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, modelScope and agent definitions from the project, although Pi loads nothing from an untrusted project's .pi directory. Such a session now ignores project subagent settings, project agents and chains, and agents from packages installed for the project or declared in its package.json. The default scope behaves as user, and an explicit agentScope: "project" is rejected. Public preflight takes projectTrusted to match. Thanks to @Saturnoon for the report. (#2764)
  • In a session that declined project trust, a malformed project .pi/settings.json still made subagent discovery and launch checks fail with "Failed to parse settings file", and a project's agentExcludeDirs could 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 (.ts or .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_wait was 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-error user_input result 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_overlap and the schedule did nothing until someone deleted its active.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 as failed_run with 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 ~/.pi was 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 .pi that 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 subagent tool'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 /subagents admin screen's "Change model" and "Change thinking level", /subagents-fleet and /subagents-stop closed 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.list or a missionId launch through another path to the same directory, such as /tmp/x and its real path /private/tmp/x on 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 configured directory or globalIndexDir still 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_output call failed with "Cannot load typebox/compile for structured output validation" unless PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT was 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 MissingSessionID when forkContext.model was an OpenCode or OpenCode Go model, because the summary request carried no session id. It now sends the parent session id and the x-opencode-session header, as the watchdog and prompt-audit requests do. (#2759)
  • The subagent tool schema marked the reviewed value of acceptance with deprecated: true, which strict tool-schema validators reject with HTTP 400, so the tool failed on those providers. The schema no longer includes deprecated. 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_start reached 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.prototype everywhere, including the workflow sandbox. The runner now works with a frozen Promise.prototype and 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, although return accepted the same object. emit now drops undefined object fields and turns undefined array entries into null, as return does, 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 output reused 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.status or inspector.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)

Don't miss a new pi-subagents release

NewReleases is sending notifications on new releases.