pi-subagents 0.65.1 → 0.67.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 +123 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/researcher.md +23 -13
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +20 -3
- package/docs/configuration.md +25 -5
- package/docs/extension-api.md +124 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +59 -2
- package/docs/observability.md +46 -6
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +20 -10
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +40 -19
- package/inspector-runner.mjs +2 -2
- package/package.json +2 -1
- package/prompts/parallel-review.md +1 -1
- package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
- package/skills/pi-subagents/SKILL.md +14 -0
- package/skills/pi-subagents/references/execution-controls.md +20 -5
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +94 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +14 -0
- package/src/agents/builtin-names.ts +1 -0
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +76 -45
- package/src/api/shared-types.ts +3 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +58 -8
- package/src/extension/public-execution.ts +4 -3
- package/src/extension/rpc.ts +8 -21
- package/src/extension/schemas.ts +71 -80
- package/src/extension/tool-description.ts +29 -81
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +104 -67
- package/src/runs/background/active-async-capacity.ts +22 -18
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +5 -9
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +44 -30
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +117 -13
- package/src/runs/background/owned-process-tree.ts +35 -8
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +121 -36
- package/src/runs/background/run-status.ts +78 -5
- package/src/runs/background/runner-aliases.ts +28 -9
- package/src/runs/background/runner-child-launch.ts +88 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +458 -239
- package/src/runs/background/subagent-wait.ts +54 -8
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +37 -7
- package/src/runs/foreground/execution.ts +145 -56
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +584 -297
- package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
- package/src/runs/shared/acceptance.ts +7 -4
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +51 -47
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +167 -3
- package/src/runs/shared/child-launch.ts +28 -13
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +3 -1
- package/src/runs/shared/child-session.ts +75 -8
- package/src/runs/shared/child-tool-plan.ts +124 -5
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +6 -3
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +21 -11
- package/src/runs/shared/model-fallback.ts +12 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +7 -1
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +366 -208
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +65 -2
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/types.ts +85 -61
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -1
- package/src/slash/subagents-admin.ts +11 -4
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +27 -19
- package/src/tui/render.ts +172 -33
- package/src/watchdog/child-status.ts +8 -0
- package/src/watchdog/model-selection.ts +20 -0
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +1 -0
- package/src/watchdog/register-main.ts +31 -27
- package/src/watchdog/review.ts +132 -67
- package/src/watchdog/runtime.ts +82 -20
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +9 -3
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +4 -0
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +275 -17
- package/src/workflows/workflow-checklist.ts +13 -17
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
package/docs/watchdog.md
CHANGED
|
@@ -7,10 +7,11 @@ The watchdog is an opt-in second model that reviews what the agent just did and
|
|
|
7
7
|
| Timing | Trigger | Gate | Delivery |
|
|
8
8
|
|---|---|---|---|
|
|
9
9
|
| Boundary review | `agent_end` of every main or child turn | Repo changed | Steered into the transcript; the agent gets one continuation, then that turn is reviewed again |
|
|
10
|
+
| Main activity review | `agent_end`, with `clarification: true` | New delivered orchestration evidence; at most one additional review per user prompt | Same warning/clarification path, even without local edits |
|
|
10
11
|
| Cadence review | Every `cadence.everyNTools` tool results, minimum 5 | Opt-in | Steered after the current tool, before the next step |
|
|
11
12
|
| LSP pre-pass | Before boundary review | Changed TypeScript/JavaScript files | Diagnostics become watchdog findings without a model call |
|
|
12
13
|
|
|
13
|
-
Boundary reviews coalesce a turn's edits into one final-state review. Unchanged or reverted diffs are skipped
|
|
14
|
+
Boundary reviews coalesce a turn's edits into one final-state review. Unchanged or reverted diffs are skipped unless the main-only activity opt-in below admits new evidence; `.pi/subagents/` and `tmp/` artifacts remain excluded. In orchestrated runs, each writing child reviews its own worktree and the parent reviews the aggregate diff after child changes land. There are no idle timer reviews. Cadence monitoring is inspired by [Scopey](https://github.com/ArchAstro/scopey).
|
|
14
15
|
|
|
15
16
|
Children get the same boundary, cadence, and LSP behavior. Child cadence resolves from `children.overrides.<agent>.cadence`, then `children.cadence`, then top-level `cadence`:
|
|
16
17
|
|
|
@@ -67,7 +68,7 @@ Child watchdog findings are lifted into the parent in three ways:
|
|
|
67
68
|
## What the reviewer is given
|
|
68
69
|
|
|
69
70
|
- **Turn delta** with changed repo paths. Over-long input keeps the first 6,000 characters and the tail.
|
|
70
|
-
- **Current scope** (`scope.enabled`, default on): bounded real user prompts
|
|
71
|
+
- **Current scope** (`scope.enabled`, default on): bounded real user prompts. Side questions are additive; only explicit changes supersede older requirements.
|
|
71
72
|
- **`watchdog_diff`** when inside git: diff since the session-start commit, including later commits, plus untracked paths to inspect with `read`; accepts `path` and `stat:true`.
|
|
72
73
|
- **`WATCHDOG.md`** standing instructions, read fresh on every review: `<project>/.pi/WATCHDOG.md` first, then `~/.pi/agent/WATCHDOG.md`, capped at 8,000 characters. Set `guidance.watchdogMd: false` to ignore them.
|
|
73
74
|
- **LSP diagnostics** from `typescript-language-server`, auto-detected in `node_modules/.bin` or `PATH`; it is never installed and never run over the whole workspace. Errors become blockers, warnings concerns, and info/hints stay in status.
|
|
@@ -87,7 +88,9 @@ One model setting serves both boundary and cadence reviews per endpoint. Use a s
|
|
|
87
88
|
/subagents-watchdog on
|
|
88
89
|
```
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
When a main watchdog model is configured (including a session override), recommendations keep that model and its effective thinking level rather than judging its strength or independence. An unavailable or unauthenticated configured model is reported, not replaced. Without a configured main model, the recommendation remains Opus 4.8 or GPT 5.5 at thinking high, whichever your main session is not using and is authenticated.
|
|
92
|
+
|
|
93
|
+
`session model recommended` changes only this session. `model recommended` explicitly saves the recommendation to **user settings**, affecting other projects without overrides; it does not change project settings. Project and session overrides still take precedence. Use an explicit model to replace a configured choice. Saving a model does not enable the watchdog; use `on` separately.
|
|
91
94
|
|
|
92
95
|
```json
|
|
93
96
|
{
|
|
@@ -105,11 +108,39 @@ The recommendation is Opus 4.8 or GPT 5.5 at thinking high, whichever your main
|
|
|
105
108
|
|
|
106
109
|
Omit `main.model` to inherit the session model and thinking level. A `main.model` without a thinking suffix or `main.thinking` runs with thinking off, so prefer `:high` for the strong pairing.
|
|
107
110
|
|
|
111
|
+
Set `fallbackModels` in JSON settings on `main`, `children`, or `children.overrides.<agent>` to opt into an ordered fallback chain, for example `"fallbackModels": ["openai-codex/gpt-5.5:high"]`. Child overrides win over `children.fallbackModels`; neither inherits the main watchdog's chain. Arrays replace across user → project → session settings, and `[]` clears an inherited chain. Status shows configured chains.
|
|
112
|
+
|
|
113
|
+
Unavailable configured candidates are skipped and resolved duplicates are tried once. Each attempt uses a fresh reviewer with its own model auth, provider stream, and thinking; an inherited primary keeps the actual session model/thinking, while fallbacks use explicit-model thinking rules. Fallback follows normal subagent provider-failure semantics (including rate limits, quota, auth, unavailability, and provider timeouts), **only before any tool work**, including read-only inspection. Clean/normal completion, length limits, findings, clarification, cancellation, and the overall watchdog deadline never trigger fallback. All attempts share the original deadline; exhaustion remains a failed review. With no fallback chain, existing single-model behavior is unchanged.
|
|
114
|
+
|
|
108
115
|
Agents can call `subagent({ action: "watchdog.recommend-model" })` and `subagent({ action: "watchdog.configure", model: "recommended", scope: "session" | "user" | "project" })`. They should use `scope: "session"` unless you ask for a lasting default.
|
|
109
116
|
|
|
117
|
+
## Optional main-session clarification
|
|
118
|
+
|
|
119
|
+
Use `watchdog_warn` directly for evidence-backed reminders of forgotten authorized work; a question is not a prerequisite. Distinguish forgotten work from dependencies still pending or explicit holds. Use clarification when task status or intent is genuinely unclear. The orchestrator remains owner of its task/lane board.
|
|
120
|
+
|
|
121
|
+
With this opt-in, completed `turn_end` events retain a recent actual-activity tail: paired calls/results for `subagent` dispatch (no action), `subagent` actions `status`, `resume`, `interrupt`, `steer`, `stop`, `bg_wait`, and `subagent_supervisor` actions `pending`, `list`, `reply`. Pairing requires the same tool name and exact tool-call ID; raw results, unrelated tool names and watchdog management actions do not qualify. Each activity entry is bounded to 3,000 characters, with a 6,000-character recent tail retained across ordinary new prompts and skipped edit boundaries. Session replacement, compaction, shutdown or disabling clears it. This is observed text, not an inferred task board.
|
|
122
|
+
|
|
123
|
+
New unreviewed activity permits at most one additional boundary review per user prompt even with no local edit. Side questions keep earlier authorized task evidence available; they do not themselves trigger a model call. Activity gathered after that prompt's extra review remains available for the next prompt. Warning continuations cannot supply fresh triggering activity. No polling, task scheduling, cross-worktree scans or idle calls are added.
|
|
124
|
+
|
|
125
|
+
**Visibility limit:** external task/gate completions are visible when returned through those parent tool results. Standalone native completion notifications, arbitrary custom messages, direct shell/CI output, and events not delivered to the parent through these contracts are not ingested by this activity tail. Existing scope retains at most eight prompts (2,000 characters each); new streaming user input cancels an active review but is not added to scope unless `before_agent_start` fires. Reminders depend on retained evidence and model judgment, not an exhaustive view of running work.
|
|
126
|
+
|
|
127
|
+
Set `subagents.watchdog.clarification: true` in Pi settings alongside `enabled: true`. It defaults to `false` and applies **only to the main watchdog**, not child watchdogs or child permission arbitration.
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{ "subagents": { "watchdog": { "enabled": true, "clarification": true } } }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
At an eligible activity or repo-edit boundary, the reviewer may use `watchdog_ask` for one focused question when missing orchestrator context prevents a concrete judgment. It cannot ask during cadence reviews, after an accepted warning, or during stalemate. There is at most one question per real user prompt.
|
|
134
|
+
|
|
135
|
+
The tool **yields and ends that review**. A visible question with concrete evidence steers the main session into Pi's native automatic continuation after the boundary hook returns. The orchestrator handles the context as needed and continues; no answer, receipt, deadline or follow-up review is required or tracked. Questions are **not approval, permission, or warnings**.
|
|
136
|
+
|
|
137
|
+
Asking consumes the current review evidence, so an unchanged Git-backed boundary does not immediately review it again. Later reviews use the normal edit, cadence and bounded activity triggers, with the same read-only tools, warning thresholds, budgets and stalemate protections. Non-Git observed edits can also prompt a question: there is no cross-answer evidence guarantee to verify. Disabling watchdog or clarification, new user input, model changes and session lifecycle resets cancel applicable active reviews and suppress stale results; already delivered questions remain ordinary transcript messages.
|
|
138
|
+
|
|
139
|
+
Reviews retain the existing `agentEndTimeoutMs`. Questions and evidence are capped at 1,000 and 2,000 characters; scope, activity and delta share the existing 24,000-character input limit. Enabled cost adds at most one activity boundary review and one question-triggered continuation per prompt, not a dedicated answer review. Disabled execution does not collect activity or add polling, model calls or reviewer prompt/tool content. Child warning messaging and permission decisions remain unchanged.
|
|
140
|
+
|
|
110
141
|
## Child watchdogs
|
|
111
142
|
|
|
112
|
-
Opt in under `subagents.watchdog.children`. `model` and `thinking` set the default child watchdog; `overrides.<agent>` can set `model`, `thinking`, `enabled`, or `cadence` per role.
|
|
143
|
+
Opt in under `subagents.watchdog.children`. `model`, `fallbackModels`, and `thinking` set the default child watchdog; `overrides.<agent>` can set `model`, `fallbackModels`, `thinking`, `enabled`, or `cadence` per role.
|
|
113
144
|
|
|
114
145
|
## Launch rules
|
|
115
146
|
|
package/docs/workflows.md
CHANGED
|
@@ -45,6 +45,10 @@ Add `autofix` to `/parallel-review` or `/parallel-cleanup` to apply only the syn
|
|
|
45
45
|
|
|
46
46
|
Use direct `{ agent, task }` for one bounded child. Use `workflowScript` when the parent needs a stable keyed child, sequence, fanout, steering, retry, or aggregation. For ordinary parallel fanout, use `await runs.all([{ key, agent, task }, ...])`. It resolves to an ordered array, not a key map, so use indexes, destructuring, or `.map(...)`, not `results.<key>`. Do not read `.output` from unawaited `runs.run` launches. Store a `runs.run` promise only when the script later observes it with `await`, `Promise.race`, or `Promise.all`, such as steering a live child before awaiting its result. Scripts are ordinary JavaScript statement bodies. Use an explicit `return` for a useful result:
|
|
47
47
|
|
|
48
|
+
For multi-step or parallel work, make exactly one top-level `subagent` workflow call with `async:true` and launch children only inside it. Read this guide for recipes rather than constructing a second top-level orchestration. Available sandbox helpers include `runs.run`, `runs.all`, `runs.lanes`, `runs.steer`, `runs.status`, `runs.ref`/`runs.refs`, `emit`, `console`, standard JavaScript, and mission `state` when enabled. No filesystem, shell, arbitrary Pi tools, or host globals are available; named resources alone may grant `runs.host` authority.
|
|
49
|
+
|
|
50
|
+
Workflow-level child controls default onto each `runs.run`/`runs.all` launch; explicit child fields override them. See [retained children](tool-reference.md#retained-children) for follow-up challenges, [output binding](tool-reference.md#output-mode-details) for durable artifacts, and [schedules](missions.md#schedules) for delayed/recurring scripts.
|
|
51
|
+
|
|
48
52
|
Child results cross into the script as plain JSON data. Non-JSON host metadata is omitted, so use returned fields such as `runId`, `ok`, `output`, and `structuredOutput` for workflow control.
|
|
49
53
|
|
|
50
54
|
Validate a script without launching children:
|
|
@@ -96,17 +100,18 @@ subagent({
|
|
|
96
100
|
- `toolBudget` becomes the default for each child unless that child supplies a narrower value.
|
|
97
101
|
- `usageBudget` accounts for reported usage across completed workflow children. Once exhausted, it rejects later child launches but does not stop children that are already running.
|
|
98
102
|
- Budget and timeout stops return a structured `terminalOutcome` with `state: "partial"` and reason `budget_exhausted` or `timeout`. Workflow receipts keep settled child evidence for recovery.
|
|
103
|
+
- After an async workflow receipt is successfully published, `workflowReceiptPath` exposes its exact path in wait completion details, completion notifications, and exact status/debug details. Text responses also identify the receipt. Pending runs and failed receipt publications omit the reference; older status records are not backfilled. The reference records publication, not a guarantee against later retention cleanup. Raw result files retain `workflowReceipt: { path, receipt }`.
|
|
99
104
|
|
|
100
105
|
These controls are opt-in. Avoid tight hard budgets for mutation-capable workers unless the workflow has an explicit checkpoint and handoff path.
|
|
101
106
|
|
|
102
|
-
The result is `{ ok, errors }`. Invalid scripts return a tool error and include line and column data when available. Validation checks syntax, portable nested-async rules, literal `runs.run` and `runs.all` keys, duplicate literal keys in one `runs.all` group, direct keyed access to a known `runs.all` result, and statically clear non-JSON boundary values. Dynamic keys and other runtime-only values are accepted without a warning. Validation does not discover agents, launch children, or create run artifacts.
|
|
107
|
+
The result is `{ ok, errors }`. Invalid scripts return a tool error and include line and column data when available. Validation checks syntax, portable nested-async rules, literal `runs.run` and `runs.all` keys and child `baseRef` values, duplicate literal keys in one `runs.all` group, direct keyed access to a known `runs.all` result, and statically clear non-JSON boundary values. Dynamic keys and other runtime-only values are accepted without a warning. Validation does not discover agents, launch children, or create run artifacts.
|
|
103
108
|
|
|
104
109
|
```js
|
|
105
110
|
subagent({ workflowScript: `
|
|
106
|
-
const scan = await runs.run("scan", { agent: "scout", task: "Scan the codebase" });
|
|
111
|
+
const scan = await runs.run("scan", { label: "Map codebase behavior", agent: "scout", task: "Scan the codebase" });
|
|
107
112
|
const reviews = await runs.all([
|
|
108
|
-
{ key: "correctness", agent: "reviewer", task: "Review correctness: " + scan.output },
|
|
109
|
-
{ key: "tests", agent: "reviewer", task: "Review tests: " + scan.output }
|
|
113
|
+
{ key: "correctness", label: "Review codebase correctness", agent: "reviewer", task: "Review correctness: " + scan.output },
|
|
114
|
+
{ key: "tests", label: "Review test coverage", agent: "reviewer", task: "Review tests: " + scan.output }
|
|
110
115
|
]);
|
|
111
116
|
return reviews.map(result => result.output);
|
|
112
117
|
` });
|
|
@@ -117,7 +122,7 @@ Keep helper functions portable across Node and Bun. Use top-level `await`, plain
|
|
|
117
122
|
```js
|
|
118
123
|
subagent({ workflowScript: `
|
|
119
124
|
function scan() {
|
|
120
|
-
return runs.run("scan", { agent: "scout", task: "Scan the codebase" });
|
|
125
|
+
return runs.run("scan", { label: "Map codebase behavior", agent: "scout", task: "Scan the codebase" });
|
|
121
126
|
}
|
|
122
127
|
const result = await scan();
|
|
123
128
|
return result.output;
|
|
@@ -128,8 +133,8 @@ Chaining is still supported. The supported form is scripted chaining: await one
|
|
|
128
133
|
|
|
129
134
|
```js
|
|
130
135
|
subagent({ workflowScript: `
|
|
131
|
-
const plan = await runs.run("plan", { agent: "scout", task: "Plan the migration" });
|
|
132
|
-
const patch = await runs.run("patch", { agent: "worker", task: "Implement this plan:\n" + plan.output });
|
|
136
|
+
const plan = await runs.run("plan", { label: "Plan migration behavior", agent: "scout", task: "Plan the migration" });
|
|
137
|
+
const patch = await runs.run("patch", { label: "Implement migration behavior", agent: "worker", task: "Implement this plan:\n" + plan.output });
|
|
133
138
|
return patch.output;
|
|
134
139
|
` });
|
|
135
140
|
```
|
|
@@ -144,16 +149,16 @@ subagent({ workflowScript: `
|
|
|
144
149
|
{
|
|
145
150
|
key: "api",
|
|
146
151
|
stages: [
|
|
147
|
-
{ key: "writer", agent: "worker", task: "Implement the API change" },
|
|
148
|
-
{ key: "challenge", resume: "previous", task: "Challenge the API implementation" },
|
|
149
|
-
{ key: "review", agent: "reviewer", task: "Review the API lane" }
|
|
152
|
+
{ key: "writer", label: "Implement API behavior", agent: "worker", task: "Implement the API change" },
|
|
153
|
+
{ key: "challenge", label: "Challenge API behavior", resume: "previous", task: "Challenge the API implementation" },
|
|
154
|
+
{ key: "review", label: "Review API behavior", agent: "reviewer", task: "Review the API lane" }
|
|
150
155
|
]
|
|
151
156
|
},
|
|
152
157
|
{
|
|
153
158
|
key: "ui",
|
|
154
159
|
stages: [
|
|
155
|
-
{ key: "writer", agent: "worker", task: "Implement the UI change" },
|
|
156
|
-
{ key: "review", agent: "reviewer", task: "Review the UI lane" }
|
|
160
|
+
{ key: "writer", label: "Implement UI behavior", agent: "worker", task: "Implement the UI change" },
|
|
161
|
+
{ key: "review", label: "Review UI behavior", agent: "reviewer", task: "Review the UI lane" }
|
|
157
162
|
]
|
|
158
163
|
}
|
|
159
164
|
]);
|
|
@@ -202,7 +207,7 @@ subagent({ workflowScript: `
|
|
|
202
207
|
` });
|
|
203
208
|
```
|
|
204
209
|
|
|
205
|
-
The receipt state is `queued`, `delivered`, `missed`, or `failed`. `delivered` means the child Pi
|
|
210
|
+
The receipt state is `queued`, `delivered`, `missed`, or `failed`. For an async child, `delivered` means it consumed the correlated user input; for a foreground child, it means the in-process Pi transport accepted the input. It does not mean the model followed it. `missed` means the keyed child became terminal or had no live route before delivery. This first slice uses the existing foreground and async steering transports but does not start steering recovery. Workflow traces include one steering attempt entry and one receipt entry.
|
|
206
211
|
|
|
207
212
|
Always await or return a `runs.steer` promise. The workflow waits for an observed steering side effect to settle before it exits and rejects fire-and-forget calls. Use ordinary `Promise.race` when the first child or steering receipt should advance the script. There is no callback API or child inbox access.
|
|
208
213
|
|
|
@@ -355,6 +360,8 @@ known, or for explicit emergency hotfix lanes.
|
|
|
355
360
|
|
|
356
361
|
For watched same-repo workflows, pass `async:false` only when the parent must block until completion. That blocking mode also shows the live in-chat workflow card. `chatProgress` can force `off` or `live-card` when the automatic policy is not what you want. Blocking workflows default to a 30-minute timeout; async workflows have no default timeout. See the [tool reference](tool-reference.md) for the full parameter list.
|
|
357
362
|
|
|
363
|
+
Synchronous workflows publish trace and `emit(...)` updates through the tool update callback regardless of `chatProgress`, including RPC/headless and cross-repository runs. These updates include `details.workflow` and `details.workflowChildren`; `chatProgress: "off"` disables the live card, not transport progress. Running foreground child rows additionally expose bounded `activity` (current tool, timing, and counters), plus resolved model/thinking when available, keyed by `childId`. Activity-only updates coalesce over 100 ms; lifecycle updates remain immediate. Activity clears when children settle, and is not persisted for async workflows. Tool names are limited to 256 UTF-8 bytes and each activity object is below 2 KiB (including JSON escaping); arguments and transcripts are not forwarded.
|
|
364
|
+
|
|
358
365
|
The legacy `/chain`, `/parallel`, and `/run-chain` commands are not registered.
|
|
359
366
|
|
|
360
367
|
## Direct commands
|
|
@@ -377,10 +384,14 @@ Each child uses the existing worktree lifecycle: it branches from clean HEAD, jo
|
|
|
377
384
|
|
|
378
385
|
A top-level `{ workflowScript, worktree: true }` makes isolation the default for every workflow child. An individual child can override that default with `worktree: false`. Keep one writer when parallel writes are not intentionally isolated.
|
|
379
386
|
|
|
380
|
-
|
|
387
|
+
Before a materialized `runs.run` or `runs.all` group dispatches fresh children, isolated sources must be Git repositories with clean working trees (excluding `.pi/subagents/` runtime state). A rejected group dispatches no children and spends no fan-out slots or child output claims; key-level failure traces can remain. Checks are shared only within that group, are cancellable, and run again at allocation because sources can change. Retained resumes keep their stored contracts. Select the correct cwd or arrange an operator-approved commit/stash; isolation is never dropped automatically.
|
|
388
|
+
|
|
389
|
+
Use `baseRef` to branch managed worktrees from `HEAD` or a supported named ref such as `refs/heads/release`, `refs/tags/v1`, or `origin/main`. Full 40/64-character commit IDs and revision expressions such as `HEAD~1` are unsupported. For example, `{ workflowScript, worktree: true, baseRef: "refs/heads/release" }` applies the release ref to children unless a child supplies its own `baseRef`. If omitted, the default `HEAD` is resolved at worktree allocation, not when the script is validated or a schedule is created. The source checkout must still be clean, and the ref must resolve to a commit before any worktree is allocated.
|
|
381
390
|
|
|
382
391
|
Configure the worktree provider, native path layout, base directory, and setup hook in [configuration.md](configuration.md).
|
|
383
392
|
|
|
393
|
+
Setup waits remain nonblocking and cancellable. Normal cleanup, including detached foreground finalization, waits for the same in-process setup turn rather than retaining worktrees merely because another setup is active. This is not a cross-process lock. Hooks must follow the [finite setup contract](configuration.md#worktreesetuphook).
|
|
394
|
+
|
|
384
395
|
### Lane metadata lifecycle
|
|
385
396
|
|
|
386
397
|
Workflow children may declare a bounded `lane` object (`version`, `key`, optional
|
|
@@ -402,11 +413,13 @@ Older runs without lane metadata remain readable and retain their existing
|
|
|
402
413
|
handoff/cleanup behavior. Missing lane, receipt, or handoff metadata is
|
|
403
414
|
unknown—not eligible for destructive cleanup.
|
|
404
415
|
|
|
405
|
-
|
|
406
|
-
display-only status
|
|
407
|
-
|
|
408
|
-
ownership
|
|
409
|
-
|
|
416
|
+
Managed setup records actual allocation attempts in the handoff; only validated
|
|
417
|
+
allocations become cleanup tasks and display-only status paths/branches. On
|
|
418
|
+
cancellation or failure with unknown settlement, it retains actual/attempted
|
|
419
|
+
ownership evidence and artifacts for manual reconciliation, blocking further
|
|
420
|
+
unsafe setup and cleanup in that process. An allocator interrupted before
|
|
421
|
+
reporting its path may leave branch-only diagnostics, never an invented path.
|
|
422
|
+
Inspect the handoff before reconciliation; cleanup still requires fresh checks.
|
|
410
423
|
|
|
411
424
|
## Supervisor coordination (child asks parent)
|
|
412
425
|
|
|
@@ -432,6 +445,14 @@ Children should not ask for clarification when the only conflict is review-only/
|
|
|
432
445
|
|
|
433
446
|
The parent replies with `subagent_supervisor({ action: "reply", replyTo, message })` or checks pending requests with `subagent_supervisor({ action: "pending" })`. Supervisor messages are scoped to the exact Pi session id that spawned the child. A second Pi session in the same repository does not receive those requests.
|
|
434
447
|
|
|
448
|
+
A nested coordinator needs both directions of coordination. If its agent declares an explicit `tools` allowlist, include `subagent_supervisor` to answer its own children, alongside `subagent` for delegation and `contact_supervisor` for asking its parent:
|
|
449
|
+
|
|
450
|
+
```yaml
|
|
451
|
+
tools: read, subagent, contact_supervisor, subagent_supervisor
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
For A → B → C, C's request belongs to B, not A. B can escalate a separate question to A with `contact_supervisor`, then answer C using C's original `replyTo` request id. A's reply to B does not resolve C's request, and steering is not a substitute for replying. Only fanout-authorized children get the downward supervisor provider; explicit tool exclusions and capability ceilings still apply, and ordinary leaves do not gain delegation or reply tools. Requesting `subagent_supervisor` without fanout authorization fails at launch with an actionable error. A coordinator that excludes the reply tool does not start downward supervision or receive prompts to use it. Explicitly selected native coordination tools survive host-builtin filtering because their providers are child runtime hooks, not host builtins.
|
|
455
|
+
|
|
435
456
|
Child-side routine completion handoffs are not expected. If a child appears stalled, needs-attention notices show up in the parent session with useful next actions, such as checking `subagent({ action: "status" })`, interrupting the run, or nudging the child.
|
|
436
457
|
|
|
437
458
|
If a `workflowScript` child detaches through `contact_supervisor`, the enclosing async workflow stays `paused` until that child exits. Then the extension reconciles it to `complete` or `failed`. Wait on the child until that happens.
|
package/inspector-runner.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createJiti } from "jiti";
|
|
2
2
|
|
|
3
3
|
const jiti = createJiti(import.meta.url);
|
|
4
|
-
const { runInspector } = await jiti.import("./src/inspectors/
|
|
4
|
+
const { runInspector } = await jiti.import("./src/inspectors/inspector-runner.ts");
|
|
5
5
|
|
|
6
6
|
try {
|
|
7
7
|
runInspector();
|
|
8
8
|
} catch (cause) {
|
|
9
|
-
process.stderr.write(`
|
|
9
|
+
process.stderr.write(`Inspector failed: ${cause instanceof Error ? cause.message : String(cause)}\n`);
|
|
10
10
|
process.exitCode = 1;
|
|
11
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.0",
|
|
4
4
|
"description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"./agents": "./src/api/agents.ts",
|
|
14
14
|
"./delegation": "./src/api/delegation.ts",
|
|
15
15
|
"./capability-ceiling": "./src/api/capability-ceiling.ts",
|
|
16
|
+
"./workflow-resources": "./src/api/workflow-resources.ts",
|
|
16
17
|
"./preflight": "./src/api/preflight.ts",
|
|
17
18
|
"./control-channel": "./src/api/control-channel.ts",
|
|
18
19
|
"./intercom-bridge": "./src/api/intercom-bridge.ts",
|
|
@@ -28,7 +28,7 @@ Choose or adapt angles when the work calls for it:
|
|
|
28
28
|
|
|
29
29
|
Prefer three strong reviewers over many vague reviewers.
|
|
30
30
|
|
|
31
|
-
Give every reviewer a specific task prompt naming its angle. Ask reviewers to return concise, evidence-backed findings with file/line references and suggested fixes. Filter on evidence, not severity: a finding must be concrete
|
|
31
|
+
Give every reviewer a specific task prompt naming its angle. Ask reviewers to return concise, evidence-backed findings with file/line references and suggested fixes. Filter on evidence, not severity: a finding must be concrete and current within the named review target, and supported by source proof, a test or repro, or a contract contradiction. For a diff review, require that the issue is caused or made reachable by that diff. Label findings P0/P1/P2. P0 blocks merge. P1 should be fixed before release. P2 is report-only. End each review with `Merge verdict: BLOCK`, `Merge verdict: OK`, or `Merge verdict: OK with notes`. If nothing qualifies, ask the reviewer to say exactly `No issues found.` The response should be review feedback, not a context summary. Reviewers must not edit files unless I explicitly ask for a writer pass.
|
|
32
32
|
|
|
33
33
|
Do not default first-pass reviews to `blockers only`. That phrase is valid only for final pre-merge re-checks after P1/P2 findings are already inventoried, or for explicit emergency hotfix lanes where non-blocking findings are intentionally deferred.
|
|
34
34
|
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
// Only loaded when the parent supplies Pi 0.85.0's missing server exports.
|
|
2
1
|
import { registerHooks } from "node:module";
|
|
3
2
|
import { pathToFileURL } from "node:url";
|
|
4
3
|
|
|
5
|
-
const aliases = JSON.parse(process.env.JITI_ALIAS);
|
|
4
|
+
const aliases = JSON.parse(process.env.JITI_ALIAS ?? "{}");
|
|
5
|
+
const redirected = new Set([
|
|
6
|
+
"@earendil-works/pi-server",
|
|
7
|
+
"@earendil-works/pi-server/unix",
|
|
8
|
+
"@earendil-works/pi-tui",
|
|
9
|
+
]);
|
|
10
|
+
|
|
6
11
|
registerHooks({
|
|
7
12
|
resolve(specifier, context, nextResolve) {
|
|
8
|
-
if (specifier
|
|
13
|
+
if (redirected.has(specifier) && aliases[specifier]) {
|
|
9
14
|
return nextResolve(pathToFileURL(aliases[specifier]).href, context);
|
|
10
15
|
}
|
|
11
16
|
return nextResolve(specifier, context);
|
|
@@ -50,6 +50,18 @@ use ordinary `runs.run(...)` / `runs.all(...)`. See the [canonical staged-lane
|
|
|
50
50
|
example](../../docs/workflows.md#parallel-sequential-lanes). Keep assignments
|
|
51
51
|
bounded, but do not add stages or ceremony just to satisfy this skill.
|
|
52
52
|
|
|
53
|
+
When composing `runs.run(...)`, `runs.all(...)`, or `runs.lanes(...)`, always
|
|
54
|
+
supply a short verb + behavior display `label` derived from the task, unless
|
|
55
|
+
the user supplied an explicit label; preserve that label. Keep the stable
|
|
56
|
+
machine `key` independent (for example, `issue2011-writer` with
|
|
57
|
+
`label: "Fix workflow steering"`). For `runs.lanes`, put labels on stage
|
|
58
|
+
items, not lane objects. Use stage-appropriate labels for reviews and retained-child
|
|
59
|
+
follow-ups too (for example, `Review workflow steering`). Generate labels in
|
|
60
|
+
the orchestrator while composing the launch—no extra model call, runtime
|
|
61
|
+
generator, or schema change. Native direct `{ agent, task }` calls have no
|
|
62
|
+
top-level `label` parameter; do not invent one or wrap a tiny single task in
|
|
63
|
+
a workflow just to label it.
|
|
64
|
+
|
|
53
65
|
Use async/background by default. Set `async:false` only when the parent must
|
|
54
66
|
block. Final reviews, validation gates, oracle checks, and publication checks
|
|
55
67
|
stay async.
|
|
@@ -71,6 +83,8 @@ that runner explicitly supports the option.
|
|
|
71
83
|
|
|
72
84
|
## Read the reference for the branch
|
|
73
85
|
|
|
86
|
+
For exact API fields and worked examples, call `subagent({action:"guide",topic:"tool-reference"})` or `topic:"workflows"`. The compact tool definition is not the recipe catalog; use `topic:"missions"` for mission updates and schedules.
|
|
87
|
+
|
|
74
88
|
| Branch | Read |
|
|
75
89
|
| --- | --- |
|
|
76
90
|
| Delegate or choose roles, prompts, models, or slash commands | `references/prompting-and-roles.md` |
|
|
@@ -83,10 +83,10 @@ lanes, or a fanout that the parent will consume together.
|
|
|
83
83
|
```js
|
|
84
84
|
subagent({
|
|
85
85
|
workflowScript: `
|
|
86
|
-
const scan = await runs.run("scan", { agent: "scout", task: "Map the target" });
|
|
86
|
+
const scan = await runs.run("scan", { label: "Map target behavior", agent: "scout", task: "Map the target" });
|
|
87
87
|
const reviews = await runs.all([
|
|
88
|
-
{ key: "correctness", agent: "reviewer", task: "Review correctness: " + scan.output },
|
|
89
|
-
{ key: "tests", agent: "reviewer", task: "Review tests: " + scan.output }
|
|
88
|
+
{ key: "correctness", label: "Review target correctness", agent: "reviewer", task: "Review correctness: " + scan.output },
|
|
89
|
+
{ key: "tests", label: "Review target test coverage", agent: "reviewer", task: "Review tests: " + scan.output }
|
|
90
90
|
]);
|
|
91
91
|
return reviews.map(result => result.output);
|
|
92
92
|
`
|
|
@@ -109,6 +109,7 @@ Terminal async workflows also persist `workflow-receipt.json` beside `status.jso
|
|
|
109
109
|
|
|
110
110
|
```js
|
|
111
111
|
return runs.run("cross-oracle", {
|
|
112
|
+
label: "Challenge proposed direction",
|
|
112
113
|
resume: { workflowRunId: "<pass-1-workflow-id>", key: "advisor-oracle", latest: true },
|
|
113
114
|
task: "Review the focused challenge packet."
|
|
114
115
|
});
|
|
@@ -120,7 +121,8 @@ Keyed resume reads that one exact receipt and revalidates the retained run at la
|
|
|
120
121
|
|
|
121
122
|
For a broad plan with a known set of narrow, visible stages per lane, use
|
|
122
123
|
`runs.lanes(...)` inside a `workflowScript`; it is a nested helper, not a
|
|
123
|
-
top-level `subagent` mode. Give each lane and stage a stable key
|
|
124
|
+
top-level `subagent` mode. Give each lane and stage a stable key; give stage
|
|
125
|
+
items a short verb + behavior `label`, preserving explicit user labels. The first
|
|
124
126
|
stage from every lane is launched together, then later stages sequence per lane.
|
|
125
127
|
`resume: "previous"` requires the retained predecessor, and a failed or blocked
|
|
126
128
|
stage blocks only that lane. The returned board exposes lane/stage results for
|
|
@@ -258,7 +260,7 @@ A cooperating terminal runtime can register read-only external records through `
|
|
|
258
260
|
|
|
259
261
|
### Scheduled subagent runs
|
|
260
262
|
|
|
261
|
-
Schedules are durable project records under `.pi/subagents/schedules/`. They are enabled by default; set `{ "scheduledRuns": { "enabled": false } }` in `~/.pi/agent/extensions/subagent/config.json` to disable them. Only schedule explicit work the user asked for.
|
|
263
|
+
Schedules are durable project records under `.pi/subagents/schedules/`. They are enabled by default; set `{ "scheduledRuns": { "enabled": false } }` in `~/.pi/agent/extensions/subagent/config.json` to disable them. Only schedule explicit work the user asked for. To keep schedules outside the project repository, set `{ "scheduledRuns": { "storeRoot": "~/.pi/subagent-schedules" } }` in the same config: `storeRoot` accepts an absolute path or a `~/`-prefixed path, and records land under `<storeRoot>/<sha256(path.resolve(cwd)) first 20 hex>/<scheduleId>/`.
|
|
262
264
|
|
|
263
265
|
```typescript
|
|
264
266
|
// One-shot reviewer
|
|
@@ -327,6 +329,19 @@ subagent({ action: "steer", id: "abc123", message: "Focus on the failing test."
|
|
|
327
329
|
|
|
328
330
|
The action waits up to three seconds for the child Pi session to accept the correlated user input and returns a request id with `delivered`, `scheduled`, `pending`, `partial`, `recovered`, or `failed` plus per-child states. Indexed pending children return `scheduled` immediately. Only a top-level single-child run may automatically interrupt after a missed acknowledgment and recover after confirmed pause within a further 15 seconds. Recovery preserves the original child contract and only its remaining deadline, turn, and tool budgets. If the session is missing, a budget is exhausted, the pause cannot be confirmed, or replacement launch fails, the source remains paused when pausing succeeded and the action returns the exact failure. Chain, parallel, and nested runs never auto-interrupt; inspect their per-child outcomes and handle failures explicitly. A late acknowledgment is recorded and cannot cancel committed recovery.
|
|
329
331
|
|
|
332
|
+
Steering supports three delivery modes via the `mode` parameter (`steer` is the default):
|
|
333
|
+
|
|
334
|
+
- `mode: "steer"` — interrupt the child at the next safe point of its current turn and deliver the message.
|
|
335
|
+
- `mode: "follow_up"` — do not interrupt; queue input through Pi's native follow-up path for the next turn boundary. Eligible completed retained workflow children (single-step runs in state `complete` with a stored session file) receive the message as a revival brief (`queueRevivalBrief`) when they are revived; paused children reject follow-up steering outright. The 20-message queue limit applies to retained revival briefs, not live follow-up input.
|
|
336
|
+
- `mode: "auto"` — same next-safe-point delivery path as `steer`, but without the automatic pause-and-revive recovery after a missed acknowledgment.
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
subagent({ action: "steer", id: "abc123", mode: "follow_up", message: "After this step, also validate the config file." })
|
|
340
|
+
subagent({ action: "steer", id: "abc123", mode: "auto", message: "Switch to the failing test now." })
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
For async runs, `delivered` records that the child consumed the correlated user input; foreground `delivered` records in-process transport acceptance. Neither is proof of model compliance. A live foreground follow-up acknowledgment reports `queued`, meaning Pi accepted it into its follow-up queue, not that it was delivered. The foreground transport does not provide a later correlated queued-to-delivered receipt.
|
|
344
|
+
|
|
330
345
|
## Watchdog
|
|
331
346
|
|
|
332
347
|
The subagent watchdog is an **opt-in** adversarial change reviewer. It is not the
|
|
@@ -96,6 +96,7 @@ A minimal agent file looks like this:
|
|
|
96
96
|
name: my-agent
|
|
97
97
|
package: code-analysis
|
|
98
98
|
description: What this agent does
|
|
99
|
+
advertise: true
|
|
99
100
|
aliases: developer, coder
|
|
100
101
|
model: provider/model-id
|
|
101
102
|
thinking: high
|
|
@@ -111,7 +112,7 @@ skillPath: ./skills, ../shared-skills
|
|
|
111
112
|
Your system prompt here.
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
That is only a starting point. Omit `package` for the traditional unqualified runtime name. Common optional fields include:
|
|
115
|
+
That is only a starting point. Omit `package` for the traditional unqualified runtime name. Set `advertise: true` only when the parent should receive this agent's name and description before deciding whether to delegate; advertisement is off by default. Common optional fields include:
|
|
115
116
|
- `defaultProgress`
|
|
116
117
|
- `defaultReads`
|
|
117
118
|
- `output`
|
|
@@ -63,7 +63,7 @@ Council advisors are read-only. User or project `council-*` profiles choose allo
|
|
|
63
63
|
|
|
64
64
|
### Parallel review technique
|
|
65
65
|
|
|
66
|
-
Use this when the user wants adversarial review of a diff, plan, issue, file, or implemented work. Launch fresh-context `reviewer` agents with distinct angles generated from the actual target. Common angles are correctness/regressions, tests/validation, and simplicity/maintainability; adapt for TypeScript, UI, security, docs, or large structural changes. Reviewers should inspect files and diffs directly, return concise evidence-backed findings with file/line references, and avoid edits unless the user explicitly asks for a writer pass. Filter on evidence, not severity: report
|
|
66
|
+
Use this when the user wants adversarial review of a diff, plan, issue, file, or implemented work. Launch fresh-context `reviewer` agents with distinct angles generated from the actual target. Common angles are correctness/regressions, tests/validation, and simplicity/maintainability; adapt for TypeScript, UI, security, docs, or large structural changes. Reviewers should inspect files and diffs directly, return concise evidence-backed findings with file/line references, and avoid edits unless the user explicitly asks for a writer pass. Filter on evidence, not severity: report concrete current issues within the named review target, with source proof, a test or repro, or a contract contradiction. For a diff review, require that the issue is caused or made reachable by that diff. Label findings P0/P1/P2 and end with `Merge verdict: BLOCK`, `Merge verdict: OK`, or `Merge verdict: OK with notes`. Use `blockers only` only for final pre-merge re-checks after P1/P2 findings are already captured, or for explicit emergency hotfix lanes where non-blocking findings are intentionally deferred. For targeted follow-up, ask only whether the named finding was resolved, whether the fix introduced a new defect in the fix blast radius, and whether prior P1/P2 notes still stand. For bot or PR-comment triage, classify each comment as VALID, STALE, INVALID, or OUT-OF-POLICY against current HEAD, then assign P0/P1/P2 only to VALID comments. The parent synthesizes fixes worth doing now, optional improvements, and feedback to ignore/defer before applying anything.
|
|
67
67
|
|
|
68
68
|
### Proactive skill-specialist technique
|
|
69
69
|
|
|
@@ -283,7 +283,7 @@ Keep the parent/orchestrator on the ordinary strong default model because omissi
|
|
|
283
283
|
|
|
284
284
|
Examples are illustrative, not requirements. Map these tiers to concrete models in user/project settings or a profile. A non-OpenAI setup should choose comparable available models by capability.
|
|
285
285
|
|
|
286
|
-
Use `fallbackModels` when a tier has provider quota or availability risk.
|
|
286
|
+
Use `fallbackModels` when a tier has provider quota or availability risk. Forked children keep their requested thinking level even when provider-specific reasoning blocks are stripped from the inherited transcript.
|
|
287
287
|
|
|
288
288
|
If a provider rejects model IDs with thinking suffixes, use
|
|
289
289
|
`subagents.disableThinking: true` in user or project settings to clear bundled
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import type { ResolvedSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
3
|
+
import { isAgentAllowedByCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
4
|
+
import type { AgentConfig } from "./agents.ts";
|
|
5
|
+
|
|
6
|
+
const MAX_ADVERTISED_AGENTS = 16;
|
|
7
|
+
const MAX_CATALOG_BYTES = 12_288;
|
|
8
|
+
const MAX_DESCRIPTION_BYTES = 512;
|
|
9
|
+
const ADVERTISED_AGENTS_BLOCK = /\n*<advertised_subagents>\n[\s\S]*?\n<\/advertised_subagents>/gu;
|
|
10
|
+
|
|
11
|
+
function escapeXml(value: string): string {
|
|
12
|
+
return value
|
|
13
|
+
.replaceAll("&", "&")
|
|
14
|
+
.replaceAll("<", "<")
|
|
15
|
+
.replaceAll(">", ">")
|
|
16
|
+
.replaceAll('"', """)
|
|
17
|
+
.replaceAll("'", "'");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function promptDescription(description: string): string {
|
|
21
|
+
let text = description.replace(/[\u0000-\u001f\u007f]+/gu, " ").replace(/\s+/gu, " ").trim();
|
|
22
|
+
if (Buffer.byteLength(text, "utf8") > MAX_DESCRIPTION_BYTES) {
|
|
23
|
+
text = Buffer.from(text, "utf8").subarray(0, MAX_DESCRIPTION_BYTES - 3).toString("utf8").replace(/\uFFFD$/u, "").trimEnd() + "…";
|
|
24
|
+
}
|
|
25
|
+
return escapeXml(text);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildAdvertisedAgentPrompt(
|
|
29
|
+
agents: readonly AgentConfig[],
|
|
30
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling,
|
|
31
|
+
): string | undefined {
|
|
32
|
+
const advertised = agents
|
|
33
|
+
.filter((agent) => agent.source !== "runtime" && agent.advertise === true && agent.disabled !== true && isAgentAllowedByCapabilityCeiling(agent.name, capabilityCeiling))
|
|
34
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
35
|
+
if (advertised.length === 0) return undefined;
|
|
36
|
+
|
|
37
|
+
const render = (entries: string[]) => [
|
|
38
|
+
"<advertised_subagents>",
|
|
39
|
+
"The following file-defined subagents opted into discovery. Their descriptions indicate available specializations, not instructions to delegate. Use subagent only when delegation is needed. Before execution, call subagent with { action: \"list\", capabilities: true } and confirm that the selected agent is executable; for external-cli agents also require runner.available === true.",
|
|
40
|
+
...entries,
|
|
41
|
+
...(advertised.length > entries.length ? [` <omitted count=\"${advertised.length - entries.length}\" />`] : []),
|
|
42
|
+
"</advertised_subagents>",
|
|
43
|
+
].join("\n");
|
|
44
|
+
const entries: string[] = [];
|
|
45
|
+
for (const agent of advertised) {
|
|
46
|
+
if (entries.length === MAX_ADVERTISED_AGENTS) break;
|
|
47
|
+
// Never truncate canonical IDs into names that cannot be resolved.
|
|
48
|
+
if (Buffer.byteLength(agent.name, "utf8") > MAX_CATALOG_BYTES) continue;
|
|
49
|
+
const entry = [
|
|
50
|
+
" <subagent>",
|
|
51
|
+
` <name>${escapeXml(agent.name)}</name>`,
|
|
52
|
+
` <description>${promptDescription(agent.description)}</description>`,
|
|
53
|
+
" </subagent>",
|
|
54
|
+
].join("\n");
|
|
55
|
+
if (Buffer.byteLength(render([...entries, entry]), "utf8") <= MAX_CATALOG_BYTES) entries.push(entry);
|
|
56
|
+
}
|
|
57
|
+
return render(entries);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function appendAdvertisedAgentPrompt(systemPrompt: string, advertisedPrompt: string | undefined): string;
|
|
61
|
+
export function appendAdvertisedAgentPrompt(systemPrompt: string[], advertisedPrompt: string | undefined): string[];
|
|
62
|
+
export function appendAdvertisedAgentPrompt(systemPrompt: undefined, advertisedPrompt: string | undefined): string | undefined;
|
|
63
|
+
export function appendAdvertisedAgentPrompt(
|
|
64
|
+
systemPrompt: string | string[] | undefined,
|
|
65
|
+
advertisedPrompt: string | undefined,
|
|
66
|
+
): string | string[] | undefined;
|
|
67
|
+
export function appendAdvertisedAgentPrompt(
|
|
68
|
+
systemPrompt: string | string[] | undefined,
|
|
69
|
+
advertisedPrompt: string | undefined,
|
|
70
|
+
): string | string[] | undefined {
|
|
71
|
+
if (Array.isArray(systemPrompt)) {
|
|
72
|
+
let changed = false;
|
|
73
|
+
const cleaned = systemPrompt
|
|
74
|
+
.map((part) => {
|
|
75
|
+
if (typeof part !== "string") return part;
|
|
76
|
+
const stripped = part.replace(ADVERTISED_AGENTS_BLOCK, "");
|
|
77
|
+
if (stripped !== part) changed = true;
|
|
78
|
+
return stripped;
|
|
79
|
+
})
|
|
80
|
+
.filter((b) => typeof b === "string" && b.length > 0);
|
|
81
|
+
|
|
82
|
+
if (advertisedPrompt) {
|
|
83
|
+
return [...cleaned, advertisedPrompt];
|
|
84
|
+
}
|
|
85
|
+
return changed ? cleaned : systemPrompt;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof systemPrompt === "string") {
|
|
89
|
+
const base = systemPrompt.replace(ADVERTISED_AGENTS_BLOCK, "");
|
|
90
|
+
return advertisedPrompt ? (base.trim() ? `${base.trimEnd()}\n\n${advertisedPrompt}` : advertisedPrompt) : base;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return advertisedPrompt;
|
|
94
|
+
}
|
|
@@ -42,7 +42,7 @@ import { listExternalJobProviders } from "../api/external-job-provider.ts";
|
|
|
42
42
|
|
|
43
43
|
type ManagementAction = "list" | "get" | "models" | "create" | "update" | "delete" | "eject" | "disable" | "enable" | "reset";
|
|
44
44
|
type ManagementScope = "user" | "project";
|
|
45
|
-
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { model?: ExtensionContext["model"]; config?: ExtensionConfig; currentSessionId?: string; runtimeAgentOwner?: RuntimeAgentOwner };
|
|
45
|
+
type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { model?: ExtensionContext["model"]; config?: ExtensionConfig; currentSessionId?: string; runtimeAgentOwner?: RuntimeAgentOwner; onAgentsChanged?: () => void };
|
|
46
46
|
|
|
47
47
|
interface ManagementParams {
|
|
48
48
|
action?: string;
|
|
@@ -348,6 +348,7 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
|
|
|
348
348
|
if (hasKey(cfg, "name")) changed("name");
|
|
349
349
|
if (hasKey(cfg, "package")) changed("package");
|
|
350
350
|
if (hasKey(cfg, "description")) changed("description");
|
|
351
|
+
if (hasKey(cfg, "advertise")) changed("advertise");
|
|
351
352
|
if (hasKey(cfg, "aliases")) changed("alias", "aliases");
|
|
352
353
|
if (hasKey(cfg, "systemPrompt")) changed("systemPrompt");
|
|
353
354
|
if (hasKey(cfg, "runner")) changed("runner");
|
|
@@ -415,6 +416,11 @@ function parseTools(raw: string): { tools?: string[]; mcpDirectTools?: string[]
|
|
|
415
416
|
}
|
|
416
417
|
|
|
417
418
|
function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): string | undefined {
|
|
419
|
+
if (hasKey(cfg, "advertise")) {
|
|
420
|
+
if (cfg.advertise === "") delete target.advertise;
|
|
421
|
+
else if (typeof cfg.advertise === "boolean") target.advertise = cfg.advertise;
|
|
422
|
+
else return "config.advertise must be a boolean or empty string when provided.";
|
|
423
|
+
}
|
|
418
424
|
if (hasKey(cfg, "aliases")) {
|
|
419
425
|
if (cfg.aliases === false || cfg.aliases === "") delete target.aliases;
|
|
420
426
|
else if (typeof cfg.aliases === "string") {
|
|
@@ -1176,6 +1182,7 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
1176
1182
|
const sw = skillsWarning(ctx.cwd, agent);
|
|
1177
1183
|
if (sw) warnings.push(sw);
|
|
1178
1184
|
fs.writeFileSync(targetPath, serializeAgent(agent), "utf-8");
|
|
1185
|
+
ctx.onAgentsChanged?.();
|
|
1179
1186
|
return result([`Created agent '${runtimeName}' at ${targetPath}.`, ...warnings].join("\n"));
|
|
1180
1187
|
}
|
|
1181
1188
|
|
|
@@ -1241,6 +1248,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
1241
1248
|
updated.filePath = renamed.filePath!;
|
|
1242
1249
|
}
|
|
1243
1250
|
fs.writeFileSync(updated.filePath, serializeAgent(updated, { preserveFrontmatterFields }), "utf-8");
|
|
1251
|
+
ctx.onAgentsChanged?.();
|
|
1244
1252
|
const headline = updated.name === oldName
|
|
1245
1253
|
? `Updated agent '${updated.name}' at ${updated.filePath}.`
|
|
1246
1254
|
: `Updated agent '${oldName}' to '${updated.name}' at ${updated.filePath}.`;
|
|
@@ -1254,6 +1262,7 @@ function handleDelete(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
1254
1262
|
if ("content" in targetOrError) return targetOrError;
|
|
1255
1263
|
const target = targetOrError;
|
|
1256
1264
|
fs.unlinkSync(target.filePath);
|
|
1265
|
+
ctx.onAgentsChanged?.();
|
|
1257
1266
|
return result(`Deleted agent '${target.name}' at ${target.filePath}.`);
|
|
1258
1267
|
}
|
|
1259
1268
|
|
|
@@ -1292,6 +1301,7 @@ function handleEject(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1292
1301
|
return result(`Failed to read source agent at ${source.filePath}: ${message}`, true);
|
|
1293
1302
|
}
|
|
1294
1303
|
fs.writeFileSync(targetPath, content, "utf-8");
|
|
1304
|
+
ctx.onAgentsChanged?.();
|
|
1295
1305
|
return result(`Ejected agent '${runtimeName}' from ${source.source} to ${scope} scope at ${targetPath}. Edit it there to customize; it shadows the bundled ${source.source} agent of the same name.`);
|
|
1296
1306
|
}
|
|
1297
1307
|
|
|
@@ -1314,6 +1324,7 @@ function handleDisable(params: ManagementParams, ctx: ManagementContext): AgentT
|
|
|
1314
1324
|
const settingsPath = mergeBuiltinAgentOverride(ctx.cwd, runtimeName, scope, { disabled: true });
|
|
1315
1325
|
const after = resolveEffectiveAgent(discoverAgentsAll(ctx.cwd), raw).agent;
|
|
1316
1326
|
if (after?.disabled === true) {
|
|
1327
|
+
ctx.onAgentsChanged?.();
|
|
1317
1328
|
return result(`Disabled agent '${runtimeName}' via ${scope} settings override at ${settingsPath}. It is now hidden from runtime discovery and { action: "list" }.`);
|
|
1318
1329
|
}
|
|
1319
1330
|
return result(`Wrote a disabled override for '${runtimeName}' at ${settingsPath}, but the agent is still enabled. A higher-precedence ${after?.override?.scope ?? "project"} override is likely winning. Try agentScope: '${after?.override?.scope ?? "project"}'.`, true);
|
|
@@ -1338,6 +1349,7 @@ function handleEnable(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
1338
1349
|
const { path: settingsPath, removed } = removeBuiltinAgentOverrideFields(ctx.cwd, runtimeName, scope, ["disabled"]);
|
|
1339
1350
|
const after = resolveEffectiveAgent(discoverAgentsAll(ctx.cwd), raw).agent;
|
|
1340
1351
|
if (after && after.disabled !== true) {
|
|
1352
|
+
if (removed) ctx.onAgentsChanged?.();
|
|
1341
1353
|
if (removed) return result(`Enabled agent '${runtimeName}' (removed disabled override at ${settingsPath}).`);
|
|
1342
1354
|
return result(`Agent '${runtimeName}' is already enabled.`);
|
|
1343
1355
|
}
|
|
@@ -1385,6 +1397,7 @@ function handleReset(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1385
1397
|
return result(`Agent '${runtimeName}' has no ${scope} customization to reset.${note} It is at its bundled ${bundled.source} default.`);
|
|
1386
1398
|
}
|
|
1387
1399
|
lines.push(`Reset agent '${runtimeName}' to its bundled ${bundled.source} default.`);
|
|
1400
|
+
ctx.onAgentsChanged?.();
|
|
1388
1401
|
return result(lines.join("\n"));
|
|
1389
1402
|
}
|
|
1390
1403
|
|