@henryqw/pi-subagent 18.0.1 → 22.0.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/CONTEXT.md +76 -24
- package/README.md +116 -142
- package/dist/composition.d.ts +11 -0
- package/dist/composition.js +107 -0
- package/dist/direct-herdr.d.ts +17 -0
- package/dist/direct-herdr.js +219 -0
- package/dist/ephemeral.d.ts +2 -2
- package/dist/ephemeral.js +7 -7
- package/dist/git-runtime.d.ts +110 -0
- package/dist/git-runtime.js +472 -0
- package/dist/herdr-runtime.d.ts +137 -0
- package/dist/herdr-runtime.js +1228 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.js +20 -16
- package/dist/integration-git.d.ts +59 -0
- package/dist/integration-git.js +484 -0
- package/dist/launch-runtime.d.ts +54 -0
- package/dist/launch-runtime.js +195 -0
- package/dist/process.d.ts +9 -0
- package/dist/process.js +11 -0
- package/dist/runner.d.ts +356 -0
- package/dist/runner.js +2710 -0
- package/dist/schema.d.ts +2234 -0
- package/dist/schema.js +1236 -0
- package/dist/store.d.ts +50 -0
- package/dist/store.js +288 -0
- package/dist/working-evidence.d.ts +25 -0
- package/dist/working-evidence.js +261 -0
- package/docs/adr/001-composable-ephemeral-execution.md +3 -3
- package/docs/delegation-routing.html +89 -0
- package/docs/delegation-routing.svg +78 -0
- package/docs/orchestration-architecture.html +238 -0
- package/docs/orchestration-architecture.svg +189 -0
- package/docs/orchestration.md +122 -331
- package/example.png +0 -0
- package/examples/roles/implementer.md +2 -3
- package/extensions/admission.ts +66 -0
- package/extensions/config.ts +51 -37
- package/extensions/isolated.ts +638 -0
- package/extensions/result-transport.ts +13 -103
- package/extensions/role-tools.ts +11 -11
- package/extensions/subagent-command.ts +167 -0
- package/extensions/subagent.ts +264 -540
- package/extensions/task-name.ts +1 -2
- package/extensions/workflow.ts +60 -96
- package/package.json +14 -6
- package/skills/pi-subagent/SKILL.md +35 -0
package/extensions/task-name.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DISPLAY_TEXT_CONTRACT
|
|
1
|
+
import { DISPLAY_TEXT_CONTRACT } from "@henryqw/pi-subagent";
|
|
2
2
|
import { Type, type Static } from "typebox";
|
|
3
3
|
|
|
4
4
|
const TASK_NAME_MAX_LENGTH = 29 as const;
|
|
@@ -22,7 +22,6 @@ export const TaskNameSchema = Type.String({
|
|
|
22
22
|
export type TaskName = Static<typeof TaskNameSchema>;
|
|
23
23
|
|
|
24
24
|
export function normalizeTaskName(value: TaskName, path: string): TaskName {
|
|
25
|
-
if (hasDisplayControlCharacters(value)) throw new Error(`${path} must not contain C0/C1 control characters.`);
|
|
26
25
|
const normalized = value.trim();
|
|
27
26
|
if (!normalized) throw new Error(`${path} must be non-empty text.`);
|
|
28
27
|
return normalized;
|
package/extensions/workflow.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
2
2
|
import { DISPLAY_TEXT_CONTRACT } from "@henryqw/pi-subagent";
|
|
3
3
|
import { PROFILE_NAMES } from "@henryqw/pi-task-models";
|
|
4
4
|
import { Type, type Static } from "typebox";
|
|
5
|
-
import { Check } from "typebox/value";
|
|
5
|
+
import { Check, Errors } from "typebox/value";
|
|
6
|
+
import { ExecuteRequestSchema, parseExecuteRequest, type ExecuteRequest } from "../dist/schema.js";
|
|
6
7
|
import { TaskNameSchema, normalizeTaskName } from "./task-name.ts";
|
|
7
8
|
|
|
8
9
|
export const MAX_WORKFLOW_ENTRIES = 8;
|
|
@@ -11,47 +12,42 @@ const RoleSchema = Type.String({ minLength: 1, pattern: DISPLAY_TEXT_CONTRACT.pa
|
|
|
11
12
|
const TaskSchema = Type.String({ minLength: 1, description: "Bounded task packet" });
|
|
12
13
|
const ModelSchema = Type.String({ minLength: 1, pattern: DISPLAY_TEXT_CONTRACT.pattern, description: "Designated model as provider/modelId; replaces the selected route model" });
|
|
13
14
|
const ModelClassSchema = StringEnum(PROFILE_NAMES, { description: "Task model profile" });
|
|
15
|
+
const DirectKindSchema = StringEnum(["text"] as const);
|
|
14
16
|
|
|
15
17
|
export const DelegationSchema = Type.Object({
|
|
16
18
|
role: RoleSchema,
|
|
17
19
|
name: TaskNameSchema,
|
|
18
20
|
task: TaskSchema,
|
|
21
|
+
kind: Type.Optional(DirectKindSchema),
|
|
19
22
|
model: Type.Optional(ModelSchema),
|
|
20
23
|
modelClass: Type.Optional(ModelClassSchema),
|
|
21
24
|
}, { additionalProperties: false });
|
|
22
25
|
|
|
23
|
-
export const
|
|
26
|
+
export const DirectWorkflowSchema = Type.Object({
|
|
27
|
+
mode: Type.Literal("direct"),
|
|
24
28
|
role: Type.Optional(RoleSchema),
|
|
25
29
|
name: Type.Optional(TaskNameSchema),
|
|
26
30
|
task: Type.Optional(TaskSchema),
|
|
31
|
+
kind: Type.Optional(DirectKindSchema),
|
|
27
32
|
model: Type.Optional(ModelSchema),
|
|
28
33
|
modelClass: Type.Optional(ModelClassSchema),
|
|
29
|
-
tasks: Type.Optional(Type.Array(DelegationSchema, {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
description: "Dependent delegations to run sequentially",
|
|
38
|
-
})),
|
|
39
|
-
background: Type.Optional(Type.Boolean({ description: "Run the selected workflow without blocking" })),
|
|
40
|
-
}, {
|
|
41
|
-
additionalProperties: false,
|
|
42
|
-
description: "Exactly one mode: role, name, and task; tasks; or chain",
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
export type Delegation = Static<typeof DelegationSchema>;
|
|
34
|
+
tasks: Type.Optional(Type.Array(DelegationSchema, { minItems: 1, maxItems: MAX_WORKFLOW_ENTRIES })),
|
|
35
|
+
chain: Type.Optional(Type.Array(DelegationSchema, { minItems: 1, maxItems: MAX_WORKFLOW_ENTRIES })),
|
|
36
|
+
}, { additionalProperties: false, description: "Direct mode: exactly one compact single, tasks, or chain workflow" });
|
|
37
|
+
|
|
38
|
+
export const DelegateTaskSchema = Type.Union([DirectWorkflowSchema, ExecuteRequestSchema]);
|
|
39
|
+
export const WorkflowSchema = DirectWorkflowSchema;
|
|
40
|
+
|
|
41
|
+
export type Delegation = Static<typeof DelegationSchema> & { kind: "text" };
|
|
46
42
|
export type WorkflowMode = "single" | "parallel" | "chain";
|
|
47
43
|
export type ParsedWorkflow =
|
|
48
|
-
| { mode: "single";
|
|
49
|
-
| { mode: "parallel";
|
|
50
|
-
| { mode: "chain";
|
|
44
|
+
| { mode: "single"; delegations: [Delegation] }
|
|
45
|
+
| { mode: "parallel"; delegations: Delegation[] }
|
|
46
|
+
| { mode: "chain"; delegations: Delegation[] };
|
|
47
|
+
export type ParsedDelegateTask = { mode: "direct"; workflow: ParsedWorkflow } | { mode: "isolated"; request: ExecuteRequest };
|
|
51
48
|
|
|
52
|
-
type
|
|
53
|
-
|
|
54
|
-
const DELEGATION_KEYS = ["role", "name", "task", "model", "modelClass"] as const;
|
|
49
|
+
type DirectInput = Static<typeof DirectWorkflowSchema>;
|
|
50
|
+
const DELEGATION_KEYS = ["role", "name", "task", "kind", "model", "modelClass"] as const;
|
|
55
51
|
|
|
56
52
|
function text(value: string, path: string): string {
|
|
57
53
|
const normalized = value.trim();
|
|
@@ -59,17 +55,18 @@ function text(value: string, path: string): string {
|
|
|
59
55
|
return normalized;
|
|
60
56
|
}
|
|
61
57
|
|
|
62
|
-
function normalizeDelegation(value:
|
|
58
|
+
function normalizeDelegation(value: Static<typeof DelegationSchema>, path: string): Delegation {
|
|
63
59
|
return {
|
|
64
60
|
role: text(value.role, `${path}.role`),
|
|
65
61
|
name: normalizeTaskName(value.name, `${path}.name`),
|
|
66
62
|
task: text(value.task, `${path}.task`),
|
|
63
|
+
kind: "text",
|
|
67
64
|
...(value.model === undefined ? {} : { model: text(value.model, `${path}.model`) }),
|
|
68
65
|
...(value.modelClass === undefined ? {} : { modelClass: value.modelClass }),
|
|
69
66
|
};
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
function hasDelegation(value:
|
|
69
|
+
function hasDelegation(value: DirectInput): value is DirectInput & Static<typeof DelegationSchema> {
|
|
73
70
|
return Object.hasOwn(value, "role") && Object.hasOwn(value, "name") && Object.hasOwn(value, "task");
|
|
74
71
|
}
|
|
75
72
|
|
|
@@ -78,108 +75,75 @@ function workflowMode(value: unknown): WorkflowMode | undefined {
|
|
|
78
75
|
const single = DELEGATION_KEYS.some((key) => Object.hasOwn(value, key));
|
|
79
76
|
const parallel = Object.hasOwn(value, "tasks");
|
|
80
77
|
const chain = Object.hasOwn(value, "chain");
|
|
81
|
-
if (Number(single) + Number(parallel) + Number(chain) !== 1)
|
|
82
|
-
throw new Error("workflow must select exactly one mode: role, name, and task; tasks; or chain.");
|
|
83
|
-
}
|
|
78
|
+
if (Number(single) + Number(parallel) + Number(chain) !== 1) throw new Error("direct workflow must select exactly one of single, tasks, or chain.");
|
|
84
79
|
return single ? "single" : parallel ? "parallel" : "chain";
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
export function parseWorkflow(value: unknown): ParsedWorkflow {
|
|
88
83
|
const mode = workflowMode(value);
|
|
89
|
-
if (!Check(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
if (!Check(DirectWorkflowSchema, value)) {
|
|
85
|
+
const issue = Errors(DirectWorkflowSchema, value)[0];
|
|
86
|
+
throw new Error(`direct workflow must match the declared tool schema${issue ? ` at ${issue.instancePath || "/"}: ${issue.message}` : ""}. Direct mode is read-only; use mode isolated for changesets.`);
|
|
87
|
+
}
|
|
88
|
+
if (!mode) throw new Error("direct workflow must select exactly one of single, tasks, or chain.");
|
|
89
|
+
const input = value as DirectInput;
|
|
93
90
|
if (mode === "single") {
|
|
94
|
-
if (!hasDelegation(input)) throw new Error("workflow requires role, name, and task.");
|
|
95
|
-
return { mode,
|
|
91
|
+
if (!hasDelegation(input)) throw new Error("direct workflow requires role, name, and task.");
|
|
92
|
+
return { mode, delegations: [normalizeDelegation(input, "workflow")] };
|
|
96
93
|
}
|
|
97
|
-
if (mode === "parallel") return {
|
|
98
|
-
|
|
99
|
-
background,
|
|
100
|
-
delegations: input.tasks!.map((delegation, index) => normalizeDelegation(delegation, `tasks[${index}]`)),
|
|
101
|
-
};
|
|
102
|
-
return {
|
|
103
|
-
mode,
|
|
104
|
-
background,
|
|
105
|
-
delegations: input.chain!.map((delegation, index) => normalizeDelegation(delegation, `chain[${index}]`)),
|
|
106
|
-
};
|
|
94
|
+
if (mode === "parallel") return { mode, delegations: input.tasks!.map((item, index) => normalizeDelegation(item, `tasks[${index}]`)) };
|
|
95
|
+
return { mode, delegations: input.chain!.map((item, index) => normalizeDelegation(item, `chain[${index}]`)) };
|
|
107
96
|
}
|
|
108
97
|
|
|
109
|
-
export
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
};
|
|
98
|
+
export function parseDelegateTask(value: unknown): ParsedDelegateTask {
|
|
99
|
+
if (value && typeof value === "object" && !Array.isArray(value) && (value as { mode?: unknown }).mode === "isolated") {
|
|
100
|
+
return { mode: "isolated", request: parseExecuteRequest(value) };
|
|
101
|
+
}
|
|
102
|
+
return { mode: "direct", workflow: parseWorkflow(value) };
|
|
103
|
+
}
|
|
114
104
|
|
|
105
|
+
export type WorkflowEntry = { id: string; index: number; delegation: Delegation };
|
|
115
106
|
export function identifyWorkflowEntries(toolCallId: string, workflow: ParsedWorkflow): WorkflowEntry[] {
|
|
116
|
-
return workflow.delegations.map((delegation, index) => ({
|
|
117
|
-
id: `${toolCallId}:${workflow.mode}:${index}`,
|
|
118
|
-
index,
|
|
119
|
-
delegation,
|
|
120
|
-
}));
|
|
107
|
+
return workflow.delegations.map((delegation, index) => ({ id: `${toolCallId}:${workflow.mode}:${index}`, index, delegation }));
|
|
121
108
|
}
|
|
122
109
|
|
|
123
|
-
export type
|
|
124
|
-
| { ok: true; assistantOutput: string; result: T }
|
|
125
|
-
| { ok: false; result: T };
|
|
110
|
+
export type DelegationRunner = (entry: WorkflowEntry) => string | Promise<string>;
|
|
126
111
|
|
|
127
|
-
|
|
128
|
-
entry: WorkflowEntry,
|
|
129
|
-
) => DelegationExecution<T> | Promise<DelegationExecution<T>>;
|
|
130
|
-
|
|
131
|
-
export type WorkflowEntryOutcome<T> =
|
|
132
|
-
| { status: "succeeded"; entry: WorkflowEntry; assistantOutput: string; result: T }
|
|
133
|
-
| { status: "failed"; entry: WorkflowEntry; result: T }
|
|
134
|
-
| { status: "rejected"; entry: WorkflowEntry; reason: unknown };
|
|
135
|
-
|
|
136
|
-
async function runEntry<T>(entry: WorkflowEntry, run: DelegationRunner<T>): Promise<WorkflowEntryOutcome<T>> {
|
|
112
|
+
async function runEntry(entry: WorkflowEntry, run: DelegationRunner): Promise<string | undefined> {
|
|
137
113
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
: { status: "failed", entry, result: execution.result };
|
|
142
|
-
} catch (reason) {
|
|
143
|
-
return { status: "rejected", entry, reason };
|
|
114
|
+
return await run(entry);
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined;
|
|
144
117
|
}
|
|
145
118
|
}
|
|
146
119
|
|
|
147
|
-
|
|
148
|
-
export async function runForegroundWorkflow<T>(
|
|
120
|
+
export async function runForegroundWorkflow(
|
|
149
121
|
toolCallId: string,
|
|
150
122
|
workflow: ParsedWorkflow,
|
|
151
|
-
run: DelegationRunner
|
|
123
|
+
run: DelegationRunner,
|
|
152
124
|
signal?: AbortSignal,
|
|
153
|
-
): Promise<
|
|
154
|
-
if (workflow.background) throw new Error("Background workflows cannot use foreground orchestration.");
|
|
125
|
+
): Promise<void> {
|
|
155
126
|
signal?.throwIfAborted();
|
|
156
127
|
const entries = identifyWorkflowEntries(toolCallId, workflow);
|
|
157
128
|
if (workflow.mode === "single") {
|
|
158
|
-
|
|
129
|
+
await runEntry(entries[0]!, run);
|
|
159
130
|
signal?.throwIfAborted();
|
|
160
|
-
return
|
|
131
|
+
return;
|
|
161
132
|
}
|
|
162
133
|
if (workflow.mode === "parallel") {
|
|
163
|
-
|
|
134
|
+
await Promise.all(entries.map((entry) => runEntry(entry, run)));
|
|
164
135
|
signal?.throwIfAborted();
|
|
165
|
-
return
|
|
136
|
+
return;
|
|
166
137
|
}
|
|
167
|
-
|
|
168
|
-
const outcomes: WorkflowEntryOutcome<T>[] = [];
|
|
169
138
|
let previous = "";
|
|
170
139
|
for (const entry of entries) {
|
|
171
|
-
const
|
|
140
|
+
const selected = {
|
|
172
141
|
...entry,
|
|
173
|
-
delegation: {
|
|
174
|
-
...entry.delegation,
|
|
175
|
-
task: entry.delegation.task.replaceAll("{previous}", () => previous),
|
|
176
|
-
},
|
|
142
|
+
delegation: { ...entry.delegation, task: entry.delegation.task.replaceAll("{previous}", () => previous) },
|
|
177
143
|
};
|
|
178
|
-
const
|
|
144
|
+
const output = await runEntry(selected, run);
|
|
179
145
|
signal?.throwIfAborted();
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
previous = outcome.assistantOutput;
|
|
146
|
+
if (output === undefined) break;
|
|
147
|
+
previous = output;
|
|
183
148
|
}
|
|
184
|
-
return outcomes;
|
|
185
149
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-subagent",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Delegate bounded
|
|
3
|
+
"version": "22.0.1",
|
|
4
|
+
"description": "Delegate bounded direct work or durable checked isolated task graphs to Pi roles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi",
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
"extensions",
|
|
20
20
|
"docs",
|
|
21
21
|
"examples",
|
|
22
|
+
"example.png",
|
|
22
23
|
"README.md",
|
|
23
|
-
"CONTEXT.md"
|
|
24
|
+
"CONTEXT.md",
|
|
25
|
+
"skills"
|
|
24
26
|
],
|
|
25
27
|
"types": "./dist/index.d.ts",
|
|
26
28
|
"exports": {
|
|
@@ -58,17 +60,23 @@
|
|
|
58
60
|
"extensions": [
|
|
59
61
|
"./extensions/subagent.ts"
|
|
60
62
|
],
|
|
63
|
+
"skills": [
|
|
64
|
+
"./skills"
|
|
65
|
+
],
|
|
61
66
|
"image": "https://raw.githubusercontent.com/HenryQW/pi-harness/main/extensions/pi-subagent/example.png"
|
|
62
67
|
},
|
|
63
68
|
"dependencies": {
|
|
64
69
|
"@henryqw/pi-config-store": "^2.0.0",
|
|
70
|
+
"@henryqw/pi-herdr": "^0.4.7",
|
|
65
71
|
"@henryqw/pi-multi-codex": "^2.0.0",
|
|
66
|
-
"@henryqw/pi-process": "^0.
|
|
67
|
-
"@henryqw/pi-task-models": "^7.0.0"
|
|
72
|
+
"@henryqw/pi-process": "^0.2.0",
|
|
73
|
+
"@henryqw/pi-task-models": "^7.0.0",
|
|
74
|
+
"proper-lockfile": "^4.1.2"
|
|
68
75
|
},
|
|
69
76
|
"devDependencies": {
|
|
70
77
|
"@earendil-works/pi-ai": "0.87.0",
|
|
71
78
|
"@earendil-works/pi-coding-agent": "0.87.0",
|
|
72
|
-
"@earendil-works/pi-tui": "0.87.0"
|
|
79
|
+
"@earendil-works/pi-tui": "0.87.0",
|
|
80
|
+
"@types/proper-lockfile": "^4.1.4"
|
|
73
81
|
}
|
|
74
82
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-subagent
|
|
3
|
+
description: Delegate bounded direct work or run durable checked isolated task graphs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Choose and authorize delegation
|
|
7
|
+
|
|
8
|
+
Use this Skill only from Main.
|
|
9
|
+
|
|
10
|
+
Record the requested outcome, allowed scope and exclusions, local delegation/check/review/integration permission, and any external-action permission before substantial work. A clear small request can supply this authorization directly. Ask again only for a material scope or contract change, a missing decision or authorization, a user-owned conflict, or a consequential action outside that scope.
|
|
11
|
+
|
|
12
|
+
Keep trivial, mechanically verifiable work in Main. Choose the user-requested mode when one is explicit. Otherwise:
|
|
13
|
+
|
|
14
|
+
- Use `mode: direct` for bounded read-only research, analysis, and review with known read-only Roles.
|
|
15
|
+
- Use `mode: isolated` for implementation and other writing tasks, independently checked changes, or an exploratory or discardable candidate. A single isolated task is valid.
|
|
16
|
+
- Keep one implementation owner for tightly coupled work. Size alone does not justify splitting it.
|
|
17
|
+
- Never silently fall back from isolated to direct. Scope growth requires a checkpoint and replan, not migration of dirty work.
|
|
18
|
+
|
|
19
|
+
Roles describe responsibility and capabilities; they do not select isolation. Bash, MCP tools, unknown tools, and arbitrary extensions are potentially writing. The checkout coordinator reduces races among Pi-owned calls but is not an OS sandbox and cannot control external processes.
|
|
20
|
+
|
|
21
|
+
# Direct requests
|
|
22
|
+
|
|
23
|
+
Use one compact `role`/`name`/`task` packet, `tasks` for independent packets, or `chain` for dependent packets. Only text tasks are accepted; a write-capable Role, `kind: changeset`, checks, and judgment require isolated mode. The tool returns a handle after launching Herdr tabs in the current workspace. Main receives the verified result as a follow-up after the workflow finishes, without interrupting an active turn. If observation stops, open `/subagent` to inspect exact recorded tabs and session files on this session branch; recorded does not prove still running.
|
|
24
|
+
|
|
25
|
+
# Isolated requests
|
|
26
|
+
|
|
27
|
+
Give the request a unique ID and goal. Give each task a unique ID, kind, Role, model class, bounded requirements, deliverable, `dependsOn`, and `contextFrom`. Changesets require focused task checks, and a graph containing changesets requires final checks. The root must have a `package.json` test script; `pnpm test` is added to final checks if missing and runs against the combined tip before Main promotion. Text context may name only completed text tasks and preserves declared order.
|
|
28
|
+
|
|
29
|
+
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 input. There is no guaranteed post-completion editing window, and late follow-ups fail visibly.
|
|
30
|
+
|
|
31
|
+
A ready candidate does not integrate automatically. Inspect `subagent_status` and use `subagent_stage` with the exact `generation`, `taskId`, `attempt`, candidate `tip`, and `expectedTip` to select and merge candidates into the owned integration checkout in explicit order. Resolve conflicts there and verify with `resolve`; never edit Main to resolve them. For dependents, `subagent_integrate advance` runs ready tasks against the exact staged snapshot. When selected work is staged, use `subagent_integrate validate` on the exact `combinedTip` for full root checks and optional final judgment; `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.
|
|
32
|
+
|
|
33
|
+
Use `subagent_status` to inspect 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; it may finish once all are released. 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.
|
|
34
|
+
|
|
35
|
+
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.
|