@henryqw/pi-subagent 5.0.0 → 6.0.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/CONTEXT.md +8 -8
- package/README.md +12 -10
- package/docs/adr/001-composable-ephemeral-execution.md +3 -1
- package/docs/adr/002-package-owned-delegate-flow-orchestration.md +8 -8
- package/docs/orchestration.md +17 -16
- package/examples/roles/reviewer.md +2 -2
- package/extensions/delegate-flow.ts +100 -59
- package/extensions/subagent.ts +5 -3
- package/package.json +2 -2
- package/skills/pi-subagent-delegated-development/SKILL.md +7 -7
package/CONTEXT.md
CHANGED
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Provide validated built-in and user Roles, shared task-model Pi launch policy, generic managed Herdr Subagent hosting, generic `delegate_task` delegation, and package-owned `delegate_flow` Git orchestration. `delegate_task` remains a flat bounded delegation tool
|
|
5
|
+
Provide validated built-in and user Roles, shared task-model Pi launch policy, generic managed Herdr Subagent hosting, generic `delegate_task` delegation, and package-owned `delegate_flow` Git orchestration. Main plans and orchestrates; `delegate_task` remains a flat bounded delegation tool, while Flow uses the effective Implementer and only invokes its effective Reviewer for explicit judgment criteria after authoritative validation. The bundled Main-side Skill adds no runtime behavior or changes generic fallback.
|
|
6
6
|
|
|
7
7
|
## Domain glossary
|
|
8
8
|
|
|
9
|
-
- **Main**: Pi session
|
|
9
|
+
- **Main**: Pi session that plans and orchestrates delegated work.
|
|
10
10
|
- **Subagent**: isolated Pi child process handling one task.
|
|
11
11
|
- **Role**: package-shipped built-in or user-owned Markdown definition of a reusable responsibility, with a name, description, system instructions, required `tools`, `extensions`, and `skills` arrays, and optional isolation.
|
|
12
|
-
- **Model Class**: `fast`, `balanced`, `frontier`, or `fav`,
|
|
12
|
+
- **Model Class**: `fast`, `balanced`, `frontier`, or `fav`, resolved through shared task-model settings; Main normally selects `fast`, may select `balanced` upfront for obvious complexity, and leaves direct model/thinking overrides to explicit user requests.
|
|
13
13
|
- **Route**: configured model and thinking-level pair selected from a shared Model Class profile; the primary route precedes its optional fallback.
|
|
14
14
|
- **Delegated Task**: one bounded work request sent from Main to one Role.
|
|
15
15
|
- **Workflow**: generic orchestration of one or more Delegated Tasks; `delegate_task` owns its selected mode, while library callers compose executor runs in JavaScript.
|
|
16
16
|
- **Workflow Mode**: `delegate_task` tool policy selected per call for `single`, `parallel`, or `chain` execution; not a Role property or executor API.
|
|
17
17
|
- **Flow**: package-owned, memory-only Git implementation and integration workflow started by `delegate_flow`.
|
|
18
|
-
- **Unit Worktree**: one Flow-owned worktree and branch for one Flow unit; it is reused for rebase, validation, review, and one repair.
|
|
19
|
-
- **Review Packet**: exact `{base, tip, patchPath}` evidence delivered to the Flow Reviewer
|
|
18
|
+
- **Unit Worktree**: one Flow-owned worktree and branch for one Flow unit; it is reused for rebase, validation, optional exact review, and one repair.
|
|
19
|
+
- **Review Packet**: exact `{base, tip, patchPath}` evidence delivered to the Flow Reviewer only for a unit's explicit `review` criterion.
|
|
20
20
|
- **Resource Policy**: Role ownership of base tools, extensions, and Skill names, plus explicit caller additions of tools, extensions, and environment through `createRoleLaunch`.
|
|
21
21
|
- **Pi Launch**: reusable `{env,args}` policy for one Role, resolved model route, explicit caller resources, and project trust.
|
|
22
22
|
- **Ephemeral Executor**: mechanism that receives a prepared Pi Launch, runs one bounded Delegated Task in one no-session child process, and returns its result without discovering resources or composing a Workflow.
|
|
@@ -28,11 +28,11 @@ Provide validated built-in and user Roles, shared task-model Pi launch policy, g
|
|
|
28
28
|
- Up to five active ephemeral `delegate_task` children run per Main by default, configurable via `maxSubagents` in `~/.pi/agent/config/pi-subagent/pi-subagent.json` or the `PI_SUBAGENT_MAX_SUBAGENTS` environment variable; excess calls wait FIFO. Queued calls do not start a child or consume child timeout. Managed Herdr workers are unaffected.
|
|
29
29
|
- Ambient child extensions and Skills stay disabled. Every Role requires `tools`, `extensions`, and `skills` YAML arrays, and every launch installs the Role tool policy. `tools: []` activates no base built-ins but does activate all tools from explicitly selected trusted extension bundles and explicit caller tool additions; `skills: []` selects no separately named Role Skills but trusted selected extension Skills still load; `extensions: []` selects no Role extension bundle. A Role/caller explicitly selected extension is a trusted atomic capability bundle: all tools it registers and all Skills supplied through its Pi package metadata or dynamic `resources_discover` load alongside separately named Role Skills. This intentionally includes the extension's executable lifecycle/prompt behavior; pi-subagent does not infer or externally narrow undocumented dependencies, and loading an extension is not sandboxing. Scope children by selecting fewer trusted extensions; finer granularity requires separate entry points/configuration or an upstream split. Explicit Role/caller tool names still verify against the final filtered registry, while parent-only recursive orchestration tools remain excluded.
|
|
30
30
|
- Role Skill names resolve through Main's effective Pi Skill registry; unavailable names warn and skip without blocking delegation. Explicit Role/caller tool names verify against the final filtered child registry after explicit provider `session_start` handlers, and unavailable names fail before the first turn.
|
|
31
|
-
- Main
|
|
31
|
+
- Main policy populates direct `model` and `thinking` only for explicit user overrides; otherwise it selects only `modelClass` (`fast` normally, `balanced` upfront for obvious complexity). This has no provenance tracking or runtime enforcement. An omitted class uses the shared `pi-subagent/delegateTask` assignment; library callers select a Role plus their own shared task ID.
|
|
32
32
|
- The selected profile resolves primary then fallback only before launch when a route, model, or thinking level is unavailable. If neither route is usable, launch rejects with `Run /task-models`; a started child is never retried by this package.
|
|
33
33
|
- User Role Markdown files and Subagent JSON config live only in the user `config/pi-subagent` directory; model routes live in shared `config/pi-task-models.json`. Package-shipped built-in Roles (`implementer`, `reviewer`) resolve from the package's own `examples/roles/` Markdown through the same parser; a same-named user file explicitly overrides a built-in for `delegate_task` and `delegate_flow`.
|
|
34
|
-
- `delegate_flow` accepts 1–8 independent units with direct validation commands. At Flow start
|
|
35
|
-
- Flow is memory-only. Only a post-rebase commit drop produces a no-op (`base === tip`); it validates, skips Reviewer and merge, then cleans up ordinarily. Initial zero-commit implementations block. Implementer, validation, or reviewer blocks allow one `delegate_flow_continue({ guidance })` repair in the same worktree;
|
|
34
|
+
- `delegate_flow` accepts 1–8 independent units with direct validation commands, optional `modelClass`, and optional non-empty `review` judgment text. Omitted classes use the shared `pi-subagent/delegateTask` assignment; each unit's current class resolves through its shared profile route for its Implementer and, when `review` exists, Reviewer. At Flow start it always resolves/freezes the effective Implementer and resolves/freezes a Reviewer only when at least one requested unit has `review`. One active Flow creates every Unit Worktree before launching Implementers in parallel, then processes settled units in declared order. For each unit Flow rebases in place when earlier units advanced Main, inspects committed Git state, and runs declared validation. Validation is authoritative for objective verification: units without `review` fast-forward their exact validated tip; units with `review` send the exact Review Packet to the Reviewer in the same worktree and require exact `PASS` before the same guarded `git merge --ff-only` path.
|
|
35
|
+
- Flow is memory-only. Only a post-rebase commit drop produces a no-op (`base === tip`); it validates, skips Reviewer and merge, then cleans up ordinarily. Initial zero-commit implementations block. Implementer, validation, or reviewer blocks allow one `delegate_flow_continue({ guidance, modelClass? })` repair in the same worktree; omission retains the Unit's current class and presence replaces it for that repair. A second block is terminal. Rebase and evidence/Reviewer/infrastructure failures retain worktrees. A reported fast-forward failure retains its worktree unless Main is clean at the exact integrated tip, which completes with the merge diagnostic as a warning. Cleanup uses non-forced worktree removal and branch deletion; cleanup refusal is a completion warning.
|
|
36
36
|
- Flow has no graph, saved recovery, automatic retry, aggregate review, or post-merge validation. It never changes generic `delegate_task` Role resolution, isolation, non-Git fallback, or ordinary direct plan/file review.
|
|
37
37
|
- Numbered Codex routes prefer Main's active account slot and explicitly load the multi-Codex child extension.
|
|
38
38
|
- Generic Herdr host functions validate workspace ownership and provisioning identity while callers retain domain state, prompts, and lifecycle decisions.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@henryqw/pi-subagent`
|
|
2
2
|
|
|
3
|
-
Delegate bounded work to isolated Pi child processes.
|
|
3
|
+
Delegate bounded work to isolated Pi child processes. Main plans and orchestrates; generic `delegate_task` selects one flat single, parallel, or chain mode. Package-owned `delegate_flow` runs a fixed Git implementation-and-verification Flow. Package authors can reuse the same Role launch policy and active-Pi executor from JavaScript.
|
|
4
4
|
|
|
5
5
|
## Why
|
|
6
6
|
|
|
@@ -45,7 +45,7 @@ Select exactly one shape:
|
|
|
45
45
|
{ chain: [{ role, task, model?, modelClass?, thinking? }], background? }
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
`model` is `provider/modelId` and overrides `modelClass`. `modelClass` is `fast`, `balanced`, `frontier`, or `fav`; omission uses the shared `pi-subagent/delegateTask` assignment. `background` applies to the entire selected mode and is never a per-delegation field.
|
|
48
|
+
`model` is `provider/modelId` and overrides `modelClass`. Main populates `model` and `thinking` only for an explicit user override; otherwise it chooses only `modelClass`—`fast` normally, or `balanced` upfront for obviously complex work. This is Main policy only: the runtime records no provenance and does not enforce it. `modelClass` is `fast`, `balanced`, `frontier`, or `fav`; omission uses the shared `pi-subagent/delegateTask` assignment. `background` applies to the entire selected mode and is never a per-delegation field.
|
|
49
49
|
|
|
50
50
|
Parallel mode starts entries concurrently, waits for every entry, and reports them in input order. Chain mode is sequential and fail-fast; every literal `{previous}` receives only the immediately preceding successful assistant output. Foreground failures throw after retaining bounded sibling and recovery evidence. One tool call has one aggregate 50 KiB Main-visible transport cap, not 50 KiB per child.
|
|
51
51
|
|
|
@@ -59,18 +59,20 @@ See [Orchestration, isolation, and the public API](./docs/orchestration.md) for
|
|
|
59
59
|
|
|
60
60
|
### `delegate_flow`
|
|
61
61
|
|
|
62
|
-
Use Flow only for independent, commuting Git changes. It accepts 1–8 uniquely identified units, each with a bounded task
|
|
62
|
+
Use Flow only for independent, commuting Git changes. It accepts 1–8 uniquely identified units, each with a bounded task, optional `modelClass`, direct command/argument validation gate, and optional non-empty `review` judgment criterion:
|
|
63
63
|
|
|
64
64
|
```text
|
|
65
|
-
delegate_flow({ units: [{ id, task, validation: [{ command, args }] }] })
|
|
66
|
-
delegate_flow_continue({ guidance })
|
|
65
|
+
delegate_flow({ units: [{ id, task, modelClass?, validation: [{ command, args }], review? }] })
|
|
66
|
+
delegate_flow_continue({ guidance, modelClass? })
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
Objective verification is authoritative. Flow always inspects committed Git state and runs declared validation. A unit without `review` skips review evidence and Reviewer launch, then fast-forwards its exact validated tip through the existing guarded `git merge --ff-only` path. Add `review` only for an explicit judgment that automation cannot establish; that unit retains the exact `{base, tip, patchPath}` protocol and requires exact `PASS` before the same integration path.
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
One memory-only Flow may be active. At start it resolves/freezes the effective `implementer` Role, including a same-named user override, and resolves/freezes the effective `reviewer` only if at least one requested unit has `review`. Omitted `modelClass` uses the shared `pi-subagent/delegateTask` assignment; a selected class resolves through its shared profile model-and-thinking route for the unit's Implementer and, when applicable, Reviewer. It creates one Unit Worktree per unit, runs Implementers in parallel, then processes settled results in declared order. It removes the worktree and branch non-forcibly after integration; a refusal is a completion warning with the retained worktree path and/or branch.
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
A rebase that drops all unit commits is a no-op: Flow validates it, skips Reviewer and merge, then cleans up ordinarily. Implementer, validation, or review blocks can be repaired once through `delegate_flow_continue` in the same worktree. Omitted continuation `modelClass` retains the blocked unit's current class; a supplied class replaces it for that one repair. Rebase and infrastructure failures are terminal. A reported fast-forward failure completes with its diagnostic as a warning only when Git left Main clean at the exact integrated tip; otherwise it is terminal and retains the affected worktree. Flow has no graph, saved recovery, automatic retry, aggregate review, or post-merge gate.
|
|
74
|
+
|
|
75
|
+
`delegate_task` remains generic with its ordinary isolation behavior. Flow uses the package-shipped Implementer by default and the package-shipped Reviewer only when a unit requests review; same-named user Roles remain supported overrides.
|
|
74
76
|
|
|
75
77
|
## Config
|
|
76
78
|
|
|
@@ -109,7 +111,7 @@ An unreadable or invalid Role file fails role loading fast; duplicate role names
|
|
|
109
111
|
The package ships two working built-in Roles, always available without any configuration:
|
|
110
112
|
|
|
111
113
|
- `implementer`: focused edits requesting worktree isolation; commits completed scoped changes locally and never pushes or opens PRs without authorization
|
|
112
|
-
- `reviewer`: read-only correctness review of supplied plans/files, or of Flow's exact `{base, tip, patchPath}` packet in its Unit Worktree; never edits or commits
|
|
114
|
+
- `reviewer`: read-only correctness review of supplied plans/files, or—only when a Flow unit declares `review`—of Flow's exact `{base, tip, patchPath}` packet in its Unit Worktree; never edits or commits
|
|
113
115
|
|
|
114
116
|
A same-named Markdown file in `~/.pi/agent/config/pi-subagent/` explicitly overrides the built-in default.
|
|
115
117
|
|
|
@@ -131,7 +133,7 @@ The package never installs or writes Role configuration. Sample names are not bu
|
|
|
131
133
|
|
|
132
134
|
## Skill
|
|
133
135
|
|
|
134
|
-
The bundled [`pi-subagent-delegated-development`](./skills/pi-subagent-delegated-development/SKILL.md) Skill is Main-side policy only. `delegate_flow` owns its fixed Git mechanics; the Skill adds no runtime code, configuration, or Role installation. Generic orchestration remains outside the executor under [ADR 001](./docs/adr/001-composable-ephemeral-execution.md).
|
|
136
|
+
The bundled [`pi-subagent-delegated-development`](./skills/pi-subagent-delegated-development/SKILL.md) Skill is Main-side planner/orchestrator policy only. `delegate_flow` owns its fixed Git mechanics and validation authority; the Skill adds no runtime code, configuration, or Role installation. Generic orchestration remains outside the executor under [ADR 001](./docs/adr/001-composable-ephemeral-execution.md).
|
|
135
137
|
|
|
136
138
|
A Role explicitly owns base tools, extensions, named Skills, instructions, and optional `isolation: worktree`. Every launch installs its Role tool policy: `tools: []` activates no base built-ins, while trusted selected extension tools and explicit caller tool additions still activate. `skills: []` selects no separately named Role Skills, while trusted selected extension Skills still load; `extensions: []` selects no Role extension bundle. Ambient extension and Skill discovery is disabled in children. Selecting an extension explicitly is selecting a trusted atomic capability bundle, not just a provider path: every tool it registers and every Skill supplied through its Pi package metadata or dynamic `resources_discover` loads alongside separately named Role Skills. This is intentional because an extension may depend on its own tools, Skills, lifecycle, and prompt behavior; loading it permits that executable behavior and is not sandboxing. To scope a child, select fewer trusted extensions. Finer-grained selection requires separate extension entry points/configuration or an upstream split—pi-subagent does not infer or externally narrow undocumented dependencies. Parent-only recursive orchestration tools stay excluded. Explicit Role or caller tool names are verified against the child’s final filtered active registry after provider extensions finish `session_start`; all unavailable names fail before the first model turn with provider-extension guidance, while unavailable named Skills warn and skip.
|
|
137
139
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Decision
|
|
4
4
|
|
|
5
|
-
The public task executor is an execution mechanism: it receives a prepared Pi Launch, runs one bounded Delegated Task, and returns the result. `delegate_task` selects its flat `single`, `parallel`, or `chain` tool policy
|
|
5
|
+
The public task executor is an execution mechanism: it receives a prepared Pi Launch, runs one bounded Delegated Task, and returns the result. Main plans and orchestrates; `delegate_task` selects its flat `single`, `parallel`, or `chain` tool policy, while those modes are not executor primitives.
|
|
6
6
|
|
|
7
7
|
Generic callers compose workflows with JavaScript. Fan-out and fan-in use promises and collections; sequencing uses ordinary control flow; review loops use explicit caller-owned bounds. The package does not define a recursive workflow AST.
|
|
8
8
|
|
|
@@ -14,6 +14,8 @@ Resource Policy is split at launch preparation:
|
|
|
14
14
|
|
|
15
15
|
Built-in `implementer` and `reviewer` Roles ship as Markdown in `examples/roles/` and use the same parser as user Roles. For generic delegation, a same-named user Role explicitly overrides a built-in. The package does not install, copy, or write user configuration.
|
|
16
16
|
|
|
17
|
+
Main populates direct `model` and `thinking` only for explicit user overrides; otherwise it chooses only `modelClass` (`fast` normally, `balanced` upfront for obvious complexity). This is tool policy, not executor provenance tracking or runtime enforcement.
|
|
18
|
+
|
|
17
19
|
## Scope boundary
|
|
18
20
|
|
|
19
21
|
`delegate_flow` is a fixed package-owned Git workflow, documented in [ADR 002](./002-package-owned-delegate-flow-orchestration.md). It reuses the prepared-child runner but is not a general executor workflow primitive: it has its own fixed unit, worktree, validation, review, integration, and cleanup contract. `delegate_task` and library callers remain generic.
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## Decision
|
|
4
4
|
|
|
5
|
-
`delegate_task` remains the generic bounded Role tool described by [ADR 001](./001-composable-ephemeral-execution.md). `delegate_flow` is a separate package-owned Git workflow with this fixed interface:
|
|
5
|
+
`delegate_task` remains the generic bounded Role tool described by [ADR 001](./001-composable-ephemeral-execution.md). Main plans and orchestrates; `delegate_flow` is a separate package-owned Git workflow with this fixed interface:
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
|
-
delegate_flow({ units: [{ id, task, validation: [{ command, args }] }] });
|
|
9
|
-
delegate_flow_continue({ guidance });
|
|
8
|
+
delegate_flow({ units: [{ id, task, modelClass?, validation: [{ command, args }], review? }] });
|
|
9
|
+
delegate_flow_continue({ guidance, modelClass? });
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
A Flow accepts 1–8 independent units with unique IDs.
|
|
12
|
+
A Flow accepts 1–8 independent units with unique IDs. `modelClass` is optional and otherwise uses the shared `pi-subagent/delegateTask` assignment; its class resolves through the existing `pi-task-models` profile model-and-thinking route for the unit's Implementer and, when applicable, Reviewer. `review` is optional non-empty text for the explicit judgment that declared validation cannot establish.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Only one memory-only Flow may be active. At start it resolves/freezes the effective `implementer` Role, including a same-named user override. It resolves/freezes the effective `reviewer` Role only when at least one requested unit has `review`. It requires a clean committed attached Main branch and creates one Unit Worktree per unit before launching Implementers in parallel. All started Implementers settle; Flow then processes units in declared order.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
For each unit, Flow verifies Main, rebases the Unit Worktree in place when earlier Flow units advanced Main, inspects committed Git state, and runs declared validation. Validation is authoritative for objective verification. Without `review`, Flow skips exact evidence and Reviewer launch, then fast-forwards the exact validated tip through the existing guarded `git merge --ff-only` path. With `review`, it gives the Reviewer the exact `{base, tip, patchPath}` packet in that same worktree; only trimmed output exactly equal to `PASS` permits the same full-OID fast-forward. Cleanup uses non-forced worktree removal and branch deletion; cleanup refusal does not undo integration and returns completion with retained-work warnings.
|
|
17
17
|
|
|
18
|
-
Implementer failure, dirty or missing committed work, validation failure, and reviewer findings block the first affected declared unit. `delegate_flow_continue({ guidance })`
|
|
18
|
+
If rebase drops all unit commits, `base === tip` is a no-op. Flow validates the state, skips Reviewer and merge, then cleans up ordinarily. Implementer failure, dirty or missing committed work, validation failure, and reviewer findings block the first affected declared unit. `delegate_flow_continue({ guidance, modelClass? })` launches a fresh ephemeral child with the frozen Implementer Role in that same Unit Worktree once, with original requirements, authoritative validation, previous block evidence, and Main guidance. Omitted continuation class retains the Unit's current class; a supplied class replaces it for that one repair, including any subsequent Reviewer launch. A second block is terminal. Failed rebase, evidence/Reviewer, and other infrastructure failures retain worktrees. A reported fast-forward failure completes with its diagnostic as a warning only when Main is clean at the exact integrated tip; otherwise it is terminal. Earlier integrations are never rolled back.
|
|
19
19
|
|
|
20
20
|
## Consequences
|
|
21
21
|
|
|
22
|
-
Flow owns
|
|
22
|
+
Flow owns narrow deterministic Git mechanics while allowing user-owned Implementer and conditional Reviewer policy through same-named Role overrides. Overrides do not change validation authority, exact review protocol, approval, integration, or cleanup. Flow has no dependency graph, saved recovery, automatic retry, aggregate review, post-merge validation, Planner Role, or generic-role restriction. Units that overlap files, APIs, schemas, generated output, package metadata, lockfiles, or invariants must be combined or sequenced outside Flow.
|
package/docs/orchestration.md
CHANGED
|
@@ -10,9 +10,9 @@ caller-owned task, cwd, signal ────────────────
|
|
|
10
10
|
active-Pi ephemeral executor
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
`delegate_task` owns its flat single/parallel/chain policy
|
|
13
|
+
Main plans and orchestrates. `delegate_task` owns its flat single/parallel/chain policy, while the public executor runs one prepared delegation. Downstream packages compose their own workflows with ordinary JavaScript and own semantic protocols, shared workspace/state, retry decisions, and bounds. There is no recursive workflow AST.
|
|
14
14
|
|
|
15
|
-
`delegate_flow` is the exception: it is a fixed package-owned Git workflow, not an executor primitive or general workflow language. It uses the effective `implementer` and `reviewer`
|
|
15
|
+
`delegate_flow` is the exception: it is a fixed package-owned Git workflow, not an executor primitive or general workflow language. It uses the effective `implementer` Role and, only for explicit judgment review, the effective `reviewer` Role through the same prepared-child runner; its contract is below.
|
|
16
16
|
|
|
17
17
|
## Frozen `delegate_task` contract
|
|
18
18
|
|
|
@@ -67,11 +67,11 @@ Single mode puts one delegation's fields at the top level.
|
|
|
67
67
|
| --- | --- | --- |
|
|
68
68
|
| `role` | yes | Name of a Role in the user's effective `config/pi-subagent` directory or a package-shipped built-in (`implementer`, `reviewer`); a same-named user file overrides the built-in. |
|
|
69
69
|
| `task` | yes | Non-empty bounded task packet. |
|
|
70
|
-
| `model` | no | Designated `provider/modelId`; takes precedence over `modelClass
|
|
71
|
-
| `modelClass` | no | `fast`, `balanced`, `frontier`, or `fav`; omission uses shared task assignment. |
|
|
72
|
-
| `thinking` | no | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`;
|
|
70
|
+
| `model` | no | Designated `provider/modelId`; takes precedence over `modelClass`, and Main supplies it only for an explicit user override. |
|
|
71
|
+
| `modelClass` | no | `fast`, `balanced`, `frontier`, or `fav`; Main normally chooses `fast`, may choose `balanced` upfront for obvious complexity, and omission uses shared task assignment. |
|
|
72
|
+
| `thinking` | no | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`; Main supplies it only for an explicit user override. Route selection skips models that cannot honor it. |
|
|
73
73
|
|
|
74
|
-
Those five fields are the complete delegation object. `tasks`, `chain`, and `background` cannot be nested. Route fallback occurs only before launch; a started child is never retried by this package.
|
|
74
|
+
Those five fields are the complete delegation object. The direct-model/thinking rule is Main-facing policy only: the runtime adds no provenance tracking or enforcement. `tasks`, `chain`, and `background` cannot be nested. Route fallback occurs only before launch; a started child is never retried by this package.
|
|
75
75
|
|
|
76
76
|
### Background, failures, and transport
|
|
77
77
|
|
|
@@ -83,9 +83,9 @@ All Main-visible text for one tool call shares one aggregate 50 KiB UTF-8 transp
|
|
|
83
83
|
|
|
84
84
|
## `delegate_flow`
|
|
85
85
|
|
|
86
|
-
`delegate_flow({ units })` accepts 1–8 units with unique non-empty `id` and `task
|
|
86
|
+
`delegate_flow({ units })` accepts 1–8 units with unique non-empty `id` and `task`, one or more direct `{command, args}` validation commands, optional `modelClass`, and optional non-empty `review` text. `delegate_flow_continue({ guidance, modelClass? })` is available only for the one blocked unit of the active Flow.
|
|
87
87
|
|
|
88
|
-
A Flow is memory-only and permits one active Flow. At start it resolves the effective `implementer`
|
|
88
|
+
A Flow is memory-only and permits one active Flow. At start it always resolves/freezes the effective `implementer` Role, including a same-named user override. It resolves/freezes the effective `reviewer` only if at least one requested unit declares `review`. Omitted unit classes use the shared `pi-subagent/delegateTask` assignment; a selected class resolves through its existing `pi-task-models` profile model-and-thinking route for the unit's Implementer and, when applicable, Reviewer. It requires clean committed Git Main and creates every Unit Worktree before launching work; setup failure launches no Implementer. Each unit gets exactly one worktree and one Implementer. Implementers run in parallel and all settle. Flow then processes units in declared order:
|
|
89
89
|
|
|
90
90
|
```text
|
|
91
91
|
Implementers (parallel, one Unit Worktree each)
|
|
@@ -93,19 +93,20 @@ Implementers (parallel, one Unit Worktree each)
|
|
|
93
93
|
v
|
|
94
94
|
for each declared unit:
|
|
95
95
|
rebase in its Unit Worktree when earlier units advanced Main
|
|
96
|
-
run declared validation
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
inspect committed state; run declared validation (objective authority)
|
|
97
|
+
├─ no review: git merge --ff-only <exact validated tip>
|
|
98
|
+
└─ review: Reviewer receives exact {base, tip, patchPath}
|
|
99
|
+
exact PASS → git merge --ff-only <full reviewed OID>
|
|
99
100
|
git worktree remove; git branch -d
|
|
100
101
|
```
|
|
101
102
|
|
|
102
|
-
Flow derives identity from Git, not child output. Reviewer reads the exact patch as authoritative and may use the same worktree only for referenced context. A full-OID fast-forward is the only integration path. Cleanup is non-forced; after a successful integration, cleanup refusal returns `completed` with a retained path/branch warning.
|
|
103
|
+
Flow derives identity from Git, not child output. Add `review` only for an explicit judgment criterion that automated validation cannot establish; it is not a second generic verification pass. The Reviewer reads the exact patch as authoritative and may use the same worktree only for referenced context. A full-OID fast-forward is the only integration path. Cleanup is non-forced; after a successful integration, cleanup refusal returns `completed` with a retained path/branch warning.
|
|
103
104
|
|
|
104
|
-
If rebase drops all unit commits, `base === tip` is a no-op: Flow validates current state, skips Reviewer and merge, then cleans up ordinarily. Implementer failure, dirty or missing committed work, validation failure, or reviewer findings block the first affected declared unit. `delegate_flow_continue({ guidance })` reruns the Flow's frozen Implementer Role in that same worktree once, then repeats derivation, validation, and review with fresh exact evidence. A second block is terminal. A failed rebase is aborted and terminates as an infrastructure failure with Git diagnostics; other infrastructure failures are terminal. A reported fast-forward failure completes with its diagnostic as a warning only when Main is clean at the exact
|
|
105
|
+
If rebase drops all unit commits, `base === tip` is a no-op: Flow validates current state, skips Reviewer and merge, then cleans up ordinarily. Implementer failure, dirty or missing committed work, validation failure, or reviewer findings block the first affected declared unit. `delegate_flow_continue({ guidance, modelClass? })` reruns the Flow's frozen Implementer Role in that same worktree once, then repeats derivation, validation, and conditional review with fresh exact evidence. Omitting continuation `modelClass` retains the blocked unit's current class; providing it replaces that class for the one repair. A second block is terminal. A failed rebase is aborted and terminates as an infrastructure failure with Git diagnostics; other infrastructure failures are terminal. A reported fast-forward failure completes with its diagnostic as a warning only when Main is clean at the exact integrated tip; otherwise it is terminal. Terminal outcomes retain worktrees for Main to reslice. Earlier integrated units are never rolled back.
|
|
105
106
|
|
|
106
107
|
Flow has no dependency graph, saved state, automatic retry, aggregate review, or post-merge validation. Use it only for commuting changes; combine or sequence units that overlap files, APIs, schemas, generated output, package metadata, lockfiles, or invariants.
|
|
107
108
|
|
|
108
|
-
`delegate_task` remains generic: its optional worktree isolation, non-Git behavior, and direct plan/file review are unchanged. Flow uses
|
|
109
|
+
`delegate_task` remains generic: its optional worktree isolation, non-Git behavior, and direct plan/file review are unchanged. Flow uses package-shipped Roles as defaults while retaining same-named user Role overrides; the Reviewer is needed only for a requested review criterion.
|
|
109
110
|
|
|
110
111
|
## Per-delegation resources and isolation
|
|
111
112
|
|
|
@@ -356,7 +357,7 @@ The package ships two working built-in Roles, validated by the same parser as us
|
|
|
356
357
|
| Built-in | Behavior |
|
|
357
358
|
| --- | --- |
|
|
358
359
|
| `implementer` | Focused implementation requesting `isolation: worktree`; commits scoped changes locally, never pushes or opens PRs without authorization. Non-Git or unborn-`HEAD` contexts may use Main's cwd. |
|
|
359
|
-
| `reviewer` | Read-only correctness review of supplied plans/files, or Flow's exact `{base, tip, patchPath}` packet in its Unit Worktree; never edits or commits. |
|
|
360
|
+
| `reviewer` | Read-only correctness review of supplied plans/files, or—when a Flow unit declares `review`—Flow's exact `{base, tip, patchPath}` packet in its Unit Worktree; never edits or commits. |
|
|
360
361
|
|
|
361
362
|
A same-named Markdown file in `config/pi-subagent/` explicitly overrides the built-in default.
|
|
362
363
|
|
|
@@ -376,6 +377,6 @@ cp <package-install-dir>/examples/roles/scout.md ~/.pi/agent/config/pi-subagent/
|
|
|
376
377
|
|
|
377
378
|
The package never creates, copies, updates, or removes files in `~/.pi/agent/config/pi-subagent/`. Once copied, the files and their names are entirely user-owned.
|
|
378
379
|
|
|
379
|
-
The bundled [`pi-subagent-delegated-development`](../skills/pi-subagent-delegated-development/SKILL.md) Skill is Main-side policy only. `delegate_flow` owns its fixed Git mechanics; the Skill defines no runtime code or configuration. `delegate_task` remains the generic flat single/parallel/chain mechanism.
|
|
380
|
+
The bundled [`pi-subagent-delegated-development`](../skills/pi-subagent-delegated-development/SKILL.md) Skill is Main-side planner/orchestrator policy only. `delegate_flow` owns its fixed Git mechanics and objective validation authority; the Skill defines no runtime code or configuration. `delegate_task` remains the generic flat single/parallel/chain mechanism.
|
|
380
381
|
|
|
381
382
|
See [ADR 001](./adr/001-composable-ephemeral-execution.md) for the executor boundary and [ADR 002](./adr/002-package-owned-delegate-flow-orchestration.md) for Flow.
|
|
@@ -15,9 +15,9 @@ Perform a read-only correctness review of one bounded change.
|
|
|
15
15
|
Support exactly two review modes:
|
|
16
16
|
|
|
17
17
|
1. For ordinary delegation, review the supplied plan and explicitly named files directly. Do not prepare Git, require commits, or require a patch packet.
|
|
18
|
-
2. For Flow exact review, require a Review Packet `{base, tip, patchPath}` and the same assigned Unit Worktree context.
|
|
18
|
+
2. For Flow exact review, only when the task supplies an explicit judgment criterion, require a Review Packet `{base, tip, patchPath}` and the same assigned Unit Worktree context. Declared validation is authoritative for objective verification; judge only that criterion. Read the exact patch as authoritative, then read only the files it references and relevant criterion context. Do not infer a diff from a branch or another worktree.
|
|
19
19
|
|
|
20
|
-
In either mode, review only the supplied requirements and explicitly referenced context. Use only `read`, `grep`, `find`, or `ls` for that review.
|
|
20
|
+
In either mode, review only the supplied requirements and explicitly referenced context. Use only `read`, `grep`, `find`, or `ls` for that review. For ordinary delegation, check correctness, regressions, trust-boundary validation, error handling, and missing high-value tests. Do not run commands or tests. Never manage Main, Git, or tests; never edit or write files, commit, push, or otherwise modify state.
|
|
21
21
|
|
|
22
22
|
Never invoke external LLM APIs, SDKs, agent harnesses, or model CLIs.
|
|
23
23
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { StringEnum, type Usage } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { PROFILE_NAMES } from "@henryqw/pi-task-models";
|
|
3
4
|
import {
|
|
4
5
|
addUsage,
|
|
5
6
|
capEphemeralSubagentOutput as capOutput,
|
|
@@ -28,10 +29,14 @@ const ValidationSchema = Type.Object({
|
|
|
28
29
|
args: Type.Array(Type.String()),
|
|
29
30
|
}, { additionalProperties: false });
|
|
30
31
|
|
|
32
|
+
const ModelClassSchema = StringEnum(PROFILE_NAMES, { description: "Task model profile" });
|
|
33
|
+
|
|
31
34
|
const UnitSchema = Type.Object({
|
|
32
35
|
id: Type.String({ minLength: 1 }),
|
|
33
36
|
task: Type.String({ minLength: 1 }),
|
|
34
37
|
validation: Type.Array(ValidationSchema, { minItems: 1 }),
|
|
38
|
+
modelClass: Type.Optional(ModelClassSchema),
|
|
39
|
+
review: Type.Optional(Type.String({ minLength: 1 })),
|
|
35
40
|
}, { additionalProperties: false });
|
|
36
41
|
|
|
37
42
|
export const DelegateFlowSchema = Type.Object({
|
|
@@ -40,10 +45,12 @@ export const DelegateFlowSchema = Type.Object({
|
|
|
40
45
|
|
|
41
46
|
export const DelegateFlowContinueSchema = Type.Object({
|
|
42
47
|
guidance: Type.String({ minLength: 1 }),
|
|
48
|
+
modelClass: Type.Optional(ModelClassSchema),
|
|
43
49
|
}, { additionalProperties: false });
|
|
44
50
|
|
|
45
51
|
type FlowRequest = Static<typeof DelegateFlowSchema>;
|
|
46
52
|
type FlowUnitRequest = Static<typeof UnitSchema>;
|
|
53
|
+
type FlowModelClass = FlowUnitRequest["modelClass"];
|
|
47
54
|
type FlowClassification = "setup" | "implementer" | "validation" | "reviewer_findings" | "main" | "infrastructure" | "integration";
|
|
48
55
|
type FlowPhase = "running" | "blocked";
|
|
49
56
|
type WidgetStatus = "success" | "failure" | "aborted";
|
|
@@ -54,6 +61,7 @@ type ChildSettlement =
|
|
|
54
61
|
|
|
55
62
|
type UnitState = {
|
|
56
63
|
request: FlowUnitRequest;
|
|
64
|
+
modelClass: FlowModelClass;
|
|
57
65
|
worktree: WorktreeInfo;
|
|
58
66
|
base: string;
|
|
59
67
|
implementation?: ChildSettlement;
|
|
@@ -87,7 +95,7 @@ type FlowState = {
|
|
|
87
95
|
generation: number;
|
|
88
96
|
sessionController: AbortController;
|
|
89
97
|
implementer: Role;
|
|
90
|
-
reviewer
|
|
98
|
+
reviewer?: Role;
|
|
91
99
|
main?: MainState;
|
|
92
100
|
units: UnitState[];
|
|
93
101
|
setupRecoveries: SetupRecovery[];
|
|
@@ -111,7 +119,7 @@ export interface DelegateFlowRuntime {
|
|
|
111
119
|
maxRuntimeMs: number;
|
|
112
120
|
getSessionGeneration: () => number;
|
|
113
121
|
loadRoles: () => Role[];
|
|
114
|
-
resolveLaunch: (role: Role, ctx: ExtensionContext) => ResolvedRoleLaunch;
|
|
122
|
+
resolveLaunch: (role: Role, modelClass: FlowModelClass, ctx: ExtensionContext) => ResolvedRoleLaunch;
|
|
115
123
|
startWidget: (
|
|
116
124
|
id: string,
|
|
117
125
|
role: string,
|
|
@@ -150,6 +158,8 @@ export function parseDelegateFlow(value: unknown): FlowRequest {
|
|
|
150
158
|
command: text(validation.command, `units[${unitIndex}].validation[${validationIndex}].command`),
|
|
151
159
|
args: validation.args.map((value, argumentIndex) => argument(value, `units[${unitIndex}].validation[${validationIndex}].args[${argumentIndex}]`)),
|
|
152
160
|
})),
|
|
161
|
+
...(unit.modelClass === undefined ? {} : { modelClass: unit.modelClass }),
|
|
162
|
+
...(unit.review === undefined ? {} : { review: text(unit.review, `units[${unitIndex}].review`) }),
|
|
153
163
|
};
|
|
154
164
|
}),
|
|
155
165
|
};
|
|
@@ -157,7 +167,10 @@ export function parseDelegateFlow(value: unknown): FlowRequest {
|
|
|
157
167
|
|
|
158
168
|
export function parseDelegateFlowContinue(value: unknown): Static<typeof DelegateFlowContinueSchema> {
|
|
159
169
|
if (!Check(DelegateFlowContinueSchema, value)) throw new Error("delegate_flow_continue must match the declared tool schema.");
|
|
160
|
-
return {
|
|
170
|
+
return {
|
|
171
|
+
guidance: text(value.guidance, "guidance"),
|
|
172
|
+
...(value.modelClass === undefined ? {} : { modelClass: value.modelClass }),
|
|
173
|
+
};
|
|
161
174
|
}
|
|
162
175
|
|
|
163
176
|
function errorText(error: unknown): string {
|
|
@@ -176,6 +189,11 @@ function implementerTask(unit: FlowUnitRequest): string {
|
|
|
176
189
|
return [
|
|
177
190
|
`Flow Unit ${JSON.stringify(unit.id)} requirements:`,
|
|
178
191
|
unit.task,
|
|
192
|
+
...(unit.review === undefined ? [] : [
|
|
193
|
+
"",
|
|
194
|
+
"Review criterion to satisfy; the Reviewer alone decides approval:",
|
|
195
|
+
unit.review,
|
|
196
|
+
]),
|
|
179
197
|
"",
|
|
180
198
|
"Authoritative Flow validation (do not duplicate this final gate):",
|
|
181
199
|
...unit.validation.map((validation) => `- ${JSON.stringify(validation)}`),
|
|
@@ -188,6 +206,11 @@ function repairTask(unit: FlowUnitRequest, blocked: BlockedState, guidance: stri
|
|
|
188
206
|
"",
|
|
189
207
|
"Original requirements:",
|
|
190
208
|
unit.task,
|
|
209
|
+
...(unit.review === undefined ? [] : [
|
|
210
|
+
"",
|
|
211
|
+
"Review criterion to satisfy; the Reviewer alone decides approval:",
|
|
212
|
+
unit.review,
|
|
213
|
+
]),
|
|
191
214
|
"",
|
|
192
215
|
"Authoritative Flow validation (do not duplicate this final gate):",
|
|
193
216
|
...unit.validation.map((validation) => `- ${JSON.stringify(validation)}`),
|
|
@@ -200,16 +223,19 @@ function repairTask(unit: FlowUnitRequest, blocked: BlockedState, guidance: stri
|
|
|
200
223
|
].join("\n");
|
|
201
224
|
}
|
|
202
225
|
|
|
203
|
-
function reviewerTask(unit: FlowUnitRequest, packet: { base: string; tip: string; patchPath: string }): string {
|
|
226
|
+
function reviewerTask(unit: FlowUnitRequest, review: string, packet: { base: string; tip: string; patchPath: string }): string {
|
|
204
227
|
return [
|
|
205
|
-
`Review Flow Unit ${JSON.stringify(unit.id)}
|
|
228
|
+
`Review Flow Unit ${JSON.stringify(unit.id)} for this explicit judgment criterion:`,
|
|
229
|
+
review,
|
|
230
|
+
"",
|
|
231
|
+
"Original requirements (context only):",
|
|
206
232
|
unit.task,
|
|
207
233
|
"",
|
|
208
|
-
"Declared validation already passed:",
|
|
234
|
+
"Declared validation already passed and is authoritative for objective verification:",
|
|
209
235
|
...unit.validation.map((validation) => `- ${JSON.stringify(validation)}`),
|
|
210
236
|
"",
|
|
211
237
|
`Review Packet: ${JSON.stringify(packet)}`,
|
|
212
|
-
"Read the exact patch as authoritative and emit exactly PASS only when there are zero findings.",
|
|
238
|
+
"Review only the criterion above. Read the exact patch as authoritative and emit exactly PASS only when there are zero findings.",
|
|
213
239
|
].join("\n");
|
|
214
240
|
}
|
|
215
241
|
|
|
@@ -308,6 +334,7 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
308
334
|
const runChild = async (
|
|
309
335
|
flow: FlowState,
|
|
310
336
|
role: Role,
|
|
337
|
+
modelClass: FlowModelClass,
|
|
311
338
|
task: string,
|
|
312
339
|
cwd: string,
|
|
313
340
|
widgetId: string,
|
|
@@ -323,7 +350,7 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
323
350
|
onTokens: (tokens) => runtime.updateWidgetTokens(widgetId, tokens),
|
|
324
351
|
prepare: async () => {
|
|
325
352
|
assertCurrent(flow);
|
|
326
|
-
const launch = runtime.resolveLaunch(role, ctx);
|
|
353
|
+
const launch = runtime.resolveLaunch(role, modelClass, ctx);
|
|
327
354
|
if (launch.missingSkills.length) {
|
|
328
355
|
ctx.ui.notify(`Subagent role ${role.name} skipped unavailable Pi skills: ${launch.missingSkills.join(", ")}.`, "warning");
|
|
329
356
|
}
|
|
@@ -624,45 +651,53 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
624
651
|
continue;
|
|
625
652
|
}
|
|
626
653
|
|
|
627
|
-
let
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
return terminal(flow, "infrastructure",
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
let review: ChildSettlement;
|
|
635
|
-
let cleanupError: unknown;
|
|
636
|
-
try {
|
|
637
|
-
assertCurrent(flow);
|
|
638
|
-
review = await runChild(
|
|
639
|
-
flow,
|
|
640
|
-
flow.reviewer,
|
|
641
|
-
reviewerTask(unit.request, { base: evidence.base, tip: evidence.tip, patchPath: evidence.patchPath }),
|
|
642
|
-
unit.worktree.cwd,
|
|
643
|
-
`${toolCallId}:flow:${flow.index}:review`,
|
|
644
|
-
signal,
|
|
645
|
-
ctx,
|
|
646
|
-
meter,
|
|
647
|
-
);
|
|
648
|
-
} finally {
|
|
654
|
+
let approvedTip = tip;
|
|
655
|
+
const reviewCriterion = unit.request.review;
|
|
656
|
+
if (reviewCriterion !== undefined) {
|
|
657
|
+
const reviewer = flow.reviewer;
|
|
658
|
+
if (!reviewer) return terminal(flow, "infrastructure", "Flow Reviewer was not resolved for a unit that requires review.", meter);
|
|
659
|
+
let evidence;
|
|
649
660
|
try {
|
|
650
|
-
await
|
|
661
|
+
evidence = await prepareExactReviewEvidence({ base: main.expectedHead, tip, worktree: unit.worktree.path }, signal);
|
|
651
662
|
} catch (error) {
|
|
652
|
-
|
|
663
|
+
return terminal(flow, "infrastructure", errorText(error), meter);
|
|
653
664
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
665
|
+
|
|
666
|
+
let review: ChildSettlement;
|
|
667
|
+
let cleanupError: unknown;
|
|
668
|
+
try {
|
|
669
|
+
assertCurrent(flow);
|
|
670
|
+
review = await runChild(
|
|
671
|
+
flow,
|
|
672
|
+
reviewer,
|
|
673
|
+
unit.modelClass,
|
|
674
|
+
reviewerTask(unit.request, reviewCriterion, { base: evidence.base, tip: evidence.tip, patchPath: evidence.patchPath }),
|
|
675
|
+
unit.worktree.cwd,
|
|
676
|
+
`${toolCallId}:flow:${flow.index}:review`,
|
|
677
|
+
signal,
|
|
678
|
+
ctx,
|
|
679
|
+
meter,
|
|
680
|
+
);
|
|
681
|
+
} finally {
|
|
682
|
+
try {
|
|
683
|
+
await evidence.cleanup();
|
|
684
|
+
} catch (error) {
|
|
685
|
+
cleanupError = error;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (cleanupError !== undefined) return terminal(flow, "infrastructure", errorText(cleanupError), meter);
|
|
689
|
+
assertCurrent(flow);
|
|
690
|
+
if ("error" in review) return terminal(flow, "infrastructure", errorText(review.error), meter);
|
|
691
|
+
if (review.result.outcome !== "success") {
|
|
692
|
+
return terminal(flow, "infrastructure", settlementFailure(review)!, meter);
|
|
693
|
+
}
|
|
694
|
+
if (TRUNCATED_OUTPUT.test(review.result.output)) {
|
|
695
|
+
return terminal(flow, "infrastructure", "Reviewer transport output was truncated; approval is invalid.", meter);
|
|
696
|
+
}
|
|
697
|
+
if (review.result.output.trim() !== "PASS") {
|
|
698
|
+
return block(flow, unit, "reviewer_findings", review.result.output || "Reviewer returned no PASS approval.", meter);
|
|
699
|
+
}
|
|
700
|
+
approvedTip = evidence.tip;
|
|
666
701
|
}
|
|
667
702
|
|
|
668
703
|
try {
|
|
@@ -671,12 +706,12 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
671
706
|
return terminal(flow, "main", errorText(error), meter);
|
|
672
707
|
}
|
|
673
708
|
assertCurrent(flow);
|
|
674
|
-
const merged = await git(["merge", "--no-overwrite-ignore", "--ff-only",
|
|
709
|
+
const merged = await git(["merge", "--no-overwrite-ignore", "--ff-only", approvedTip], main.root, signal);
|
|
675
710
|
assertCurrent(flow);
|
|
676
711
|
if (merged.code !== 0 || merged.killed) {
|
|
677
|
-
const diagnostic = commandFailure(`git merge --no-overwrite-ignore --ff-only ${
|
|
712
|
+
const diagnostic = commandFailure(`git merge --no-overwrite-ignore --ff-only ${approvedTip}`, merged);
|
|
678
713
|
const previousHead = main.expectedHead;
|
|
679
|
-
main.expectedHead =
|
|
714
|
+
main.expectedHead = approvedTip;
|
|
680
715
|
try {
|
|
681
716
|
await checkMain(main);
|
|
682
717
|
} catch (error) {
|
|
@@ -688,7 +723,7 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
688
723
|
].join("\n")), meter);
|
|
689
724
|
}
|
|
690
725
|
flow.warnings.push(capOutput(`Unit ${JSON.stringify(unit.request.id)} integrated after merge reported failure: ${diagnostic}`));
|
|
691
|
-
} else main.expectedHead =
|
|
726
|
+
} else main.expectedHead = approvedTip;
|
|
692
727
|
flow.completed.push({ id: unit.request.id, noOp: false });
|
|
693
728
|
try {
|
|
694
729
|
await checkMain(main);
|
|
@@ -696,7 +731,7 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
696
731
|
return terminal(flow, "integration", errorText(error), meter);
|
|
697
732
|
}
|
|
698
733
|
assertCurrent(flow);
|
|
699
|
-
const cleanupWarning = await cleanupUnit(unit, main,
|
|
734
|
+
const cleanupWarning = await cleanupUnit(unit, main, approvedTip, flow.sessionController.signal);
|
|
700
735
|
assertCurrent(flow);
|
|
701
736
|
if (cleanupWarning) flow.warnings.push(`Unit ${JSON.stringify(unit.request.id)} integrated, but cleanup refused: ${cleanupWarning}`);
|
|
702
737
|
flow.index += 1;
|
|
@@ -709,12 +744,12 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
709
744
|
pi.registerTool({
|
|
710
745
|
name: "delegate_flow",
|
|
711
746
|
label: "Delegate Flow",
|
|
712
|
-
description: "Run 1–8 independent Implementers in isolated Unit Worktrees,
|
|
713
|
-
promptSnippet: "Run a deterministic parallel-implementation, serial-
|
|
747
|
+
description: "Run 1–8 independent Implementers in isolated Unit Worktrees, validate and serially fast-forward each tip, with exact review only for units that declare a judgment criterion.",
|
|
748
|
+
promptSnippet: "Run a deterministic parallel-implementation, serial-verification Flow",
|
|
714
749
|
promptGuidelines: [
|
|
715
750
|
"Use delegate_flow only for cohesive units expected to commute; combine work that overlaps files, APIs, schemas, generated output, package metadata, lockfiles, or invariants.",
|
|
716
|
-
"Each unit must include explicit bounded requirements and its authoritative direct command/argument validation gate.",
|
|
717
|
-
"If a Flow blocks, inspect its classification and call delegate_flow_continue once with explicit repair guidance.",
|
|
751
|
+
"Each unit must include explicit bounded requirements and its authoritative direct command/argument validation gate. Add review only for an explicit judgment that validation cannot establish.",
|
|
752
|
+
"If a Flow blocks, inspect its classification and call delegate_flow_continue once with explicit repair guidance; modelClass may replace that one repair's current class.",
|
|
718
753
|
],
|
|
719
754
|
parameters: DelegateFlowSchema,
|
|
720
755
|
prepareArguments: parseDelegateFlow,
|
|
@@ -723,14 +758,16 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
723
758
|
if (active) throw new Error("delegate_flow rejected because another Flow is active.");
|
|
724
759
|
const roles = runtime.loadRoles();
|
|
725
760
|
const implementer = roles.find(({ name }) => name === "implementer");
|
|
726
|
-
const
|
|
727
|
-
|
|
761
|
+
const needsReviewer = request.units.some(({ review }) => review !== undefined);
|
|
762
|
+
const reviewer = needsReviewer ? roles.find(({ name }) => name === "reviewer") : undefined;
|
|
763
|
+
if (!implementer) throw new Error("delegate_flow requires an implementer Role.");
|
|
764
|
+
if (needsReviewer && !reviewer) throw new Error("delegate_flow requires a reviewer Role when a unit declares review.");
|
|
728
765
|
const flow: FlowState = {
|
|
729
766
|
phase: "running",
|
|
730
767
|
generation: runtime.getSessionGeneration(),
|
|
731
768
|
sessionController: new AbortController(),
|
|
732
769
|
implementer,
|
|
733
|
-
reviewer,
|
|
770
|
+
...(reviewer === undefined ? {} : { reviewer }),
|
|
734
771
|
units: [],
|
|
735
772
|
setupRecoveries: [],
|
|
736
773
|
index: 0,
|
|
@@ -762,6 +799,7 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
762
799
|
if (!worktree) throw new Error("Flow Unit Worktrees require a Git repository with a committed HEAD; generic cwd fallback is disabled.");
|
|
763
800
|
flow.units.push({
|
|
764
801
|
request: unit,
|
|
802
|
+
modelClass: unit.modelClass,
|
|
765
803
|
worktree,
|
|
766
804
|
base: worktree.baseCommit,
|
|
767
805
|
repairUsed: false,
|
|
@@ -777,6 +815,7 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
777
815
|
const settlements = await Promise.all(flow.units.map((unit, index) => runChild(
|
|
778
816
|
flow,
|
|
779
817
|
flow.implementer,
|
|
818
|
+
unit.modelClass,
|
|
780
819
|
implementerTask(unit.request),
|
|
781
820
|
unit.worktree.cwd,
|
|
782
821
|
`${toolCallId}:flow:${index}:implement`,
|
|
@@ -805,13 +844,13 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
805
844
|
pi.registerTool({
|
|
806
845
|
name: "delegate_flow_continue",
|
|
807
846
|
label: "Continue Delegate Flow",
|
|
808
|
-
description: "Repair the one blocked Flow Unit in its existing Unit Worktree, then resume declared-order validation, exact review, and integration.",
|
|
847
|
+
description: "Repair the one blocked Flow Unit in its existing Unit Worktree, optionally replace its model class, then resume declared-order validation, conditional exact review, and integration.",
|
|
809
848
|
promptSnippet: "Repair and continue the blocked deterministic Flow",
|
|
810
849
|
promptGuidelines: ["Call delegate_flow_continue only after delegate_flow reports a repairable block, with explicit guidance addressing that block."],
|
|
811
850
|
parameters: DelegateFlowContinueSchema,
|
|
812
851
|
prepareArguments: parseDelegateFlowContinue,
|
|
813
852
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
814
|
-
const { guidance } = parseDelegateFlowContinue(params);
|
|
853
|
+
const { guidance, modelClass } = parseDelegateFlowContinue(params);
|
|
815
854
|
const flow = active;
|
|
816
855
|
if (!flow) throw new Error("delegate_flow_continue requires an active blocked Flow.");
|
|
817
856
|
assertCurrent(flow);
|
|
@@ -822,12 +861,14 @@ export function registerDelegateFlow(pi: ExtensionAPI, runtime: DelegateFlowRunt
|
|
|
822
861
|
flow.phase = "running";
|
|
823
862
|
flow.blocked = undefined;
|
|
824
863
|
unit.repairUsed = true;
|
|
864
|
+
if (modelClass !== undefined) unit.modelClass = modelClass;
|
|
825
865
|
const operationSignal = bindSignal(flow, signal);
|
|
826
866
|
const meter: UsageMeter = {};
|
|
827
867
|
try {
|
|
828
868
|
unit.implementation = await runChild(
|
|
829
869
|
flow,
|
|
830
870
|
flow.implementer,
|
|
871
|
+
unit.modelClass,
|
|
831
872
|
repairTask(unit.request, blocked, guidance),
|
|
832
873
|
unit.worktree.cwd,
|
|
833
874
|
`${toolCallId}:flow:${flow.index}:repair`,
|
package/extensions/subagent.ts
CHANGED
|
@@ -443,11 +443,13 @@ export default function subagentExtension(
|
|
|
443
443
|
maxRuntimeMs: timeoutPolicy.maxMs,
|
|
444
444
|
getSessionGeneration: () => sessionEpoch,
|
|
445
445
|
loadRoles,
|
|
446
|
-
resolveLaunch: (role, ctx) => {
|
|
446
|
+
resolveLaunch: (role, modelClass, ctx) => {
|
|
447
447
|
const launchCtx = latestCtx ?? ctx;
|
|
448
448
|
return createRoleLaunch(pi, launchCtx, {
|
|
449
449
|
role,
|
|
450
|
-
route:
|
|
450
|
+
route: modelClass === undefined
|
|
451
|
+
? resolveConfiguredTaskRoute(launchCtx, SUBAGENT_TASK)
|
|
452
|
+
: resolveTaskRoute(launchCtx, modelClass),
|
|
451
453
|
});
|
|
452
454
|
},
|
|
453
455
|
startWidget: startWidgetItem,
|
|
@@ -463,7 +465,7 @@ export default function subagentExtension(
|
|
|
463
465
|
promptGuidelines: [
|
|
464
466
|
"Call delegate_task with exactly one mode: role+task for one task, tasks for 1–8 independent parallel tasks, or chain for 1–8 dependent sequential tasks using {previous} for the immediately preceding assistant output.",
|
|
465
467
|
"Every delegate_task entry must state its objective, exact scope and exclusions, relevant context and constraints, expected deliverable, and validation; never pass the parent request unchanged.",
|
|
466
|
-
"For each delegate_task entry,
|
|
468
|
+
"For each delegate_task entry, populate model and thinking only for an explicit user override; otherwise choose only modelClass: fast normally, or balanced upfront for obviously complex work. This is Main policy, not runtime enforcement.",
|
|
467
469
|
"Parallel delegate_task entries must own non-overlapping files. Keep integration and cross-cutting decisions in Main, and use the minimum number of Subagents needed.",
|
|
468
470
|
"delegate_task background applies to the whole selected workflow and returns before results exist; use it only when the user explicitly asks for non-blocking work.",
|
|
469
471
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-subagent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Delegate bounded single, parallel, or chained tasks to isolated Pi roles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -66,6 +66,6 @@
|
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@henryqw/pi-herdr": "^0.4.0",
|
|
68
68
|
"@henryqw/pi-multi-codex": "^0.3.8",
|
|
69
|
-
"@henryqw/pi-task-models": "^
|
|
69
|
+
"@henryqw/pi-task-models": "^2.0.0"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -5,28 +5,28 @@ description: Run bounded independent implementation through the runtime-managed
|
|
|
5
5
|
|
|
6
6
|
# Delegated Development
|
|
7
7
|
|
|
8
|
-
You are Main: slice work and call `delegate_flow`. Do not implement child work yourself or use external model tools, push, publish, or release.
|
|
8
|
+
You are Main, the planner/orchestrator: slice work and call `delegate_flow`. Do not implement child work yourself or use external model tools, push, publish, or release.
|
|
9
9
|
|
|
10
10
|
## Slice
|
|
11
11
|
|
|
12
12
|
Use the fewest cohesive units. `delegate_flow` is for independent units expected to commute; combine or sequence work that overlaps files, APIs, schemas, generated output, package metadata, lockfiles, or invariants. Dependent work remains outside Flow, in one task or ordinary caller-controlled sequencing.
|
|
13
13
|
|
|
14
|
-
Give every unit a bounded objective, owned scope and exclusions,
|
|
14
|
+
Give every unit a bounded objective, owned scope and exclusions, and its direct validation command/argument array. Do not pass the parent request unchanged. Use `modelClass: "fast"` normally; use `"balanced"` upfront only for obviously complex work. Add non-empty `review` only for an explicit judgment that automated validation cannot establish. Call `delegate_flow` with 1–8 units; the runtime always supplies the effective Implementer and supplies the Reviewer only when a unit needs review.
|
|
15
15
|
|
|
16
16
|
## Runtime Flow
|
|
17
17
|
|
|
18
|
-
The runtime owns the unit worktrees and all Git identity, rebasing, declared validation, exact read-only review, fast-forward integration, and cleanup.
|
|
18
|
+
The runtime owns the unit worktrees and all Git identity, rebasing, committed-state inspection, declared validation, conditional exact read-only review, fast-forward integration, and cleanup. Declared validation is the authority for objective verification. A validated unit without `review` skips review evidence and Reviewer launch, then integrates its exact validated tip. A unit with `review` receives the existing exact `{base, tip, patchPath}` protocol and must receive exactly `PASS` before integration.
|
|
19
19
|
|
|
20
20
|
Trust the structured Flow outcome. Never edit a child worktree, manage its branches, prepare review evidence, reimplement Flow, or manually integrate its changes. Do not repeat Flow validation after it has completed or integrated a unit.
|
|
21
21
|
|
|
22
|
-
A successful Flow owns integration and cleanup. A blocked outcome is repairable: provide one explicit continuation and no more:
|
|
22
|
+
A successful Flow owns integration and cleanup. A blocked outcome is repairable once: provide one explicit continuation and no more:
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
|
-
delegate_flow_continue({ guidance: "Address the reported block and complete the bounded unit." })
|
|
25
|
+
delegate_flow_continue({ guidance: "Address the reported block and complete the bounded unit.", modelClass: "balanced" })
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Make the guidance specific to the reported implementation, validation, or review failure. Do not call continuation unless Flow reports a repairable block. If continuation or Flow returns a terminal failure, inspect every retained path reported by the runtime, then reslice or manually recover from Main; do not retry the Flow or guess a rebase resolution. A cleanup warning does not undo successful integration.
|
|
28
|
+
Make the guidance specific to the reported implementation, validation, or review failure. Omit `modelClass` to retain the blocked unit's current class; supply it only to replace that one repair's class. Do not call continuation unless Flow reports a repairable block. If continuation or Flow returns a terminal failure, inspect every retained path reported by the runtime, then reslice or manually recover from Main; do not retry the Flow or guess a rebase resolution. A cleanup warning does not undo successful integration.
|
|
29
29
|
|
|
30
30
|
## Ordinary delegation
|
|
31
31
|
|
|
32
|
-
Use `delegate_task` for a single bounded task, independent parallel tasks, or dependent chain work that is not a Flow. Give each entry its objective, exact scope and exclusions, relevant context and constraints, expected deliverable, and focused validation.
|
|
32
|
+
Use `delegate_task` for a single bounded task, independent parallel tasks, or dependent chain work that is not a Flow. Give each entry its objective, exact scope and exclusions, relevant context and constraints, expected deliverable, and focused validation. Populate direct `model` and `thinking` only when the user explicitly asks for those overrides; otherwise choose only `modelClass` (`fast` normally, `balanced` upfront for obviously complex work). Keep integration and cross-cutting decisions in Main, and use the minimum number of Subagents needed.
|