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.
@@ -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
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-background-tasks",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Pi extension for durable background shell tasks, watchers, logs, and status inspection.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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; call `release_workflow` after the workflow's completion audit, or use `/workflow clear` to release ownership manually. `release_workflow` also pauses an active Goal bound to that coordinator, preserving its objective and incomplete status. Reinvoke the skill explicitly before `/goal resume`; automatic continuation cannot reacquire ownership. Unrelated ordinary Goals are unchanged. Completing an active goal also releases ownership.
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-goal",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Pi extension for goal tracking with background-aware continuation.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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" && goal.pauseReason === "interrupt";
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
- `Pi Better Goal is paused because the user pressed escape. Goal: ${goal.objective}`,
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
- const owner = workflowOwnerFromSkill(goal.command.name.slice("skill:".length), goal.command.path, registeredSkillPath);
460
- const activeOwner = getWorkflow(ctx);
461
- if (owner && (activeOwner?.name !== owner.name || activeOwner.path !== owner.path)) {
462
- return `Reinvoke /${goal.command.name} before resuming its workflow goal.`;
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 after its final handoff and completion audit. Its bound active Goal pauses; explicitly reinvoke the skill before resuming that Goal.",
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. Reinvoke /${resumeCommand} before /goal resume.` : ""}` }],
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
- export type GoalPauseReason = "interrupt" | "permission-blocker";
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. Delegate independent, sufficiently substantial work early with subagents, and use background tasks for long-running processes or repeated checks. Keep doing unblocked foreground work after launch; do not poll workers.
8
-
9
- Before the first implementation milestone, check for independent work and follow
10
- the active subagent delegation mode. Without `pi-better-subagents`, the plan uses
11
- adaptive guidance. Manual keeps planned work in the foreground unless the user or
12
- an active workflow explicitly requires delegation. Adaptive delegates substantial
13
- independent work when useful. Coordinator consults current catalog roles and
14
- delegates nontrivial role-owned work while retaining integration and final
15
- verification in the foreground.
16
-
17
- Independent foreground and delegated milestones may both be `in_progress` only
18
- when both are actually underway. Use steps for distinct deliverables, not
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 and the plan only after every relevant
21
- delegated task is terminal and its result or failure has been inspected and
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-plan",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Structured execution plans with persistent progress for Pi.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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. Start only ready steps whose prerequisites are completed; independent ready steps may run in parallel when the active delegation mode permits it.",
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
- if (workflowPlanOwner(ctx) || workflowBinding(ctx.sessionManager.getBranch())) return;
514
- if (!currentPlan) return;
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: `${event.systemPrompt}\n\n${planPrompt(currentPlan, activeDelegationMode(pi))}`,
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. Work in the foreground unless the user explicitly asks or an active workflow explicitly requires 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; perform planned work in the foreground unless explicitly required otherwise.";
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 -----------------------------------------
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-subagents",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Pi extension for detached, sandboxed subagent runs that keep the foreground session free.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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.28.1",
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.8.0",
57
- "pi-better-goal": "0.8.3",
58
- "pi-better-plan": "0.8.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.17.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"