pi-better-harness 0.28.1 → 0.29.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/extensions/execution-policy.ts +18 -0
- package/node_modules/pi-better-background-tasks/package.json +1 -1
- package/node_modules/pi-better-background-tasks/src/index.ts +2 -0
- package/node_modules/pi-better-background-tasks/src/shared-execution-policy.ts +19 -0
- package/node_modules/pi-better-background-tasks/src/tools.ts +1 -0
- package/node_modules/pi-better-goal/README.md +1 -1
- package/node_modules/pi-better-goal/package.json +1 -1
- package/node_modules/pi-better-goal/src/goal-state.ts +1 -1
- package/node_modules/pi-better-goal/src/index.ts +26 -12
- package/node_modules/pi-better-goal/src/types.ts +2 -1
- package/node_modules/pi-better-plan/REFERENCE.md +29 -15
- package/node_modules/pi-better-plan/package.json +1 -1
- package/node_modules/pi-better-plan/src/index.ts +9 -6
- package/node_modules/pi-better-plan/src/shared-execution-policy.ts +19 -0
- package/node_modules/pi-better-subagents/delegation.ts +1 -1
- package/node_modules/pi-better-subagents/index.ts +3 -1
- package/node_modules/pi-better-subagents/package.json +1 -1
- package/node_modules/pi-better-subagents/shared-execution-policy.ts +19 -0
- package/package.json +6 -6
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Harness-wide scheduling guidance, vendored so standalone extensions share it. */
|
|
2
|
+
const EXECUTION_POLICY = [
|
|
3
|
+
"Harness execution policy: concurrency is separate from subagent delegation.",
|
|
4
|
+
"Before multi-unit work, identify ready, authorized, independent units, actual prerequisites, shared mutable resources, and capacity or availability constraints. Represent independently verifiable deliverables (such as each host migration) separately in the plan; do not hide them in one serial rollout step or create dependencies merely from list order.",
|
|
5
|
+
"Start all ready, authorized, non-conflicting units concurrently within known resource limits before waiting for any one to finish. Reassess ready work after launches and results; continue unblocked foreground work instead of polling running tasks. Do not equate one background launch with scheduling all ready work.",
|
|
6
|
+
"Serialize only for a concrete dependency, conflicting writes or ownership, resource or concurrency limit, documented rollout gate, or explicit availability requirement. Record the reason in the plan or explain it to the user. Do not invent a canary or host-by-host availability requirement.",
|
|
7
|
+
"If concurrency raises an unresolved material operational trade-off (for example, draining all hosts could leave no available runners), ask the user once before the affected operations. Continue independent preparation meanwhile. Existing authorization, safety checks, workflow prerequisites, and human-approved rollout constraints remain binding; urgency does not waive them.",
|
|
8
|
+
"Choose the execution mechanism after deciding what can run together: foreground tools for small, tightly coupled, or interactive work; subagents for bounded independent reasoning or implementation when the active delegation mode permits; background tasks for independent long-running commands or waits. Manual delegation forbids proactive subagents, not concurrent foreground tools or background commands. Use available batch or parallel tool interfaces where appropriate and inspect every launch receipt for failures or skipped work.",
|
|
9
|
+
"A unit waiting or failing does not block other ready independent units unless the evidence shows a shared blocker. Preserve each unit's guards and rollback boundary. Before completing verification, the plan, or the goal, inspect and integrate every relevant terminal result or failure; launch is not completion.",
|
|
10
|
+
].join("\n");
|
|
11
|
+
|
|
12
|
+
export function appendExecutionPolicy(systemPrompt: string): string {
|
|
13
|
+
// Inspect the prompt, not module-global state: vendored copies have separate
|
|
14
|
+
// module identities, and every fresh turn or reload needs the policy again.
|
|
15
|
+
return systemPrompt.includes(EXECUTION_POLICY)
|
|
16
|
+
? systemPrompt
|
|
17
|
+
: `${systemPrompt}\n\n${EXECUTION_POLICY}`;
|
|
18
|
+
}
|
|
@@ -5,8 +5,10 @@ import { clearBackgroundTasksNavigatorSession, ensureBackgroundTasksNavigator, e
|
|
|
5
5
|
import { resumeScheduledWork, suspendScheduledWork } from "./runtime.js";
|
|
6
6
|
import { observeForegroundSandboxPolicy } from "./sandbox.js";
|
|
7
7
|
import { registerTools } from "./tools.js";
|
|
8
|
+
import { appendExecutionPolicy } from "./shared-execution-policy.js";
|
|
8
9
|
|
|
9
10
|
export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
11
|
+
pi.on("before_agent_start", (event) => ({ systemPrompt: appendExecutionPolicy(event.systemPrompt) }));
|
|
10
12
|
registerBackgroundTasksGoalProvider(pi);
|
|
11
13
|
ensureBackgroundTasksNavigatorProvider(pi);
|
|
12
14
|
// Subscribed at load so a sandbox extension that publishes later is heard, and
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Generated from packages/pi-better-harness/extensions/execution-policy.ts. Do not edit directly.
|
|
2
|
+
/** Harness-wide scheduling guidance, vendored so standalone extensions share it. */
|
|
3
|
+
const EXECUTION_POLICY = [
|
|
4
|
+
"Harness execution policy: concurrency is separate from subagent delegation.",
|
|
5
|
+
"Before multi-unit work, identify ready, authorized, independent units, actual prerequisites, shared mutable resources, and capacity or availability constraints. Represent independently verifiable deliverables (such as each host migration) separately in the plan; do not hide them in one serial rollout step or create dependencies merely from list order.",
|
|
6
|
+
"Start all ready, authorized, non-conflicting units concurrently within known resource limits before waiting for any one to finish. Reassess ready work after launches and results; continue unblocked foreground work instead of polling running tasks. Do not equate one background launch with scheduling all ready work.",
|
|
7
|
+
"Serialize only for a concrete dependency, conflicting writes or ownership, resource or concurrency limit, documented rollout gate, or explicit availability requirement. Record the reason in the plan or explain it to the user. Do not invent a canary or host-by-host availability requirement.",
|
|
8
|
+
"If concurrency raises an unresolved material operational trade-off (for example, draining all hosts could leave no available runners), ask the user once before the affected operations. Continue independent preparation meanwhile. Existing authorization, safety checks, workflow prerequisites, and human-approved rollout constraints remain binding; urgency does not waive them.",
|
|
9
|
+
"Choose the execution mechanism after deciding what can run together: foreground tools for small, tightly coupled, or interactive work; subagents for bounded independent reasoning or implementation when the active delegation mode permits; background tasks for independent long-running commands or waits. Manual delegation forbids proactive subagents, not concurrent foreground tools or background commands. Use available batch or parallel tool interfaces where appropriate and inspect every launch receipt for failures or skipped work.",
|
|
10
|
+
"A unit waiting or failing does not block other ready independent units unless the evidence shows a shared blocker. Preserve each unit's guards and rollback boundary. Before completing verification, the plan, or the goal, inspect and integrate every relevant terminal result or failure; launch is not completion.",
|
|
11
|
+
].join("\n");
|
|
12
|
+
|
|
13
|
+
export function appendExecutionPolicy(systemPrompt: string): string {
|
|
14
|
+
// Inspect the prompt, not module-global state: vendored copies have separate
|
|
15
|
+
// module identities, and every fresh turn or reload needs the policy again.
|
|
16
|
+
return systemPrompt.includes(EXECUTION_POLICY)
|
|
17
|
+
? systemPrompt
|
|
18
|
+
: `${systemPrompt}\n\n${EXECUTION_POLICY}`;
|
|
19
|
+
}
|
|
@@ -184,6 +184,7 @@ const StatusActionParams = Type.Object({
|
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
const BACKGROUND_ORCHESTRATION_GUIDELINES = [
|
|
187
|
+
"Follow the Harness execution policy when scheduling independent background operations; subagent delegation mode does not require background commands to run serially.",
|
|
187
188
|
"Use background tasks for genuinely long-running processes or repeated checks. Run short commands in the foreground.",
|
|
188
189
|
"When a structured plan is active, keep it as the coordinator ledger: launch relevant background work early, continue unblocked foreground work without polling, and update the plan after inspecting each terminal result or failure.",
|
|
189
190
|
"Do not treat launch as completion of the parent milestone; relevant background work must be terminal, inspected, and integrated before verification or completion.",
|
|
@@ -44,7 +44,7 @@ metadata:
|
|
|
44
44
|
workflow-role: coordinator
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Invoke the skill with Pi's `/skill:name` command, or supervise it with `/goal /skill:name task`. The goal extension resolves the command against Pi's registry, persists its source, and expands the skill on kickoff and continuation (including after session resume). A bound command that disappears or changes source pauses the goal. `/goal /template task` also re-expands prompt templates on continuation; `/goal /extension-command task` dispatches the extension command once, then continues with ordinary goal supervision to avoid repeating side effects. Plain-language goals work as before. This command binding requires Pi 0.84.4 or later. Legacy slash-shaped goals without a binding pause on resume rather than running without the skill. `/workflow` shows a coordinator skill owner
|
|
47
|
+
Invoke the skill with Pi's `/skill:name` command, or supervise it with `/goal /skill:name task`. The goal extension resolves the command against Pi's registry, persists its source, and expands the skill on kickoff and continuation (including after session resume). A bound command that disappears or changes source pauses the goal. `/goal /template task` also re-expands prompt templates on continuation; `/goal /extension-command task` dispatches the extension command once, then continues with ordinary goal supervision to avoid repeating side effects. Plain-language goals work as before. This command binding requires Pi 0.84.4 or later. Legacy slash-shaped goals without a binding pause on resume rather than running without the skill. `/workflow` shows a coordinator skill owner. When the workflow is done, complete the goal (`update_goal`), which also releases ownership. When it hands back unfinished work, call `release_workflow`, or use `/workflow clear` to release ownership manually. `release_workflow` also pauses an active Goal bound to that coordinator as a handback, preserving its objective and incomplete status. Resuming that Goal (`/goal resume`, `alt+g`, or, with conversational resume on, the user's go-ahead such as "continue") rebinds the coordinator it was created with, provided the skill is still registered at its original source; automatic continuation and synthetic input never reacquire ownership. Unrelated ordinary Goals are unchanged.
|
|
48
48
|
|
|
49
49
|
A skill that is only an alias of a coordinator declares the target instead of a role:
|
|
50
50
|
|
|
@@ -66,7 +66,7 @@ function isGoalStatus(value: unknown): value is GoalStatus {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function isPauseReason(value: unknown): value is GoalPauseReason {
|
|
69
|
-
return value === "interrupt" || value === "permission-blocker";
|
|
69
|
+
return value === "interrupt" || value === "permission-blocker" || value === "handback";
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function isGoalLike(value: unknown): value is GoalLike {
|
|
@@ -87,7 +87,8 @@ const GOAL_RESUME_RULE =
|
|
|
87
87
|
|
|
88
88
|
/** True when the agent may resume this goal with `goal_resume`. */
|
|
89
89
|
export function agentResumable(goal: GoalSnapshot | null, conversationalResume = true): boolean {
|
|
90
|
-
return conversationalResume && goal?.status === "paused" &&
|
|
90
|
+
return conversationalResume && goal?.status === "paused" &&
|
|
91
|
+
(goal.pauseReason === "interrupt" || goal.pauseReason === "handback");
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
/** Footer status for a paused goal, telling the user how to resume it. */
|
|
@@ -98,7 +99,9 @@ export function pausedGoalStatus(goal: GoalSnapshot, conversationalResume = true
|
|
|
98
99
|
|
|
99
100
|
function pausedGoalPrompt(goal: GoalSnapshot, conversationalResume: boolean): string {
|
|
100
101
|
return [
|
|
101
|
-
|
|
102
|
+
goal.pauseReason === "handback"
|
|
103
|
+
? `Pi Better Goal is paused because its workflow handed back unfinished work for the user's answer. Goal: ${goal.objective}`
|
|
104
|
+
: `Pi Better Goal is paused because the user pressed escape. Goal: ${goal.objective}`,
|
|
102
105
|
"Treat the user's messages as ordinary conversation: answer them, but do not continue the goal's work until it is resumed.",
|
|
103
106
|
conversationalResume ? GOAL_RESUME_RULE : "Only the user can resume this goal, with /goal resume or alt+g. A conversational go-ahead does not resume it.",
|
|
104
107
|
].join("\n");
|
|
@@ -456,15 +459,21 @@ export default function (pi: ExtensionAPI): void {
|
|
|
456
459
|
return `Cannot resume: /${goal.command.name} is no longer registered at its original source.`;
|
|
457
460
|
}
|
|
458
461
|
if (goal.command?.source === "skill") {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return `Reinvoke /${goal.command.name}
|
|
462
|
+
try {
|
|
463
|
+
goalWorkflowOwner(goal);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
return `Cannot resume: ${error instanceof Error ? error.message : String(error)} Reinvoke /${goal.command.name}.`;
|
|
463
466
|
}
|
|
464
467
|
}
|
|
465
468
|
return null;
|
|
466
469
|
};
|
|
467
470
|
|
|
471
|
+
/** The workflow a skill-bound goal owns, re-resolved from its registered skill; null for other goals. */
|
|
472
|
+
const goalWorkflowOwner = (goal: GoalSnapshot): ReturnType<typeof currentWorkflowOwner> =>
|
|
473
|
+
goal.command?.source === "skill"
|
|
474
|
+
? workflowOwnerFromSkill(goal.command.name.slice("skill:".length), goal.command.path, registeredSkillPath)
|
|
475
|
+
: null;
|
|
476
|
+
|
|
468
477
|
/**
|
|
469
478
|
* The one resume path shared by `/goal resume`, the hotkey, and `goal_resume`:
|
|
470
479
|
* reactivate a paused goal or reopen an active no-progress hold.
|
|
@@ -498,6 +507,11 @@ export default function (pi: ExtensionAPI): void {
|
|
|
498
507
|
}
|
|
499
508
|
pi.appendEntry(EXTENSION_NAME, permissionRecord(current.goalId, "permission-release"));
|
|
500
509
|
}
|
|
510
|
+
// Resuming a skill-bound goal is explicit re-entry into its workflow: rebind the owner the
|
|
511
|
+
// goal was created with (released by a handback) before the continuation runs.
|
|
512
|
+
const owner = goalWorkflowOwner(current);
|
|
513
|
+
const activeOwner = getWorkflow(ctx);
|
|
514
|
+
if (owner && (activeOwner?.name !== owner.name || activeOwner.path !== owner.path)) recordWorkflow(owner);
|
|
501
515
|
setGoal(goal, ctx, source);
|
|
502
516
|
queueGoalContinuation(goal, ctx);
|
|
503
517
|
return { ok: true, goal };
|
|
@@ -1049,8 +1063,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1049
1063
|
name: GOAL_RESUME_TOOL,
|
|
1050
1064
|
label: "Resume Goal",
|
|
1051
1065
|
description:
|
|
1052
|
-
"Resume the goal the user paused with escape, exactly like /goal resume. " + GOAL_RESUME_RULE,
|
|
1053
|
-
promptSnippet: "Resume the escape-paused goal, only on the user's clear go-ahead.",
|
|
1066
|
+
"Resume the goal the user paused with escape, or one its workflow handed back, exactly like /goal resume. " + GOAL_RESUME_RULE,
|
|
1067
|
+
promptSnippet: "Resume the escape-paused or handed-back goal, only on the user's clear go-ahead.",
|
|
1054
1068
|
parameters: Type.Object({
|
|
1055
1069
|
reason: Type.Optional(Type.String({ description: "Short quote or summary of the user's go-ahead." })),
|
|
1056
1070
|
}),
|
|
@@ -1115,7 +1129,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1115
1129
|
pi.registerTool({
|
|
1116
1130
|
name: "release_workflow",
|
|
1117
1131
|
label: "Release Workflow",
|
|
1118
|
-
description: "Release the active skill-owned workflow
|
|
1132
|
+
description: "Release the active skill-owned workflow when it hands back unfinished work (a blocked unit awaiting the human). Its bound active Goal pauses; /goal resume rebinds the workflow. When the workflow is done, call update_goal complete instead, which also releases it.",
|
|
1119
1133
|
parameters: Type.Object({}),
|
|
1120
1134
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
1121
1135
|
const owner = getWorkflow(ctx);
|
|
@@ -1125,13 +1139,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1125
1139
|
if (goal?.status === "active" && goal.command?.source === "skill") {
|
|
1126
1140
|
const boundOwner = workflowOwnerFromSkill(goal.command.name.slice("skill:".length), goal.command.path, registeredSkillPath);
|
|
1127
1141
|
if (boundOwner?.name === owner.name && boundOwner.path === owner.path) {
|
|
1128
|
-
setGoal(goalWithStatus(goal, "paused"), ctx, "tool");
|
|
1142
|
+
setGoal(goalWithStatus(goal, "paused", undefined, "handback"), ctx, "tool");
|
|
1129
1143
|
resumeCommand = goal.command.name;
|
|
1130
1144
|
}
|
|
1131
1145
|
}
|
|
1132
1146
|
recordWorkflow(null);
|
|
1133
1147
|
return {
|
|
1134
|
-
content: [{ type: "text", text: `Released ${owner.name} workflow ownership.${resumeCommand ? ` Its Goal is paused, not complete
|
|
1148
|
+
content: [{ type: "text", text: `Released ${owner.name} workflow ownership.${resumeCommand ? ` Its Goal is paused, not complete; the user's go-ahead or /goal resume rebinds /${resumeCommand}.` : ""}` }],
|
|
1135
1149
|
details: { released: true, owner: owner.name, goalPaused: resumeCommand !== undefined },
|
|
1136
1150
|
};
|
|
1137
1151
|
},
|
|
@@ -1261,7 +1275,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1261
1275
|
if (permissionPrompt && goal?.status === "paused") {
|
|
1262
1276
|
return { systemPrompt: `${event.systemPrompt}\n\n${permissionPrompt}` };
|
|
1263
1277
|
}
|
|
1264
|
-
const pausedInstruction = permissionPrompt || (goal?.status === "paused" && goal.pauseReason === "interrupt"
|
|
1278
|
+
const pausedInstruction = permissionPrompt || (goal?.status === "paused" && (goal.pauseReason === "interrupt" || goal.pauseReason === "handback")
|
|
1265
1279
|
? pausedGoalPrompt(goal, preferences.conversationalResume) : "");
|
|
1266
1280
|
if (!isPokeable(goal) && !owner) {
|
|
1267
1281
|
return pausedInstruction ? { systemPrompt: `${event.systemPrompt}\n\n${pausedInstruction}` } : undefined;
|
|
@@ -75,7 +75,8 @@ export type GoalStatus = "active" | "paused" | "budgetLimited" | "complete";
|
|
|
75
75
|
* requires those human controls, and releases just one same-scope retry while
|
|
76
76
|
* keeping its separate blocker/release history.
|
|
77
77
|
*/
|
|
78
|
-
|
|
78
|
+
/** Why a goal paused: escape, a permission hold, or a workflow handing back unfinished work for the user's answer. */
|
|
79
|
+
export type GoalPauseReason = "interrupt" | "permission-blocker" | "handback";
|
|
79
80
|
|
|
80
81
|
export interface GoalUsage {
|
|
81
82
|
tokensUsed: number;
|
|
@@ -4,22 +4,36 @@ Technical details for workflow authors and contributors. For installation, scree
|
|
|
4
4
|
|
|
5
5
|
## Coordinating Delegated Work
|
|
6
6
|
|
|
7
|
-
Use the plan as the foreground coordinator's milestone ledger.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
Use the plan as the foreground coordinator's milestone ledger. Before the first
|
|
8
|
+
implementation milestone, identify ready, authorized independent units, including
|
|
9
|
+
separate per-host migrations, and start them concurrently within resource
|
|
10
|
+
constraints before waiting. Serialize only for real dependencies, shared writes,
|
|
11
|
+
resource or capacity limits, or explicit rollout or availability requirements;
|
|
12
|
+
record the reason in the plan.
|
|
13
|
+
|
|
14
|
+
Execution concurrency is separate from subagent delegation. Use foreground tools
|
|
15
|
+
for small, tightly coupled, or interactive work, and background tasks for
|
|
16
|
+
independent long-running commands or waits. Follow the active subagent delegation
|
|
17
|
+
mode; without `pi-better-subagents`, the plan uses adaptive guidance. Manual
|
|
18
|
+
forbids proactive subagents, not parallel foreground or background execution;
|
|
19
|
+
an explicit user request or active workflow requirement may still authorize
|
|
20
|
+
delegation. Adaptive delegates substantial independent work when useful.
|
|
21
|
+
Coordinator consults current catalog roles and delegates nontrivial role-owned
|
|
22
|
+
work while retaining integration and final verification in the foreground.
|
|
23
|
+
Continue unblocked foreground work after launch; do not poll workers.
|
|
24
|
+
|
|
25
|
+
Ask once about an unresolved material operational availability tradeoff while
|
|
26
|
+
independent preparation continues. A host-local failure does not block other
|
|
27
|
+
independent hosts unless there is evidence of a shared cause. Safety,
|
|
28
|
+
authorization, and workflow prerequisites remain unchanged. This policy adds no
|
|
29
|
+
auto-launcher, scheduler, or reminder loops.
|
|
30
|
+
|
|
31
|
+
Distinct ready milestones may be `in_progress` concurrently only when their work
|
|
32
|
+
is actually underway. Use separate steps for per-host deliverables, not
|
|
19
33
|
individual worker processes; worker tools and the background-work navigator own
|
|
20
|
-
run status. Complete verification
|
|
21
|
-
|
|
22
|
-
integrated.
|
|
34
|
+
run status. Complete verification, the plan, or the active goal only after every
|
|
35
|
+
relevant subagent and background task is terminal and its result or failure has
|
|
36
|
+
been inspected and integrated.
|
|
23
37
|
|
|
24
38
|
For a DAG, assign stable ids to prerequisite steps and list those ids in dependent steps' `dependsOn`. Dependencies must exist in the same plan; cycles and starting or completing a step before its prerequisites are complete are rejected. `get_plan` reports pending steps whose prerequisites are complete as ready. Plans without edges keep their existing behavior.
|
|
25
39
|
|
|
@@ -2,6 +2,7 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { Text, type AutocompleteItem } from "@earendil-works/pi-tui";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
|
+
import { appendExecutionPolicy } from "./shared-execution-policy.js";
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
8
|
completedPlanClearDelay,
|
|
@@ -342,8 +343,8 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
342
343
|
promptGuidelines: [
|
|
343
344
|
"Use update_plan for work with three or more meaningful steps unless a skill owns planning; update it immediately when a step completes, becomes blocked, or scope changes.",
|
|
344
345
|
"For generic plans only, mark distinct milestones in_progress when work is actually underway. Do not mark a delegated step completed until its result or failure has been inspected and integrated.",
|
|
345
|
-
"For generic plans with prerequisites, give steps stable ids and dependsOn edges
|
|
346
|
-
"For generic plans, follow the active delegation mode. Manual forbids proactive delegation even in plan mode; adaptive favors substantial independent work; coordinator delegates nontrivial role-owned tasks after agents_catalog discovery.",
|
|
346
|
+
"For generic plans with prerequisites, give steps stable ids and dependsOn edges for actual dependencies, not list order. Start ready independent steps concurrently under the Harness execution policy; subagent delegation mode controls subagents, not background-command concurrency.",
|
|
347
|
+
"For generic plans, follow the active delegation mode for subagents. Manual forbids proactive subagent delegation even in plan mode; adaptive favors substantial independent work; coordinator delegates nontrivial role-owned tasks after agents_catalog discovery.",
|
|
347
348
|
"Use a generic plan as a milestone ledger only when no workflow owns planning. Otherwise follow the workflow's task plan and foreground role.",
|
|
348
349
|
"For generic plans, use separate steps for distinct deliverables, not one step per worker process. Before completing verification or the plan, inspect and integrate every relevant delegated result or failure.",
|
|
349
350
|
"While a workflow owns planning, bind its persisted task plan with update_plan({workflow:{path,revision}}). Then record every transition with update_plan's workflow event and changes instead of editing task-plan.json or its profiling log; it saves the rows, the next planRevision, and the matching profiling event together. Path and revision can accompany the first transition to bind and update in one call.",
|
|
@@ -510,10 +511,12 @@ export default function planExtension(pi: ExtensionAPI): void {
|
|
|
510
511
|
restore(ctx);
|
|
511
512
|
});
|
|
512
513
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
513
|
-
|
|
514
|
-
if (!currentPlan)
|
|
514
|
+
const systemPrompt = appendExecutionPolicy(event.systemPrompt);
|
|
515
|
+
if (workflowPlanOwner(ctx) || workflowBinding(ctx.sessionManager.getBranch()) || !currentPlan) {
|
|
516
|
+
return systemPrompt === event.systemPrompt ? undefined : { systemPrompt };
|
|
517
|
+
}
|
|
515
518
|
return {
|
|
516
|
-
systemPrompt: `${
|
|
519
|
+
systemPrompt: `${systemPrompt}\n\n${planPrompt(currentPlan, activeDelegationMode(pi))}`,
|
|
517
520
|
};
|
|
518
521
|
});
|
|
519
522
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
@@ -555,7 +558,7 @@ function formatPlan(plan: PlanSnapshot): string {
|
|
|
555
558
|
|
|
556
559
|
function planPrompt(plan: PlanSnapshot, mode: DelegationMode): string {
|
|
557
560
|
const delegation = mode === "manual"
|
|
558
|
-
? "Manual delegation mode: a plan does not authorize proactive delegation.
|
|
561
|
+
? "Manual delegation mode: a plan does not authorize proactive delegation to subagents. Independent foreground and background operations still follow the Harness execution policy. Use subagents only when the user explicitly asks or an active workflow explicitly requires delegation."
|
|
559
562
|
: mode === "coordinator"
|
|
560
563
|
? "Coordinator delegation mode: consult agents_catalog and delegate nontrivial role-owned milestones according to current role descriptions. Keep orchestration, cross-role decisions, unowned or ambiguous work, integration, and final verification in the foreground."
|
|
561
564
|
: "Adaptive delegation mode: identify substantial independent work before implementation. Delegate a bounded task when useful and available while continuing unblocked foreground work; keep tightly coupled work in the foreground.";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Generated from packages/pi-better-harness/extensions/execution-policy.ts. Do not edit directly.
|
|
2
|
+
/** Harness-wide scheduling guidance, vendored so standalone extensions share it. */
|
|
3
|
+
const EXECUTION_POLICY = [
|
|
4
|
+
"Harness execution policy: concurrency is separate from subagent delegation.",
|
|
5
|
+
"Before multi-unit work, identify ready, authorized, independent units, actual prerequisites, shared mutable resources, and capacity or availability constraints. Represent independently verifiable deliverables (such as each host migration) separately in the plan; do not hide them in one serial rollout step or create dependencies merely from list order.",
|
|
6
|
+
"Start all ready, authorized, non-conflicting units concurrently within known resource limits before waiting for any one to finish. Reassess ready work after launches and results; continue unblocked foreground work instead of polling running tasks. Do not equate one background launch with scheduling all ready work.",
|
|
7
|
+
"Serialize only for a concrete dependency, conflicting writes or ownership, resource or concurrency limit, documented rollout gate, or explicit availability requirement. Record the reason in the plan or explain it to the user. Do not invent a canary or host-by-host availability requirement.",
|
|
8
|
+
"If concurrency raises an unresolved material operational trade-off (for example, draining all hosts could leave no available runners), ask the user once before the affected operations. Continue independent preparation meanwhile. Existing authorization, safety checks, workflow prerequisites, and human-approved rollout constraints remain binding; urgency does not waive them.",
|
|
9
|
+
"Choose the execution mechanism after deciding what can run together: foreground tools for small, tightly coupled, or interactive work; subagents for bounded independent reasoning or implementation when the active delegation mode permits; background tasks for independent long-running commands or waits. Manual delegation forbids proactive subagents, not concurrent foreground tools or background commands. Use available batch or parallel tool interfaces where appropriate and inspect every launch receipt for failures or skipped work.",
|
|
10
|
+
"A unit waiting or failing does not block other ready independent units unless the evidence shows a shared blocker. Preserve each unit's guards and rollback boundary. Before completing verification, the plan, or the goal, inspect and integrate every relevant terminal result or failure; launch is not completion.",
|
|
11
|
+
].join("\n");
|
|
12
|
+
|
|
13
|
+
export function appendExecutionPolicy(systemPrompt: string): string {
|
|
14
|
+
// Inspect the prompt, not module-global state: vendored copies have separate
|
|
15
|
+
// module identities, and every fresh turn or reload needs the policy again.
|
|
16
|
+
return systemPrompt.includes(EXECUTION_POLICY)
|
|
17
|
+
? systemPrompt
|
|
18
|
+
: `${systemPrompt}\n\n${EXECUTION_POLICY}`;
|
|
19
|
+
}
|
|
@@ -15,7 +15,7 @@ export function normalizeDelegationMode(value: unknown): DelegationMode {
|
|
|
15
15
|
export function delegationPrompt(mode: DelegationMode): string {
|
|
16
16
|
switch (mode) {
|
|
17
17
|
case "manual":
|
|
18
|
-
return "Delegation mode: manual. Do not proactively delegate work. Use subagents only when the user explicitly asks for delegation or an active workflow explicitly requires it. A structured plan or plan mode does not override this restriction
|
|
18
|
+
return "Delegation mode: manual. Do not proactively delegate work to subagents. Use subagents only when the user explicitly asks for delegation or an active workflow explicitly requires it. A structured plan or plan mode does not override this restriction. This restricts subagent delegation, not execution concurrency: independent foreground tools and background tasks still follow the Harness execution policy.";
|
|
19
19
|
case "coordinator":
|
|
20
20
|
return "Delegation mode: coordinator. The foreground coordinates, integrates, and verifies. Call agents_catalog to discover current available roles and inspect their descriptions. Pass a role to subagent_spawn by its short name, as listed (role: \"developer\"). Delegate every nontrivial task covered by an available role according to its current description; do not assume bundled roles are unchanged. Keep orchestration, cross-role decisions, unowned or ambiguous work, integration, and final verification in the foreground. Inspect results and failures before concluding; do not poll for running children.";
|
|
21
21
|
case "adaptive":
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
} from "./timing.ts";
|
|
55
55
|
import { readAppendedLines, type LogCursor } from "./log-cursor.ts";
|
|
56
56
|
import { DELEGATION_MODES, DELEGATION_MODE_REQUEST, delegationPrompt, isDelegationMode, normalizeDelegationMode, type DelegationMode } from "./delegation.ts";
|
|
57
|
+
import { appendExecutionPolicy } from "./shared-execution-policy.ts";
|
|
57
58
|
import { resolveExtensions, extensionArgs } from "./extensions.ts";
|
|
58
59
|
import { prepareTaskRuntime } from "./task-policy.ts";
|
|
59
60
|
import { canonicalizePath, takeRecoverySnapshot } from "./shared-sandbox-core.ts";
|
|
@@ -203,6 +204,7 @@ const CATALOG_GUIDELINES = [
|
|
|
203
204
|
];
|
|
204
205
|
|
|
205
206
|
const SUBAGENT_ORCHESTRATION_GUIDELINES = [
|
|
207
|
+
"Follow the Harness execution policy for concurrency; the active delegation mode determines whether to use subagents, not whether independent background commands can overlap.",
|
|
206
208
|
"When a structured plan is active, follow the current delegation mode. Track delegated and foreground deliverables only when delegation is permitted and actually underway; continue unblocked foreground work and integrate results before marking work complete.",
|
|
207
209
|
"Do not treat launching a subagent as completion of the parent milestone; relevant terminal results must be inspected and integrated before verification or completion.",
|
|
208
210
|
];
|
|
@@ -2352,7 +2354,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
2352
2354
|
pi.registerTool(discoveryTool as Parameters<ExtensionAPI["registerTool"]>[0]);
|
|
2353
2355
|
|
|
2354
2356
|
pi.on("before_agent_start", (event) => ({
|
|
2355
|
-
systemPrompt: `${event.systemPrompt}\n\n${delegationPrompt(activeDelegationMode())}`,
|
|
2357
|
+
systemPrompt: `${appendExecutionPolicy(event.systemPrompt)}\n\n${delegationPrompt(activeDelegationMode())}`,
|
|
2356
2358
|
}));
|
|
2357
2359
|
|
|
2358
2360
|
// ---- live-status lifecycle -----------------------------------------
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Generated from packages/pi-better-harness/extensions/execution-policy.ts. Do not edit directly.
|
|
2
|
+
/** Harness-wide scheduling guidance, vendored so standalone extensions share it. */
|
|
3
|
+
const EXECUTION_POLICY = [
|
|
4
|
+
"Harness execution policy: concurrency is separate from subagent delegation.",
|
|
5
|
+
"Before multi-unit work, identify ready, authorized, independent units, actual prerequisites, shared mutable resources, and capacity or availability constraints. Represent independently verifiable deliverables (such as each host migration) separately in the plan; do not hide them in one serial rollout step or create dependencies merely from list order.",
|
|
6
|
+
"Start all ready, authorized, non-conflicting units concurrently within known resource limits before waiting for any one to finish. Reassess ready work after launches and results; continue unblocked foreground work instead of polling running tasks. Do not equate one background launch with scheduling all ready work.",
|
|
7
|
+
"Serialize only for a concrete dependency, conflicting writes or ownership, resource or concurrency limit, documented rollout gate, or explicit availability requirement. Record the reason in the plan or explain it to the user. Do not invent a canary or host-by-host availability requirement.",
|
|
8
|
+
"If concurrency raises an unresolved material operational trade-off (for example, draining all hosts could leave no available runners), ask the user once before the affected operations. Continue independent preparation meanwhile. Existing authorization, safety checks, workflow prerequisites, and human-approved rollout constraints remain binding; urgency does not waive them.",
|
|
9
|
+
"Choose the execution mechanism after deciding what can run together: foreground tools for small, tightly coupled, or interactive work; subagents for bounded independent reasoning or implementation when the active delegation mode permits; background tasks for independent long-running commands or waits. Manual delegation forbids proactive subagents, not concurrent foreground tools or background commands. Use available batch or parallel tool interfaces where appropriate and inspect every launch receipt for failures or skipped work.",
|
|
10
|
+
"A unit waiting or failing does not block other ready independent units unless the evidence shows a shared blocker. Preserve each unit's guards and rollback boundary. Before completing verification, the plan, or the goal, inspect and integrate every relevant terminal result or failure; launch is not completion.",
|
|
11
|
+
].join("\n");
|
|
12
|
+
|
|
13
|
+
export function appendExecutionPolicy(systemPrompt: string): string {
|
|
14
|
+
// Inspect the prompt, not module-global state: vendored copies have separate
|
|
15
|
+
// module identities, and every fresh turn or reload needs the policy again.
|
|
16
|
+
return systemPrompt.includes(EXECUTION_POLICY)
|
|
17
|
+
? systemPrompt
|
|
18
|
+
: `${systemPrompt}\n\n${EXECUTION_POLICY}`;
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Pi extension bundle for a write sandbox, subagents, background tasks, SSH, goals, and structured plans.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,16 +49,16 @@
|
|
|
49
49
|
],
|
|
50
50
|
"scripts": {
|
|
51
51
|
"prepack": "node ../../scripts/sync-shared-log-utils.mjs && node ../../scripts/stage-harness-dependencies.mjs",
|
|
52
|
-
"typecheck": "tsc --noEmit --allowImportingTsExtensions --module NodeNext --moduleResolution NodeNext --target ES2022 --strict --skipLibCheck --types node extensions/minimal-output/index.ts extensions/minimal-output/hook.ts extensions/settings/index.ts",
|
|
52
|
+
"typecheck": "tsc --noEmit --allowImportingTsExtensions --module NodeNext --moduleResolution NodeNext --target ES2022 --strict --skipLibCheck --types node extensions/minimal-output/index.ts extensions/minimal-output/hook.ts extensions/settings/index.ts extensions/execution-policy.ts",
|
|
53
53
|
"test": "node --test test/*.test.mjs"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"pi-better-background-tasks": "0.
|
|
57
|
-
"pi-better-goal": "0.8.
|
|
58
|
-
"pi-better-plan": "0.
|
|
56
|
+
"pi-better-background-tasks": "0.9.0",
|
|
57
|
+
"pi-better-goal": "0.8.4",
|
|
58
|
+
"pi-better-plan": "0.9.0",
|
|
59
59
|
"pi-better-sandbox": "0.12.0",
|
|
60
60
|
"pi-better-ssh": "0.1.2",
|
|
61
|
-
"pi-better-subagents": "0.
|
|
61
|
+
"pi-better-subagents": "0.18.0",
|
|
62
62
|
"proper-lockfile": "^4.1.2",
|
|
63
63
|
"smol-toml": "1.9.0",
|
|
64
64
|
"yaml": "^2.9.1"
|