pi-subagents 0.36.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +93 -8
  3. package/agents/delegate.md +2 -0
  4. package/agents/worker.md +2 -0
  5. package/package.json +4 -2
  6. package/skills/pi-subagents/SKILL.md +20 -4
  7. package/src/agents/agent-management.ts +7 -1
  8. package/src/agents/agents.ts +99 -12
  9. package/src/api/capability-ceiling.ts +17 -0
  10. package/src/api/delegation.ts +3 -1
  11. package/src/api/preflight.ts +399 -0
  12. package/src/extension/index.ts +2 -0
  13. package/src/extension/rpc.ts +4 -0
  14. package/src/extension/schemas.ts +8 -2
  15. package/src/extension/tool-description.ts +2 -0
  16. package/src/runs/background/async-execution.ts +125 -14
  17. package/src/runs/background/async-resume.ts +24 -7
  18. package/src/runs/background/async-status.ts +18 -0
  19. package/src/runs/background/process-terminal.ts +280 -0
  20. package/src/runs/background/run-status.ts +7 -1
  21. package/src/runs/background/scheduled-runs.ts +6 -1
  22. package/src/runs/background/stale-run-reconciler.ts +6 -0
  23. package/src/runs/background/subagent-runner.ts +182 -12
  24. package/src/runs/foreground/chain-execution.ts +5 -0
  25. package/src/runs/foreground/execution.ts +59 -13
  26. package/src/runs/foreground/subagent-executor.ts +32 -2
  27. package/src/runs/shared/acceptance.ts +41 -30
  28. package/src/runs/shared/capability-ceiling.ts +177 -0
  29. package/src/runs/shared/dynamic-fanout.ts +1 -1
  30. package/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
  31. package/src/runs/shared/nested-events.ts +8 -1
  32. package/src/runs/shared/parallel-utils.ts +5 -0
  33. package/src/runs/shared/pi-args.ts +141 -58
  34. package/src/runs/shared/session-lease.ts +25 -5
  35. package/src/runs/shared/subagent-prompt-runtime.ts +14 -1
  36. package/src/runs/shared/tool-availability.ts +18 -2
  37. package/src/shared/launch-contract.ts +123 -0
  38. package/src/shared/types.ts +104 -7
  39. package/src/shared/utils.ts +17 -42
  40. package/src/slash/delegation-adapters.ts +1 -1
  41. package/src/slash/slash-commands.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.37.0] - 2026-07-25
6
+
7
+ ### Added
8
+ - Bound public launch preflight to versioned selected-agent definition digests, projected async lifecycle/status/result/process-terminal roots, and actual foreground/async execution digests in result and status metadata. Thanks to @shaggitza for #637.
9
+ - Added `subagents.defaultExtensions` for shared child extension allowlists and `agentOverrides.<name>.extensions` for per-agent settings. Thanks to chronoAP for #642.
10
+ - Added a public `pi-subagents/preflight` API that resolves an ordinary single-agent launch contract without creating child sessions, temp prompt files, structured-output runtimes, or run artifacts. Thanks to @shaggitza for #634.
11
+ - Added an out-of-band, session-scoped capability-ceiling API for monotonic child tool and extension restrictions, with inherited async/nested propagation and bounded audit metadata. Thanks to aoguai for #585.
12
+ - Added durable v3 process-terminal proof for detached async runners, with exact close observation, conservative unknown states after observer loss, and status/RPC projections. Thanks to shaggitza for #626.
13
+ - Added `subagents.defaultThinking` for project- or user-scoped default thinking levels on agents without explicit thinking settings. Thanks to corrius for #612.
14
+ - Documented that builtin worker and delegate agents use strict tool allowlists and do not inherit ambient parent extension tools; custom agents must explicitly name extension tools and load their providers. Thanks to buihongduc132 for #586.
15
+
16
+ ### Fixed
17
+ - Preferred direct empty terminal-response evidence over stale tool errors so fallback models can retry abandoned child turns, and stopped treating successful tool output as a hidden failure. Thanks to Dmitry S. (@nuzayets) for #645.
18
+ - Separated evidence acceptance from independent review: evidence levels now end at `verified`, risky runs carry an orthogonal review requirement, `review-required` reports pending review while preserving `evidenceStatus`, and `reviewed` is reserved for achieved independent review. Explicit `reviewed` remains schema-recognized solely for actionable preflight recovery. Thanks to Theodor Hillmann (@t0dorakis) for #440.
19
+ - Bound public preflight launch digests to resolved skill injection metadata, matching execution when skill descriptions change.
20
+ - Classified missing resolved MCP direct tools as a host/pi-mcp-adapter child-registration problem while preserving strict fail-closed diagnostics. Thanks to peedrr for #638.
21
+
5
22
  ## [0.36.0] - 2026-07-24
6
23
 
7
24
  ### Added
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  `pi-subagents` lets Pi delegate work to focused child agents. Use it for code review, scouting, implementation, parallel audits, saved workflows, background jobs, and anything else that benefits from a second or third set of model eyes.
8
8
 
9
- https://github.com/user-attachments/assets/702554ec-faaf-4635-80aa-fb5d6e292fd1
9
+ <https://github.com/user-attachments/assets/702554ec-faaf-4635-80aa-fb5d6e292fd1>
10
10
 
11
11
  ## Installation
12
12
 
@@ -157,7 +157,37 @@ For a persistent override, edit settings. This example pins the reviewer everywh
157
157
 
158
158
  Use `~/.pi/agent/settings.json` for a user override or the project config settings file (`.pi/settings.json` in standard Pi) for a project override. `subagents.defaultModel` applies to builtin, package, user, and project agents that do not set `model` in frontmatter. Per-run model overrides and `agentOverrides.<name>.model` still win, and explicit agent frontmatter still wins over the global default. The same `agentOverrides` block can change `tools`, `skills`, inherited context, prompt text, or disable a builtin. Matching user and project agents also receive override fields that their frontmatter leaves unset, so a shared project config agent can keep the persona while local settings choose the model.
159
159
 
160
- If your provider rejects model IDs with thinking suffixes, set `subagents.disableThinking: true` in user or project settings. That clears bundled builtin thinking defaults in one place; an explicit higher-precedence `agentOverrides.<name>.thinking` value can opt a role back in.
160
+ Set `subagents.defaultThinking` to give builtin, package, user, and project agents without a `thinking` value a shared thinking level, independent of the parent session's default. Project settings win over user settings. Explicit frontmatter, `agentOverrides.<name>.thinking`, and per-run thinking overrides still win; `thinking: false` remains an explicit opt-out:
161
+
162
+ ```json
163
+ {
164
+ "subagents": {
165
+ "defaultThinking": "medium",
166
+ "agentOverrides": {
167
+ "reviewer": { "thinking": "high" }
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ If your provider rejects model IDs with thinking suffixes, set `subagents.disableThinking: true` in user or project settings. That clears bundled builtin thinking defaults in one place; an explicit higher-precedence `agentOverrides.<name>.thinking` value can opt a role back in. Existing custom-agent frontmatter remains authoritative.
174
+
175
+ Set `subagents.defaultExtensions` to give builtin, package, user, and project agents without an `extensions` field a shared extension allowlist. Absent preserves Pi's normal ambient extension discovery. Present as an empty array, the default sets `extensions: []` for agents that do not explicitly define it, disabling ambient extension loading. Present as a non-empty array, the default supplies that allowlist to agents that do not explicitly define one. Project settings win over user settings. Use `agentOverrides.<name>.extensions` for per-agent settings; explicit custom-agent frontmatter remains authoritative.
176
+
177
+ ```json
178
+ {
179
+ "subagents": {
180
+ "defaultExtensions": [],
181
+ "agentOverrides": {
182
+ "researcher": {
183
+ "extensions": ["./tools/research.ts"]
184
+ }
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ A non-array value, an array containing a non-string entry, or an empty/whitespace-only string raises a settings error naming `defaultExtensions` and the offending settings file, matching the validation pattern used by `defaultModel` and `defaultThinking`.
161
191
 
162
192
  To inspect what `pi-subagents` has actually loaded right now, use:
163
193
 
@@ -256,6 +286,8 @@ You can also ask naturally:
256
286
  Show me the current async runs.
257
287
  ```
258
288
 
289
+ Lifecycle artifact v3 adds `process-terminal-candidate.json` (private runner evidence) and `process-terminal.json` (the public proof projection). A proof is `observed` only after the live parent observes the exact detached runner's `close` event, every recorded child writer has a close record, and any tracked canonical-session lease is free. If the observer is unavailable, the proof is `unknown`; do not infer process exit from `endedAt`, result-file existence, PID disappearance, or lease-directory absence. The `subagent:process-terminal` event and RPC `ping.capabilities.processTerminalProof` expose this status. Process proof is point-in-time evidence and remains separate from execution success or stopped/non-resumable state.
290
+
259
291
  Async runs also write machine-readable lifecycle artifacts for observability and workflow gates. For a top-level async run, `details.asyncDir` points at a directory containing `status.json`, `events.jsonl`, `output-<index>.log`, and `subagent-log-<runId>.md`; the final summary is written to Pi's subagent results directory as `<runId>.json`. Nested async runs use the same shape under the nested async root and are discoverable through status projections that read the nested-run registry. These files are append/update artifacts only; interactive foreground behavior is unchanged.
260
292
 
261
293
  Foreground and async runners share bounded child-protocol handling. A child JSONL line above 4 MiB fails with structured `protocolError` code `protocol_output_limit`, stderr retains only its latest 128 KiB, split UTF-8 and final unterminated JSON events remain valid, and `agent_end.willRetry` defers completion until the child settles. Current Pi builds use `agent_settled` as the terminal watermark; older builds retain the bounded terminal-message fallback.
@@ -565,7 +597,7 @@ Append `[key=value,...]` to an agent name to override defaults. `/chain` applies
565
597
  | `cwd` | `cwd=packages/api` | Run the step in a subdirectory. |
566
598
  | `count` | `count=3` | Fan a group task into N copies (only inside a `( ... )` group). |
567
599
  | `outputSchema` | `outputSchema=schema.json` | Validate structured output against a JSON Schema file (path resolved against the session cwd, not an inline step `cwd`). |
568
- | `acceptance` | `acceptance=checked` | Inline acceptance level: `auto`, `attested`, or `checked`. Use the tool API or saved `.chain.json` for object contracts such as `none` or `verified`; `reviewed` is inferred-only. |
600
+ | `acceptance` | `acceptance=checked` | Inline evidence level: `auto`, `attested`, or `checked`. Use the tool API or saved `.chain.json` for object contracts such as `none`, `verified`, or an orthogonal review requirement. `reviewed` is an achieved status, not an input level. |
569
601
 
570
602
  Set `output=false`, `reads=false`, or `skills=false` to disable that behavior explicitly. Do not use `output=false` for file-only returns; use `outputMode=file-only` with an `output` path.
571
603
 
@@ -796,7 +828,7 @@ Examples:
796
828
  - `tools: subagent, read`: a child-safe `subagent` tool is available inside that child so it can run explicitly assigned nested fanout.
797
829
  - `tools: read, fixture_search` plus `subagentOnlyExtensions: ./tools/fixture-search.ts`: the provider loads only in this agent's child process, and the registered `fixture_search` name survives the strict allowlist.
798
830
 
799
- Direct MCP tools require [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter). Subagents only receive direct MCP tools when `mcp:` entries are listed in their frontmatter; global `directTools: true` in `mcp.json` is not enough by itself. The generic `mcp` proxy tool can still be used for discovery when available. The adapter caches tool metadata at startup, so after connecting a new MCP server for the first time, restart Pi before relying on direct tools. An `mcp:` entry named `subagent` does not authorize nested fanout; only the builtin `subagent` tool name does.
831
+ Direct MCP tools require [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter). Subagents only receive direct MCP tools when `mcp:` entries are listed in their frontmatter; global `directTools: true` in `mcp.json` is not enough by itself. The generic `mcp` proxy tool can still be used for discovery when available. The adapter caches tool metadata at startup, so after connecting a new MCP server for the first time, restart Pi before relying on direct tools. An `mcp:` entry named `subagent` does not authorize nested fanout; only the builtin `subagent` tool name does. If a resolved direct MCP name is missing from the child registry, pi-subagents keeps the launch failed under the strict allowlist and identifies the condition as a host/pi-mcp-adapter registration problem; verify that the adapter registers the selected tools before child startup.
800
832
 
801
833
  `extensions` controls child extension loading:
802
834
 
@@ -814,6 +846,8 @@ When `extensions` is present, normal discovered extensions are disabled; the lis
814
846
 
815
847
  Use `subagentOnlyExtensions` when a custom extension tool should exist only inside child sessions. It is scoped by agent config: every run of that agent receives those extension paths, while other agents do not unless they declare the same field. The current model does not have a separate named-subagent audience inside one agent definition.
816
848
 
849
+ To apply the same `extensions` allowlist to every agent that does not declare its own, set `subagents.defaultExtensions` in user or project settings. Omit it to preserve ambient extension discovery or set it to `[]` to disable ambient extensions by default; project settings win over user settings. Agents that explicitly define `extensions` keep their own value, including an empty `extensions:` field.
850
+
817
851
  Before the first model turn, the child runtime compares every explicit tool name with Pi's final filtered registry. A missing provider now fails the run with the unavailable names and concrete `subagentOnlyExtensions`/`extensions` guidance instead of letting a direct or chained child silently continue without its requested tools.
818
852
 
819
853
  ## Chain files
@@ -973,6 +1007,7 @@ Missing skills do not fail execution. The result summary shows a warning.
973
1007
  The package bundles a `pi-subagents` skill that is automatically available to the parent agent when the extension is installed. It is for the orchestrating parent only: child subagents never receive it, and their context is explicitly filtered to strip parent-only orchestration instructions.
974
1008
 
975
1009
  What the bundled skill covers:
1010
+
976
1011
  - **Delegation patterns**: when to launch which agent, whether to use single, parallel, chain, or async mode, and whether to use fresh or forked context
977
1012
  - **Prompt workflow recipes**: how to apply the packaged techniques directly with `subagent(...)` when the user describes the workflow in natural language instead of invoking a slash command. This includes parallel review, review-loop, parallel research, parallel context-build, parallel handoff-plan, gather-context-and-clarify, and parallel cleanup
978
1013
  - **Role-agent prompting guidance**: compact contract prompts instead of long scripts, what to include in role-specific meta prompts, and retrieval budgets for researchers
@@ -987,6 +1022,33 @@ If you are writing an agent that orchestrates subagents, the bundled skill helps
987
1022
  Pi extensions can request configured foreground agents through the public event
988
1023
  contract exported by `pi-subagents/delegation`.
989
1024
 
1025
+ ### Launch contract preflight
1026
+
1027
+ Use `pi-subagents/preflight` when an extension needs to inspect the resolved child launch contract before deciding whether to run anything:
1028
+
1029
+ ```ts
1030
+ import { resolveSubagentLaunchContract } from "pi-subagents/preflight";
1031
+
1032
+ const result = await resolveSubagentLaunchContract({
1033
+ agent: "reviewer",
1034
+ task: "Review the current diff.",
1035
+ context: "fresh",
1036
+ cwd: ctx.cwd,
1037
+ sessionRoot: "/tmp/my-extension-preflight-session-root",
1038
+ availableModels: ctx.modelRegistry.getAvailable(),
1039
+ });
1040
+
1041
+ if (!result.ok) {
1042
+ // missing_agent, ambiguous_agent, missing_skill, denied_required_tool,
1043
+ // invalid_artifact_dir, invalid_cwd, or unsupported_mode
1044
+ throw new Error(result.message);
1045
+ }
1046
+
1047
+ console.log(result.contract.digest, result.contract.tools.effectiveAllowlist);
1048
+ ```
1049
+
1050
+ Preflight covers ordinary single-agent launch resolution under public contract version 2: selected agent identity and shadowed candidates, a versioned parsed-definition digest (including system prompt and launch-affecting model, tool, skill, extension, output, and memory fields), fresh/fork context, effective model and thinking, skill and tool resolution, direct MCP selections, runtime/configured extensions, artifact/session paths, async lifecycle/status/result/event/process-terminal paths, package/lifecycle versions, capability-ceiling audit data, and stable digests. `launchContractDigest` is the canonical digest of the caller task, effective system prompt (including the resolved `turnBudget` prompt augmentation when supplied), model candidates, effective tools/extensions/MCP (including inherited capability ceilings), output binding, and structured-output schema that ordinary foreground and async execution report in results/status/events and metadata. Runtime acceptance prose and output-task annotations are intentionally excluded because side-effect-free preflight does not resolve those host/runtime augmentations; the contract version and task digest make that boundary explicit. Raw prompts are not exposed in public contract output. It is side-effect-free for launch state: it does not create child sessions, temp prompt files, structured-output runtimes, tool-diagnostic files, or run artifacts. Some host-owned facts, such as exact fork snapshots, nested async roots, and live model registries, can only be proven by the Pi host; those appear as `host_required` diagnostics instead of silently pretending to be exact.
1051
+
990
1052
  ### Delegation v1
991
1053
 
992
1054
  The compatibility v1 contract runs one configured foreground agent per request:
@@ -1104,6 +1166,26 @@ tools, skills, context, model policy, and workspace authority; it is not a
1104
1166
  sandbox or a durable task broker. `pi-subagents/delegation` is the canonical
1105
1167
  contract for extension integrations.
1106
1168
 
1169
+ ## Capability ceilings
1170
+
1171
+ Parent extensions can enforce an out-of-band, session-scoped capability ceiling without adding a model-visible field to `subagent`:
1172
+
1173
+ ```ts
1174
+ import { registerSubagentCapabilityCeiling } from "pi-subagents/capability-ceiling";
1175
+
1176
+ const restriction = registerSubagentCapabilityCeiling({
1177
+ sessionId: ctx.sessionManager.getSessionId(),
1178
+ source: "plan-mode",
1179
+ ceiling: { allowedTools: ["read", "grep", "find", "ls"], denyExtensions: true },
1180
+ });
1181
+ // restriction.update(...) replaces this provider's policy atomically.
1182
+ // restriction.dispose() removes only this provider's registration.
1183
+ ```
1184
+
1185
+ Active registrations intersect their `allowedTools` sets and OR `denyExtensions`; an explicit empty list means no caller-facing tools, while an omitted list does not restrict names. The resolved snapshot is propagated monotonically to nested and async children and is retained for recovery. `structured_output` may remain as a package-owned internal protocol tool when an output schema requires it; it is not a caller capability. A denied lazy-skill `read` requirement fails before spawn rather than widening the ceiling.
1186
+
1187
+ `denyExtensions` suppresses ambient, configured, and MCP provider extensions while retaining the package runtime needed for child protocol enforcement. This is a same-process policy boundary, not a sandbox against malicious code already running in the parent process. Schedules created while a ceiling is active are rejected until durable schedule persistence is available; unrestricted schedules remain subject to any policy active when they fire. Public status exposes bounded audit counts and sources, never full extension paths.
1188
+
1107
1189
  ## Background-work provider API
1108
1190
 
1109
1191
  Other Pi extensions can make their current-session jobs visible to `subagent_wait` through the versioned process-local provider contract:
@@ -1309,7 +1391,7 @@ Agent definitions are not loaded into context by default. Management actions let
1309
1391
  | `includeProgress` | boolean | false | Include full progress in result. |
1310
1392
  | `share` | boolean | false | Upload session export to GitHub Gist. |
1311
1393
  | `sessionDir` | string | derived | Override session log directory. |
1312
- | `acceptance` | string/object/false | inferred | Override inferred gates with `"auto"`, `"attested"`, `"checked"`, `"verified"`, or `{ level: "none", reason: "..." }`. `reviewed` is inferred-only; explicit requests fail preflight. `false` disables gates. With `agentContract: { version: 1 }`, omitted, `"auto"`, and `false` mean no acceptance request for that run; explicit acceptance is reported separately from execution. |
1394
+ | `acceptance` | string/object/false | inferred | Configure evidence gates with `"auto"`, `"attested"`, `"checked"`, `"verified"`, or `{ level: "none", reason: "..." }`. Independent review is orthogonal: use `review: { required: true, agent?: "reviewer", focus?: "..." }`. `review-required` means evidence passed but review is pending; `reviewed` is achieved only after a real independent result. Explicit `"reviewed"` remains schema-recognized solely for actionable preflight recovery. For reviewer/read-only calls, omit acceptance. `false` disables gates. With `agentContract: { version: 1 }`, omitted, `"auto"`, and `false` mean no acceptance request for that run; explicit acceptance is reported separately from execution. |
1313
1395
 
1314
1396
  `agentContract: { version: 1 }` keeps existing fields and artifacts but adds derived `execution`, `acceptance`, `review`, and `effects` projections. In v1, acceptance failures do not rewrite execution success, and an explicit completion guard reports `effects.fileMutation` instead of failing the run by itself. Chain steps default to advancing on execution under v1; set `gateOn: "acceptance"` on a v1 step or parallel task when rejected acceptance should stop the chain.
1315
1397
 
@@ -1644,15 +1726,18 @@ Every run resolves an effective acceptance policy. Callers may omit `acceptance`
1644
1726
  }
1645
1727
  ```
1646
1728
 
1647
- Acceptance policies use the levels `auto`, `none`, `attested`, `checked`, `verified`, and `reviewed`. `acceptance: "auto"` is the default. Callers may explicitly request levels through `verified`; `reviewed` is reserved for inferred policy because the current execution path cannot supply an independent reviewer result. Explicit `reviewed` fails preflight instead of spawning a child that is guaranteed to be rejected. Read-only tasks infer lightweight attestation, normal writer tasks infer checked evidence, and async/risky/dynamic writer contexts infer a reviewed gate. Agent frontmatter or `subagents.agentOverrides` may set `acceptanceRole: "read-only" | "writer"` for ambiguous tasks; explicit task mutation or no-edit intent wins over that role, while omitted metadata preserves the existing reviewer/scout/worker name heuristics. The role affects acceptance inference only and does not change tool access. The bare string `"none"` is rejected; use `{ level: "none", reason: "..." }` instead. `acceptance: false` is accepted only as a deprecated shorthand for disabling gates.
1729
+ Acceptance evidence levels are `auto`, `none`, `attested`, `checked`, and `verified`. `acceptance: "auto"` is the default. Review is a separate gate configured with `acceptance.review`; async, risky, and dynamic writer contexts infer checked evidence plus `review: { agent: "reviewer", required: true }`. Read-only tasks infer lightweight attestation, while normal writer tasks infer checked evidence without review. Agent frontmatter or `subagents.agentOverrides` may set `acceptanceRole: "read-only" | "writer"` for ambiguous tasks; explicit task mutation or no-edit intent wins over that role, while omitted metadata preserves the existing reviewer/scout/worker name heuristics. The role affects acceptance inference only and does not change tool access. The bare string `"none"` is rejected; use `{ level: "none", reason: "..." }` instead. `acceptance: false` is accepted only as a deprecated shorthand for disabling gates.
1730
+
1731
+ For reviewer/read-only calls, omit `acceptance`. The explicit value `"reviewed"` is not a policy level: it remains schema-recognized only so semantic preflight can explain the mistake without spawning a child. To require review of a writer result, use `acceptance: { level: "checked", review: { required: true, agent: "reviewer" } }` and orchestrate the reviewer separately.
1648
1732
 
1649
- Acceptance provenance is stored separately from child prose:
1733
+ Acceptance provenance is stored separately from child prose. `evidenceStatus` preserves evidence progress when the overall status is waiting on or has completed review:
1650
1734
 
1651
1735
  - `claimed`: child finished but did not provide structured evidence.
1652
1736
  - `attested`: child returned a structured acceptance report.
1653
1737
  - `checked`: runtime structural checks passed, such as required evidence and no staged files.
1654
1738
  - `verified`: configured runtime verification commands passed. Child-reported command success does not count.
1655
- - `reviewed`: an independent reviewer result is present.
1739
+ - `review-required`: required evidence passed, but no independent reviewer result has been supplied.
1740
+ - `reviewed`: an independent reviewer result is present and has no blockers.
1656
1741
  - `rejected`: attestation, structural checks, verification, or review failed.
1657
1742
 
1658
1743
  For `attested` or stricter levels, the child prompt includes a standardized acceptance section and asks for a fenced `acceptance-report` JSON block. The parser canonicalizes known enum synonyms, snake_case report keys and wrappers, underscore fence tags, unambiguous scalar arrays, string booleans, and criterion-id separators. Unknown or ambiguous keys and enum values fail with field-level diagnostics. Explicit empty `changedFiles` and `testsAddedOrUpdated` arrays are recorded as not applicable; missing fields and empty required command or validation evidence still fail.
@@ -9,4 +9,6 @@ inheritSkills: false
9
9
 
10
10
  You are a delegated agent. Execute the assigned task using the provided tools. Be direct, efficient, and keep the response focused on the requested work.
11
11
 
12
+ The builtin delegate uses a strict tool allowlist and does not inherit ambient extension tools from the parent session. To use an extension tool, configure a custom agent with the tool name explicitly listed in `tools` and load its provider through `extensions` or `subagentOnlyExtensions`.
13
+
12
14
  If runtime bridge instructions identify a safe supervisor target and you are blocked or need a decision, use `contact_supervisor` with `reason: "need_decision"` and stay alive for the reply. Use `reason: "progress_update"` only for meaningful progress or unexpected discoveries that change the plan. Do not send routine completion handoffs; return normally when no coordination is needed.
package/agents/worker.md CHANGED
@@ -17,6 +17,8 @@ You are the single writer thread. Your job is to execute the assigned task or ap
17
17
 
18
18
  Use the provided tools directly. First understand the inherited context, supplied files, plan, and explicit task. Then implement carefully and minimally.
19
19
 
20
+ The builtin worker uses a strict tool allowlist. It does not inherit ambient extension tools from the parent session. To use an extension tool, configure a custom agent with the tool name explicitly listed in `tools` and load its provider through `extensions` or `subagentOnlyExtensions`.
21
+
20
22
  If the task is framed as an approved direction, oracle handoff, or execution plan, treat that direction as the contract. Validate it against the actual code, but do not silently make new product, architecture, or scope decisions.
21
23
 
22
24
  If the implementation reveals a decision that was not approved and is required to continue safely, pause and escalate through the live coordination channel. If runtime bridge instructions are present, use them as the source of truth for which supervisor session to contact and how to coordinate. Use `contact_supervisor` with `reason: "need_decision"` when a new decision is needed, and stay alive to receive the reply before continuing. Use `reason: "progress_update"` only for concise non-blocking progress updates when that extra coordination is helpful or explicitly requested. Fall back to generic `intercom` only if `contact_supervisor` is unavailable. Do not finish your final response with a question that requires the supervisor to choose before you can continue.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
@@ -8,7 +8,9 @@
8
8
  "exports": {
9
9
  ".": "./index.ts",
10
10
  "./background-work": "./src/api/background-work.ts",
11
- "./delegation": "./src/api/delegation.ts"
11
+ "./delegation": "./src/api/delegation.ts",
12
+ "./capability-ceiling": "./src/api/capability-ceiling.ts",
13
+ "./preflight": "./src/api/preflight.ts"
12
14
  },
13
15
  "repository": {
14
16
  "type": "git",
@@ -14,6 +14,10 @@ This skill is for the main parent orchestrator only. Do not inject or follow it
14
14
 
15
15
  Use this skill when the parent orchestrator needs to launch a specialized subagent, compose multiple agents into a workflow, or create/edit agents and chains on demand.
16
16
 
17
+ ## Capability ceilings
18
+
19
+ Parent extensions may register a session-scoped, out-of-band ceiling through `pi-subagents/capability-ceiling`. Child tools are intersected with every active registration and inherited snapshot; `denyExtensions` removes ambient/provider extension loading while retaining package protocol runtime. Do not add a model-visible ceiling field or rely on role selection for enforcement. Restricted schedules are rejected until their ceiling can be persisted safely.
20
+
17
21
  ## When to Use
18
22
 
19
23
  - **Complex work orchestration**: use Fable mode as the default parent-agent loop for complex work. Complex means the task has multiple moving parts, unclear acceptance, cross-cutting code, meaningful user-visible impact, expensive or irreversible validation, broad review surface, or the user asks for orchestration. Lightweight one-off delegation can stay lightweight.
@@ -195,6 +199,16 @@ and user/project agents override builtins with the same name.
195
199
 
196
200
  Builtin agents inherit the current Pi default model unless a run, user setting, project setting, or `subagents.defaultModel` overrides `model`. Set `subagents.defaultModel` when subagents should use a different default model than the parent session. Override builtin defaults before copying full agent files when a small tweak is enough.
197
201
 
202
+ Set `subagents.defaultThinking` to apply a shared thinking level to builtin, package, user, and project agents whose frontmatter leaves `thinking` unset. Project settings win over user settings; explicit frontmatter (including `thinking: false`), `agentOverrides.<name>.thinking`, and per-run overrides remain more specific. This setting affects child agents only and does not change the parent session's default thinking level.
203
+
204
+ ```json
205
+ {
206
+ "subagents": {
207
+ "defaultThinking": "medium"
208
+ }
209
+ }
210
+ ```
211
+
198
212
  For one run, use inline config:
199
213
 
200
214
  ```text
@@ -247,14 +261,16 @@ Direct settings example:
247
261
 
248
262
  Useful override fields: `model`, `fallbackModels`, `thinking`,
249
263
  `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `defaultContext`,
250
- `acceptanceRole`, `disabled`, `skills`, `tools`, and `systemPrompt`. Use
251
- `acceptanceRole: false` to clear an override. Create a user or project
264
+ `acceptanceRole`, `disabled`, `skills`, `tools`, `extensions`, and `systemPrompt`.
265
+ Use `acceptanceRole: false` to clear an override. Create a user or project
252
266
  agent with the same name only when you want a substantially different agent.
253
267
 
254
268
  If a provider rejects model IDs with thinking suffixes, use
255
269
  `subagents.disableThinking: true` in user or project settings to clear bundled
256
270
  builtin thinking defaults globally. A higher-precedence per-agent `thinking`
257
- override can opt one builtin back in.
271
+ override can opt one builtin back in. Existing custom-agent frontmatter remains authoritative.
272
+
273
+ Set `subagents.defaultExtensions` to give agents without an `extensions` field a shared child extension allowlist. Omit it to preserve ambient extension discovery, set it to `[]` to disable ambient extensions by default, or use `agentOverrides.<name>.extensions` for one agent. Explicit custom-agent frontmatter still wins.
258
274
 
259
275
  Tool description modes live in `~/.pi/agent/extensions/subagent/config.json`, not `subagents` settings. Set `toolDescriptionMode` to `compact` to reduce tool-description prompt cost while keeping the execution, async/`subagent_wait`, child-safety, one-writer, management/action, and artifact/status guardrails. Set it to `custom` to read `subagent-tool-description.md` from the project config dir or agent dir; invalid custom files fall back to full mode and the safety guidance is still appended.
260
276
 
@@ -860,7 +876,7 @@ clarify → validation contract → planner → async worker → parallel async
860
876
 
861
877
  The validation contract defines acceptance before code is written: expected behavior, acceptance checks, commands or user flows to exercise, and evidence the worker should return. Keep it lightweight for small tasks, but make it explicit enough that reviewers and validators are checking the intended outcome rather than the worker’s own assumptions.
862
878
 
863
- Use the structured `acceptance` field when the run should carry an explicit acceptance contract. If omitted, subagents infer an effective acceptance policy from role, mode, and risk. Use `level: "checked"` for ordinary writer evidence gates and `level: "verified"` when the runtime should run explicit validation commands. Do not explicitly request `level: "reviewed"`: the current run cannot supply an independent reviewer result, so that level is reserved for inferred policy. Orchestrate a separate reviewer instead. To disable gates, use `{ level: "none", reason: "..." }`; the bare string `"none"` is rejected, and `false` is accepted only as a deprecated shorthand. Do not call a run reviewed just because the worker says it is done; reviewed means a reviewer gate returned a result. Child-reported command success is evidence, not runtime verification.
879
+ Use the structured `acceptance` field when the run should carry an explicit acceptance contract. If omitted, subagents infer an effective policy from role, mode, and risk. Evidence levels end at `verified`: use `level: "checked"` for ordinary writer evidence and `level: "verified"` when the runtime should run explicit validation commands. Independent review is orthogonal; use `review: { required: true, agent: "reviewer" }` and orchestrate the reviewer separately. `review-required` means evidence passed but review is pending, while `reviewed` means a real independent result found no blockers. For reviewer/read-only calls, omit `acceptance`. Never explicitly request `level: "reviewed"`; that value remains recognized only so preflight can return an actionable correction. To disable gates, use `{ level: "none", reason: "..." }`; the bare string `"none"` is rejected, and `false` is accepted only as a deprecated shorthand. Child-reported command success is evidence, not runtime verification.
864
880
 
865
881
  The first `worker` implements the approved plan. The parent continues with independent inspection or validation prep while it runs, not parallel edits to the same worktree. When the async worker completes, treat its handoff as the transition into review, not as final completion, unless the user explicitly asked for worker-only work, review-only output, or to stop after implementation. Parallel reviewers inspect the resulting diff from fresh context. Validators check behavior with the best available evidence: commands, tests, browser/CLI interaction, screenshots, logs, or manual reproduction notes. The final `worker` applies synthesized review fixes in forked context, then the parent looks over the final diff before completing. The parent may launch these steps as an initial async chain when the workflow is already clear, or as follow-up subagent runs after each async completion. Initial chains should pass `async: true` so the main chat is unblocked; avoid `clarify: true` unless the user asked for foreground clarification. Do not stop after parallel review unless the user explicitly asked for review-only output or the review surfaced a decision that needs approval first.
866
882
 
@@ -203,7 +203,12 @@ function skillsWarning(cwd: string, agent: Pick<AgentConfig, "skills" | "skillPa
203
203
 
204
204
  export function editableAgentConfig(agent: AgentConfig): AgentConfig {
205
205
  const base = agent.override?.base;
206
- if (!base) return { ...agent };
206
+ if (!base) {
207
+ return {
208
+ ...agent,
209
+ extensions: agent.extensionsFromDefault ? undefined : agent.extensions ? [...agent.extensions] : undefined,
210
+ };
211
+ }
207
212
 
208
213
  return {
209
214
  ...agent,
@@ -221,6 +226,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
221
226
  skillPath: base.skillPath ? [...base.skillPath] : undefined,
222
227
  tools: base.tools ? [...base.tools] : undefined,
223
228
  mcpDirectTools: base.mcpDirectTools ? [...base.mcpDirectTools] : undefined,
229
+ extensions: base.extensions ? [...base.extensions] : undefined,
224
230
  subagentOnlyExtensions: base.subagentOnlyExtensions ? [...base.subagentOnlyExtensions] : undefined,
225
231
  completionGuard: base.completionGuard,
226
232
  override: undefined,
@@ -73,6 +73,7 @@ export interface BuiltinAgentOverrideBase {
73
73
  skillPath?: string[];
74
74
  tools?: string[];
75
75
  mcpDirectTools?: string[];
76
+ extensions?: string[];
76
77
  subagentOnlyExtensions?: string[];
77
78
  completionGuard?: boolean;
78
79
  toolBudget?: ToolBudgetConfig;
@@ -91,6 +92,7 @@ interface BuiltinAgentOverrideConfig {
91
92
  systemPrompt?: string;
92
93
  skills?: string[] | false;
93
94
  tools?: string[] | false;
95
+ extensions?: string[] | false;
94
96
  subagentOnlyExtensions?: string[] | false;
95
97
  completionGuard?: boolean;
96
98
  toolBudget?: ToolBudgetConfig | false;
@@ -134,6 +136,7 @@ export interface AgentConfig {
134
136
  skills?: string[];
135
137
  skillPath?: string[];
136
138
  extensions?: string[];
139
+ extensionsFromDefault?: boolean;
137
140
  subagentOnlyExtensions?: string[];
138
141
  output?: string;
139
142
  defaultReads?: string[];
@@ -152,6 +155,8 @@ export interface AgentConfig {
152
155
  interface SubagentSettings {
153
156
  overrides: Record<string, BuiltinAgentOverrideConfig>;
154
157
  defaultModel?: string;
158
+ defaultThinking?: string;
159
+ defaultExtensions?: string[];
155
160
  disableBuiltins?: boolean;
156
161
  disableThinking?: boolean;
157
162
  modelScope?: ModelScopeConfig;
@@ -516,6 +521,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
516
521
  skillPath: agent.skillPath ? [...agent.skillPath] : undefined,
517
522
  tools: agent.tools ? [...agent.tools] : undefined,
518
523
  mcpDirectTools: agent.mcpDirectTools ? [...agent.mcpDirectTools] : undefined,
524
+ extensions: agent.extensionsFromDefault ? undefined : agent.extensions ? [...agent.extensions] : undefined,
519
525
  subagentOnlyExtensions: agent.subagentOnlyExtensions ? [...agent.subagentOnlyExtensions] : undefined,
520
526
  completionGuard: agent.completionGuard,
521
527
  toolBudget: agent.toolBudget,
@@ -538,6 +544,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
538
544
  ...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
539
545
  ...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
540
546
  ...(override.tools !== undefined ? { tools: override.tools === false ? false : [...override.tools] } : {}),
547
+ ...(override.extensions !== undefined ? { extensions: override.extensions === false ? false : [...override.extensions] } : {}),
541
548
  ...(override.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: override.subagentOnlyExtensions === false ? false : [...override.subagentOnlyExtensions] } : {}),
542
549
  ...(override.completionGuard !== undefined ? { completionGuard: override.completionGuard } : {}),
543
550
  ...(override.toolBudget !== undefined ? { toolBudget: override.toolBudget === false ? false : { ...override.toolBudget, ...(Array.isArray(override.toolBudget.block) ? { block: [...override.toolBudget.block] } : {}) } } : {}),
@@ -717,6 +724,9 @@ function parseBuiltinOverrideEntry(
717
724
  const tools = parseOverrideStringArrayOrFalse(input.tools, { filePath, name, field: "tools" });
718
725
  if (tools !== undefined) override.tools = tools;
719
726
 
727
+ const extensions = parseOverrideStringArrayOrFalse(input.extensions, { filePath, name, field: "extensions" });
728
+ if (extensions !== undefined) override.extensions = extensions;
729
+
720
730
  const subagentOnlyExtensions = parseOverrideStringArrayOrFalse(input.subagentOnlyExtensions, { filePath, name, field: "subagentOnlyExtensions" });
721
731
  if (subagentOnlyExtensions !== undefined) override.subagentOnlyExtensions = subagentOnlyExtensions;
722
732
 
@@ -754,18 +764,34 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
754
764
  throw new Error(`Subagent settings in '${filePath}' have invalid 'defaultModel'; expected a non-empty string.`);
755
765
  }
756
766
  }
767
+ let defaultThinking: string | undefined;
768
+ if ("defaultThinking" in subagentsObject) {
769
+ if (typeof subagentsObject.defaultThinking === "string" && subagentsObject.defaultThinking.trim()) {
770
+ defaultThinking = subagentsObject.defaultThinking.trim();
771
+ } else {
772
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'defaultThinking'; expected a non-empty string.`);
773
+ }
774
+ }
775
+ let defaultExtensions: string[] | undefined;
776
+ if ("defaultExtensions" in subagentsObject) {
777
+ if (!Array.isArray(subagentsObject.defaultExtensions)
778
+ || subagentsObject.defaultExtensions.some((item) => typeof item !== "string" || !item.trim())) {
779
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'defaultExtensions'; expected an array of non-empty strings.`);
780
+ }
781
+ defaultExtensions = subagentsObject.defaultExtensions.map((item) => item.trim());
782
+ }
757
783
  const modelScope = parseModelScopeConfig(subagentsObject.modelScope, { filePath });
758
784
 
759
785
  const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
760
786
  const agentOverrides = subagentsObject.agentOverrides;
761
787
  if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) {
762
- return { overrides: parsed, defaultModel, disableBuiltins, disableThinking, modelScope };
788
+ return { overrides: parsed, defaultModel, defaultThinking, defaultExtensions, disableBuiltins, disableThinking, modelScope };
763
789
  }
764
790
  for (const [name, value] of Object.entries(agentOverrides)) {
765
791
  const override = parseBuiltinOverrideEntry(name, value, filePath);
766
792
  if (override) parsed[name] = override;
767
793
  }
768
- return { overrides: parsed, defaultModel, disableBuiltins, disableThinking, modelScope };
794
+ return { overrides: parsed, defaultModel, defaultThinking, defaultExtensions, disableBuiltins, disableThinking, modelScope };
769
795
  }
770
796
 
771
797
  function resolveSubagentDefaultModel(
@@ -793,6 +819,58 @@ function applySubagentDefaultModel(agents: AgentConfig[], defaultModel: AgentMod
793
819
  });
794
820
  }
795
821
 
822
+ function resolveSubagentDefaultThinking(
823
+ userSettings: SubagentSettings,
824
+ projectSettings: SubagentSettings,
825
+ projectSettingsPath: string | null,
826
+ ): string | undefined {
827
+ if (projectSettingsPath && projectSettings.defaultThinking !== undefined) return projectSettings.defaultThinking;
828
+ return userSettings.defaultThinking;
829
+ }
830
+
831
+ function applySubagentDefaultThinking(agents: AgentConfig[], defaultThinking: string | undefined): AgentConfig[] {
832
+ if (defaultThinking === undefined) return agents;
833
+ return agents.map((agent) => {
834
+ if (agent.thinking !== undefined) return agent;
835
+ const next = { ...agent, thinking: defaultThinking };
836
+ const frontmatterFields = agentFrontmatterFields.get(agent);
837
+ if (frontmatterFields) agentFrontmatterFields.set(next, frontmatterFields);
838
+ return next;
839
+ });
840
+ }
841
+
842
+ function resolveSubagentDefaultExtensions(
843
+ userSettings: SubagentSettings,
844
+ projectSettings: SubagentSettings,
845
+ projectSettingsPath: string | null,
846
+ ): string[] | undefined {
847
+ if (projectSettingsPath && projectSettings.defaultExtensions !== undefined) return projectSettings.defaultExtensions;
848
+ return userSettings.defaultExtensions;
849
+ }
850
+
851
+ function applySubagentDefaultExtensions(agents: AgentConfig[], defaultExtensions: string[] | undefined): AgentConfig[] {
852
+ if (defaultExtensions === undefined) return agents;
853
+ return agents.map((agent) => {
854
+ if (agent.extensions !== undefined) return agent;
855
+ const next = { ...agent, extensions: [...defaultExtensions], extensionsFromDefault: true };
856
+ const frontmatterFields = agentFrontmatterFields.get(agent);
857
+ if (frontmatterFields) agentFrontmatterFields.set(next, frontmatterFields);
858
+ return next;
859
+ });
860
+ }
861
+
862
+ function applySubagentDefaults(
863
+ agents: AgentConfig[],
864
+ defaultModel: AgentModelSourceInfo | undefined,
865
+ defaultThinking: string | undefined,
866
+ defaultExtensions: string[] | undefined,
867
+ ): AgentConfig[] {
868
+ return applySubagentDefaultExtensions(
869
+ applySubagentDefaultThinking(applySubagentDefaultModel(agents, defaultModel), defaultThinking),
870
+ defaultExtensions,
871
+ );
872
+ }
873
+
796
874
  function applyBuiltinOverride(
797
875
  agent: AgentConfig,
798
876
  override: BuiltinAgentOverrideConfig,
@@ -821,6 +899,7 @@ function applyBuiltinOverride(
821
899
  next.tools = tools;
822
900
  next.mcpDirectTools = mcpDirectTools;
823
901
  }
902
+ if (override.extensions !== undefined) next.extensions = override.extensions === false ? undefined : [...override.extensions];
824
903
  if (override.subagentOnlyExtensions !== undefined) {
825
904
  next.subagentOnlyExtensions = override.subagentOnlyExtensions === false ? undefined : [...override.subagentOnlyExtensions];
826
905
  }
@@ -964,6 +1043,9 @@ function applyCustomAgentOverride(
964
1043
  target.mcpDirectTools = mcpDirectTools;
965
1044
  anyFilled = true;
966
1045
  }
1046
+ if (override.extensions !== undefined) {
1047
+ fill("extensions", ["extensions"], override.extensions === false ? undefined : [...override.extensions]);
1048
+ }
967
1049
  if (override.subagentOnlyExtensions !== undefined) {
968
1050
  fill(
969
1051
  "subagentOnlyExtensions",
@@ -1009,7 +1091,7 @@ function applyCustomAgentOverrides(
1009
1091
 
1010
1092
  export function buildBuiltinOverrideConfig(
1011
1093
  base: BuiltinAgentOverrideBase,
1012
- draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "subagentOnlyExtensions" | "completionGuard" | "toolBudget">,
1094
+ draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "completionGuard" | "toolBudget">,
1013
1095
  ): BuiltinAgentOverrideConfig | undefined {
1014
1096
  const override: BuiltinAgentOverrideConfig = {};
1015
1097
 
@@ -1028,6 +1110,7 @@ export function buildBuiltinOverrideConfig(
1028
1110
  const baseTools = joinToolList(base);
1029
1111
  const draftTools = joinToolList(draft);
1030
1112
  if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
1113
+ if (!arraysEqual(draft.extensions, base.extensions)) override.extensions = draft.extensions ? [...draft.extensions] : false;
1031
1114
  if (!arraysEqual(draft.subagentOnlyExtensions, base.subagentOnlyExtensions)) {
1032
1115
  override.subagentOnlyExtensions = draft.subagentOnlyExtensions ? [...draft.subagentOnlyExtensions] : false;
1033
1116
  }
@@ -1460,6 +1543,8 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1460
1543
  const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
1461
1544
  const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
1462
1545
  const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath);
1546
+ const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
1547
+ const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
1463
1548
  const modelScope = projectSettings.modelScope ?? userSettings.modelScope;
1464
1549
  const packageSubagentPaths = collectPackageSubagentPaths(cwd, {
1465
1550
  includeUser: scope !== "project",
@@ -1467,7 +1552,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1467
1552
  });
1468
1553
 
1469
1554
  const builtinAgents = applyBuiltinOverrides(
1470
- applySubagentDefaultModel(loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"), defaultModel),
1555
+ applySubagentDefaults(loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"), defaultModel, defaultThinking, defaultExtensions),
1471
1556
  userSettings,
1472
1557
  projectSettings,
1473
1558
  userSettingsPath,
@@ -1478,7 +1563,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1478
1563
  const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");
1479
1564
  const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
1480
1565
  const userAgents = applyCustomAgentOverrides(
1481
- applySubagentDefaultModel([...userAgentsExtra, ...userAgentsOld, ...userAgentsNew], defaultModel),
1566
+ applySubagentDefaults([...userAgentsExtra, ...userAgentsOld, ...userAgentsNew], defaultModel, defaultThinking, defaultExtensions),
1482
1567
  userSettings,
1483
1568
  projectSettings,
1484
1569
  userSettingsPath,
@@ -1486,14 +1571,14 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
1486
1571
  );
1487
1572
 
1488
1573
  const projectAgents = applyCustomAgentOverrides(
1489
- applySubagentDefaultModel(scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project")), defaultModel),
1574
+ applySubagentDefaults(scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project")), defaultModel, defaultThinking, defaultExtensions),
1490
1575
  userSettings,
1491
1576
  projectSettings,
1492
1577
  userSettingsPath,
1493
1578
  projectSettingsPath,
1494
1579
  );
1495
1580
  const packageAgents = applyCustomAgentOverrides(
1496
- applySubagentDefaultModel(packageSubagentPaths.agents.flatMap((dir) => loadAgentsFromDir(dir, "package")), defaultModel),
1581
+ applySubagentDefaults(packageSubagentPaths.agents.flatMap((dir) => loadAgentsFromDir(dir, "package")), defaultModel, defaultThinking, defaultExtensions),
1497
1582
  userSettings,
1498
1583
  projectSettings,
1499
1584
  userSettingsPath,
@@ -1529,21 +1614,23 @@ export function discoverAgentsAll(cwd: string): {
1529
1614
  const userSettings = readSubagentSettings(userSettingsPath);
1530
1615
  const projectSettings = readSubagentSettings(projectSettingsPath);
1531
1616
  const defaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath);
1617
+ const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
1618
+ const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
1532
1619
  const packageSubagentPaths = collectPackageSubagentPaths(cwd);
1533
1620
 
1534
1621
  const builtin = applyBuiltinOverrides(
1535
- applySubagentDefaultModel(loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"), defaultModel),
1622
+ applySubagentDefaults(loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"), defaultModel, defaultThinking, defaultExtensions),
1536
1623
  userSettings,
1537
1624
  projectSettings,
1538
1625
  userSettingsPath,
1539
1626
  projectSettingsPath,
1540
1627
  );
1541
1628
  const user = applyCustomAgentOverrides(
1542
- applySubagentDefaultModel([
1629
+ applySubagentDefaults([
1543
1630
  ...extraUserAgentDirs().flatMap((dir) => loadAgentsFromDir(dir, "user")),
1544
1631
  ...loadAgentsFromDir(userDirOld, "user"),
1545
1632
  ...loadAgentsFromDir(userDirNew, "user"),
1546
- ], defaultModel),
1633
+ ], defaultModel, defaultThinking, defaultExtensions),
1547
1634
  userSettings,
1548
1635
  projectSettings,
1549
1636
  userSettingsPath,
@@ -1556,7 +1643,7 @@ export function discoverAgentsAll(cwd: string): {
1556
1643
  }
1557
1644
  }
1558
1645
  const packageAgents = applyCustomAgentOverrides(
1559
- applySubagentDefaultModel(Array.from(packageMap.values()), defaultModel),
1646
+ applySubagentDefaults(Array.from(packageMap.values()), defaultModel, defaultThinking, defaultExtensions),
1560
1647
  userSettings,
1561
1648
  projectSettings,
1562
1649
  userSettingsPath,
@@ -1569,7 +1656,7 @@ export function discoverAgentsAll(cwd: string): {
1569
1656
  }
1570
1657
  }
1571
1658
  const project = applyCustomAgentOverrides(
1572
- applySubagentDefaultModel(Array.from(projectMap.values()), defaultModel),
1659
+ applySubagentDefaults(Array.from(projectMap.values()), defaultModel, defaultThinking, defaultExtensions),
1573
1660
  userSettings,
1574
1661
  projectSettings,
1575
1662
  userSettingsPath,