@henryqw/pi-subagent 26.1.0 → 26.1.1
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/README.md +5 -1
- package/dist/runner.js +11 -2
- package/dist/schema.js +4 -1
- package/docs/orchestration.md +2 -2
- package/extensions/isolated.ts +13 -3
- package/package.json +1 -1
- package/skills/pi-subagent/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Commands are for you; tools and the packaged skill are for Main. You do not need
|
|
|
35
35
|
| `subagent_status` | tool | Read durable isolated state and the exact allowed continuation without replaying work. Declares an output schema, so `codemode` scripts receive the same bounded public projection as structured content. |
|
|
36
36
|
| `subagent_resume` | tool | Perform the reported `retry`, `verify`, or `finalize` continuation; does not replace staging or integration. |
|
|
37
37
|
| `subagent_stage` | tool | Stage or resolve an exact candidate, or reject/revise it; rejection of a staged candidate freezes that generation. |
|
|
38
|
-
| `subagent_integrate` | tool | Advance staged dependents, refresh after clean Main drift, validate, record one correction, promote, reconcile interrupted promotion, clean up proven promotion, or explicitly release rejected/superseded resources. |
|
|
38
|
+
| `subagent_integrate` | tool | Advance staged dependents, refresh after clean Main drift or evidence-less validation failure, validate in the background, record one correction, promote, reconcile interrupted promotion, clean up proven promotion, or explicitly release rejected/superseded resources. |
|
|
39
39
|
| `subagent_abort` | tool | Abort an isolated request only when no retained candidates or integration worktrees remain; cannot discard them. |
|
|
40
40
|
| `pi-subagent` | skill | Guide Main through delegation, authorization, checks, integration, and recovery. |
|
|
41
41
|
|
|
@@ -148,6 +148,10 @@ A queued follow-up runs after the current turn settles and must produce a new cl
|
|
|
148
148
|
|
|
149
149
|
### Isolated recovery
|
|
150
150
|
|
|
151
|
+
`subagent_integrate validate` acknowledges only after saving the exact generation's validation intent, then runs checks and any final judgment in the background. A later turn cancellation does not cancel acknowledged validation; session replacement or shutdown still stops it. Completion sends a follow-up to the launching session. Use `subagent_status` if delivery is missed; acknowledgement is not passing evidence.
|
|
152
|
+
|
|
153
|
+
If validation was interrupted, first prove the old coordinator/check processes have stopped; never take over a live productive lease. Use guarded `reconcile` on the saved generation and `combinedTip`. Missing checks/review never authorize `correct` or `promote`. For a `validation_failed` generation with **no recorded checks, review, or prior combined correction**, `refresh` also accepts identical `expectedMain` and `newMain`. Supply the exact old combined `expectedTip` and the next generation number. Both Main and the retained integration tip must remain exact and clean. This explicitly allocates a new checkout; it does not resume old checks. Restage selected immutable candidates and run fresh full-suite checks and required final judgment before promotion. The old generation stays retained, the two-checkout limit still applies, and correction allowances are not reset. Other failures retain their existing recovery requirements.
|
|
154
|
+
|
|
151
155
|
After a coordinator interruption, run `/subagent recover` once. It acquires the repository productive lease before changing any orphaned `pending` or `running` state, preserves unreadable state files and retained candidates, and reports blockers and existing continuations to Main without replaying prompts, merges, promotion, or cleanup. If another productive run holds the lease, it only reads status and reports the live owner; wait rather than taking over. Repeating the command is safe. Main uses `subagent_status` for exact identities and the recovery tools in the interface table above for deliberate decisions; the command never stages or promotes. Direct Herdr tab recovery remains in `/subagent`.
|
|
152
156
|
|
|
153
157
|
If an isolated worker produced no candidate, `subagent_abort` terminates it and removes only proven unchanged, clean owned resources. Cleanup includes ignored-file checks and never discards unique commits. If productive work is still settling, termination is uncertain, or cleanup fails, the request stays aborted with retained-resource evidence. Inspect `subagent_status`, resolve the reported blocker, then repeat `subagent_abort` to reconcile cleanup; it does not replay worker prompts. Dirty work, unique commits, and uncertain allocations remain for deliberate manual recovery.
|
package/dist/runner.js
CHANGED
|
@@ -1151,7 +1151,16 @@ export class IsolatedRunner {
|
|
|
1151
1151
|
if (!isCleanCommitted(action.newMain) || !sameIdentity(actualMain, action.newMain)) {
|
|
1152
1152
|
throw new Error("Refresh requires the exact new clean Main identity.");
|
|
1153
1153
|
}
|
|
1154
|
-
|
|
1154
|
+
const unchangedMain = sameIdentity(action.expectedMain, action.newMain);
|
|
1155
|
+
if (unchangedMain) {
|
|
1156
|
+
if (generation?.status !== "validation_failed" || generation.checks || generation.review || generation.correction) {
|
|
1157
|
+
throw new Error("Unchanged Main refresh requires evidence-less failed validation without a prior correction.");
|
|
1158
|
+
}
|
|
1159
|
+
await this.requireExactIntegration(handle, generation, this.stageReceipts(generation), scope);
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
await scope.call((context) => this.integrationGit.inspectMainAdvance(root, action.expectedMain, action.newMain, context.signal));
|
|
1163
|
+
}
|
|
1155
1164
|
// Old dependent snapshots cannot be replayed on a new Main tip. Retain their resources and
|
|
1156
1165
|
// invalidate their readiness before admitting any new stages or dependent launches.
|
|
1157
1166
|
const stale = new Set();
|
|
@@ -1174,7 +1183,7 @@ export class IsolatedRunner {
|
|
|
1174
1183
|
}
|
|
1175
1184
|
generation.supersededFrom = generation.status;
|
|
1176
1185
|
generation.status = "superseded";
|
|
1177
|
-
generation.failure = "Main advanced; retained generation is read-only. Restage immutable candidates in a new generation
|
|
1186
|
+
generation.failure = `${unchangedMain ? "Validation evidence missing" : "Main advanced"}; retained generation is read-only. Restage immutable candidates in a new generation.`;
|
|
1178
1187
|
delete generation.combinedTip;
|
|
1179
1188
|
delete generation.checks;
|
|
1180
1189
|
delete generation.review;
|
package/dist/schema.js
CHANGED
|
@@ -784,7 +784,10 @@ export function parseIntegrationState(value, request) {
|
|
|
784
784
|
}
|
|
785
785
|
const refresh = state.refresh;
|
|
786
786
|
if (refresh && (!isCleanCommitted(refresh.from) || !isCleanCommitted(refresh.to)
|
|
787
|
-
|| refresh.from.branch !== refresh.to.branch
|
|
787
|
+
|| refresh.from.branch !== refresh.to.branch
|
|
788
|
+
|| (refresh.from.head === refresh.to.head && (!sameIdentity(refresh.from, refresh.to)
|
|
789
|
+
|| state.generations[refresh.generation - 2]?.supersededFrom !== "validation_failed"
|
|
790
|
+
|| state.generations[refresh.generation - 2]?.correction !== undefined))
|
|
788
791
|
|| refresh.generation !== state.generations.length + (refresh.status === "pending" ? 1 : 0)
|
|
789
792
|
|| (refresh.status === "ready" ? refresh.failure !== undefined : refresh.failure !== undefined && !refresh.failure.trim())
|
|
790
793
|
|| (refresh.generation > 1 && (state.generations[refresh.generation - 2]?.status !== "superseded"
|
package/docs/orchestration.md
CHANGED
|
@@ -97,7 +97,7 @@ Every task declares `id`, `kind`, `role`, `modelClass`, `requirements`, `deliver
|
|
|
97
97
|
|
|
98
98
|
Admission and worker-wave dispatch pin Main's exact committed branch, HEAD, and tree without inspecting or modifying its staged, unstaged, or untracked edits. Worker allocation verifies the same committed identity around worktree creation, including resumed waves; staged-dependent waves verify their clean integration snapshot. A dirty Main can therefore produce a checked retained candidate, but branch/HEAD drift stops new waves. The runner executes ready worker tasks concurrently, inspects clean committed candidates, runs preliminary task checks and optional preliminary judgment, then records ready candidates for Main. **It does not automatically integrate them.** Main selects exact candidates via `subagent_stage` into an owned integration checkout in explicit order. Each stage is a committed merge of the candidate, not a mutation of Main. Conflicts retain the checkout for Main to resolve; `resolve` verifies the exact two-parent merge instead of replaying an uncertain merge. Main can explicitly run dependent tasks from an exact staged tip with `subagent_integrate advance` and stage their results afterward. Independent candidates may be staged without re-running workers.
|
|
99
99
|
|
|
100
|
-
Once all selected work is staged, Main calls `subagent_integrate validate` on the exact clean combined tip. This runs every final check, including `pnpm test`, and optional final judgment in the integration checkout before any Main mutation. A failed check/review can be followed by one Main-authored committed correction in that checkout, recorded with `correct`, then another `validate`. Staging, combined validation, and `promote` require clean Main at the recorded committed identity; they stay blocked by dirty edits even when the checked candidate is retained, and no dirty work is replayed or merged automatically. `promote` requires exact passing combined evidence and unchanged Main and uses a guarded update to Main. Only proven promotion leads to selected-worker termination and verified cleanup. A changed candidate, integration tip, or Main identity invalidates prior evidence or blocks promotion; review accepts only exact `PASS`.
|
|
100
|
+
Once all selected work is staged, Main calls `subagent_integrate validate` on the exact clean combined tip. It acknowledges the durably saved validation intent and continues in the session-owned background, independent of later turn cancellation; session replacement/shutdown still cancels it. A follow-up reports completion; status remains authoritative if delivery is missed. This runs every final check, including `pnpm test`, and optional final judgment in the integration checkout before any Main mutation. A failed check/review can be followed by one Main-authored committed correction in that checkout, recorded with `correct`, then another `validate`. Staging, combined validation, and `promote` require clean Main at the recorded committed identity; they stay blocked by dirty edits even when the checked candidate is retained, and no dirty work is replayed or merged automatically. `promote` requires exact passing combined evidence and unchanged Main and uses a guarded update to Main. Only proven promotion leads to selected-worker termination and verified cleanup. A changed candidate, integration tip, or Main identity invalidates prior evidence or blocks promotion; review accepts only exact `PASS`.
|
|
101
101
|
|
|
102
102
|
### Readiness and Main actions
|
|
103
103
|
|
|
@@ -109,7 +109,7 @@ The user's request can authorize the declared local graph, checks, review, and M
|
|
|
109
109
|
|
|
110
110
|
- `subagent_stage`: `{ id, action: "stage" | "resolve" | "reject" | "revise", generation, taskId, attempt, candidate, expectedTip, instruction? }`. `candidate` is the ready candidate's `tip`; `expectedTip` is Main's recorded identity for the first stage or the previous stage's exact tip. `resolve` reconciles a pending/conflicted merge; `reject` and `revise` arbitrate a candidate without changing Main.
|
|
111
111
|
- `subagent_integrate`: `{ id, action: "advance" | "validate" | "correct" | "promote" | "reconcile" | "cleanup", generation, expectedTip }`. Use the generation's exact `combinedTip`. `advance` dispatches ready dependents on that snapshot, `correct` records the single clean committed correction after a definitive failed check/review, `reconcile` inspects an interrupted validation or promotion without replaying an uncertain promotion, and `cleanup` completes proved post-promotion cleanup.
|
|
112
|
-
- `subagent_integrate refresh`: `{ id, action: "refresh", generation, expectedTip, expectedMain, newMain }`. If Main advanced to a clean same-branch descendant, this freezes the old generation and allocates a new integration checkout from `newMain`. Restage selected immutable candidates in explicit order, resolve fresh conflicts, then rerun combined validation. Dirty, divergent, or uncertain Main cannot refresh.
|
|
112
|
+
- `subagent_integrate refresh`: `{ id, action: "refresh", generation, expectedTip, expectedMain, newMain }`. If Main advanced to a clean same-branch descendant, this freezes the old generation and allocates a new integration checkout from `newMain`. Restage selected immutable candidates in explicit order, resolve fresh conflicts, then rerun combined validation. Dirty, divergent, or uncertain Main cannot refresh. For a reconciled `validation_failed` generation without checks, review, or a prior combined correction, identical `expectedMain` and `newMain` instead rebuild on the unchanged base. This requires the exact clean retained integration tip, leaves the old checkout retained, and never resumes its uncertain checks. Prove old coordinator/check processes stopped before recovery; a live productive lease blocks it. Restage explicitly and rerun all final gates; no correction allowance is replenished.
|
|
113
113
|
- `subagent_integrate release`: `{ id, action: "release", generation, expectedTip, taskId?, attempt? }`. With no task, release an explicitly superseded checkout using that generation's last staged tip (or integration base if empty). With both `taskId` and `attempt`, release a rejected, terminated worker using its exact candidate tip and the latest generation number (or 1 when none exists); release every generation that staged it first. Cleanup steps are persisted separately, and a retry reconciles already-removed resources by proving absence. Release refuses dirty/conflicted work, changed refs, unproved termination, and still-needed checkouts.
|
|
114
114
|
|
|
115
115
|
A rejection or revision of a staged candidate supersedes that generation, leaves its checkout read-only, and requires explicit restaging of chosen candidates into a new generation. At most two unreleased owned integration checkouts can coexist per request. Superseded integration checkouts and rejected candidate resources are **retained until Main explicitly releases them**. Inspect exact status first; dirty or unresolved conflicts require manual recovery without force deletion. Rejection invalidates dependent attempts; if the required fresh dependent work cannot run within the bounded attempt/lineage constraints, start a new request. `subagent_abort` cannot discard retained resources, but can finish after all candidates are rejected and all owned checkouts are released.
|
package/extensions/isolated.ts
CHANGED
|
@@ -495,7 +495,8 @@ export function registerIsolatedExtension(pi: ExtensionAPI, options: RegisterIso
|
|
|
495
495
|
if (!current() || sessionClosed) throw new Error("Session or branch changed; reopen /subagent.");
|
|
496
496
|
};
|
|
497
497
|
// FileRunStore emits the initial state only after fsync. A resumed run emits its
|
|
498
|
-
// recovery record after saving it
|
|
498
|
+
// recovery record after saving it; validation acknowledges its saved exact intent.
|
|
499
|
+
// Neither the tool call nor its abort signal
|
|
499
500
|
// owns productive work after that durable boundary.
|
|
500
501
|
const startInSession = async (
|
|
501
502
|
id: string,
|
|
@@ -610,7 +611,7 @@ export function registerIsolatedExtension(pi: ExtensionAPI, options: RegisterIso
|
|
|
610
611
|
reject(error);
|
|
611
612
|
return;
|
|
612
613
|
}
|
|
613
|
-
deliver("terminal", `Pi Subagent ${id} stopped: ${boundedPublicText(error instanceof Error ? error.message : String(error))}. Use subagent_status to inspect the durable request
|
|
614
|
+
deliver("terminal", `Pi Subagent ${id} stopped: ${boundedPublicText(error instanceof Error ? error.message : String(error))}. Use subagent_status to inspect the durable request before choosing an explicit recovery action.`);
|
|
614
615
|
},
|
|
615
616
|
);
|
|
616
617
|
const state = await durable;
|
|
@@ -668,13 +669,22 @@ export function registerIsolatedExtension(pi: ExtensionAPI, options: RegisterIso
|
|
|
668
669
|
pi.registerTool({
|
|
669
670
|
name: "subagent_integrate",
|
|
670
671
|
label: "Subagent integrate",
|
|
671
|
-
description: "Main advances staged dependents, refreshes after clean Main drift, validates, corrects, promotes, reconciles
|
|
672
|
+
description: "Main advances staged dependents, refreshes after clean Main drift or evidence-less validation failure, validates in the background, corrects, promotes, reconciles interrupted validation/promotion, cleans up promoted resources, or explicitly releases rejected/superseded owned resources. Never replays an uncertain mutation.",
|
|
672
673
|
parameters: IntegrationActionParameters,
|
|
673
674
|
prepareArguments: parseIntegrationAction,
|
|
674
675
|
exposure: "model-only",
|
|
675
676
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
676
677
|
latestCtx = ctx;
|
|
677
678
|
const root = await lookupRoot(ctx.cwd, signal);
|
|
679
|
+
if (params.action === "validate") {
|
|
680
|
+
return await startInSession(params.id, root, signal, ctx,
|
|
681
|
+
(state) => {
|
|
682
|
+
const generation = state.integration.generations.at(-1);
|
|
683
|
+
return generation?.number === params.generation && generation.status === "validating"
|
|
684
|
+
&& sameIdentity(generation.combinedTip!, params.expectedTip);
|
|
685
|
+
},
|
|
686
|
+
(runSignal) => getComponents().runner.integrate(params, root, runSignal));
|
|
687
|
+
}
|
|
678
688
|
if (params.action === "advance") {
|
|
679
689
|
return await startInSession(params.id, root, signal, ctx,
|
|
680
690
|
(state) => state.status === "running" && state.waves.at(-1)?.status === "dispatching"
|
package/package.json
CHANGED
|
@@ -32,8 +32,8 @@ Give the request a unique ID and goal. Give each task a unique ID, kind, Role, m
|
|
|
32
32
|
|
|
33
33
|
Every changeset advances automatically after preliminary checks pass. While a locally owned changeset task is actively working, the user may open `/subagent` to queue bounded same-worker revisions or withdraw all still-pending instructions into the native editor and submit one replacement. Cancellation after withdrawal leaves the originals removed; already-claimed instructions cannot be recalled. Each revision must produce a new clean commit and rerun preliminary checks. When no queued revision remains, the runner seals and durably records the exact ready candidate without waiting for sibling workers. There is no guaranteed post-completion editing window, and late follow-ups fail visibly.
|
|
34
34
|
|
|
35
|
-
A ready candidate does not integrate automatically. Each checked worker triggers a follow-up while its siblings continue; inspect `subagent_status` and immediately use `subagent_stage` with the exact `generation`, `taskId`, `attempt`, candidate `tip`, and `expectedTip` to select and merge it into the owned integration checkout. Stage and resolve are available during the active wave; wait for the wave to settle before rejecting or revising. Resolve conflicts in the integration checkout and verify with `resolve`; never edit Main to resolve them. For dependents, `subagent_integrate advance` acknowledges a durable background wave, then reports checked candidates as they finish. When selected work is staged and the wave settles, use `subagent_integrate validate` on the exact `combinedTip
|
|
35
|
+
A ready candidate does not integrate automatically. Each checked worker triggers a follow-up while its siblings continue; inspect `subagent_status` and immediately use `subagent_stage` with the exact `generation`, `taskId`, `attempt`, candidate `tip`, and `expectedTip` to select and merge it into the owned integration checkout. Stage and resolve are available during the active wave; wait for the wave to settle before rejecting or revising. Resolve conflicts in the integration checkout and verify with `resolve`; never edit Main to resolve them. For dependents, `subagent_integrate advance` acknowledges a durable background wave, then reports checked candidates as they finish. When selected work is staged and the wave settles, use `subagent_integrate validate` on the exact `combinedTip`: it acknowledges saved intent, runs full root checks and optional final judgment in the session-owned background, then sends a follow-up. Acknowledgement is not passing evidence; `promote` requires passing evidence and unchanged Main. One Main-authored committed correction to a definitively failed combined validation can be recorded with `correct`, then revalidated. Only proven promotion triggers selected-worker termination and verified cleanup. Failures, ambiguity, conflicts, interruption, child-limit exhaustion, or Main drift retain work and never waive checks or identity guards. The extension never pushes, opens a pull request, publishes, or deploys.
|
|
36
36
|
|
|
37
|
-
After a coordinator interruption in the canonical repository, ask the user to run `/subagent recover` once: it acquires the productive lease, classifies orphaned interruptions, preserves invalid files and retained work, and sends Main one follow-up with blockers and next actions. A live productive lease is read-only and cannot be taken over. Recovery never replays prompts, merges, promotion, or cleanup. Use `subagent_status` to inspect exact durable state without mutation, including candidate identities, generations, and retained resources. `subagent_resume` handles only the reported retry/verify/finalize continuation; it cannot replace Main's stage/integrate decisions or reset policy/corrections. `subagent_stage reject` or `revise` freezes a generation containing the candidate and invalidates dependent attempts; restage chosen work in a new generation. If Main advances cleanly before promotion, use `subagent_integrate refresh` with exact old/new Main and combined-tip identities; restage immutable candidates into the new generation and rerun combined validation. Dirty or divergent Main blocks refresh. At most two unreleased integration worktrees may coexist. Rejected candidates and superseded generations remain retained until Main explicitly calls `subagent_integrate release` with the exact generation and tip; for a rejected worker include its task ID and attempt after releasing all generations using it. Dirty/conflicted or unproved resources block release without force deletion. Impossible fresh dependent work after rejection needs a new request. `subagent_abort` cannot discard retained candidates or integration generations; release them first. For workers without candidates, abort cleans only proven unchanged, clean owned resources. If cleanup is deferred or blocked, inspect status and repeat abort after resolving the blocker. Dirty or committed work and uncertain allocations remain for manual recovery. Old v4 state is rejected (current state is v5), not migrated. Status, abort, process I/O, termination, and verified post-promotion cleanup keep finite safety budgets.
|
|
37
|
+
After a coordinator interruption in the canonical repository, ask the user to run `/subagent recover` once: it acquires the productive lease, classifies orphaned interruptions, preserves invalid files and retained work, and sends Main one follow-up with blockers and next actions. A live productive lease is read-only and cannot be taken over. Recovery never replays prompts, merges, promotion, or cleanup. Use `subagent_status` to inspect exact durable state without mutation, including candidate identities, generations, and retained resources. `subagent_resume` handles only the reported retry/verify/finalize continuation; it cannot replace Main's stage/integrate decisions or reset policy/corrections. `subagent_stage reject` or `revise` freezes a generation containing the candidate and invalidates dependent attempts; restage chosen work in a new generation. If Main advances cleanly before promotion, use `subagent_integrate refresh` with exact old/new Main and combined-tip identities; restage immutable candidates into the new generation and rerun combined validation. Dirty or divergent Main blocks refresh. For interrupted validation, prove old coordinator/check processes stopped, then guarded `reconcile` classifies the exact generation; never resume a live lease. A `validation_failed` generation without checks, review, or a prior combined correction can `refresh` with identical `expectedMain`/`newMain`, the old exact combined `expectedTip`, and the next generation number. Main and the retained integration tip must be exact and clean. Restage immutable candidates explicitly and rerun all final gates in the new checkout; never replay old uncertain checks, infer passing evidence, or reset correction allowances. At most two unreleased integration worktrees may coexist. Rejected candidates and superseded generations remain retained until Main explicitly calls `subagent_integrate release` with the exact generation and tip; for a rejected worker include its task ID and attempt after releasing all generations using it. Dirty/conflicted or unproved resources block release without force deletion. Impossible fresh dependent work after rejection needs a new request. `subagent_abort` cannot discard retained candidates or integration generations; release them first. For workers without candidates, abort cleans only proven unchanged, clean owned resources. If cleanup is deferred or blocked, inspect status and repeat abort after resolving the blocker. Dirty or committed work and uncertain allocations remain for manual recovery. Old v4 state is rejected (current state is v5), not migrated. Status, abort, process I/O, termination, and verified post-promotion cleanup keep finite safety budgets.
|
|
38
38
|
|
|
39
39
|
All launches use the global policy in `config/pi-subagent/config.json`: productive concurrency, per-child turns/tokens, and idle timeout. Ephemeral children also have a hard runtime limit; isolated requests have a correction cap. Productive requests have no whole-run wall-clock deadline. Request fields cannot replenish or override limits.
|