pi-subagents 0.30.0 → 0.31.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.
- package/CHANGELOG.md +26 -0
- package/README.md +116 -17
- package/agents/context-builder.md +3 -3
- package/agents/planner.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/scout.md +1 -1
- package/package.json +7 -7
- package/skills/pi-subagents/SKILL.md +5 -0
- package/src/agents/agent-management.ts +170 -6
- package/src/agents/agent-serializer.ts +31 -13
- package/src/agents/agents.ts +207 -23
- package/src/agents/frontmatter.ts +66 -2
- package/src/agents/skills.ts +117 -20
- package/src/extension/doctor.ts +20 -0
- package/src/extension/fanout-child.ts +1 -0
- package/src/extension/index.ts +47 -4
- package/src/extension/schemas.ts +10 -76
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/runs/background/async-execution.ts +14 -4
- package/src/runs/background/async-job-tracker.ts +56 -11
- package/src/runs/background/result-watcher.ts +11 -2
- package/src/runs/background/stale-run-reconciler.ts +9 -4
- package/src/runs/background/subagent-runner.ts +79 -3
- package/src/runs/foreground/chain-execution.ts +26 -2
- package/src/runs/foreground/execution.ts +113 -8
- package/src/runs/foreground/subagent-executor.ts +325 -77
- package/src/runs/shared/acceptance.ts +285 -34
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +4 -2
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +6 -1
- package/src/runs/shared/pi-args.ts +9 -1
- package/src/runs/shared/single-output.ts +15 -1
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +8 -2
- package/src/shared/utils.ts +19 -1
- package/src/slash/prompt-template-bridge.ts +26 -3
- package/src/slash/slash-commands.ts +33 -3
- package/src/tui/render.ts +265 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.31.0] - 2026-06-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added `subagents.disableThinking` so bundled builtin agents can drop thinking suffix defaults for providers that do not accept them. Thanks to Joshua Harding (@jhstatewide) for #212.
|
|
9
|
+
- Discover nested grouped skills such as `.pi/skills/group/name/SKILL.md` so subagents match the host runtime's recursive skill lookup. Thanks to Weaxs (@Weaxs) for #262.
|
|
10
|
+
- Follow Pi's configured project config directory for project-local agents, chains, skills, packages, settings, direct MCP config, and intercom package discovery instead of hardcoding `.pi`, while retaining `.pi` as the fallback for older Pi versions.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Hardened npm installs by tracking `package-lock.json`, pinning direct dependencies, and using `npm ci --ignore-scripts` in CI and release workflows. Thanks to Modestas Vainius (@modax) for #234.
|
|
14
|
+
- List configured subagent skills by name, description, and file path instead of inlining full skill bodies, and ensure tool-restricted children can read those skill files on demand. Thanks to Ruben Paz (@Istar-Eldritch) for #183.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Resolve the async result watcher directory with `fs.realpathSync.native()` before `fs.watch()` so Windows profiles with 8.3 temp paths do not crash Pi when async subagent results arrive. Thanks to kerushidao (@kerushidao) for #254.
|
|
18
|
+
- Accept structured acceptance reports emitted in JSON-family fences when the fenced body has the acceptance-report shape. Thanks to Suleiman Tawil (@stawils) for #253.
|
|
19
|
+
- Report field-level acceptance-report validation errors instead of a generic parse failure, and clarify array element types in the acceptance prompt. Thanks to Whisperfall (@Whisperfall) for #264 and josephkEA (@josephkEA) for the follow-up reproduction.
|
|
20
|
+
- Simplified the public `acceptance` and chain tool schemas so Kimi/Moonshot-style parsers can load `subagent`, while runtime validation still rejects malformed acceptance config and dynamic fanout steps. Thanks to Sergio Agosti (@sergio-agosti) for #249.
|
|
21
|
+
- Reject duplicate concurrent `subagent` execution calls while a prior subagent dispatch is still in progress, keeping intentional parallel mode within a single call unchanged. Thanks to desideratum (@desideratum) for #247.
|
|
22
|
+
- Bound async `events.jsonl` growth by dropping noisy child `message_update` snapshots, capping persisted child diagnostics, and scanning control events in chunks during status polling. Thanks to Tri Van Pham (@pvtri96) for #246.
|
|
23
|
+
- Keep crowded async subagent widgets at a stable collapsed height in short terminals, reducing destructive full-screen TUI redraws and flicker. Thanks to ssyram (@ssyram) for #186.
|
|
24
|
+
- Actually wire the previously documented foreground-only `timeoutMs`/`maxRuntimeMs` aliases through single, parallel, chain, and dynamic fanout runs, including stable `timedOut: true` results, preserved partial output, manual-interrupt precedence, and skipped acceptance verification after timeout.
|
|
25
|
+
- Apply `subagents.agentOverrides.<name>` to matching user-scope and project-scope custom agents, while keeping explicit agent frontmatter authoritative per field. Thanks to Jacek Juraszek (@jjuraszek) for #218.
|
|
26
|
+
- Preserve compact foreground `write`/`edit` tool-call evidence in prompt-template delegation responses so convergence checks do not stop loops early. Thanks to Hans Schnedlitz (@hschne) for #207.
|
|
27
|
+
- Respect each agent's `defaultContext` in mixed parallel and chain subagent calls when no explicit `context` is provided, so fresh-default scouts no longer inherit forked parent transcripts just because another agent in the same invocation defaults to fork. Thanks to Mitch Fultz (@fitchmultz) for #228.
|
|
28
|
+
- Make runtime `output` overrides authoritative in child task and system prompts, and remove stale static filenames from bundled output-format instructions. Thanks to youngshine (@smithyyang) for #223.
|
|
29
|
+
- Keep top-level parallel `defaultProgress` files in run-scoped artifact storage instead of the parent working directory. Thanks to youngshine (@smithyyang) for #224.
|
|
30
|
+
|
|
5
31
|
## [0.30.0] - 2026-06-20
|
|
6
32
|
|
|
7
33
|
### Added
|
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ The extension ships with builtin agents you can use immediately.
|
|
|
115
115
|
|
|
116
116
|
A simple rule of thumb: use `scout` before you understand the code, `researcher` before you trust external facts, `planner` before a bigger change, `worker` to implement, `reviewer` to check, and `oracle` when the decision itself feels risky.
|
|
117
117
|
|
|
118
|
-
## Changing
|
|
118
|
+
## Changing an agent's model
|
|
119
119
|
|
|
120
120
|
Builtin agents inherit your current Pi default model by default. This keeps new installs from depending on a provider you may not have configured. If you want a role to use a specific model, set an override instead of copying the bundled agent file.
|
|
121
121
|
|
|
@@ -141,7 +141,18 @@ For a persistent override, edit settings. This example pins the reviewer everywh
|
|
|
141
141
|
}
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
Use `~/.pi/agent/settings.json` for a user override or `.pi/settings.json` for a project override. The same `agentOverrides` block can change `tools`, `skills`, inherited context, prompt text, or disable a builtin.
|
|
144
|
+
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. 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. Explicit frontmatter still wins.
|
|
145
|
+
|
|
146
|
+
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.
|
|
147
|
+
|
|
148
|
+
To inspect what `pi-subagents` has actually loaded right now, use:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
/subagents-models
|
|
152
|
+
/subagents-models reviewer
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
That reports the live runtime mapping, which can differ from settings on disk until you reload Pi.
|
|
145
156
|
|
|
146
157
|
## Where running subagents show up
|
|
147
158
|
|
|
@@ -237,6 +248,79 @@ For normal use, you do not need to configure anything. Advanced users can tune t
|
|
|
237
248
|
|
|
238
249
|
At this point, you know enough to use the plugin. The rest of this README is reference material for exact command syntax, custom agents, saved chains, worktrees, and configuration.
|
|
239
250
|
|
|
251
|
+
## Optional pi-permission-system integration
|
|
252
|
+
|
|
253
|
+
[`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-packages/tree/main/packages/pi-permission-system)
|
|
254
|
+
adds a second policy layer — `allow` / `ask` / `deny` — on top of
|
|
255
|
+
pi-subagents' visibility-based tool restrictions.
|
|
256
|
+
|
|
257
|
+
The two compose independently:
|
|
258
|
+
|
|
259
|
+
| Layer | What it controls | Who provides it |
|
|
260
|
+
|-------|-----------------|-----------------|
|
|
261
|
+
| Visibility | Which tools are registered before the session starts | pi-subagents (`tools:` frontmatter key) |
|
|
262
|
+
| Policy | Runtime allow/ask/deny decisions on every tool call, bash command, MCP operation | pi-permission-system (`permission:` frontmatter key) |
|
|
263
|
+
|
|
264
|
+
### Installing
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
pi install npm:@gotgenes/pi-permission-system
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
No configuration is required for the integration — it is automatic when both
|
|
271
|
+
extensions are installed. pi-subagents passes the parent session identity
|
|
272
|
+
to child processes via the `PI_SUBAGENT_PARENT_SESSION` environment variable,
|
|
273
|
+
which the permission system uses to forward `ask` prompts from headless
|
|
274
|
+
subagent processes back to the parent session's UI.
|
|
275
|
+
|
|
276
|
+
### Per-agent permission frontmatter
|
|
277
|
+
|
|
278
|
+
Agent files can include a `permission:` block alongside the standard `tools:`
|
|
279
|
+
key. The permission system reads it independently:
|
|
280
|
+
|
|
281
|
+
```yaml
|
|
282
|
+
---
|
|
283
|
+
name: worker
|
|
284
|
+
tools: bash,read,write,edit
|
|
285
|
+
permission:
|
|
286
|
+
"*": ask
|
|
287
|
+
read: allow
|
|
288
|
+
bash:
|
|
289
|
+
"*": ask
|
|
290
|
+
"git *": allow
|
|
291
|
+
"npm test": allow
|
|
292
|
+
---
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
In this example the subagent extension restricts visibility to four tools,
|
|
296
|
+
and the permission system then applies `ask`/`allow` policy within that
|
|
297
|
+
visible set. Both keys coexist without collision.
|
|
298
|
+
|
|
299
|
+
### Checking the integration
|
|
300
|
+
|
|
301
|
+
Run `/subagents-doctor` to check the permission system status.
|
|
302
|
+
If `ask` prompts from children are not reaching the parent UI, verify both
|
|
303
|
+
extensions are installed:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
pi list
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### How it works
|
|
310
|
+
|
|
311
|
+
At session start, the interactive (root) session records its own identity in
|
|
312
|
+
`PI_SUBAGENT_PARENT_SESSION`. When pi-subagents launches a child, it passes the
|
|
313
|
+
launching session's identity to that child explicitly, falling back to the
|
|
314
|
+
inherited environment variable. When the permission system inside a child
|
|
315
|
+
encounters an `ask` permission, it reads this variable to locate the parent
|
|
316
|
+
session and forwards the confirmation request there.
|
|
317
|
+
|
|
318
|
+
This resolves an interactive prompt only when the parent it points at is the
|
|
319
|
+
interactive session — i.e. for the direct children of the root session. A
|
|
320
|
+
nested child's parent is itself a headless subagent process with no UI to
|
|
321
|
+
surface the prompt, so `ask` policies are best placed on agents that run as
|
|
322
|
+
direct children of the interactive session.
|
|
323
|
+
|
|
240
324
|
## Direct commands
|
|
241
325
|
|
|
242
326
|
Skip this section until you want exact syntax.
|
|
@@ -248,6 +332,7 @@ Skip this section until you want exact syntax.
|
|
|
248
332
|
| `/parallel agent1 "task1" -> agent2 "task2"` | Run agents in parallel |
|
|
249
333
|
| `/run-chain <chainName> -- <task>` | Launch a saved `.chain.md` or `.chain.json` workflow |
|
|
250
334
|
| `/subagents-doctor` | Show read-only setup diagnostics |
|
|
335
|
+
| `/subagents-models [agent]` | Show the runtime-loaded builtin model mapping, optionally filtered to one builtin |
|
|
251
336
|
|
|
252
337
|
Commands validate agent names locally, support tab completion, and send results back into the conversation.
|
|
253
338
|
|
|
@@ -296,7 +381,7 @@ Append `[key=value,...]` to an agent name to override defaults for that step:
|
|
|
296
381
|
| `outputMode` | `outputMode=file-only` | Return only a concise file reference for saved output instead of the full saved content. Requires `output`; default is `inline`. |
|
|
297
382
|
| `reads` | `reads=a.md+b.md` | Read files before executing. `+` separates multiple paths. |
|
|
298
383
|
| `model` | `model=anthropic/claude-sonnet-4` | Override model for this step. |
|
|
299
|
-
| `skills` | `skills=planning+review` | Override
|
|
384
|
+
| `skills` | `skills=planning+review` | Override available skills. `+` separates multiple skills. |
|
|
300
385
|
| `progress` | `progress` | Enable progress tracking. |
|
|
301
386
|
|
|
302
387
|
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.
|
|
@@ -360,9 +445,9 @@ Agent locations, lowest to highest priority:
|
|
|
360
445
|
| Builtin | `~/.pi/agent/extensions/subagent/agents/` |
|
|
361
446
|
| Installed package | `package.json` `pi-subagents.agents` or `pi.subagents.agents` |
|
|
362
447
|
| User | `~/.pi/agent/agents/**/*.md` |
|
|
363
|
-
| Project | `.pi/agents/**/*.md` |
|
|
448
|
+
| Project | Project config `agents/**/*.md` (`.pi/agents/**/*.md` in standard Pi) |
|
|
364
449
|
|
|
365
|
-
Project discovery also reads legacy `.agents/**/*.md` files. Nested subdirectories are discovered recursively. `.chain.md` files do not define agents. Installed Pi packages can expose agent directories from either `{"pi-subagents":{"agents":["./agents"]}}` or `{"pi":{"subagents":{"agents":["./agents"]}}}` in their package manifest. Package agents load above builtins and below user/project agents. If both `.agents/` and
|
|
450
|
+
Project discovery also reads legacy `.agents/**/*.md` files. Nested subdirectories are discovered recursively. `.chain.md` files do not define agents. Installed Pi packages can expose agent directories from either `{"pi-subagents":{"agents":["./agents"]}}` or `{"pi":{"subagents":{"agents":["./agents"]}}}` in their package manifest. Package agents load above builtins and below user/project agents. If both `.agents/` and the project config agents directory define the same parsed runtime agent name, the project config directory wins. Use `agentScope: "user" | "project" | "both"` to control discovery; `both` is the default and project definitions win runtime-name collisions.
|
|
366
451
|
|
|
367
452
|
Builtin agents load at the lowest priority, so a user or project agent with the same name overrides them. They do not pin a provider model; they inherit your current Pi default model unless you set `subagents.agentOverrides.<name>.model`. `oracle` is an advisory reviewer that critiques direction and proposes an execution prompt without editing files. `worker` is the implementation agent for normal tasks and approved oracle handoffs.
|
|
368
453
|
|
|
@@ -377,7 +462,7 @@ pi install npm:pi-web-access
|
|
|
377
462
|
You can override selected builtin fields without copying the whole agent. Overrides live in settings:
|
|
378
463
|
|
|
379
464
|
- User: `~/.pi/agent/settings.json`
|
|
380
|
-
- Project: `.pi/settings.json`
|
|
465
|
+
- Project: project config settings file (`.pi/settings.json` in standard Pi)
|
|
381
466
|
|
|
382
467
|
Example:
|
|
383
468
|
|
|
@@ -397,6 +482,8 @@ Supported override fields are `model`, `fallbackModels`, `thinking`, `systemProm
|
|
|
397
482
|
|
|
398
483
|
Set `disabled: true` to hide a builtin from runtime discovery and agent-facing `subagent({ action: "list" })` output. For bulk control, set `subagents.disableBuiltins: true` in settings.
|
|
399
484
|
|
|
485
|
+
Set `subagents.disableThinking: true` to clear bundled builtin thinking defaults globally for providers that do not support `:low`, `:medium`, `:high`, or similar model suffixes. A higher-precedence per-agent `thinking` override can opt one builtin back in.
|
|
486
|
+
|
|
400
487
|
### Prompt assembly
|
|
401
488
|
|
|
402
489
|
Subagents are designed to be narrow by default. Custom agents start with a clean system prompt and only the context you intentionally give them. They do not automatically inherit Pi’s whole base prompt, project instruction files, or discovered skills catalog.
|
|
@@ -458,7 +545,7 @@ Important fields:
|
|
|
458
545
|
| `inheritProjectContext` | Keeps or strips inherited project instruction blocks. |
|
|
459
546
|
| `inheritSkills` | Keeps or strips Pi’s discovered skills catalog. |
|
|
460
547
|
| `defaultContext` | Optional `fresh` or `fork` launch context default for this agent. |
|
|
461
|
-
| `skills` |
|
|
548
|
+
| `skills` | Adds specific skills to the child’s available skill list, regardless of `inheritSkills`. |
|
|
462
549
|
| `output` | Default single-agent output file. |
|
|
463
550
|
| `defaultReads` | Files to read before running in chain/parallel behavior. |
|
|
464
551
|
| `defaultProgress` | Maintain `progress.md`. |
|
|
@@ -503,7 +590,7 @@ Chains are reusable workflows stored separately from agent files. Use `.chain.md
|
|
|
503
590
|
|-------|------|
|
|
504
591
|
| Installed package | `package.json` `pi-subagents.chains` or `pi.subagents.chains` |
|
|
505
592
|
| User | `~/.pi/agent/chains/**/*.chain.md`, `~/.pi/agent/chains/**/*.chain.json` |
|
|
506
|
-
| Project |
|
|
593
|
+
| Project | Project config `chains/**/*.chain.md`, `chains/**/*.chain.json` (`.pi/chains/...` in standard Pi) |
|
|
507
594
|
|
|
508
595
|
Nested subdirectories are discovered recursively. Installed Pi packages can expose chain directories from either `{"pi-subagents":{"chains":["./chains"]}}` or `{"pi":{"subagents":{"chains":["./chains"]}}}` in their package manifest. Package chains load below user/project chains. If both `.chain.md` and `.chain.json` define the same parsed runtime chain name in the same scope, `.chain.json` wins. If user and project scopes define the same parsed runtime chain name, the project chain wins. Chains support the same optional `package` frontmatter as agents; `name: review-flow` plus `package: code-analysis` runs as `code-analysis.review-flow`.
|
|
509
596
|
|
|
@@ -605,14 +692,14 @@ Parallel outputs are aggregated with clear separators before being passed to the
|
|
|
605
692
|
|
|
606
693
|
## Skills
|
|
607
694
|
|
|
608
|
-
Skills are `SKILL.md` files
|
|
695
|
+
Skills are `SKILL.md` files made available to an agent. The prompt includes skill metadata and the file location; the agent reads the full skill file only when the task matches.
|
|
609
696
|
|
|
610
697
|
Discovery uses project-first precedence:
|
|
611
698
|
|
|
612
|
-
1. `.pi/skills/{name}/SKILL.md`
|
|
699
|
+
1. Project config `skills/{name}/SKILL.md` (`.pi/skills/{name}/SKILL.md` in standard Pi)
|
|
613
700
|
2. Project packages and project settings packages via `package.json -> pi.skills`
|
|
614
701
|
3. Current task cwd package via `package.json -> pi.skills`
|
|
615
|
-
4.
|
|
702
|
+
4. Project config `settings.json -> skills`
|
|
616
703
|
5. `~/.pi/agent/skills/{name}/SKILL.md`
|
|
617
704
|
6. User packages and user settings packages via `package.json -> pi.skills`
|
|
618
705
|
7. `~/.pi/agent/settings.json -> skills`
|
|
@@ -627,14 +714,24 @@ Use agent defaults, override them at runtime, or disable them:
|
|
|
627
714
|
|
|
628
715
|
For chains, `skill` at the top level is additive. A step-level `skill` overrides that step; `false` disables skills for that step.
|
|
629
716
|
|
|
630
|
-
|
|
717
|
+
Available skills use this shape:
|
|
631
718
|
|
|
632
719
|
```xml
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
720
|
+
The following configured skills are available to this subagent.
|
|
721
|
+
Use the read tool to load a skill's file when the task matches its description.
|
|
722
|
+
When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.
|
|
723
|
+
|
|
724
|
+
<available_skills>
|
|
725
|
+
<skill>
|
|
726
|
+
<name>safe-bash</name>
|
|
727
|
+
<description>Run shell commands safely.</description>
|
|
728
|
+
<location>/absolute/path/to/safe-bash/SKILL.md</location>
|
|
729
|
+
</skill>
|
|
730
|
+
</available_skills>
|
|
636
731
|
```
|
|
637
732
|
|
|
733
|
+
If an agent has an explicit `tools` allowlist and resolved skills, `read` is added for that child run so the listed skill files can be loaded on demand.
|
|
734
|
+
|
|
638
735
|
Missing skills do not fail execution. The result summary shows a warning.
|
|
639
736
|
|
|
640
737
|
### Bundled skill
|
|
@@ -743,6 +840,8 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
743
840
|
{ action: "list" }
|
|
744
841
|
{ action: "list", agentScope: "project" }
|
|
745
842
|
{ action: "get", agent: "scout" }
|
|
843
|
+
{ action: "models" }
|
|
844
|
+
{ action: "models", agent: "reviewer" }
|
|
746
845
|
{ action: "get", agent: "code-analysis.scout" }
|
|
747
846
|
{ action: "get", chainName: "review-pipeline" }
|
|
748
847
|
|
|
@@ -801,7 +900,7 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
801
900
|
| `concurrency` | number | config or `4` | Top-level parallel concurrency. |
|
|
802
901
|
| `worktree` | boolean | false | Create isolated git worktrees for parallel tasks. |
|
|
803
902
|
| `chain` | array | - | Sequential, static parallel, and dynamic fanout chain steps. Steps and chain parallel tasks support `phase`, `label`, `as`, `outputSchema`, and `acceptance` in addition to the usual execution fields. Dynamic fanout uses `expand`, one child `parallel` template, and `collect`. With `action: "append-step"`, pass exactly one step to append to a running async chain. |
|
|
804
|
-
| `context` | `fresh \| fork` | agent default or `fresh` | `fork` creates real branched sessions from the parent leaf. Packaged `planner`, `worker`, and `oracle` default to `fork`. |
|
|
903
|
+
| `context` | `fresh \| fork` | per-agent default or `fresh` | Explicit `fresh` or `fork` overrides every child. When omitted, each agent uses its own `defaultContext`; `fork` creates real branched sessions from the parent leaf. Packaged `planner`, `worker`, and `oracle` default to `fork`. |
|
|
805
904
|
| `chainDir` | string | temp chain dir | Persistent directory for chain artifacts. |
|
|
806
905
|
| `clarify` | boolean | true for chains | Show TUI preview/edit flow. |
|
|
807
906
|
| `agentScope` | `user \| project \| both` | `both` | Agent discovery scope. Project wins on collisions. |
|
|
@@ -814,7 +913,7 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
814
913
|
| `sessionDir` | string | derived | Override session log directory. |
|
|
815
914
|
| `acceptance` | string/object/false | inferred | Override the run's inferred acceptance gates. Use `"auto"`, `"attested"`, `"checked"`, `"verified"`, `"reviewed"`, or `{ level: "none", reason: "..." }`. |
|
|
816
915
|
|
|
817
|
-
`context: "fork"` fails fast when the parent session is not persisted, the current leaf is missing, or the branched child session cannot be created. It never silently downgrades to `fresh`. In multi-agent runs
|
|
916
|
+
`context: "fork"` fails fast when the parent session is not persisted, the current leaf is missing, or the branched child session cannot be created. It never silently downgrades to `fresh`. In multi-agent runs that omit `context`, each agent/task/step follows its own `defaultContext`, so a fresh-default scout can run fresh beside a fork-default worker. Pass explicit `context: "fork"` or `context: "fresh"` when you intentionally want one context for every child.
|
|
818
917
|
|
|
819
918
|
Use `outputMode: "file-only"` when a saved output may be large and the parent only needs a pointer. The returned text is a compact reference like `Output saved to: /abs/report.md (48.2 KB, 2847 lines). Read this file if needed.` Failed runs and save errors still return normal inline output for debugging. In chains, later `{previous}` steps receive the same compact reference when the prior step used file-only mode.
|
|
820
919
|
|
|
@@ -23,14 +23,14 @@ Working rules:
|
|
|
23
23
|
- Write the requested output files clearly and concretely.
|
|
24
24
|
- Prefer distilled, high-signal context over exhaustive dumps, but do not omit a relevant file or source just to keep the handoff short.
|
|
25
25
|
|
|
26
|
-
When running in a chain, expect to generate
|
|
26
|
+
When running in a chain, expect to generate context and meta-prompt handoff material. Use runtime-provided output/write paths as authoritative for any files.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Context handoff:
|
|
29
29
|
- relevant files with line numbers and key snippets
|
|
30
30
|
- important patterns already used in the codebase
|
|
31
31
|
- dependencies, constraints, and implementation risks
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Meta-prompt handoff:
|
|
34
34
|
- goal: the concrete outcome the next agent should produce
|
|
35
35
|
- context/evidence: relevant files, diffs, decisions, constraints, and source-backed facts
|
|
36
36
|
- success criteria: what must be true before the next agent can finish
|
package/agents/planner.md
CHANGED
|
@@ -23,7 +23,7 @@ Working rules:
|
|
|
23
23
|
- Call out risks, dependencies, and anything that needs explicit validation.
|
|
24
24
|
- If the task is underspecified, surface the ambiguity in the plan instead of guessing.
|
|
25
25
|
|
|
26
|
-
Output format
|
|
26
|
+
Output format:
|
|
27
27
|
|
|
28
28
|
# Implementation Plan
|
|
29
29
|
|
package/agents/researcher.md
CHANGED
package/agents/scout.md
CHANGED
|
@@ -28,7 +28,7 @@ Working rules:
|
|
|
28
28
|
- If you are told to write output, write it to the provided path and keep the final response short.
|
|
29
29
|
- When running solo, summarize what you found after writing the output.
|
|
30
30
|
|
|
31
|
-
Output format
|
|
31
|
+
Output format:
|
|
32
32
|
|
|
33
33
|
# Code Context
|
|
34
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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",
|
|
@@ -68,13 +68,13 @@
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@earendil-works/pi-tui": "
|
|
72
|
-
"jiti": "
|
|
73
|
-
"typebox": "
|
|
71
|
+
"@earendil-works/pi-tui": "0.74.0",
|
|
72
|
+
"jiti": "2.7.0",
|
|
73
|
+
"typebox": "1.1.24"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@earendil-works/pi-agent-core": "
|
|
77
|
-
"@earendil-works/pi-ai": "
|
|
78
|
-
"@earendil-works/pi-coding-agent": "
|
|
76
|
+
"@earendil-works/pi-agent-core": "0.74.0",
|
|
77
|
+
"@earendil-works/pi-ai": "0.74.0",
|
|
78
|
+
"@earendil-works/pi-coding-agent": "0.74.0"
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -236,6 +236,11 @@ Useful override fields: `model`, `fallbackModels`, `thinking`,
|
|
|
236
236
|
`disabled`, `skills`, `tools`, and `systemPrompt`. Create a user or project
|
|
237
237
|
agent with the same name only when you want a substantially different agent.
|
|
238
238
|
|
|
239
|
+
If a provider rejects model IDs with thinking suffixes, use
|
|
240
|
+
`subagents.disableThinking: true` in user or project settings to clear bundled
|
|
241
|
+
builtin thinking defaults globally. A higher-precedence per-agent `thinking`
|
|
242
|
+
override can opt one builtin back in.
|
|
243
|
+
|
|
239
244
|
## Discovery and Scope Rules
|
|
240
245
|
|
|
241
246
|
Agent files can live in:
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type AgentSource,
|
|
9
9
|
type ChainConfig,
|
|
10
10
|
type ChainStepConfig,
|
|
11
|
+
BUILTIN_AGENT_NAMES,
|
|
11
12
|
defaultInheritProjectContext,
|
|
12
13
|
defaultInheritSkills,
|
|
13
14
|
defaultSystemPromptMode,
|
|
@@ -22,11 +23,15 @@ import { discoverAvailableSkills } from "./skills.ts";
|
|
|
22
23
|
import {
|
|
23
24
|
buildProactiveSkillSubagentRecommendationLines,
|
|
24
25
|
} from "./proactive-skills.ts";
|
|
26
|
+
import { parseFrontmatter } from "./frontmatter.ts";
|
|
27
|
+
import { toModelInfo } from "../shared/model-info.ts";
|
|
28
|
+
import { resolveSubagentModelOverride, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
25
29
|
import type { Details, ExtensionConfig } from "../shared/types.ts";
|
|
30
|
+
import { getProjectConfigDir } from "../shared/utils.ts";
|
|
26
31
|
|
|
27
|
-
type ManagementAction = "list" | "get" | "create" | "update" | "delete";
|
|
32
|
+
type ManagementAction = "list" | "get" | "models" | "create" | "update" | "delete";
|
|
28
33
|
type ManagementScope = "user" | "project";
|
|
29
|
-
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { config?: ExtensionConfig };
|
|
34
|
+
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { model?: ExtensionContext["model"]; config?: ExtensionConfig };
|
|
30
35
|
|
|
31
36
|
interface ManagementParams {
|
|
32
37
|
action?: string;
|
|
@@ -166,6 +171,84 @@ function skillsWarning(cwd: string, skills: string[] | undefined): string | unde
|
|
|
166
171
|
return missing.length ? `Warning: skills not found: ${missing.join(", ")}.` : undefined;
|
|
167
172
|
}
|
|
168
173
|
|
|
174
|
+
function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
175
|
+
const base = agent.override?.base;
|
|
176
|
+
if (!base) return { ...agent };
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
...agent,
|
|
180
|
+
model: base.model,
|
|
181
|
+
fallbackModels: base.fallbackModels ? [...base.fallbackModels] : undefined,
|
|
182
|
+
thinking: base.thinking,
|
|
183
|
+
systemPromptMode: base.systemPromptMode,
|
|
184
|
+
inheritProjectContext: base.inheritProjectContext,
|
|
185
|
+
inheritSkills: base.inheritSkills,
|
|
186
|
+
defaultContext: base.defaultContext,
|
|
187
|
+
disabled: base.disabled,
|
|
188
|
+
systemPrompt: base.systemPrompt,
|
|
189
|
+
skills: base.skills ? [...base.skills] : undefined,
|
|
190
|
+
tools: base.tools ? [...base.tools] : undefined,
|
|
191
|
+
mcpDirectTools: base.mcpDirectTools ? [...base.mcpDirectTools] : undefined,
|
|
192
|
+
subagentOnlyExtensions: base.subagentOnlyExtensions ? [...base.subagentOnlyExtensions] : undefined,
|
|
193
|
+
completionGuard: base.completionGuard,
|
|
194
|
+
override: undefined,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function readAgentFrontmatterFields(filePath: string): Set<string> {
|
|
199
|
+
try {
|
|
200
|
+
const { frontmatter } = parseFrontmatter(fs.readFileSync(filePath, "utf-8"));
|
|
201
|
+
return new Set(Object.keys(frontmatter));
|
|
202
|
+
} catch {
|
|
203
|
+
return new Set();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<string, unknown>): Set<string> {
|
|
208
|
+
const fields = readAgentFrontmatterFields(agent.filePath);
|
|
209
|
+
const changed = (...names: string[]) => {
|
|
210
|
+
for (const name of names) fields.delete(name);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
if (hasKey(cfg, "name")) changed("name");
|
|
214
|
+
if (hasKey(cfg, "package")) changed("package");
|
|
215
|
+
if (hasKey(cfg, "description")) changed("description");
|
|
216
|
+
if (hasKey(cfg, "systemPrompt")) changed("systemPrompt");
|
|
217
|
+
if (hasKey(cfg, "model")) changed("model");
|
|
218
|
+
if (hasKey(cfg, "fallbackModels")) changed("fallbackModels");
|
|
219
|
+
if (hasKey(cfg, "tools")) changed("tools");
|
|
220
|
+
if (hasKey(cfg, "skills")) changed("skill", "skills");
|
|
221
|
+
if (hasKey(cfg, "extensions")) changed("extensions");
|
|
222
|
+
if (hasKey(cfg, "subagentOnlyExtensions")) changed("subagentOnlyExtensions");
|
|
223
|
+
if (hasKey(cfg, "thinking")) {
|
|
224
|
+
changed("thinking");
|
|
225
|
+
if (cfg.thinking === "off") fields.add("thinking");
|
|
226
|
+
}
|
|
227
|
+
if (hasKey(cfg, "systemPromptMode")) {
|
|
228
|
+
changed("systemPromptMode");
|
|
229
|
+
fields.add("systemPromptMode");
|
|
230
|
+
}
|
|
231
|
+
if (hasKey(cfg, "inheritProjectContext")) {
|
|
232
|
+
changed("inheritProjectContext");
|
|
233
|
+
fields.add("inheritProjectContext");
|
|
234
|
+
}
|
|
235
|
+
if (hasKey(cfg, "inheritSkills")) {
|
|
236
|
+
changed("inheritSkills");
|
|
237
|
+
fields.add("inheritSkills");
|
|
238
|
+
}
|
|
239
|
+
if (hasKey(cfg, "defaultContext")) changed("defaultContext");
|
|
240
|
+
if (hasKey(cfg, "output")) changed("output");
|
|
241
|
+
if (hasKey(cfg, "reads")) changed("defaultReads");
|
|
242
|
+
if (hasKey(cfg, "progress")) changed("defaultProgress");
|
|
243
|
+
if (hasKey(cfg, "maxSubagentDepth")) changed("maxSubagentDepth");
|
|
244
|
+
if (hasKey(cfg, "completionGuard")) {
|
|
245
|
+
changed("completionGuard");
|
|
246
|
+
if (cfg.completionGuard === true) fields.add("completionGuard");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return fields;
|
|
250
|
+
}
|
|
251
|
+
|
|
169
252
|
function parseStepList(raw: unknown): { steps?: ChainStepConfig[]; error?: string } {
|
|
170
253
|
if (!Array.isArray(raw)) return { error: "config.steps must be an array." };
|
|
171
254
|
if (raw.length === 0) return { error: "config.steps must include at least one step." };
|
|
@@ -480,6 +563,84 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
480
563
|
return result(lines.join("\n"));
|
|
481
564
|
}
|
|
482
565
|
|
|
566
|
+
function formatModelSource(agent: AgentConfig, currentModel: ParentModel | undefined): string {
|
|
567
|
+
if (agent.override && agent.model !== agent.override.base.model) {
|
|
568
|
+
return `${agent.override.scope} override`;
|
|
569
|
+
}
|
|
570
|
+
if (agent.model) return "builtin agent config";
|
|
571
|
+
if (currentModel) return "inherits current session model";
|
|
572
|
+
return "inherit requested, but no current session model is available";
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function handleModels(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
576
|
+
const requestedAgent = params.agent?.trim();
|
|
577
|
+
if (requestedAgent && !(BUILTIN_AGENT_NAMES as readonly string[]).includes(requestedAgent)) {
|
|
578
|
+
return result(`Builtin agent '${requestedAgent}' not found. Available: ${BUILTIN_AGENT_NAMES.join(", ")}.`, true);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const discovered = discoverAgentsAll(ctx.cwd);
|
|
582
|
+
const builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));
|
|
583
|
+
const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
584
|
+
const currentModel = ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined;
|
|
585
|
+
const preferredProvider = ctx.model?.provider;
|
|
586
|
+
const names = requestedAgent ? [requestedAgent] : [...BUILTIN_AGENT_NAMES];
|
|
587
|
+
|
|
588
|
+
if (requestedAgent) {
|
|
589
|
+
const agent = builtinByName.get(requestedAgent);
|
|
590
|
+
if (!agent) return result(`Builtin agent '${requestedAgent}' not found.`, true);
|
|
591
|
+
const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
|
|
592
|
+
const lines = [
|
|
593
|
+
"Builtin subagent model",
|
|
594
|
+
"",
|
|
595
|
+
`Agent: ${requestedAgent}`,
|
|
596
|
+
"Effective model:",
|
|
597
|
+
` ${resolvedModel ?? "(unresolved)"}`,
|
|
598
|
+
`Source: ${formatModelSource(agent, currentModel)}`,
|
|
599
|
+
];
|
|
600
|
+
if (agent.override) {
|
|
601
|
+
lines.push("Override file:");
|
|
602
|
+
lines.push(` ${agent.override.path}`);
|
|
603
|
+
}
|
|
604
|
+
if (agent.model && resolvedModel && agent.model !== resolvedModel) {
|
|
605
|
+
lines.push("Requested model setting:");
|
|
606
|
+
lines.push(` ${agent.model}`);
|
|
607
|
+
}
|
|
608
|
+
if (agent.disabled) lines.push("Disabled: true");
|
|
609
|
+
lines.push("Current session model:");
|
|
610
|
+
lines.push(` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`);
|
|
611
|
+
return result(lines.join("\n"));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const lines = [
|
|
615
|
+
"Builtin subagent models",
|
|
616
|
+
"",
|
|
617
|
+
"Current session model:",
|
|
618
|
+
` ${currentModel ? `${currentModel.provider}/${currentModel.id}` : "(unavailable)"}`,
|
|
619
|
+
"",
|
|
620
|
+
];
|
|
621
|
+
|
|
622
|
+
for (const name of names) {
|
|
623
|
+
const agent = builtinByName.get(name);
|
|
624
|
+
if (!agent) {
|
|
625
|
+
lines.push(name);
|
|
626
|
+
lines.push(" model:");
|
|
627
|
+
lines.push(" (builtin definition not found)");
|
|
628
|
+
lines.push(" source: missing");
|
|
629
|
+
lines.push("");
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
|
|
633
|
+
const source = `${formatModelSource(agent, currentModel)}${agent.disabled ? "; disabled" : ""}`;
|
|
634
|
+
lines.push(name);
|
|
635
|
+
lines.push(" model:");
|
|
636
|
+
lines.push(` ${resolvedModel ?? "(unresolved)"}`);
|
|
637
|
+
lines.push(` source: ${source}`);
|
|
638
|
+
lines.push("");
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return result(lines.join("\n"));
|
|
642
|
+
}
|
|
643
|
+
|
|
483
644
|
function handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
484
645
|
if (!params.agent && !params.chainName) return result("Specify 'agent' or 'chainName' for get.", true);
|
|
485
646
|
const hasBoth = Boolean(params.agent && params.chainName);
|
|
@@ -527,9 +688,10 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
527
688
|
const scope = scopeRaw as ManagementScope;
|
|
528
689
|
const isChain = hasKey(cfg, "steps");
|
|
529
690
|
const d = discoverAgentsAll(ctx.cwd);
|
|
691
|
+
const projectConfigDir = getProjectConfigDir(ctx.cwd);
|
|
530
692
|
const targetDir = isChain
|
|
531
|
-
? scope === "user" ? d.userChainDir : d.projectChainDir ?? path.join(
|
|
532
|
-
: scope === "user" ? d.userDir : d.projectDir ?? path.join(
|
|
693
|
+
? scope === "user" ? d.userChainDir : d.projectChainDir ?? path.join(projectConfigDir, "chains")
|
|
694
|
+
: scope === "user" ? d.userDir : d.projectDir ?? path.join(projectConfigDir, "agents");
|
|
533
695
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
534
696
|
if (nameExistsInScope(ctx.cwd, scope, runtimeName)) return result(`Name '${runtimeName}' already exists in ${scope} scope. Use update instead.`, true);
|
|
535
697
|
const targetPath = path.join(targetDir, isChain ? `${runtimeName}.chain.md` : `${runtimeName}.md`);
|
|
@@ -583,7 +745,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
583
745
|
const targetOrError = resolveTarget("agent", params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, params.agentScope);
|
|
584
746
|
if ("content" in targetOrError) return targetOrError;
|
|
585
747
|
const target = targetOrError;
|
|
586
|
-
const updated
|
|
748
|
+
const updated = editableAgentConfig(target);
|
|
587
749
|
const oldName = target.name;
|
|
588
750
|
if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
|
|
589
751
|
if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
|
|
@@ -600,6 +762,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
600
762
|
}
|
|
601
763
|
const applyError = applyAgentConfig(updated, cfg);
|
|
602
764
|
if (applyError) return result(applyError, true);
|
|
765
|
+
const preserveFrontmatterFields = preservedAgentFrontmatterFields(target, cfg);
|
|
603
766
|
updated.localName = newLocalName;
|
|
604
767
|
updated.packageName = newPackageName;
|
|
605
768
|
updated.name = buildRuntimeName(newLocalName, newPackageName);
|
|
@@ -621,7 +784,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
621
784
|
if (renamed.error) return result(renamed.error, true);
|
|
622
785
|
updated.filePath = renamed.filePath!;
|
|
623
786
|
}
|
|
624
|
-
fs.writeFileSync(updated.filePath, serializeAgent(updated), "utf-8");
|
|
787
|
+
fs.writeFileSync(updated.filePath, serializeAgent(updated, { preserveFrontmatterFields }), "utf-8");
|
|
625
788
|
if (updated.name !== oldName) {
|
|
626
789
|
const refs = discoverAgentsAll(ctx.cwd).chains.filter((c) => c.steps.some((s) => s.agent === oldName)).map((c) => `${c.name} (${c.source})`);
|
|
627
790
|
if (refs.length) warnings.push(`Warning: chains still reference '${oldName}': ${refs.join(", ")}.`);
|
|
@@ -703,6 +866,7 @@ export function handleManagementAction(action: string, params: ManagementParams,
|
|
|
703
866
|
switch (action as ManagementAction) {
|
|
704
867
|
case "list": return handleList(params, ctx);
|
|
705
868
|
case "get": return handleGet(params, ctx);
|
|
869
|
+
case "models": return handleModels(params, ctx);
|
|
706
870
|
case "create": return handleCreate(params, ctx);
|
|
707
871
|
case "update": return handleUpdate(params, ctx);
|
|
708
872
|
case "delete": return handleDelete(params, ctx);
|