@narumitw/pi-subagents 0.53.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +85 -15
  2. package/package.json +1 -1
  3. package/src/agents/built-ins.ts +124 -0
  4. package/src/agents/catalog.ts +224 -0
  5. package/src/agents/discovery.ts +249 -0
  6. package/src/agents/types.ts +98 -0
  7. package/src/agents.ts +47 -670
  8. package/src/auto-transport.ts +2 -1
  9. package/src/automation.ts +7 -2
  10. package/src/capability-router.ts +1 -1
  11. package/src/completion-delivery.ts +82 -11
  12. package/src/config-status.ts +2 -2
  13. package/src/config-ui.ts +8 -8
  14. package/src/consult-resources.ts +1 -1
  15. package/src/consult.ts +9 -7
  16. package/src/create-stateful-transport.ts +2 -1
  17. package/src/cwd-policy.ts +1 -1
  18. package/src/execution/budget.ts +56 -0
  19. package/src/execution/runtime-policy.ts +19 -0
  20. package/src/execution-plan.ts +1 -1
  21. package/src/execution-profiles.ts +1 -1
  22. package/src/execution-ui.ts +1 -1
  23. package/src/execution.ts +269 -100
  24. package/src/in-process-transport.ts +3 -2
  25. package/src/inspect.ts +39 -8
  26. package/src/limits.ts +1 -0
  27. package/src/orchestration-metrics.ts +12 -5
  28. package/src/panel-execution.ts +1 -1
  29. package/src/panel-planning.ts +1 -1
  30. package/src/params.ts +3 -1
  31. package/src/persistence.ts +106 -7
  32. package/src/registry-types.ts +22 -5
  33. package/src/registry.ts +205 -37
  34. package/src/render.ts +1 -1
  35. package/src/retained-semantic-state.ts +1 -1
  36. package/src/rpc-transport-metadata.ts +1 -1
  37. package/src/rpc-transport.ts +2 -1
  38. package/src/runner.ts +6 -1
  39. package/src/settings/inspection.ts +275 -0
  40. package/src/settings/schema.ts +186 -0
  41. package/src/settings.ts +72 -420
  42. package/src/spawn-idempotency.ts +1 -1
  43. package/src/stateful-agent-view.ts +87 -0
  44. package/src/stateful-config.ts +1 -1
  45. package/src/stateful-guidance.ts +2 -2
  46. package/src/stateful-limits.ts +1 -1
  47. package/src/stateful-prompt.ts +2 -2
  48. package/src/stateful-render.ts +0 -1
  49. package/src/stateful-safety.ts +2 -1
  50. package/src/stateful-tool-params.ts +13 -20
  51. package/src/stateful.ts +36 -117
  52. package/src/subagents.ts +8 -9
  53. package/src/subprocess-transport.ts +2 -6
  54. package/src/transport-types.ts +1 -1
  55. package/src/transport-ui.ts +1 -1
  56. package/src/verification-harness.ts +516 -0
  57. package/src/verification-receipt.ts +275 -0
  58. package/src/verified-execution-benchmark.ts +86 -0
  59. package/src/verified-execution-contract.ts +219 -0
  60. package/src/work-item-ledger.ts +510 -37
  61. package/src/work-item-persistence.ts +31 -0
  62. package/src/workflow-completion-controller.ts +397 -0
  63. package/src/workflow-plan-compiler.ts +1 -1
  64. package/src/workflow-plan-patch.ts +1 -1
  65. package/src/workflow-planning.ts +11 -1
  66. package/src/workflow-ui.ts +1 -1
package/src/execution.ts CHANGED
@@ -2,17 +2,18 @@
2
2
  * Blocking execution stays in one module so preflight, confirmation, cancellation generation,
3
3
  * launch, and settlement retain one ordered lifecycle owner across every mode.
4
4
  */
5
+
5
6
  import type { AgentToolResult, AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
6
7
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
7
8
  import { AdaptiveScheduler } from "./adaptive-scheduler.js";
8
9
  import { evaluateDelegationAdmission } from "./admission-policy.js";
9
- import {
10
- type AgentConfig,
11
- type AgentScope,
12
- discoverAgents,
13
- type SubagentSettings,
14
- type SubagentThinkingLevel,
15
- } from "./agents.js";
10
+ import { discoverAgents } from "./agents/discovery.js";
11
+ import type {
12
+ AgentConfig,
13
+ AgentScope,
14
+ SubagentSettings,
15
+ SubagentThinkingLevel,
16
+ } from "./agents/types.js";
16
17
  import {
17
18
  chainStatus,
18
19
  fanInStatus,
@@ -20,7 +21,7 @@ import {
20
21
  singleStatus,
21
22
  startSubagentStatus,
22
23
  } from "./blocking-status.js";
23
- import { issueCapabilityGrant } from "./capability-grant.js";
24
+ import { issueCapabilityGrant, revokeCapabilityGrant } from "./capability-grant.js";
24
25
  import { redactPrivateText } from "./context.js";
25
26
  import {
26
27
  assertDelegationTargetAllowed,
@@ -33,6 +34,15 @@ import {
33
34
  type DelegationContract,
34
35
  normalizeDelegationContract,
35
36
  } from "./delegation-contract.js";
37
+ import {
38
+ calculateExecutionBudget,
39
+ mergeTurnLimits,
40
+ resolveConfiguredTimeout,
41
+ } from "./execution/budget.js";
42
+ import {
43
+ assertSubagentDepthAllowed,
44
+ resolveDefaultSubagentTimeoutMs,
45
+ } from "./execution/runtime-policy.js";
36
46
  import {
37
47
  acknowledgeExecutionPlan,
38
48
  createExecutionPlan,
@@ -63,10 +73,9 @@ import {
63
73
  import { safeTerminalLine } from "./safe-text.js";
64
74
  import {
65
75
  DEFAULT_DELEGATION_CWD_POLICY,
66
- readSubagentSettings,
67
76
  resolveBlockingMaxParallelTasks,
68
- resolveSubagentThinkingLevel,
69
- } from "./settings.js";
77
+ } from "./settings/inspection.js";
78
+ import { readSubagentSettings, resolveSubagentThinkingLevel } from "./settings.js";
70
79
  import { isRetryableResult, runHedgedAttempt, supervisionDelay } from "./supervision.js";
71
80
  import { TimeoutProgressJournal, TURN_TERMINATION_VERSION } from "./timeout-checkpoint.js";
72
81
  import type { TurnLimits } from "./turn-budget.js";
@@ -74,11 +83,16 @@ import {
74
83
  requiresIndependentVerification,
75
84
  validateWorkflowVerificationGraph,
76
85
  } from "./verification-policy.js";
86
+ import { prepareVerifiedWorkflow } from "./verified-execution-contract.js";
77
87
  import type { WorkItemLedger } from "./work-item-ledger.js";
78
88
  import {
79
89
  createSessionWorkItemPersistence,
80
90
  type WorkItemPersistence,
81
91
  } from "./work-item-persistence.js";
92
+ import {
93
+ WorkflowCompletionController,
94
+ workflowCompletionFailureReason,
95
+ } from "./workflow-completion-controller.js";
82
96
  import { createBlockingWorkLedger, resolveWorkflowTasks } from "./workflow-planning.js";
83
97
  import { captureWorkflowTreeIdentity, sameWorkflowTreeIdentity } from "./workflow-tree-identity.js";
84
98
  import {
@@ -86,25 +100,12 @@ import {
86
100
  workflowVerificationInstruction,
87
101
  } from "./workflow-verification.js";
88
102
 
89
- export const FALLBACK_TIMEOUT_MS = 10 * 60 * 1000;
90
-
91
- export function parsePositiveInteger(value: string | undefined): number | undefined {
92
- if (!value) return undefined;
93
- const parsed = Number.parseInt(value, 10);
94
- return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
95
- }
96
-
97
- export function resolveDefaultSubagentTimeoutMs(): number {
98
- return parsePositiveInteger(process.env.PI_SUBAGENT_TIMEOUT_MS) ?? FALLBACK_TIMEOUT_MS;
99
- }
100
-
101
- export function assertSubagentDepthAllowed(): void {
102
- const depth = Number.parseInt(process.env.PI_SUBAGENT_DEPTH ?? "0", 10) || 0;
103
- const maxDepth = parsePositiveInteger(process.env.PI_SUBAGENT_MAX_DEPTH) ?? 1;
104
- if (depth >= maxDepth) {
105
- throw new Error(`Subagent recursion depth limit reached (${maxDepth})`);
106
- }
107
- }
103
+ export {
104
+ assertSubagentDepthAllowed,
105
+ FALLBACK_TIMEOUT_MS,
106
+ parsePositiveInteger,
107
+ resolveDefaultSubagentTimeoutMs,
108
+ } from "./execution/runtime-policy.js";
108
109
 
109
110
  export async function executeSubagent(
110
111
  toolCallId: string,
@@ -124,50 +125,54 @@ export async function executeSubagent(
124
125
  const maxParallelTasks = resolveBlockingMaxParallelTasks(config);
125
126
  const discovery = discoverAgents(ctx.cwd, agentScope, config);
126
127
  const agents = discovery.agents;
127
- const resolvedWorkflowTasks = resolveWorkflowTasks(params, agents);
128
+ let resolvedWorkflowTasks = resolveWorkflowTasks(params, agents);
129
+ const verifiedWorkflow = params.workflow?.verifiedExecution
130
+ ? prepareVerifiedWorkflow(resolvedWorkflowTasks, params.workflow.verifiedExecution)
131
+ : undefined;
132
+ if (verifiedWorkflow) {
133
+ const verifierAgent = agents.find(
134
+ (agent) => agent.name === params.workflow?.verifiedExecution?.verifierAgent,
135
+ );
136
+ if (!verifierAgent) {
137
+ throw new Error(
138
+ `Unknown verified execution agent: ${params.workflow?.verifiedExecution?.verifierAgent}`,
139
+ );
140
+ }
141
+ if (
142
+ !verifierAgent.capabilityManifest?.verificationRoles.includes("independent-review") ||
143
+ !verifierAgent.capabilityManifest.resultFormats.includes("structured-v2")
144
+ ) {
145
+ throw new Error(
146
+ `Verified execution agent ${verifierAgent.name} lacks independent structured-v2 review capability`,
147
+ );
148
+ }
149
+ resolvedWorkflowTasks = verifiedWorkflow.tasks;
150
+ }
128
151
  const confirmProjectAgents = params.confirmProjectAgents ?? true;
129
152
  const resolveTimeoutMs = (agentName: string, localTimeoutMs?: number) =>
130
- localTimeoutMs ??
131
- params.timeoutMs ??
132
- agents.find((agent) => agent.name === agentName)?.timeoutMs ??
133
- resolveDefaultSubagentTimeoutMs();
153
+ resolveConfiguredTimeout(
154
+ agents,
155
+ agentName,
156
+ localTimeoutMs,
157
+ params.timeoutMs,
158
+ resolveDefaultSubagentTimeoutMs(),
159
+ );
134
160
  const resolveThinkingLevel = (agentName: string, localThinkingLevel?: SubagentThinkingLevel) =>
135
161
  resolveSubagentThinkingLevel(agents, agentName, params.thinkingLevel, localThinkingLevel);
136
162
  let orchestrationDeadline: number | undefined;
137
- const resolveTurnLimits = (local?: TurnLimits): TurnLimits => ({
138
- idleTimeoutMs: local?.idleTimeoutMs ?? params.idleTimeoutMs,
139
- maxTurns: local?.maxTurns ?? params.maxTurns,
140
- maxToolCalls: local?.maxToolCalls ?? params.maxToolCalls,
141
- });
142
- const resolveExecutionBudget = (
143
- agentName: string,
144
- localTimeoutMs?: number,
145
- ):
146
- | {
147
- timeoutMs: number;
148
- workTimeoutReason: "work_timeout" | "orchestration_timeout";
149
- workTimeoutReportLimit: number;
150
- }
151
- | undefined => {
152
- const requested = resolveTimeoutMs(agentName, localTimeoutMs);
153
- if (orchestrationDeadline === undefined) {
154
- return {
155
- timeoutMs: requested,
156
- workTimeoutReason: "work_timeout",
157
- workTimeoutReportLimit: requested,
158
- };
159
- }
160
- const remaining = Math.floor(orchestrationDeadline - Date.now());
161
- if (remaining < 1) return undefined;
162
- const orchestrationLimited = remaining < requested;
163
- return {
164
- timeoutMs: Math.min(requested, remaining),
165
- workTimeoutReason: orchestrationLimited ? "orchestration_timeout" : "work_timeout",
166
- workTimeoutReportLimit: orchestrationLimited
167
- ? Math.floor(params.totalTimeoutMs as number)
168
- : requested,
169
- };
170
- };
163
+ const resolveTurnLimits = (local?: TurnLimits): TurnLimits =>
164
+ mergeTurnLimits(local, {
165
+ idleTimeoutMs: params.idleTimeoutMs,
166
+ maxTurns: params.maxTurns,
167
+ maxToolCalls: params.maxToolCalls,
168
+ });
169
+ const resolveExecutionBudget = (agentName: string, localTimeoutMs?: number) =>
170
+ calculateExecutionBudget({
171
+ requestedTimeoutMs: resolveTimeoutMs(agentName, localTimeoutMs),
172
+ orchestrationDeadline,
173
+ totalTimeoutMs: params.totalTimeoutMs,
174
+ now: Date.now(),
175
+ });
171
176
 
172
177
  const hasChain = (params.chain?.length ?? 0) > 0;
173
178
  const hasTasks = (params.tasks?.length ?? 0) > 0;
@@ -386,7 +391,17 @@ export async function executeSubagent(
386
391
  verificationTargetIds,
387
392
  );
388
393
  }
389
- workLedger = createBlockingWorkLedger(params, resolvedWorkflowTasks, aggregator);
394
+ workLedger = createBlockingWorkLedger(
395
+ params,
396
+ resolvedWorkflowTasks,
397
+ aggregator,
398
+ verifiedWorkflow
399
+ ? {
400
+ id: verifiedWorkflow.targetTaskId,
401
+ maxReworkCycles: verifiedWorkflow.maxReworkCycles,
402
+ }
403
+ : undefined,
404
+ );
390
405
  let workflowPersistence: WorkItemPersistence | undefined;
391
406
  if (hasWorkflow && workLedger) {
392
407
  const owner =
@@ -698,13 +713,34 @@ export async function executeSubagent(
698
713
  const deadline = orchestrationDeadline;
699
714
  const cancelWorkflowGeneration = () => {
700
715
  for (const item of workLedger.snapshot().items) {
701
- if (item.state === "running" || item.state === "awaiting-verification") {
716
+ if (
717
+ item.state === "running" ||
718
+ item.state === "awaiting-verification" ||
719
+ (item.state === "completed" && item.acceptanceState === "pending")
720
+ ) {
702
721
  workLedger.invalidate(item.id, "parent-aborted");
703
722
  }
704
723
  }
705
724
  };
706
- signal?.addEventListener("abort", cancelWorkflowGeneration, { once: true });
725
+ let completionController: WorkflowCompletionController | undefined;
726
+ let abortListenerAttached = false;
707
727
  try {
728
+ completionController = verifiedWorkflow
729
+ ? new WorkflowCompletionController({
730
+ ledger: workLedger,
731
+ cwd:
732
+ workflowTargets[
733
+ resolvedWorkflowTasks.findIndex((task) => task.id === verifiedWorkflow.targetTaskId)
734
+ ]?.cwd ?? ctx.cwd,
735
+ targetTaskId: verifiedWorkflow.targetTaskId,
736
+ verifierTaskId: verifiedWorkflow.verifierTaskId,
737
+ checks: verifiedWorkflow.checks,
738
+ signal,
739
+ deadlineAt: deadline,
740
+ })
741
+ : undefined;
742
+ signal?.addEventListener("abort", cancelWorkflowGeneration, { once: true });
743
+ abortListenerAttached = signal !== undefined;
708
744
  while (true) {
709
745
  const snapshot = workLedger.snapshot();
710
746
  const remainingBudgetMs =
@@ -732,6 +768,8 @@ export async function executeSubagent(
732
768
  const dependencies = (task.dependsOn ?? [])
733
769
  .map((dependency) => resultsById.get(dependency))
734
770
  .filter((result): result is SingleResult => result !== undefined);
771
+ const managedVerifier =
772
+ completionController !== undefined && workItemId === verifiedWorkflow?.verifierTaskId;
735
773
  const verifierDependency = task.verifierFor
736
774
  ? resultsById.get(task.verifierFor)
737
775
  : undefined;
@@ -739,15 +777,17 @@ export async function executeSubagent(
739
777
  verifierDependency?.structuredResult?.version === "pi-subagents:result:v2"
740
778
  ? verifierDependency.structuredResult
741
779
  : undefined;
742
- const dependencyContext = task.verifierFor
743
- ? verifierStructuredResult
744
- ? `\n\nStaged target result:\n${redactPrivateText(
745
- JSON.stringify(verifierStructuredResult),
746
- )}`
747
- : ""
748
- : dependencies.length
749
- ? `\n\nDependency results:\n${buildFanInContext(dependencies)}`
750
- : "";
780
+ const dependencyContext = managedVerifier
781
+ ? ""
782
+ : task.verifierFor
783
+ ? verifierStructuredResult
784
+ ? `\n\nStaged target result:\n${redactPrivateText(
785
+ JSON.stringify(verifierStructuredResult),
786
+ )}`
787
+ : ""
788
+ : dependencies.length
789
+ ? `\n\nDependency results:\n${buildFanInContext(dependencies)}`
790
+ : "";
751
791
  const displayTask = task.task;
752
792
  const target = workflowTargets[index];
753
793
  const thinkingLevel = resolveThinkingLevel(task.agent, task.thinkingLevel);
@@ -782,8 +822,15 @@ export async function executeSubagent(
782
822
  const verificationTargetContract = normalizeDelegationContract(
783
823
  verificationTargetTask?.contract,
784
824
  );
785
- const verificationSuffix =
786
- task.verifierFor && verifierTreeIdentity
825
+ const managedVerificationInstruction = managedVerifier
826
+ ? completionController?.verifierPrompt()
827
+ : undefined;
828
+ if (managedVerifier && !managedVerificationInstruction) {
829
+ throw new Error("Verified execution verifier instruction is unavailable");
830
+ }
831
+ const verificationSuffix = managedVerifier
832
+ ? `\n\n${managedVerificationInstruction}`
833
+ : task.verifierFor && verifierTreeIdentity
787
834
  ? `\n\n${workflowVerificationInstruction(task.verifierFor, verifierTreeIdentity, {
788
835
  acceptanceCriteria: [
789
836
  ...(verificationTargetTask?.acceptanceCriteria ?? []),
@@ -792,12 +839,19 @@ export async function executeSubagent(
792
839
  requiredEvidence: verificationTargetContract?.requiredEvidence ?? [],
793
840
  })}`
794
841
  : "";
842
+ const reworkSuffix =
843
+ completionController &&
844
+ workItemId === verifiedWorkflow?.targetTaskId &&
845
+ (workLedger.get(workItemId)?.taskGeneration ?? 1) > 1
846
+ ? `\n\n${completionController.reworkPrompt()}`
847
+ : "";
795
848
  const baseTask = `${task.task}${dependencyContext}`;
849
+ const requiredSuffix = `${reworkSuffix}${verificationSuffix}`;
796
850
  const baseBudget = Math.max(
797
851
  0,
798
- DEFAULT_MAX_CONTEXT_BYTES - Buffer.byteLength(verificationSuffix, "utf8"),
852
+ DEFAULT_MAX_CONTEXT_BYTES - Buffer.byteLength(requiredSuffix, "utf8"),
799
853
  );
800
- const taskWithContext = `${truncateUtf8(baseTask, baseBudget).text}${verificationSuffix}`;
854
+ const taskWithContext = `${truncateUtf8(baseTask, baseBudget).text}${requiredSuffix}`;
801
855
  const prepared = prepareTask(taskWithContext, task);
802
856
  const startedItem = startWorkItem(workItemId, task.agent);
803
857
  const acceptedTaskGeneration = startedItem?.taskGeneration ?? 0;
@@ -807,6 +861,17 @@ export async function executeSubagent(
807
861
  if (!budget) {
808
862
  return Promise.resolve(exhaustedResult(task.agent, displayTask, thinkingLevel));
809
863
  }
864
+ const childPolicy = launchPolicy(
865
+ target,
866
+ prepared,
867
+ displayTask,
868
+ task.agent,
869
+ thinkingLevel,
870
+ budget.timeoutMs,
871
+ acceptedTaskGeneration,
872
+ budget,
873
+ resolveTurnLimits(task),
874
+ );
810
875
  return runSingleAgent(
811
876
  ctx.cwd,
812
877
  agents,
@@ -820,17 +885,15 @@ export async function executeSubagent(
820
885
  undefined,
821
886
  makeDetails("workflow"),
822
887
  undefined,
823
- launchPolicy(
824
- target,
825
- prepared,
826
- displayTask,
827
- task.agent,
828
- thinkingLevel,
829
- budget.timeoutMs,
830
- acceptedTaskGeneration,
831
- budget,
832
- resolveTurnLimits(task),
833
- ),
888
+ managedVerifier
889
+ ? {
890
+ ...childPolicy,
891
+ disableExtensions: true,
892
+ disableSkills: true,
893
+ disablePromptTemplates: true,
894
+ disableContextFiles: true,
895
+ }
896
+ : childPolicy,
834
897
  );
835
898
  };
836
899
  const maxAttempts = task.retryPolicy?.maxAttempts ?? 1;
@@ -875,6 +938,72 @@ export async function executeSubagent(
875
938
  resultsById.set(workItemId, result);
876
939
  if (result.outcome?.status === "stale") {
877
940
  // Cancellation or replacement already rotated and invalidated this generation.
941
+ } else if (managedVerifier && completionController) {
942
+ const structured =
943
+ result.structuredResult?.version === "pi-subagents:result:v2"
944
+ ? result.structuredResult
945
+ : undefined;
946
+ try {
947
+ if (!structured || !result.executionPlan?.id) {
948
+ throw new Error(
949
+ "Verified execution verifier did not return a current structured-v2 result",
950
+ );
951
+ }
952
+ const completion = await completionController.completeVerifier({
953
+ taskGeneration: acceptedTaskGeneration,
954
+ executionPlanId: result.executionPlan.id,
955
+ verifierAgent: task.agent,
956
+ result: structured,
957
+ sourceTruncated: result.truncated,
958
+ });
959
+ const targetItem = workLedger.get(verifiedWorkflow?.targetTaskId ?? "");
960
+ if (
961
+ completion.decision === "rework" &&
962
+ targetItem?.acceptanceState === "rework-requested"
963
+ ) {
964
+ const targetResult = resultsById.get(targetItem.id);
965
+ if (targetResult?.capabilityGrant?.state === "active") {
966
+ targetResult.capabilityGrant = revokeCapabilityGrant(
967
+ targetResult.capabilityGrant,
968
+ "verification-rework",
969
+ Date.now(),
970
+ );
971
+ }
972
+ if (result.capabilityGrant?.state === "active") {
973
+ result.capabilityGrant = revokeCapabilityGrant(
974
+ result.capabilityGrant,
975
+ "verification-rework",
976
+ Date.now(),
977
+ );
978
+ }
979
+ completionController.beginRework();
980
+ } else if (completion.decision !== "accept" && targetItem) {
981
+ const targetResult = resultsById.get(targetItem.id);
982
+ if (targetResult) {
983
+ targetResult.outcome = {
984
+ status: "failed",
985
+ reasonCode: targetItem.outcomeReason ?? "verification-rejected",
986
+ recoveryActions: ["stop"],
987
+ retryable: false,
988
+ };
989
+ }
990
+ }
991
+ } catch (error) {
992
+ if (signal?.aborted) throw error;
993
+ const message = error instanceof Error ? error.message : String(error);
994
+ const reasonCode = workflowCompletionFailureReason(error);
995
+ result.outcome = {
996
+ status: "failed",
997
+ reasonCode,
998
+ recoveryActions: ["revalidate"],
999
+ retryable: false,
1000
+ };
1001
+ result.errorMessage = message;
1002
+ const verifierItem = workLedger.get(workItemId);
1003
+ if (verifierItem?.state === "running") {
1004
+ workLedger.failVerification(workItemId, reasonCode);
1005
+ }
1006
+ }
878
1007
  } else if (task.verifierFor) {
879
1008
  const staged = workLedger.get(task.verifierFor);
880
1009
  const structured =
@@ -963,6 +1092,34 @@ export async function executeSubagent(
963
1092
  result.errorMessage = failureReason;
964
1093
  workLedger.failVerification(workItemId, reasonCode);
965
1094
  }
1095
+ } else if (
1096
+ completionController &&
1097
+ workItemId === verifiedWorkflow?.targetTaskId &&
1098
+ !isResultError(result)
1099
+ ) {
1100
+ try {
1101
+ if (!result.executionPlan?.id) {
1102
+ throw new Error("Verified execution producer has no accepted ExecutionPlan");
1103
+ }
1104
+ await completionController.stageTarget({
1105
+ taskGeneration: acceptedTaskGeneration,
1106
+ executionPlanId: result.executionPlan.id,
1107
+ artifacts: artifactsFromResult(result),
1108
+ });
1109
+ } catch (error) {
1110
+ if (signal?.aborted) throw error;
1111
+ const message = error instanceof Error ? error.message : String(error);
1112
+ result.outcome = {
1113
+ status: "failed",
1114
+ reasonCode: "verification-checks-unavailable",
1115
+ recoveryActions: ["revalidate"],
1116
+ retryable: false,
1117
+ };
1118
+ result.errorMessage = message;
1119
+ if (workLedger.get(workItemId)?.state === "running") {
1120
+ settleWorkItem(workItemId, result, acceptedTaskGeneration);
1121
+ }
1122
+ }
966
1123
  } else if (verificationTargetIds.has(workItemId) && !isResultError(result)) {
967
1124
  try {
968
1125
  const treeIdentity = await captureWorkflowTreeIdentity(target.cwd, { signal });
@@ -1001,7 +1158,8 @@ export async function executeSubagent(
1001
1158
  if (
1002
1159
  item.state !== "pending" &&
1003
1160
  item.state !== "ready" &&
1004
- item.state !== "awaiting-verification"
1161
+ item.state !== "awaiting-verification" &&
1162
+ !(item.state === "completed" && item.acceptanceState === "pending")
1005
1163
  ) {
1006
1164
  continue;
1007
1165
  }
@@ -1009,7 +1167,10 @@ export async function executeSubagent(
1009
1167
  workLedger.settle(item.id, "interrupted", "parent-aborted");
1010
1168
  } else if (deadline !== undefined && Date.now() >= deadline) {
1011
1169
  workLedger.settle(item.id, "blocked", "budget-exhausted");
1012
- } else if (item.state === "awaiting-verification") {
1170
+ } else if (
1171
+ item.state === "awaiting-verification" ||
1172
+ (item.state === "completed" && item.acceptanceState === "pending")
1173
+ ) {
1013
1174
  workLedger.settle(item.id, "blocked", "verification-not-completed");
1014
1175
  } else {
1015
1176
  const dependencyBlocked = item.dependencies.some(
@@ -1027,7 +1188,12 @@ export async function executeSubagent(
1027
1188
  const completed = resultsById.get(task.id);
1028
1189
  const item = workLedger.get(task.id);
1029
1190
  if (completed) {
1030
- if (item && item.state !== "completed" && !isResultError(completed)) {
1191
+ if (
1192
+ item &&
1193
+ (item.state !== "completed" ||
1194
+ (item.acceptanceRequired && item.acceptanceState !== "accepted")) &&
1195
+ !isResultError(completed)
1196
+ ) {
1031
1197
  completed.outcome = {
1032
1198
  status:
1033
1199
  item.state === "needs-input"
@@ -1099,7 +1265,10 @@ export async function executeSubagent(
1099
1265
  isError: isError || undefined,
1100
1266
  };
1101
1267
  } finally {
1102
- signal?.removeEventListener("abort", cancelWorkflowGeneration);
1268
+ if (abortListenerAttached) {
1269
+ signal?.removeEventListener("abort", cancelWorkflowGeneration);
1270
+ }
1271
+ completionController?.dispose();
1103
1272
  try {
1104
1273
  await persistWorkLedger();
1105
1274
  } finally {
@@ -7,10 +7,11 @@ import {
7
7
  SessionManager,
8
8
  SettingsManager,
9
9
  } from "@earendil-works/pi-coding-agent";
10
- import { type AgentConfig, discoverAgents, type SubagentThinkingLevel } from "./agents.js";
10
+ import { discoverAgents } from "./agents/discovery.js";
11
+ import type { AgentConfig, SubagentThinkingLevel } from "./agents/types.js";
11
12
  import { redactPrivateText } from "./context.js";
12
13
  import { appendDelegationContract } from "./delegation-contract.js";
13
- import { resolveDefaultSubagentTimeoutMs } from "./execution.js";
14
+ import { resolveDefaultSubagentTimeoutMs } from "./execution/runtime-policy.js";
14
15
  import { DEFAULT_MAX_CONTEXT_BYTES, DEFAULT_MAX_OUTPUT_BYTES, truncateUtf8 } from "./limits.js";
15
16
  import { resolvePiPromptResources } from "./prompt-resources.js";
16
17
  import type { AgentTurn, ManagedAgent, TurnOutcome } from "./registry.js";
package/src/inspect.ts CHANGED
@@ -6,13 +6,13 @@ import {
6
6
  type ExtensionContext,
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
  import { type Static, Type } from "typebox";
9
- import {
10
- type AgentConfig,
11
- type AgentScope,
12
- type ConsultationCwdPolicy,
13
- type DelegationCwdPolicy,
14
- discoverAgents,
15
- } from "./agents.js";
9
+ import { discoverAgents } from "./agents/discovery.js";
10
+ import type {
11
+ AgentConfig,
12
+ AgentScope,
13
+ ConsultationCwdPolicy,
14
+ DelegationCwdPolicy,
15
+ } from "./agents/types.js";
16
16
  import { projectCapabilityManifest } from "./capabilities.js";
17
17
  import { resolveConsultTools } from "./consult-policy.js";
18
18
  import { buildContextSnapshot, type ContextMode } from "./context.js";
@@ -21,6 +21,7 @@ import { DEFAULT_MAX_CONTEXT_BYTES } from "./limits.js";
21
21
  import { resolvePiInvocation } from "./pi-invocation.js";
22
22
  import type { AgentRunInspectionDetail, AgentRunInspectionSummary } from "./registry.js";
23
23
  import { boundedPrivateText, boundText, safeDisplayPath, safeTerminalLine } from "./safe-text.js";
24
+ import { resolveDelegationWorkflow } from "./settings/inspection.js";
24
25
  import {
25
26
  inspectBlockingParallelLimitSettings,
26
27
  inspectCompletionDeliverySettings,
@@ -30,7 +31,6 @@ import {
30
31
  inspectStatefulLimitSettings,
31
32
  inspectStatefulTransportSettings,
32
33
  inspectSubagentSettings,
33
- resolveDelegationWorkflow,
34
34
  } from "./settings.js";
35
35
  import type { StatefulSubagentRuntimeStatus } from "./stateful.js";
36
36
  import type { WorkItemLedgerSnapshot } from "./work-item-ledger.js";
@@ -404,6 +404,8 @@ function projectRunSummary(run: AgentRunInspectionSummary): Record<string, unkno
404
404
  updatedAt: run.updatedAt,
405
405
  historyCount: run.historyCount,
406
406
  unreadMessages: run.unreadMessages,
407
+ turnGeneration: run.turnGeneration,
408
+ pendingCompletionCount: run.pendingCompletionCount,
407
409
  };
408
410
  }
409
411
 
@@ -474,6 +476,8 @@ function projectRun(run: AgentRunInspectionDetail, ctx: ExtensionContext): Recor
474
476
  : undefined,
475
477
  semanticCompatibility: run.semanticCompatibility,
476
478
  telemetry: run.telemetry,
479
+ currentRunId: run.currentRunId,
480
+ currentTurnGeneration: run.currentTurnGeneration,
477
481
  currentTask: run.currentTask ? boundedPrivateText(run.currentTask, 2 * 1024) : undefined,
478
482
  error: run.error ? boundedPrivateText(run.error, 2 * 1024) : undefined,
479
483
  target: run.target
@@ -537,6 +541,33 @@ function projectWorkflow(workflow: WorkItemLedgerSnapshot): Record<string, unkno
537
541
  verified: artifact.verified,
538
542
  })),
539
543
  verificationAccepted: item.verificationAccepted,
544
+ acceptanceStateVersion: item.acceptanceStateVersion,
545
+ acceptanceRequired: item.acceptanceRequired,
546
+ acceptanceState: item.acceptanceState,
547
+ reworkCount: item.reworkCount,
548
+ maxReworkCycles: item.maxReworkCycles,
549
+ acceptanceReceipt: item.acceptanceReceipt
550
+ ? {
551
+ version: item.acceptanceReceipt.version,
552
+ decision: item.acceptanceReceipt.decision,
553
+ targetTaskId: boundedPrivateText(item.acceptanceReceipt.targetTaskId, 256),
554
+ targetTaskGeneration: item.acceptanceReceipt.targetTaskGeneration,
555
+ targetExecutionPlanId: item.acceptanceReceipt.targetExecutionPlanId,
556
+ verifierTaskId: boundedPrivateText(item.acceptanceReceipt.verifierTaskId, 256),
557
+ verifierTaskGeneration: item.acceptanceReceipt.verifierTaskGeneration,
558
+ verifierExecutionPlanId: item.acceptanceReceipt.verifierExecutionPlanId,
559
+ verifierAgent: boundedPrivateText(item.acceptanceReceipt.verifierAgent, 256),
560
+ beforeTreeIdentity: item.acceptanceReceipt.beforeTreeIdentity,
561
+ afterTreeIdentity: item.acceptanceReceipt.afterTreeIdentity,
562
+ patchDigest: item.acceptanceReceipt.patchDigest,
563
+ changedPathCount: item.acceptanceReceipt.changedPaths.length,
564
+ checkCount: item.acceptanceReceipt.checks.length,
565
+ requiredEvidenceCount: item.acceptanceReceipt.requiredEvidenceIds.length,
566
+ createdAt: item.acceptanceReceipt.createdAt,
567
+ sourceTruncated: item.acceptanceReceipt.sourceTruncated,
568
+ }
569
+ : undefined,
570
+ acceptanceReceiptHistoryCount: item.acceptanceReceiptHistory.length,
540
571
  stagedTreeIdentity: item.stagedTreeIdentity
541
572
  ? {
542
573
  version: item.stagedTreeIdentity.version,
package/src/limits.ts CHANGED
@@ -8,6 +8,7 @@ export const DEFAULT_MAX_PARALLEL_TASKS = 8;
8
8
  export const MAX_CONFIGURABLE_PARALLEL_TASKS = 64;
9
9
  export const MAX_BLOCKING_PARALLEL_CONCURRENCY = 4;
10
10
  export const DEFAULT_MAX_MESSAGES = 200;
11
+ export const MAX_TOOL_MESSAGE_BYTES = 2 * 1024;
11
12
  export const TRUNCATION_MARKER = "\n… [truncated by pi-subagents]";
12
13
  export const TAIL_TRUNCATION_MARKER = "… [truncated by pi-subagents]\n";
13
14
 
@@ -81,10 +81,15 @@ export function calculateOrchestrationMetrics(
81
81
  ).length,
82
82
  executorAcceptedVerification: items.filter((item) => item.verificationAccepted).length,
83
83
  verificationRework: items.filter(
84
- (item) => !item.verifierFor && item.verificationReceipt?.decision === "rework",
84
+ (item) =>
85
+ !item.verifierFor &&
86
+ (item.verificationReceipt?.decision === "rework" ||
87
+ item.acceptanceReceiptHistory.some((receipt) => receipt.decision === "rework")),
85
88
  ).length,
86
89
  verificationRejected: items.filter(
87
- (item) => !item.verifierFor && item.verificationReceipt?.decision === "reject",
90
+ (item) =>
91
+ !item.verifierFor &&
92
+ (item.verificationReceipt?.decision === "reject" || item.acceptanceState === "rejected"),
88
93
  ).length,
89
94
  verificationInvalid: items.filter(
90
95
  (item) => !item.verifierFor && item.outcomeReason === "verification-receipt-invalid",
@@ -92,9 +97,11 @@ export function calculateOrchestrationMetrics(
92
97
  verificationTreeMismatch: items.filter(
93
98
  (item) =>
94
99
  !item.verifierFor &&
95
- ["verification-tree-mismatch", "verification-tree-unavailable"].includes(
96
- item.outcomeReason ?? "",
97
- ),
100
+ [
101
+ "verification-tree-mismatch",
102
+ "verification-tree-unavailable",
103
+ "verification-tree-drift",
104
+ ].includes(item.outcomeReason ?? ""),
98
105
  ).length,
99
106
  ...(panel
100
107
  ? {