Skip to content

v2.9.0

Latest

Choose a tag to compare

@edxeth edxeth released this 07 Sep 18:17

Install:

pi install git:github.com/edxeth/pi-subagents@v2.9.0

Or latest:

pi install git:github.com/edxeth/pi-subagents

✨ Features

Per-child skill visibility: =auto and =manual annotations

Refs: 3962b96e, c0be1a1d

Entries in an agent's skills: allowlist can now carry a visibility annotation:

skills: context7=auto, torpathy=manual, tdd

name=auto advertises that skill in this child's <available_skills> prompt block even when its SKILL.md sets disable-model-invocation: true. name=manual does the opposite: the skill disappears from this child's prompt only, while staying advertised in your main session and other agents. Plain names, all, and none keep their exact previous semantics, and the skill itself always stays loaded — only what the child's model sees changes.

The grammar is validated at launch time: unknown annotation values (=banana), annotations on all/none, conflicting annotations for one skill, and annotations inside inject-skills all fail with clear errors. The resolved spec travels with the child on launch, resume, and timeout wrap-up, so child-local annotations cannot leak into a grandchild or be injected through frontmatter env:. Visibility is prompt-scoped: no SKILL.md file is ever written, and the annotation is reconciled every turn, so it holds for the whole session even if an external skill manager rewrites frontmatter.

The annotation rewrite follows Pi 0.85's advertisement rules: the <available_skills> block renders when the child has read or bash (with the correct wording for each), children with neither — tool-adapter dialects like codex — still get the structured-list correction their own renderer consumes, and prompts carrying duplicate native sections can no longer leak a =manual skill. This release requires @earendil-works/pi-coding-agent >= 0.85.0 (previously >=0.79.10).

Switch orchestrator mode mid-session, with remembered preferences

Refs: d08f2bef

Orchestrator mode is no longer frozen at startup from PI_ORCHESTRATOR_MODE. Open /subagents or press Alt+S and pick the new Orchestrator tab: "Enable/Disable in this session" switches in place immediately, keeping your conversation; "Start a fresh … session" opens a confirmation (Cancel selected by default, guards rechecked on confirm) and seeds the new session in the opposite role — the old conversation stays available via /resume. A third action, "Default for new sessions", saves the global default to pi-subagents.json next to your Pi agent config.

A runtime controller now owns the mode. It narrows the session's tools to the delegation set (subagent, subagent_resume, subagent_kill, set_tab_title), restores your previous active-tool baseline when disabled (including across cold restarts), replaces the system prompt while preserving APPEND_SYSTEM.md content, and re-narrows every turn — so a disallowed tool call is blocked with an explicit message even if another extension reactivates a tool. Switching is refused while the parent is running, retrying, compacting, has queued messages, or while children run — nothing is aborted or queued automatically.

Each conversation's choice is persisted as a non-LLM custom entry on the session branch and wins over the environment and the global default on reload, restart, resume, tree navigation, and top-level forks. PI_ORCHESTRATOR_MODE=1/0 still overrides the saved default for new sessions only (any other value errors at startup), and children never inherit the parent's role.

Nothing is registered when you have no agent definitions

Refs: c3376ab2

Sessions without any named agents no longer get the subagent, subagent_kill, subagent_resume, and set_tab_title tools, the /subagents command, the Alt+S shortcut, message renderers, or delegation instructions in the system prompt — previously several hundred tokens injected into every agent-less session for a surface the model could never use.

Pi re-runs extension factories at every session boundary (/new, /resume, /fork) and on /reload, so creating an agent file and crossing a boundary restores the full surface. Trade-offs: an agent created mid-session stays invisible until a session boundary, and with all agent files deleted, new sessions lose subagent_resume/subagent_kill/overlay access to old helper sessions.

🐛 Bug Fixes

Headless parents always read the reports they wait for

Refs: ef509522

A background parent (pi -p) forces every launch synchronous because it has no durable turn for later steer delivery — but the bookkeeping still classified those launches as async, so Pi could stamp a terminate mark on the awaited result, skip the follow-up LLM call, and exit without reading the report it had just waited for. This closes the failure where a headless parent combined coordinator-only turns, a spawning agent, and auto-exit.

Four fixes at the wait boundary: forced-sync launches are recorded as blocking batches; an awaited result can never carry the stop mark; subagent_resume from a headless parent now waits like a launch instead of detaching and orphaning the resumed child; and a child finishing inside an auto-exit parent's launch turn is delivered as a steer (which Pi drains after a terminating batch) instead of nextTurn, which auto-exit would close on unread.

Roster honesty: a headless parent's roster now shows tool_return: wait_here for every agent, and the pi -p tool guidance says the report arrives in the tool result — so the model stops planning around later reports that actually arrive inline. Interactive parents keep later_message and the coordinator-only-turn guard unchanged. PI_SUBAGENT_DISABLE_COORDINATOR_ONLY_TURN remains a supported kill switch but is no longer needed.

caller_ping can no longer be looped forever

Refs: df7714b6

A child model stuck in a tool-call loop could ring caller_ping forever (748 calls over 39 minutes in one real-world session): graceful shutdown is deferred until the agent goes idle, a model that keeps emitting tool calls never gets there, and the parent consumed the ping only on process exit — while every call returned the same success text, rewarding the loop.

The first durable ping now writes the sidecar synchronously and arms a 750ms hard-exit backstop, so exit 0 always delivers it. Repeat attempts get a plain tool error instead; operator takeover (interrupt or input) suppresses and cancels the pending exit, and /auto-exit re-arms it. The tool also no longer claims delivery when a done/error outcome already owns the child.