pi-subagents 0.57.0 → 0.59.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 +104 -0
- package/docs/agents.md +18 -9
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +40 -1
- package/docs/models.md +27 -4
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +92 -6
- package/docs/workflows.md +115 -0
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +1 -1
- package/skills/pi-subagents/SKILL.md +3 -1
- package/skills/pi-subagents/references/execution-controls.md +6 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +26 -5
- package/src/agents/agent-management.ts +28 -17
- package/src/agents/agent-serializer.ts +9 -2
- package/src/agents/agents.ts +103 -27
- package/src/agents/runtime-agent-events.ts +70 -0
- package/src/agents/runtime-agent-registry.ts +19 -18
- package/src/api/agents.ts +10 -5
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +32 -10
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +66 -27
- package/src/extension/public-execution.ts +15 -2
- package/src/extension/rpc.ts +35 -14
- package/src/extension/schemas.ts +37 -12
- package/src/extension/tool-description.ts +25 -6
- package/src/integrations/herdr-status.ts +5 -0
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +78 -46
- package/src/runs/background/async-job-tracker.ts +14 -12
- package/src/runs/background/async-resume.ts +29 -27
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +68 -7
- package/src/runs/background/chain-append.ts +8 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +184 -9
- package/src/runs/background/result-delivery-ownership.ts +45 -0
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +29 -10
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +18 -8
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +389 -350
- package/src/runs/background/subagent-wait.ts +58 -10
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +22 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +134 -121
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +527 -257
- package/src/runs/foreground/workflow-detach-reconcile.ts +130 -120
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +463 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +5 -4
- package/src/runs/shared/dynamic-fanout.ts +3 -3
- package/src/runs/shared/fast-mode-extension.ts +5 -5
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/launch-cwd.ts +16 -0
- package/src/runs/shared/long-running-guard.ts +2 -1
- package/src/runs/shared/mcp-config-sources.ts +422 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
- package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
- package/src/runs/shared/model-exclusions.ts +12 -0
- package/src/runs/shared/model-fallback.ts +25 -6
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +15 -1
- package/src/runs/shared/pi-args.ts +81 -8
- package/src/runs/shared/single-output.ts +44 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +98 -13
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +11 -2
- package/src/shared/launch-contract.ts +5 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +240 -30
- package/src/shared/utils.ts +41 -84
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/slash/subagents-admin.ts +3 -0
- package/src/tui/fleet-status.ts +125 -74
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +330 -33
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +230 -0
- package/src/workflows/scripted-workflow.ts +496 -35
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +79 -5
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
package/docs/workflows.md
CHANGED
|
@@ -57,6 +57,29 @@ subagent({ action: "validate", workflowScriptPath: "workflows/review.js" });
|
|
|
57
57
|
|
|
58
58
|
The fields are mutually exclusive. Relative paths resolve against the request `cwd`; absolute paths pass through. The host reads the file before validation, schedule creation, or workflow sandbox execution. The sandbox still has no filesystem access. Missing, unreadable, and empty files return file input errors instead of script syntax errors.
|
|
59
59
|
|
|
60
|
+
### Opt-in bounded workflows
|
|
61
|
+
|
|
62
|
+
Composite workflows have no default parent deadline. Add bounds only when the workflow contract calls for them:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
subagent({
|
|
66
|
+
workflowScript: `
|
|
67
|
+
const scan = await runs.run("scan", { agent: "scout", task: "Inspect the named files." });
|
|
68
|
+
return runs.run("review", { agent: "reviewer", task: "Review:\n" + scan.output });
|
|
69
|
+
`,
|
|
70
|
+
timeoutMs: 900000,
|
|
71
|
+
toolBudget: { soft: 40, hard: 60 },
|
|
72
|
+
usageBudget: { tokens: { soft: 100000, hard: 150000 } }
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- `timeoutMs` sets the workflow deadline and bounds child deadlines to the remaining time.
|
|
77
|
+
- `toolBudget` becomes the default for each child unless that child supplies a narrower value.
|
|
78
|
+
- `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.
|
|
79
|
+
- 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.
|
|
80
|
+
|
|
81
|
+
These controls are opt-in. Avoid tight hard budgets for mutation-capable workers unless the workflow has an explicit checkpoint and handoff path.
|
|
82
|
+
|
|
60
83
|
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.
|
|
61
84
|
|
|
62
85
|
```js
|
|
@@ -92,6 +115,71 @@ subagent({ workflowScript: `
|
|
|
92
115
|
` });
|
|
93
116
|
```
|
|
94
117
|
|
|
118
|
+
### Parallel sequential lanes
|
|
119
|
+
|
|
120
|
+
For a bounded set of independent chains, `runs.lanes(...)` removes the mechanical loop that would otherwise connect each lane's stages. It is a helper inside `workflowScript`, not a new top-level `subagent` execution mode:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
subagent({ workflowScript: `
|
|
124
|
+
const board = await runs.lanes([
|
|
125
|
+
{
|
|
126
|
+
key: "api",
|
|
127
|
+
stages: [
|
|
128
|
+
{ key: "writer", agent: "worker", task: "Implement the API change" },
|
|
129
|
+
{ key: "challenge", resume: "previous", task: "Challenge the API implementation" },
|
|
130
|
+
{ key: "review", agent: "reviewer", task: "Review the API lane" }
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
key: "ui",
|
|
135
|
+
stages: [
|
|
136
|
+
{ key: "writer", agent: "worker", task: "Implement the UI change" },
|
|
137
|
+
{ key: "review", agent: "reviewer", task: "Review the UI lane" }
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
]);
|
|
141
|
+
return board.map((lane) => ({
|
|
142
|
+
key: lane.key,
|
|
143
|
+
state: lane.state,
|
|
144
|
+
failedStage: lane.failedStage,
|
|
145
|
+
stages: lane.stages.map((stage) => ({
|
|
146
|
+
key: stage.key,
|
|
147
|
+
state: stage.state,
|
|
148
|
+
ok: stage.ok,
|
|
149
|
+
runId: stage.runId,
|
|
150
|
+
outputReference: stage.outputReference,
|
|
151
|
+
verdict: stage.verdict
|
|
152
|
+
}))
|
|
153
|
+
}));
|
|
154
|
+
` });
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The first stage from every lane is launched in one existing `runs.all(...)` batch. Later stages in each lane start only after the preceding stage settles. A later stage with `resume: "previous"` requires the preceding child to return a retained `runId`; the helper then uses the existing retained-resume launch checks and does not accept an arbitrary run id. Generated child keys use `<lane>.<stage>`, while the returned board uses the local lane and stage keys.
|
|
158
|
+
|
|
159
|
+
The helper validates the complete plain-JSON lane inventory before launching anything. It bounds the inventory to 32 lanes, 16 stages per lane, 64 total stages, and 64 KiB of canonical JSON; task and path fields retain the existing 1 MiB and 32 KiB limits. Stage keys must be unique within a lane and generated keys must be unique and valid workflow keys. A child failure, stopped/detached result, or explicit `structuredOutput.verdict === "blocked"` blocks only that lane; later stages are marked `skipped` and sibling lanes continue. Reviewer prose is never parsed.
|
|
160
|
+
|
|
161
|
+
The board is bounded and contains only lane/stage keys, state, success, retained run ids, explicit output references, bounded errors, and an optional structured verdict. It does not return child transcripts or create a lane registry or cleanup authority. Use raw `runs.run(...)`/`runs.all(...)` when a workflow needs conditional or rolling orchestration beyond this helper.
|
|
162
|
+
|
|
163
|
+
### Host command steps
|
|
164
|
+
|
|
165
|
+
Use `runs.host(...)` when the operator wants one non-interactive command to be part of the workflow evidence instead of a child-agent run:
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
subagent({ workflowScript: `
|
|
169
|
+
const tests = await runs.host("unit-tests", {
|
|
170
|
+
kind: "command",
|
|
171
|
+
command: "npm run test:unit",
|
|
172
|
+
timeoutMs: 120000,
|
|
173
|
+
output: "reports/unit-tests.log",
|
|
174
|
+
role: "ci",
|
|
175
|
+
provider: "local"
|
|
176
|
+
});
|
|
177
|
+
return { state: tests.state, exitCode: tests.exitCode, outputPath: tests.outputPath };
|
|
178
|
+
` });
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The first version supports only `kind: "command"`. `command` and `timeoutMs` are required; `output` must be a relative path without traversal. `role` may be `ci` or `gate`, and `provider` is display metadata only. The command has no stdin, receives the workflow cwd, and must be awaited or returned. Stdout, stderr, and the saved log are bounded. A nonzero exit, timeout, abort, or output-write failure fails the workflow. Async status and terminal receipts store the bounded host-step state; renderers do not run commands or read command output.
|
|
182
|
+
|
|
95
183
|
### Steering a workflow child
|
|
96
184
|
|
|
97
185
|
Use `await runs.steer(key, message, options?)` after `runs.run` or `runs.all` has launched that stable key. Scripts do not target raw run ids. The optional fields are `mode: "steer" | "follow_up" | "auto"`, a non-negative child `index`, and a positive `ackTimeoutMs`.
|
|
@@ -282,6 +370,33 @@ A top-level `{ workflowScript, worktree: true }` makes isolation the default for
|
|
|
282
370
|
|
|
283
371
|
Configure the worktree base directory and setup hook in [configuration.md](configuration.md).
|
|
284
372
|
|
|
373
|
+
### Lane metadata lifecycle
|
|
374
|
+
|
|
375
|
+
Workflow children may declare a bounded `lane` object (`version`, `key`, optional
|
|
376
|
+
`mode`, opaque `sourceRef`, advisory `claims`, and advisory `outputPaths`). The
|
|
377
|
+
lane key must match the `runs.run`/`runs.all` workflow key. These fields are
|
|
378
|
+
display and triage hints only: they do not grant tools, authorization, or
|
|
379
|
+
cleanup permission, and `sourceRef` is never resolved over the network while
|
|
380
|
+
rendering status. Worktree paths and branches copied into status are also
|
|
381
|
+
display-only; the handoff manifest remains the deletion authority.
|
|
382
|
+
|
|
383
|
+
| Durable file | Owner | Pending / running / finalized / cleanup states | Release predicate | Rollback predicate | Stale-head behavior | Fail-closed cases |
|
|
384
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
385
|
+
| `status.json` | Async runner and workflow status projector | Child step starts `pending`, becomes `running`, then terminal `complete`/`failed`/`paused`/`stopped`; worktree path and branch are copied at launch | Status is terminal and the existing active-run/process proof can release the run marker; lane metadata alone never releases a worktree | Setup or persistence failure keeps the lane unknown; only the existing verified setup rollback may remove a newly created worktree | Recorded status is retained; a base/head mismatch is not repaired or inferred from render-time Git calls | Missing, malformed, or key-mismatched lane data; only one of `worktreePath`/`branch`; unverified process state |
|
|
386
|
+
| `handoffs/<run-id>.json` | Existing parallel handoff writer and cleanup engine | Group is `partial` with preserved cleanup tasks while pending/running; finalized groups contain child identity, patch, and cleanup evidence; cleanup is `partial` or `complete` | Only the existing cleanup engine's fresh Git checks and recorded task evidence can release a worktree/branch; #1621 adds no deletion path | Missing diff, failed capture, or cleanup error preserves the task and records the reason | `baseCommit` is retained as evidence; stale or changed heads remain unknown/preserved until an explicit later reconciliation | Missing/invalid manifest, mismatched run/key/task identity, duplicate identity, dirty or uncaptured work |
|
|
387
|
+
| `workflow-receipt.json` | Workflow terminal settlement | No receipt while `pending`/`running`; terminal receipt is finalized with one optional lane block per keyed child | Receipt publication is complete only after every included child entry is serialized; it does not authorize cleanup | Receipt write failure leaves status/handoff evidence authoritative and the workflow reports the missing receipt | Existing receipt is not backfilled or rewritten from a newer head | Invalid version/state, mismatched entry key or lane key, stale continuation lineage |
|
|
388
|
+
| `.active-runs` marker | Existing active-run index | `pending`/`running` while the runner is live; terminal marker remains until observed process proof | Marker removal requires the existing exact-run process-terminal proof | Unknown proof keeps the marker and lane retained for inspection | Marker state is not inferred from Git head or timestamps alone | Missing/unknown process proof, active marker, or foreign run identity |
|
|
389
|
+
|
|
390
|
+
Older runs without lane metadata remain readable and retain their existing
|
|
391
|
+
handoff/cleanup behavior. Missing lane, receipt, or handoff metadata is
|
|
392
|
+
unknown—not eligible for destructive cleanup.
|
|
393
|
+
|
|
394
|
+
For managed worktree launches, the runner writes the pending handoff and the
|
|
395
|
+
display-only status path/branch from the deterministic setup plan before the
|
|
396
|
+
first `git worktree add`. If setup then fails or is interrupted, that pending
|
|
397
|
+
ownership record remains preserved evidence; cleanup still rechecks the actual
|
|
398
|
+
worktree state before any removal.
|
|
399
|
+
|
|
285
400
|
## Supervisor coordination (child asks parent)
|
|
286
401
|
|
|
287
402
|
Child agents can talk back to the parent Pi session without installing `pi-intercom`. `pi-subagents` provides the child-facing `contact_supervisor` tool and the parent-facing `subagent_supervisor({ action: "reply" })` path natively. Generic `intercom` remains available only when an explicitly loaded external provider supplies it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -100,7 +100,9 @@
|
|
|
100
100
|
"@earendil-works/pi-agent-core": "0.81.0",
|
|
101
101
|
"@earendil-works/pi-ai": "0.81.0",
|
|
102
102
|
"@earendil-works/pi-tui": "0.81.0",
|
|
103
|
+
"@oxlint/plugins": "1.80.0",
|
|
103
104
|
"@types/node": "24.13.3",
|
|
105
|
+
"oxlint": "1.80.0",
|
|
104
106
|
"typescript": "5.9.3",
|
|
105
107
|
"@earendil-works/pi-coding-agent": "file:./test/fixtures/pi-coding-agent-shim"
|
|
106
108
|
}
|
package/prompts/review-loop.md
CHANGED
|
@@ -10,7 +10,7 @@ Default to a maximum of 3 review rounds unless I specify a different cap. Count
|
|
|
10
10
|
|
|
11
11
|
If the invocation includes an implementation request, first launch one async `worker` to implement the approved scope. If the current diff is already the target, start with review. The sequence can be launched up front with `workflowScript` when it is already clear, or continued as follow-up single-agent runs after each async completion. For an initial workflowScript, pass `async: true` so the main chat is unblocked; do not set `clarify: true` unless I explicitly want the foreground clarify UI. Use only one writer against the active worktree at a time unless I explicitly ask for isolated worktrees.
|
|
12
12
|
|
|
13
|
-
As a conservative orchestration policy, do not set
|
|
13
|
+
As a conservative orchestration policy, do not set a hard `toolBudget` or tight `usageBudget` on implementation or fix workers. A default tool budget blocks read/search tools rather than mutation tools, and reported usage has no reservation model, so count or usage limits still do not measure delivery safety. Give each writer a narrow delivery slice and an outer elapsed deadline with enough margin. Before that deadline, request a checkpoint after the current tool returns with changed files, build/test state, remaining work, and commit or PR state. An elapsed timeout is not a mutation-safe boundary and must not be the checkpoint trigger.
|
|
14
14
|
|
|
15
15
|
For each review round, launch fresh-context `reviewer` agents in parallel. Reviewers must inspect the repository, relevant instructions, and current diff directly from files and commands. They must not rely on the main conversation history and must not edit files.
|
|
16
16
|
|
|
@@ -28,7 +28,7 @@ Do not blindly apply every reviewer suggestion. If reviewers surface an unapprov
|
|
|
28
28
|
|
|
29
29
|
When an async implementation worker completes, treat its handoff as the transition into review, not as final completion, unless I explicitly asked for worker-only work, review-only output, or to stop after implementation.
|
|
30
30
|
|
|
31
|
-
When there are fixes worth doing now and the workflow is implementation-authorized, launch one async forked `worker` without hard
|
|
31
|
+
When there are fixes worth doing now and the workflow is implementation-authorized, launch one async forked `worker` without hard tool-call caps to apply only those synthesized fixes. Ask it to preserve the approved scope, run focused validation, and report changed files, commands run with exit codes, validation evidence, surprises, and anything left undone.
|
|
32
32
|
|
|
33
33
|
After a fix worker returns, run another review round only when it made material changes or addressed non-trivial findings. Do not keep looping for optional polish, speculative improvements, or findings already deferred by the parent.
|
|
34
34
|
|
|
@@ -109,7 +109,7 @@ If an advisor is not resumable, run the same profile in fresh context with its o
|
|
|
109
109
|
pass-1 report and the challenge packet. Label that response as a fresh-context
|
|
110
110
|
fallback, not a true cross-exam.
|
|
111
111
|
|
|
112
|
-
Do not set `clarify`, `worktree`, `gate`,
|
|
112
|
+
Do not set `clarify`, `worktree`, `gate`, tool budgets, or tight usage
|
|
113
113
|
budgets on advisors. Bound work through the roster, pass cap, and report length.
|
|
114
114
|
|
|
115
115
|
## Advisor contracts and pass receipts
|
|
@@ -31,6 +31,8 @@ Read the matching reference file before acting. Paths are relative to this `SKIL
|
|
|
31
31
|
|
|
32
32
|
For broad or uncertain requests, read more than one reference. For complex work, start with `references/prompting-and-roles.md` and `references/execution-controls.md`, then consult `references/constraints-and-recipes.md` before launching or reviewing child work.
|
|
33
33
|
|
|
34
|
+
External CLI agents such as `codex-exec`, `codex-exec-writer`, `claude-code`, and `cursor-agent` use their own runner contract. Do not pass native Pi child options such as model override, structured output, acceptance/agent contract, tool budgets, fast mode, fork context, skills, or native Pi tools unless that runner explicitly implements them.
|
|
35
|
+
|
|
34
36
|
## Always-on constraints
|
|
35
37
|
|
|
36
38
|
- Keep the parent as orchestrator and final decision-maker.
|
|
@@ -46,4 +48,4 @@ For broad or uncertain requests, read more than one reference. For complex work,
|
|
|
46
48
|
- Preserve capability ceilings, including child tool restrictions and session-scoped allowed-agent restrictions.
|
|
47
49
|
- Escalate unresolved product, architecture, authority, release, merge, or safety decisions upward instead of letting a child decide silently.
|
|
48
50
|
- Treat receipts, CI, review bots, and external-run records as evidence, not authority to merge, close, comment, publish, or release.
|
|
49
|
-
- As a conservative orchestration policy, do not pass
|
|
51
|
+
- As a conservative orchestration policy, do not pass a hard `toolBudget` or tight `usageBudget` to mutation-capable workers. The default tool budget blocks read/search tools rather than mutation tools, and reported usage has no reservation model. If a worker is interrupted after a tool call starts, checkpoint after the current tool returns with changed files, build/test state, and commit or PR state.
|
|
@@ -24,7 +24,7 @@ Project settings resolve from the nearest parent directory containing `.pi` or `
|
|
|
24
24
|
|
|
25
25
|
An agent may set `runner.type: external-cli` with a non-empty `command`, optional string `args`, and `promptDelivery: stdin` (the default). The command runs with `shell: false`, inherits the resolved cwd and environment, and receives the combined agent instructions and task through stdin. It must already be installed; pi-subagents adds no CLI dependency.
|
|
26
26
|
|
|
27
|
-
External CLI profiles are async-only and one-shot. They support lifecycle artifacts, stdout/stderr logs, timeout, and stop. Full stdout and stderr are retained in their log files, while the final stdout response and stderr error kept in memory are each limited to their last 64 KiB. They do not support
|
|
27
|
+
External CLI profiles are async-only and one-shot. They support lifecycle artifacts, stdout/stderr logs, timeout, and stop. Full stdout and stderr are retained in their log files, while the final stdout response and stderr error kept in memory are each limited to their last 64 KiB. They do not support native Pi child options such as model override, structured output, acceptance/agent contract, tool budgets, fast mode, fork context, skills, or native Pi tools unless the runner explicitly implements them. Foreground/clarify, steer/resume/interrupt-as-pause, nested subagents, fallbacks, and sessions are also unsupported.
|
|
28
28
|
|
|
29
29
|
### External job profiles
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ An agent may set `runner.type: external-job` with a non-empty `provider` and opt
|
|
|
32
32
|
|
|
33
33
|
External job profiles are async-only. The provider owns the remote job and Pi owns the async run record. Status persists provider name, provider job id, prompt digest, provider options, handle/conversation URLs when supplied, result artifact path, last known state, and provider failure code/message. Recovery uses existing provider job metadata to call `reattach` and `result`; it refuses to redispatch a prompt when the persisted provider job does not match the prompt digest.
|
|
34
34
|
|
|
35
|
-
External job profiles do not support foreground/clarify, steer/resume, Pi models/tools/extensions/skills, tool
|
|
35
|
+
External job profiles do not support foreground/clarify, steer/resume, Pi models/tools/extensions/skills, tool budgets, structured output, native child permissions, fallbacks, or Pi child sessions. Capacity conflicts fail closed and include the blocking provider job id when the provider supplies it.
|
|
36
36
|
|
|
37
37
|
### Single agent
|
|
38
38
|
|
|
@@ -84,6 +84,8 @@ For one host-run verification command, pass `gate: "npm test"` on a `runs.run`/`
|
|
|
84
84
|
|
|
85
85
|
Completed workflow children from this parent session stay addressable as retained children. `subagent({ action: "children.list" })` lists up to the last 10 with run ids and reports each row as `resumable` or `not resumable` with a reason. Resume only rows reported `resumable`. For a retained-child challenge, use `resume` instead of `steer` when the child is complete. If no retained child is resumable, launch a same-role fallback challenge and label it as fallback. A later workflow continues a resumable child with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`. Inside `workflowScript`, awaiting that call waits for the revived child to finish and returns its completed output and new `runId`; top-level `{ action: "resume" }` remains detached. Pass explicit follow-up task text. Assign each returned child result back to the loop variable because every resume can return a new retained `runId`; always resume the latest returned id. `resume` and `agent` are mutually exclusive, the revived child keeps its stored agent/model/tool contract, and `gate` is rejected on retained resume items.
|
|
86
86
|
|
|
87
|
+
Each workflow key identifies one result lane: use a new stable workflow key for every distinct retained resume pass; same-key calls are reused only when launch parameters are identical, and incompatible parameters are rejected.
|
|
88
|
+
|
|
87
89
|
Terminal async workflows also persist `workflow-receipt.json` beside `status.json`. It maps each stable child key to its agent, requested and resolved context when known, latest run id, resumability, output reference, and continuation lineage. A later workflow can resume the latest retained child without copying its run id:
|
|
88
90
|
|
|
89
91
|
```js
|
|
@@ -118,6 +120,8 @@ subagent({
|
|
|
118
120
|
|
|
119
121
|
File-only output mode works for workflowScript child launches. Use relative child output paths for scratch reports so the runtime stores them under the run artifact directory and age-based cleanup can remove them. Use absolute paths only for user-approved durable destinations, such as session memory, a docs folder outside the repo, or a known handoff path. For cross-codebase waves, include the repo slug or lane key in each output path so reports from different repositories cannot collide.
|
|
120
122
|
|
|
123
|
+
The `output` field is the API binding; a filename mentioned in task text is only instruction and does not override runtime routing. When a later workflow step or parent needs a durable file, set `output` on `runs.run`/`runs.all` and return the child’s `outputReference`, `outputPathMapping`, or `artifactPaths`; arbitrary literal strings returned by workflow JavaScript are not rewritten. Omitted child output may use a managed aggregate-derived sibling path.
|
|
124
|
+
|
|
121
125
|
For review fanout where the parent continues a local audit:
|
|
122
126
|
|
|
123
127
|
```typescript
|
|
@@ -18,7 +18,7 @@ subagent({ action: "list" })
|
|
|
18
18
|
subagent({ action: "children.list" })
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Lists up to the last 10 retained workflow children from this parent session with explicit `resumable` or `not resumable` rows. Resume only rows reported `resumable`. Send a simple follow-up or implementation challenge with `subagent({ action: "resume", id: "<run-id>", message: "..." })`. Continue one inside a workflow with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`;
|
|
21
|
+
Lists up to the last 10 retained workflow children from this parent session with explicit `resumable` or `not resumable` rows. Resume only rows reported `resumable`. Send a simple follow-up or implementation challenge with `subagent({ action: "resume", id: "<run-id>", message: "..." })`. Continue one inside a workflow with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`; each workflow key identifies one result lane, so use a new stable workflow key for every distinct retained resume pass. Same-key calls are reused only when launch parameters are identical, and incompatible parameters are rejected. The revived child keeps its stored agent, model, and tool contract. If no resumable child is listed, start a same-role fallback challenge and label it as fallback. `steer` with `mode: "follow_up"` only queues text for the next `resume` when the child has already completed.
|
|
22
22
|
|
|
23
23
|
### Refinement overlays
|
|
24
24
|
|
|
@@ -102,6 +102,7 @@ thinking: high
|
|
|
102
102
|
tools: read, grep, find, ls, bash
|
|
103
103
|
systemPromptMode: replace
|
|
104
104
|
inheritProjectContext: true
|
|
105
|
+
inheritGlobalContext: false
|
|
105
106
|
inheritSkills: false
|
|
106
107
|
skills: safe-bash, review-checklist
|
|
107
108
|
skillPath: ./skills, ../shared-skills
|
|
@@ -125,7 +126,6 @@ That is only a starting point. Omit `package` for the traditional unqualified ru
|
|
|
125
126
|
- `acceptanceRole`
|
|
126
127
|
- `async` — single-agent default for background launch (`true`/`false`); explicit tool-call `async` wins
|
|
127
128
|
- `timeoutMs` — single-agent default run-level max runtime in ms; foreground calls use a 30-minute package default only when neither the call nor agent provides one (tool alias `maxRuntimeMs` is also accepted)
|
|
128
|
-
- `turnBudget` — single-agent default `{ maxTurns, graceTurns? }` JSON object
|
|
129
129
|
|
|
130
130
|
`aliases` is an optional comma-separated or block-list set of alternate names for selecting an agent. Aliases resolve to the canonical `name` for execution, status, persistence, and config. Exact canonical names take precedence over aliases, and alias collisions between distinct canonical agents fail as ambiguous. Management create/update accepts a comma-separated string, string array, or `false`/empty string to clear aliases.
|
|
131
131
|
|
|
@@ -14,7 +14,7 @@ Parent extensions may register a session-scoped, out-of-band ceiling through `pi
|
|
|
14
14
|
- **Recon and planning**: use `scout`, then write a plan when needed
|
|
15
15
|
- **Parallel exploration**: run multiple non-conflicting tasks concurrently
|
|
16
16
|
- **Regular skill specialists**: when discovery shows proactive skill subagent suggestions and the current work is broad enough, launch a small fresh-context fanout that asks one subagent per relevant regularly used skill to apply that skill's perspective to the task
|
|
17
|
-
- **Long-running work**: launch async/background runs and inspect them later. For mutation-capable work, bound the delivery slice and elapsed runtime, then request checkpoints after active tool work returns. Reserve hard
|
|
17
|
+
- **Long-running work**: launch async/background runs and inspect them later. For mutation-capable work, bound the delivery slice and elapsed runtime, then request checkpoints after active tool work returns. Reserve hard tool-call caps for explicitly read-only children.
|
|
18
18
|
- **Subagent control**: watch needs-attention signals and soft-interrupt only when a delegated run is genuinely blocked
|
|
19
19
|
- **Agent authoring**: create, update, or override project agents. Treat saved chain records as legacy inspection or migration inputs, not as a current authoring target.
|
|
20
20
|
|
|
@@ -27,6 +27,7 @@ Agents use the `subagent(...)` tool with `workflowScript` for execution, and `ac
|
|
|
27
27
|
- `/subagents` — interactive admin for inspecting agents and editing model, thinking, or system prompt
|
|
28
28
|
- `/subagents-stop [run-id]` — stop a current-session top-level async run; opens a selector when no id is given
|
|
29
29
|
- `/subagents-detach [run-id]` — detach an active foreground single-subagent run without terminating its child
|
|
30
|
+
- `/subagents-steer <run-id> [--child <child-id>] <message>` — steer a live async run (or one child of it) from non-TUI sessions and RPC hosts
|
|
30
31
|
- `/subagent-cost` — show parent plus child token usage and cost for the session
|
|
31
32
|
- `/subagents-fleet` — open the live fleet inspector with per-child controls; `Ctrl+Alt+F` opens it during an active foreground turn, `↑↓`/`jk` selects children, `PgUp`/`PgDn` scrolls transcript detail, `s` steers the selected live async child, and `D` stops its top-level async run after confirmation
|
|
32
33
|
- `/subagents-watchdog` — inspect or configure the opt-in adversarial change watchdog (model, on/off, recommend-model, check)
|
|
@@ -90,7 +91,7 @@ subagent({
|
|
|
90
91
|
|
|
91
92
|
Use this when the user wants implementation or current diff review to continue until reviewers stop finding fixes worth doing now. Keep the loop in the parent session: one async `worker` implements or fixes, fresh-context `reviewer` agents inspect the actual repo and diff, the parent synthesizes accepted fixes, and one async forked `worker` applies them. The parent can express the sequence up front as an async/background `workflowScript` when the workflow is known, or continue with explicit follow-up workflowScript runs after each async completion. For an initial workflow, pass `async: true` so the main chat is unblocked. Treat an async implementation worker handoff as an intermediate state, not final completion, unless the user explicitly asked for worker-only work, review-only output, or to stop after implementation. Stop when reviewers find no P0 blockers or P1 fixes worth doing now, remaining P2 feedback is optional or deferred, an unapproved product/scope/architecture decision appears, or the max review-round cap is reached. Default to 3 review rounds unless the user sets a different cap. Do not loop for optional polish, and do not let children launch subagents or decide the loop outcome.
|
|
92
93
|
|
|
93
|
-
As a conservative orchestration policy, do not pass
|
|
94
|
+
As a conservative orchestration policy, do not pass a hard `toolBudget` to an implementation worker, fix worker, reviewer with edit authority, or other mutation-capable child. The default tool budget blocks read/search tools rather than mutation tools, but count limits still do not measure delivery safety. Use a narrow task plus an outer elapsed deadline with enough margin, then request a checkpoint after the current tool returns. The checkpoint should report changed files, build/test state, remaining work, and commit or PR state. An elapsed timeout is not a mutation-safe boundary and must not be used as the checkpoint trigger.
|
|
94
95
|
|
|
95
96
|
### Parallel research technique
|
|
96
97
|
|
|
@@ -192,6 +193,26 @@ For one run, use inline config:
|
|
|
192
193
|
|
|
193
194
|
For persistent tweaks, edit `subagents.agentOverrides` in user or project settings. User overrides apply everywhere. Project overrides apply only in that repo and win over user overrides. Use `/subagents-models` or `subagent({ action: "models" })` to inspect the live mapping after settings and overrides load.
|
|
194
195
|
|
|
196
|
+
Provider-scoped entries can layer on top of the default override for the active parent session provider. The provider is selected once from the parent model before child model fallback starts, so fallback attempts cannot switch configuration. Within each settings file, the provider entry wins per field; project settings still win over user settings.
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"subagents": {
|
|
201
|
+
"agentOverrides": {
|
|
202
|
+
"worker": { "thinking": "medium" }
|
|
203
|
+
},
|
|
204
|
+
"agentOverridesByProvider": {
|
|
205
|
+
"github-copilot": {
|
|
206
|
+
"worker": { "model": "github-copilot/gpt-5-mini" }
|
|
207
|
+
},
|
|
208
|
+
"openrouter": {
|
|
209
|
+
"worker": { "model": "openrouter/openai/gpt-5-mini" }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
195
216
|
Model ids do not have to be exact. Separator variations (`claude-haiku-4.5` vs `claude-haiku-4-5`), case (`Claude-Sonnet-4`), and optional trailing date stamps (`claude-haiku-4-5-20251001`) all resolve to the same registry model. Exact `provider/id` wins; a qualified `provider/model` never switches providers. To constrain subagents to a budget or compliance profile, set `subagents.modelScope: { enforce: true, allow: ["anthropic/*", "openai/gpt-5-*"] }` in user or project settings. Out-of-scope models you pass explicitly error and abort; models inherited from frontmatter, `subagents.defaultModel`, agent frontmatter, or the parent session only warn.
|
|
196
217
|
|
|
197
218
|
For model fleets, use the profile commands instead of hand-editing repeated overrides: `/subagents-refresh-provider-models <provider>`, `/subagents-generate-profiles <provider>`, `/subagents-load-profile <name>`, and `/subagents-check-profile <name>`. Profiles live under `~/.pi/agent/profiles/pi-subagents/` and replace only `settings.subagents` when loaded.
|
|
@@ -206,7 +227,7 @@ A strong subagent prompt usually includes:
|
|
|
206
227
|
- **Authority boundary**: whether the child may read, edit, commit, push, comment, close, merge, publish, or release. Omit or forbid actions that are not approved.
|
|
207
228
|
- **Context/evidence**: relevant plan paths, files, diffs, decisions, or user constraints already approved.
|
|
208
229
|
- **Success criteria**: what must be true before the child can finish.
|
|
209
|
-
- **Hard constraints**: true invariants only, such as no edits for review-only tasks, one writer thread, child must not run subagents unless it is
|
|
230
|
+
- **Hard constraints**: true invariants only, such as no edits for review-only tasks, one writer thread, child must not run subagents unless it is explicitly authorized through `tools: subagent` or `allowNestedSubagents: true`, or escalation for unapproved decisions.
|
|
210
231
|
- **Validation**: targeted checks to run, or the next-best check when validation is impossible.
|
|
211
232
|
- **Output**: the expected summary shape, artifact path, or finding format. Use managed artifact paths for scratch reports; reserve repo-qualified absolute paths for durable handoffs that the user approved.
|
|
212
233
|
- **Stop rules**: when to ask via `intercom` or `contact_supervisor`, when to stop after enough evidence, and when not to keep searching.
|
|
@@ -230,7 +251,7 @@ Direct settings example:
|
|
|
230
251
|
"reviewer": {
|
|
231
252
|
"model": "anthropic/claude-sonnet-4",
|
|
232
253
|
"thinking": "high",
|
|
233
|
-
"fallbackModels": ["openai/gpt-5-
|
|
254
|
+
"fallbackModels": ["openai-codex/gpt-5.6-luna:low"],
|
|
234
255
|
"acceptanceRole": "read-only"
|
|
235
256
|
}
|
|
236
257
|
}
|
|
@@ -239,7 +260,7 @@ Direct settings example:
|
|
|
239
260
|
```
|
|
240
261
|
|
|
241
262
|
Useful override fields: `description`, `model`, `fallbackModels`, `thinking`,
|
|
242
|
-
`systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `defaultContext`,
|
|
263
|
+
`systemPromptMode`, `inheritProjectContext`, `inheritGlobalContext`, `inheritSkills`, `defaultContext`,
|
|
243
264
|
`acceptanceRole`, `disabled`, `skills`, `tools`, `extensions`, and `systemPrompt`.
|
|
244
265
|
`description` replaces the discovered description for builtin and custom agents
|
|
245
266
|
in `list` output, which is useful for deployment-specific routing notes.
|
|
@@ -31,7 +31,6 @@ import { parseFrontmatter, parseFrontmatterList } from "./frontmatter.ts";
|
|
|
31
31
|
import { toModelInfo } from "../shared/model-info.ts";
|
|
32
32
|
import { resolveSubagentModelOverride, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
33
33
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
34
|
-
import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
|
|
35
34
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
36
35
|
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
37
36
|
import type { AcceptanceInput, Details, ExtensionConfig, ToolBudgetConfig } from "../shared/types.ts";
|
|
@@ -230,6 +229,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
|
230
229
|
thinking: _thinking,
|
|
231
230
|
systemPromptMode: _systemPromptMode,
|
|
232
231
|
inheritProjectContext: _inheritProjectContext,
|
|
232
|
+
inheritGlobalContext: _inheritGlobalContext,
|
|
233
233
|
inheritSkills: _inheritSkills,
|
|
234
234
|
defaultContext: _defaultContext,
|
|
235
235
|
acceptanceRole: _acceptanceRole,
|
|
@@ -240,6 +240,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
|
240
240
|
tools: _tools,
|
|
241
241
|
mcpDirectTools: _mcpDirectTools,
|
|
242
242
|
subagentOnlyExtensions: _subagentOnlyExtensions,
|
|
243
|
+
mutationTools: _mutationTools,
|
|
243
244
|
completionGuard: _completionGuard,
|
|
244
245
|
...editable
|
|
245
246
|
} = withoutExtensions;
|
|
@@ -260,6 +261,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
|
260
261
|
...(base.thinking !== undefined ? { thinking: base.thinking } : {}),
|
|
261
262
|
systemPromptMode: base.systemPromptMode,
|
|
262
263
|
inheritProjectContext: base.inheritProjectContext,
|
|
264
|
+
inheritGlobalContext: base.inheritGlobalContext,
|
|
263
265
|
inheritSkills: base.inheritSkills,
|
|
264
266
|
...(base.defaultContext !== undefined ? { defaultContext: base.defaultContext } : {}),
|
|
265
267
|
...(base.acceptanceRole !== undefined ? { acceptanceRole: base.acceptanceRole } : {}),
|
|
@@ -271,6 +273,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
|
271
273
|
...(base.mcpDirectTools !== undefined ? { mcpDirectTools: [...base.mcpDirectTools] } : {}),
|
|
272
274
|
...(base.extensions !== undefined ? { extensions: [...base.extensions] } : {}),
|
|
273
275
|
...(base.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: [...base.subagentOnlyExtensions] } : {}),
|
|
276
|
+
...(base.mutationTools !== undefined ? { mutationTools: [...base.mutationTools] } : {}),
|
|
274
277
|
...(base.completionGuard !== undefined ? { completionGuard: base.completionGuard } : {}),
|
|
275
278
|
}, agent.filePath);
|
|
276
279
|
}
|
|
@@ -303,6 +306,7 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
|
|
|
303
306
|
if (hasKey(cfg, "skillPath")) changed("skillPath");
|
|
304
307
|
if (hasKey(cfg, "extensions")) changed("extensions");
|
|
305
308
|
if (hasKey(cfg, "subagentOnlyExtensions")) changed("subagentOnlyExtensions");
|
|
309
|
+
if (hasKey(cfg, "mutationTools")) changed("mutationTools");
|
|
306
310
|
if (hasKey(cfg, "thinking")) {
|
|
307
311
|
changed("thinking");
|
|
308
312
|
if (cfg.thinking === "off") fields.add("thinking");
|
|
@@ -315,6 +319,10 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
|
|
|
315
319
|
changed("inheritProjectContext");
|
|
316
320
|
fields.add("inheritProjectContext");
|
|
317
321
|
}
|
|
322
|
+
if (hasKey(cfg, "inheritGlobalContext")) {
|
|
323
|
+
changed("inheritGlobalContext");
|
|
324
|
+
fields.add("inheritGlobalContext");
|
|
325
|
+
}
|
|
318
326
|
if (hasKey(cfg, "inheritSkills")) {
|
|
319
327
|
changed("inheritSkills");
|
|
320
328
|
fields.add("inheritSkills");
|
|
@@ -322,7 +330,6 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
|
|
|
322
330
|
if (hasKey(cfg, "defaultContext")) changed("defaultContext");
|
|
323
331
|
if (hasKey(cfg, "async")) changed("async");
|
|
324
332
|
if (hasKey(cfg, "timeoutMs")) changed("timeoutMs");
|
|
325
|
-
if (hasKey(cfg, "turnBudget")) changed("turnBudget");
|
|
326
333
|
if (hasKey(cfg, "acceptance")) changed("acceptance");
|
|
327
334
|
if (hasKey(cfg, "acceptanceRole")) changed("acceptanceRole");
|
|
328
335
|
if (hasKey(cfg, "output")) changed("output");
|
|
@@ -457,6 +464,12 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
457
464
|
else if (typeof cfg.subagentOnlyExtensions === "string") target.subagentOnlyExtensions = parseCsv(cfg.subagentOnlyExtensions);
|
|
458
465
|
else return "config.subagentOnlyExtensions must be a comma-separated string, empty string, or false when provided.";
|
|
459
466
|
}
|
|
467
|
+
if (hasKey(cfg, "mutationTools")) {
|
|
468
|
+
if (cfg.mutationTools === false) delete target.mutationTools;
|
|
469
|
+
else if (cfg.mutationTools === "") target.mutationTools = [];
|
|
470
|
+
else if (typeof cfg.mutationTools === "string") target.mutationTools = parseCsv(cfg.mutationTools);
|
|
471
|
+
else return "config.mutationTools must be a comma-separated string, empty string, or false when provided.";
|
|
472
|
+
}
|
|
460
473
|
if (hasKey(cfg, "thinking")) {
|
|
461
474
|
if (cfg.thinking === false || cfg.thinking === "") delete target.thinking;
|
|
462
475
|
else if (typeof cfg.thinking === "string") {
|
|
@@ -473,6 +486,10 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
473
486
|
if (typeof cfg.inheritProjectContext !== "boolean") return "config.inheritProjectContext must be a boolean when provided.";
|
|
474
487
|
target.inheritProjectContext = cfg.inheritProjectContext;
|
|
475
488
|
}
|
|
489
|
+
if (hasKey(cfg, "inheritGlobalContext")) {
|
|
490
|
+
if (typeof cfg.inheritGlobalContext !== "boolean") return "config.inheritGlobalContext must be a boolean when provided.";
|
|
491
|
+
target.inheritGlobalContext = cfg.inheritGlobalContext;
|
|
492
|
+
}
|
|
476
493
|
if (hasKey(cfg, "inheritSkills")) {
|
|
477
494
|
if (typeof cfg.inheritSkills !== "boolean") return "config.inheritSkills must be a boolean when provided.";
|
|
478
495
|
target.inheritSkills = cfg.inheritSkills;
|
|
@@ -492,15 +509,6 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
492
509
|
else if (typeof cfg.timeoutMs === "number" && Number.isInteger(cfg.timeoutMs) && cfg.timeoutMs > 0) target.defaultTimeoutMs = cfg.timeoutMs;
|
|
493
510
|
else return "config.timeoutMs must be a positive integer or false when provided.";
|
|
494
511
|
}
|
|
495
|
-
if (hasKey(cfg, "turnBudget")) {
|
|
496
|
-
if (cfg.turnBudget === false || cfg.turnBudget === "") delete target.defaultTurnBudget;
|
|
497
|
-
else {
|
|
498
|
-
const resolved = resolveTurnBudgetConfig(cfg.turnBudget, "config.turnBudget");
|
|
499
|
-
if (resolved.error) return resolved.error;
|
|
500
|
-
if (resolved.turnBudget !== undefined) target.defaultTurnBudget = resolved.turnBudget;
|
|
501
|
-
else delete target.defaultTurnBudget;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
512
|
if (hasKey(cfg, "acceptance")) {
|
|
505
513
|
if (cfg.acceptance === "") delete target.defaultAcceptance;
|
|
506
514
|
else {
|
|
@@ -561,6 +569,7 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
561
569
|
target.thinking ? "thinking" : undefined,
|
|
562
570
|
target.extensions?.length ? "extensions" : undefined,
|
|
563
571
|
target.subagentOnlyExtensions?.length ? "subagentOnlyExtensions" : undefined,
|
|
572
|
+
target.mutationTools?.length ? "mutationTools" : undefined,
|
|
564
573
|
target.skills?.length || target.skillPath?.length ? "skills" : undefined,
|
|
565
574
|
target.maxSubagentDepth !== undefined ? "maxSubagentDepth" : undefined,
|
|
566
575
|
target.completionGuard !== undefined ? "completionGuard" : undefined,
|
|
@@ -699,16 +708,17 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
699
708
|
if (agent.runner?.type === "external-job" && agent.runner.options) lines.push(`Runner options: ${JSON.stringify(agent.runner.options)}`);
|
|
700
709
|
}
|
|
701
710
|
lines.push(`Inherit project context: ${agent.inheritProjectContext ? "true" : "false"}`);
|
|
711
|
+
lines.push(`Inherit global context: ${agent.inheritGlobalContext ? "true" : "false"}`);
|
|
702
712
|
lines.push(`Inherit skills: ${agent.inheritSkills ? "true" : "false"}`);
|
|
703
713
|
if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);
|
|
704
714
|
if (agent.defaultAsync !== undefined) lines.push(`Async: ${agent.defaultAsync ? "true" : "false"}`);
|
|
705
715
|
if (agent.defaultTimeoutMs !== undefined) lines.push(`Timeout: ${agent.defaultTimeoutMs}ms`);
|
|
706
|
-
if (agent.defaultTurnBudget) lines.push(`Turn budget: ${JSON.stringify(agent.defaultTurnBudget)}`);
|
|
707
716
|
if (agent.defaultAcceptance !== undefined) lines.push(`Acceptance: ${typeof agent.defaultAcceptance === "object" ? JSON.stringify(agent.defaultAcceptance) : String(agent.defaultAcceptance)}`);
|
|
708
717
|
if (agent.acceptanceRole) lines.push(`Acceptance role: ${agent.acceptanceRole}`);
|
|
709
718
|
if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`);
|
|
710
719
|
if (agent.extensions !== undefined) lines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(", ") : "(none)"}`);
|
|
711
720
|
if (agent.subagentOnlyExtensions !== undefined) lines.push(`Subagent-only extensions: ${agent.subagentOnlyExtensions.length ? agent.subagentOnlyExtensions.join(", ") : "(none)"}`);
|
|
721
|
+
if (agent.mutationTools !== undefined) lines.push(`Mutation tools: ${agent.mutationTools.length ? agent.mutationTools.join(", ") : "(none)"}`);
|
|
712
722
|
if (agent.thinking) lines.push(`Thinking: ${agent.thinking}`);
|
|
713
723
|
if (agent.output) lines.push(`Output: ${agent.output}`);
|
|
714
724
|
if (agent.outputMode) lines.push(`Output mode: ${agent.outputMode}`);
|
|
@@ -724,7 +734,7 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
724
734
|
|
|
725
735
|
export function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
726
736
|
const scope = normalizeListScope(params.agentScope) ?? "both";
|
|
727
|
-
const d = discoverAgentsAll(ctx.cwd);
|
|
737
|
+
const d = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
|
|
728
738
|
let scopedAgents = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);
|
|
729
739
|
if (ctx.runtimeAgentOwner && listRuntimeAgentConfigs(ctx.runtimeAgentOwner).length > 0) {
|
|
730
740
|
const configuredAgents: AgentConfig[] = [
|
|
@@ -785,7 +795,7 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
785
795
|
return result(`Builtin agent '${requestedAgent}' not found. Available: ${BUILTIN_AGENT_NAMES.join(", ")}.`, true);
|
|
786
796
|
}
|
|
787
797
|
|
|
788
|
-
const discovered = discoverAgentsAll(ctx.cwd);
|
|
798
|
+
const discovered = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
|
|
789
799
|
const builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));
|
|
790
800
|
const resolveBuiltinModelAgent = (name: string): AgentConfig | undefined => builtinByName.get(name) ?? resolveAgentName(name, discovered.builtin).agent;
|
|
791
801
|
const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -864,7 +874,7 @@ function handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolR
|
|
|
864
874
|
if (!params.agent) return result("Specify 'agent' for get.", true);
|
|
865
875
|
const scope = normalizeListScope(params.agentScope);
|
|
866
876
|
if (!scope) return result("agentScope must be 'user', 'project', or 'both' for get.", true);
|
|
867
|
-
const discovered = discoverAgentsAll(ctx.cwd);
|
|
877
|
+
const discovered = discoverAgentsAll(ctx.cwd, ctx.model?.provider);
|
|
868
878
|
const matches = findAgentsInDiscovery(params.agent, discovered, scope);
|
|
869
879
|
const diagnostics = diagnosticsForScope(discovered.agentDiagnostics, scope);
|
|
870
880
|
const rawName = params.agent.trim();
|
|
@@ -894,7 +904,7 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
894
904
|
const runtimeName = buildRuntimeName(name, parsedPackage.packageName);
|
|
895
905
|
const scopeRaw = cfg.scope ?? "user";
|
|
896
906
|
if (scopeRaw !== "user" && scopeRaw !== "project") return result("config.scope must be 'user' or 'project'.", true);
|
|
897
|
-
const scope = scopeRaw
|
|
907
|
+
const scope = scopeRaw;
|
|
898
908
|
if (hasKey(cfg, "steps")) return result("Durable chain definitions were removed; use workflowScript or /prompt-workflow for repeatable workflows.", true);
|
|
899
909
|
const d = discoverAgentsAll(ctx.cwd);
|
|
900
910
|
const projectConfigDir = getProjectConfigDir(ctx.cwd);
|
|
@@ -915,6 +925,7 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
915
925
|
systemPrompt: "",
|
|
916
926
|
systemPromptMode: defaultSystemPromptMode(name),
|
|
917
927
|
inheritProjectContext: defaultInheritProjectContext(name),
|
|
928
|
+
inheritGlobalContext: false,
|
|
918
929
|
inheritSkills: defaultInheritSkills(),
|
|
919
930
|
};
|
|
920
931
|
const applyError = applyAgentConfig(agent, cfg);
|
|
@@ -1141,7 +1152,7 @@ function handleReset(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1141
1152
|
}
|
|
1142
1153
|
|
|
1143
1154
|
export function handleManagementAction(action: string, params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
1144
|
-
switch (action
|
|
1155
|
+
switch (action) {
|
|
1145
1156
|
case "list": return handleList(params, ctx);
|
|
1146
1157
|
case "get": return handleGet(params, ctx);
|
|
1147
1158
|
case "models": return handleModels(params, ctx);
|
|
@@ -9,18 +9,19 @@ export const KNOWN_FIELDS = new Set([
|
|
|
9
9
|
"alias",
|
|
10
10
|
"aliases",
|
|
11
11
|
"tools",
|
|
12
|
+
"allowNestedSubagents",
|
|
12
13
|
"model",
|
|
13
14
|
"fallbackModels",
|
|
14
15
|
"fast",
|
|
15
16
|
"thinking",
|
|
16
17
|
"systemPromptMode",
|
|
17
18
|
"inheritProjectContext",
|
|
19
|
+
"inheritGlobalContext",
|
|
18
20
|
"inheritSkills",
|
|
19
21
|
"defaultContext",
|
|
20
22
|
"async",
|
|
21
23
|
"timeoutMs",
|
|
22
24
|
"toolTimeoutMs",
|
|
23
|
-
"turnBudget",
|
|
24
25
|
"acceptance",
|
|
25
26
|
"acceptanceRole",
|
|
26
27
|
"skill",
|
|
@@ -28,6 +29,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
28
29
|
"skillPath",
|
|
29
30
|
"extensions",
|
|
30
31
|
"subagentOnlyExtensions",
|
|
32
|
+
"mutationTools",
|
|
31
33
|
"output",
|
|
32
34
|
"outputMode",
|
|
33
35
|
"defaultReads",
|
|
@@ -68,6 +70,9 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
|
|
|
68
70
|
];
|
|
69
71
|
const toolsValue = joinComma(tools);
|
|
70
72
|
if (toolsValue || preserve("tools")) lines.push(`tools: ${toolsValue ?? ""}`);
|
|
73
|
+
if (config.allowNestedSubagents === true || preserve("allowNestedSubagents")) {
|
|
74
|
+
lines.push(`allowNestedSubagents: ${config.allowNestedSubagents === undefined ? "" : config.allowNestedSubagents ? "true" : "false"}`);
|
|
75
|
+
}
|
|
71
76
|
|
|
72
77
|
if (config.model || preserve("model")) lines.push(`model: ${config.model ?? ""}`);
|
|
73
78
|
const fallbackModelsValue = joinComma(config.fallbackModels);
|
|
@@ -78,6 +83,7 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
|
|
|
78
83
|
}
|
|
79
84
|
if (!preservingExistingFrontmatter || preserve("systemPromptMode")) lines.push(`systemPromptMode: ${config.systemPromptMode}`);
|
|
80
85
|
if (!preservingExistingFrontmatter || preserve("inheritProjectContext")) lines.push(`inheritProjectContext: ${config.inheritProjectContext ? "true" : "false"}`);
|
|
86
|
+
if (config.inheritGlobalContext || preserve("inheritGlobalContext")) lines.push(`inheritGlobalContext: ${config.inheritGlobalContext ? "true" : "false"}`);
|
|
81
87
|
if (!preservingExistingFrontmatter || preserve("inheritSkills")) lines.push(`inheritSkills: ${config.inheritSkills ? "true" : "false"}`);
|
|
82
88
|
if (config.defaultContext || preserve("defaultContext")) lines.push(`defaultContext: ${config.defaultContext ?? ""}`);
|
|
83
89
|
if (config.runner || preserve("runner")) {
|
|
@@ -91,7 +97,6 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
|
|
|
91
97
|
if (config.defaultAsync !== undefined || preserve("async")) lines.push(`async: ${config.defaultAsync === undefined ? "" : config.defaultAsync ? "true" : "false"}`);
|
|
92
98
|
if (config.defaultTimeoutMs !== undefined || preserve("timeoutMs")) lines.push(`timeoutMs: ${config.defaultTimeoutMs ?? ""}`);
|
|
93
99
|
if (config.defaultToolTimeoutMs !== undefined || preserve("toolTimeoutMs")) lines.push(`toolTimeoutMs: ${config.defaultToolTimeoutMs ?? ""}`);
|
|
94
|
-
if (config.defaultTurnBudget || preserve("turnBudget")) lines.push(`turnBudget: ${config.defaultTurnBudget ? JSON.stringify(config.defaultTurnBudget) : ""}`);
|
|
95
100
|
if (config.defaultAcceptance !== undefined || preserve("acceptance")) {
|
|
96
101
|
lines.push(`acceptance: ${config.defaultAcceptance === undefined
|
|
97
102
|
? ""
|
|
@@ -114,6 +119,8 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
|
|
|
114
119
|
const subagentOnlyExtensionsValue = joinComma(config.subagentOnlyExtensions);
|
|
115
120
|
lines.push(`subagentOnlyExtensions: ${subagentOnlyExtensionsValue ?? ""}`);
|
|
116
121
|
}
|
|
122
|
+
const mutationToolsValue = joinComma(config.mutationTools);
|
|
123
|
+
if (mutationToolsValue || preserve("mutationTools")) lines.push(`mutationTools: ${mutationToolsValue ?? ""}`);
|
|
117
124
|
|
|
118
125
|
if (config.output || preserve("output")) lines.push(`output: ${config.output ?? ""}`);
|
|
119
126
|
if (config.outputMode || preserve("outputMode")) lines.push(`outputMode: ${config.outputMode ?? ""}`);
|