@narumitw/pi-subagents 0.51.0 → 0.53.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/README.md +129 -7
- package/package.json +1 -1
- package/src/adaptive-scheduler.ts +29 -1
- package/src/agents.ts +5 -0
- package/src/automation-contract.ts +709 -0
- package/src/automation-planner.ts +65 -0
- package/src/automation.ts +580 -0
- package/src/execution-plan.ts +1 -1
- package/src/execution.ts +291 -41
- package/src/inspect.ts +25 -0
- package/src/orchestration-metrics.ts +31 -0
- package/src/panel-execution.ts +0 -2
- package/src/params.ts +8 -1
- package/src/subagents.ts +2 -0
- package/src/verification-policy.ts +50 -0
- package/src/work-item-ledger.ts +267 -18
- package/src/work-item-persistence.ts +5 -0
- package/src/workflow-plan-compiler.ts +618 -0
- package/src/workflow-plan-patch.ts +636 -0
- package/src/workflow-planning-benchmark.ts +95 -0
- package/src/workflow-planning.ts +13 -1
- package/src/workflow-tree-identity.ts +289 -0
- package/src/workflow-ui.ts +2 -2
- package/src/workflow-verification.ts +296 -0
package/src/execution.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
startSubagentStatus,
|
|
22
22
|
} from "./blocking-status.js";
|
|
23
23
|
import { issueCapabilityGrant } from "./capability-grant.js";
|
|
24
|
+
import { redactPrivateText } from "./context.js";
|
|
24
25
|
import {
|
|
25
26
|
assertDelegationTargetAllowed,
|
|
26
27
|
type ResolvedSubagentTarget,
|
|
@@ -69,13 +70,21 @@ import {
|
|
|
69
70
|
import { isRetryableResult, runHedgedAttempt, supervisionDelay } from "./supervision.js";
|
|
70
71
|
import { TimeoutProgressJournal, TURN_TERMINATION_VERSION } from "./timeout-checkpoint.js";
|
|
71
72
|
import type { TurnLimits } from "./turn-budget.js";
|
|
72
|
-
import {
|
|
73
|
+
import {
|
|
74
|
+
requiresIndependentVerification,
|
|
75
|
+
validateWorkflowVerificationGraph,
|
|
76
|
+
} from "./verification-policy.js";
|
|
73
77
|
import type { WorkItemLedger } from "./work-item-ledger.js";
|
|
74
78
|
import {
|
|
75
79
|
createSessionWorkItemPersistence,
|
|
76
80
|
type WorkItemPersistence,
|
|
77
81
|
} from "./work-item-persistence.js";
|
|
78
82
|
import { createBlockingWorkLedger, resolveWorkflowTasks } from "./workflow-planning.js";
|
|
83
|
+
import { captureWorkflowTreeIdentity, sameWorkflowTreeIdentity } from "./workflow-tree-identity.js";
|
|
84
|
+
import {
|
|
85
|
+
createWorkflowVerificationReceipt,
|
|
86
|
+
workflowVerificationInstruction,
|
|
87
|
+
} from "./workflow-verification.js";
|
|
79
88
|
|
|
80
89
|
export const FALLBACK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
81
90
|
|
|
@@ -173,6 +182,7 @@ export async function executeSubagent(
|
|
|
173
182
|
Number(hasSingle);
|
|
174
183
|
let workLedger: WorkItemLedger | undefined;
|
|
175
184
|
const workflowScheduling: ReturnType<AdaptiveScheduler["decide"]>[] = [];
|
|
185
|
+
const verificationTargetIds = new Set<string>();
|
|
176
186
|
|
|
177
187
|
const makeDetails =
|
|
178
188
|
(mode: "single" | "parallel" | "chain" | "workflow" | "panel") =>
|
|
@@ -191,6 +201,38 @@ export async function executeSubagent(
|
|
|
191
201
|
metrics: calculateOrchestrationMetrics(workflow, metricResults),
|
|
192
202
|
};
|
|
193
203
|
};
|
|
204
|
+
const workflowFailureResult = (
|
|
205
|
+
agentName: string,
|
|
206
|
+
task: string,
|
|
207
|
+
reasonCode: string,
|
|
208
|
+
message: string,
|
|
209
|
+
thinkingLevel?: SubagentThinkingLevel,
|
|
210
|
+
): SingleResult => ({
|
|
211
|
+
agent: agentName,
|
|
212
|
+
agentSource: agents.find((agent) => agent.name === agentName)?.source ?? "unknown",
|
|
213
|
+
task,
|
|
214
|
+
exitCode: 1,
|
|
215
|
+
messages: [],
|
|
216
|
+
stderr: message,
|
|
217
|
+
errorMessage: message,
|
|
218
|
+
usage: {
|
|
219
|
+
input: 0,
|
|
220
|
+
output: 0,
|
|
221
|
+
cacheRead: 0,
|
|
222
|
+
cacheWrite: 0,
|
|
223
|
+
cost: 0,
|
|
224
|
+
contextTokens: 0,
|
|
225
|
+
turns: 0,
|
|
226
|
+
},
|
|
227
|
+
thinkingLevel,
|
|
228
|
+
finalOutput: "",
|
|
229
|
+
outcome: {
|
|
230
|
+
status: "failed",
|
|
231
|
+
reasonCode,
|
|
232
|
+
recoveryActions: ["revalidate"],
|
|
233
|
+
retryable: false,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
194
236
|
const exhaustedResult = (
|
|
195
237
|
agentName: string,
|
|
196
238
|
task: string,
|
|
@@ -292,6 +334,7 @@ export async function executeSubagent(
|
|
|
292
334
|
}
|
|
293
335
|
if (hasWorkflow && params.workflow) {
|
|
294
336
|
for (const task of resolvedWorkflowTasks) {
|
|
337
|
+
if (task.verifierFor) verificationTargetIds.add(task.verifierFor);
|
|
295
338
|
const contract = normalizeDelegationContract(task.contract);
|
|
296
339
|
if (params.workflow.honorAdmission) {
|
|
297
340
|
const admission = contract?.admission;
|
|
@@ -322,17 +365,7 @@ export async function executeSubagent(
|
|
|
322
365
|
requiredCapabilities: task.requiredCapabilities ?? [],
|
|
323
366
|
})
|
|
324
367
|
) {
|
|
325
|
-
|
|
326
|
-
(candidate) =>
|
|
327
|
-
candidate.verifierFor === task.id &&
|
|
328
|
-
candidate.dependsOn?.includes(task.id) &&
|
|
329
|
-
candidate.agent !== task.agent,
|
|
330
|
-
);
|
|
331
|
-
if (!verifier) {
|
|
332
|
-
throw new Error(
|
|
333
|
-
`Workflow task ${task.id} requires a distinct dependent verifier before launch`,
|
|
334
|
-
);
|
|
335
|
-
}
|
|
368
|
+
verificationTargetIds.add(task.id);
|
|
336
369
|
}
|
|
337
370
|
if (!task.retryPolicy && !task.hedgeAfterMs) continue;
|
|
338
371
|
const policy = contract?.sideEffectPolicy;
|
|
@@ -345,6 +378,13 @@ export async function executeSubagent(
|
|
|
345
378
|
);
|
|
346
379
|
}
|
|
347
380
|
}
|
|
381
|
+
validateWorkflowVerificationGraph(
|
|
382
|
+
resolvedWorkflowTasks.map((task) => ({
|
|
383
|
+
...task,
|
|
384
|
+
resultFormat: task.resultFormat ?? params.resultFormat,
|
|
385
|
+
})),
|
|
386
|
+
verificationTargetIds,
|
|
387
|
+
);
|
|
348
388
|
}
|
|
349
389
|
workLedger = createBlockingWorkLedger(params, resolvedWorkflowTasks, aggregator);
|
|
350
390
|
let workflowPersistence: WorkItemPersistence | undefined;
|
|
@@ -512,6 +552,19 @@ export async function executeSubagent(
|
|
|
512
552
|
);
|
|
513
553
|
}
|
|
514
554
|
|
|
555
|
+
const artifactsFromResult = (result: SingleResult) => {
|
|
556
|
+
const structured =
|
|
557
|
+
result.structuredResult?.version === "pi-subagents:result:v2"
|
|
558
|
+
? result.structuredResult
|
|
559
|
+
: undefined;
|
|
560
|
+
return (structured?.artifacts ?? []).map((artifact) => ({
|
|
561
|
+
id: artifact.id,
|
|
562
|
+
kind: artifact.kind,
|
|
563
|
+
version: artifact.version ?? artifact.digest ?? "unversioned",
|
|
564
|
+
digest: artifact.digest,
|
|
565
|
+
verified: false,
|
|
566
|
+
}));
|
|
567
|
+
};
|
|
515
568
|
const startWorkItem = (id: string, agentName: string) => {
|
|
516
569
|
if (workLedger?.get(id)?.state === "ready") {
|
|
517
570
|
return workLedger.start(id, `agent:${agentName}`);
|
|
@@ -548,24 +601,10 @@ export async function executeSubagent(
|
|
|
548
601
|
);
|
|
549
602
|
return;
|
|
550
603
|
}
|
|
551
|
-
const structured =
|
|
552
|
-
result.structuredResult?.version === "pi-subagents:result:v2"
|
|
553
|
-
? result.structuredResult
|
|
554
|
-
: undefined;
|
|
555
604
|
workLedger.complete(id, {
|
|
556
605
|
taskGeneration,
|
|
557
606
|
executionPlanId: result.executionPlan?.id,
|
|
558
|
-
artifacts: (
|
|
559
|
-
id: artifact.id,
|
|
560
|
-
kind: artifact.kind,
|
|
561
|
-
version: artifact.version ?? artifact.digest ?? "unversioned",
|
|
562
|
-
digest: artifact.digest,
|
|
563
|
-
verified:
|
|
564
|
-
structured?.verification.some((verification) => verification.status === "passed") ??
|
|
565
|
-
false,
|
|
566
|
-
})),
|
|
567
|
-
verificationAccepted:
|
|
568
|
-
structured?.verification.some((verification) => verification.status === "passed") ?? false,
|
|
607
|
+
artifacts: artifactsFromResult(result),
|
|
569
608
|
});
|
|
570
609
|
};
|
|
571
610
|
|
|
@@ -659,7 +698,9 @@ export async function executeSubagent(
|
|
|
659
698
|
const deadline = orchestrationDeadline;
|
|
660
699
|
const cancelWorkflowGeneration = () => {
|
|
661
700
|
for (const item of workLedger.snapshot().items) {
|
|
662
|
-
if (item.state === "running"
|
|
701
|
+
if (item.state === "running" || item.state === "awaiting-verification") {
|
|
702
|
+
workLedger.invalidate(item.id, "parent-aborted");
|
|
703
|
+
}
|
|
663
704
|
}
|
|
664
705
|
};
|
|
665
706
|
signal?.addEventListener("abort", cancelWorkflowGeneration, { once: true });
|
|
@@ -691,17 +732,73 @@ export async function executeSubagent(
|
|
|
691
732
|
const dependencies = (task.dependsOn ?? [])
|
|
692
733
|
.map((dependency) => resultsById.get(dependency))
|
|
693
734
|
.filter((result): result is SingleResult => result !== undefined);
|
|
694
|
-
const
|
|
695
|
-
?
|
|
696
|
-
:
|
|
735
|
+
const verifierDependency = task.verifierFor
|
|
736
|
+
? resultsById.get(task.verifierFor)
|
|
737
|
+
: undefined;
|
|
738
|
+
const verifierStructuredResult =
|
|
739
|
+
verifierDependency?.structuredResult?.version === "pi-subagents:result:v2"
|
|
740
|
+
? verifierDependency.structuredResult
|
|
741
|
+
: 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
|
+
: "";
|
|
697
751
|
const displayTask = task.task;
|
|
698
|
-
const taskWithContext = truncateUtf8(
|
|
699
|
-
`${task.task}${dependencyContext}`,
|
|
700
|
-
DEFAULT_MAX_CONTEXT_BYTES,
|
|
701
|
-
).text;
|
|
702
|
-
const prepared = prepareTask(taskWithContext, task);
|
|
703
752
|
const target = workflowTargets[index];
|
|
704
753
|
const thinkingLevel = resolveThinkingLevel(task.agent, task.thinkingLevel);
|
|
754
|
+
let verifierTreeIdentity:
|
|
755
|
+
| Awaited<ReturnType<typeof captureWorkflowTreeIdentity>>
|
|
756
|
+
| undefined;
|
|
757
|
+
let verifierPreflightError: string | undefined;
|
|
758
|
+
let verifierPreflightCode: string | undefined;
|
|
759
|
+
if (task.verifierFor) {
|
|
760
|
+
const staged = workLedger.get(task.verifierFor);
|
|
761
|
+
if (!staged?.stagedTreeIdentity) {
|
|
762
|
+
verifierPreflightCode = "verification-tree-unavailable";
|
|
763
|
+
verifierPreflightError = `Verification target ${task.verifierFor} has no staged tree identity`;
|
|
764
|
+
} else {
|
|
765
|
+
try {
|
|
766
|
+
verifierTreeIdentity = await captureWorkflowTreeIdentity(target.cwd, { signal });
|
|
767
|
+
if (!sameWorkflowTreeIdentity(staged.stagedTreeIdentity, verifierTreeIdentity)) {
|
|
768
|
+
verifierPreflightCode = "verification-tree-mismatch";
|
|
769
|
+
verifierPreflightError =
|
|
770
|
+
"Workflow verification tree changed before verifier launch";
|
|
771
|
+
}
|
|
772
|
+
} catch (error) {
|
|
773
|
+
if (signal?.aborted) throw error;
|
|
774
|
+
verifierPreflightCode = "verification-tree-unavailable";
|
|
775
|
+
verifierPreflightError = error instanceof Error ? error.message : String(error);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const verificationTargetTask = task.verifierFor
|
|
780
|
+
? taskById.get(task.verifierFor)?.task
|
|
781
|
+
: undefined;
|
|
782
|
+
const verificationTargetContract = normalizeDelegationContract(
|
|
783
|
+
verificationTargetTask?.contract,
|
|
784
|
+
);
|
|
785
|
+
const verificationSuffix =
|
|
786
|
+
task.verifierFor && verifierTreeIdentity
|
|
787
|
+
? `\n\n${workflowVerificationInstruction(task.verifierFor, verifierTreeIdentity, {
|
|
788
|
+
acceptanceCriteria: [
|
|
789
|
+
...(verificationTargetTask?.acceptanceCriteria ?? []),
|
|
790
|
+
...(verificationTargetContract?.acceptanceCriteria ?? []),
|
|
791
|
+
],
|
|
792
|
+
requiredEvidence: verificationTargetContract?.requiredEvidence ?? [],
|
|
793
|
+
})}`
|
|
794
|
+
: "";
|
|
795
|
+
const baseTask = `${task.task}${dependencyContext}`;
|
|
796
|
+
const baseBudget = Math.max(
|
|
797
|
+
0,
|
|
798
|
+
DEFAULT_MAX_CONTEXT_BYTES - Buffer.byteLength(verificationSuffix, "utf8"),
|
|
799
|
+
);
|
|
800
|
+
const taskWithContext = `${truncateUtf8(baseTask, baseBudget).text}${verificationSuffix}`;
|
|
801
|
+
const prepared = prepareTask(taskWithContext, task);
|
|
705
802
|
const startedItem = startWorkItem(workItemId, task.agent);
|
|
706
803
|
const acceptedTaskGeneration = startedItem?.taskGeneration ?? 0;
|
|
707
804
|
await persistWorkLedger();
|
|
@@ -737,9 +834,20 @@ export async function executeSubagent(
|
|
|
737
834
|
);
|
|
738
835
|
};
|
|
739
836
|
const maxAttempts = task.retryPolicy?.maxAttempts ?? 1;
|
|
740
|
-
let result: SingleResult | undefined
|
|
837
|
+
let result: SingleResult | undefined = verifierPreflightError
|
|
838
|
+
? attachTarget(
|
|
839
|
+
workflowFailureResult(
|
|
840
|
+
task.agent,
|
|
841
|
+
displayTask,
|
|
842
|
+
verifierPreflightCode ?? "verification-tree-unavailable",
|
|
843
|
+
verifierPreflightError,
|
|
844
|
+
thinkingLevel,
|
|
845
|
+
),
|
|
846
|
+
target,
|
|
847
|
+
)
|
|
848
|
+
: undefined;
|
|
741
849
|
let hedged = false;
|
|
742
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
850
|
+
for (let attempt = 1; !verifierPreflightError && attempt <= maxAttempts; attempt++) {
|
|
743
851
|
if (attempt > 1 && deadline !== undefined && Date.now() >= deadline) break;
|
|
744
852
|
const attempted = await runHedgedAttempt(runAttempt, signal, task.hedgeAfterMs);
|
|
745
853
|
hedged ||= attempted.hedged;
|
|
@@ -765,7 +873,123 @@ export async function executeSubagent(
|
|
|
765
873
|
};
|
|
766
874
|
}
|
|
767
875
|
resultsById.set(workItemId, result);
|
|
768
|
-
|
|
876
|
+
if (result.outcome?.status === "stale") {
|
|
877
|
+
// Cancellation or replacement already rotated and invalidated this generation.
|
|
878
|
+
} else if (task.verifierFor) {
|
|
879
|
+
const staged = workLedger.get(task.verifierFor);
|
|
880
|
+
const structured =
|
|
881
|
+
result.structuredResult?.version === "pi-subagents:result:v2"
|
|
882
|
+
? result.structuredResult
|
|
883
|
+
: undefined;
|
|
884
|
+
let failureReason: string | undefined = verifierPreflightError;
|
|
885
|
+
let failureCode: string | undefined = verifierPreflightCode;
|
|
886
|
+
let postVerifierIdentity = verifierTreeIdentity;
|
|
887
|
+
if (!failureReason) {
|
|
888
|
+
try {
|
|
889
|
+
postVerifierIdentity = await captureWorkflowTreeIdentity(target.cwd, { signal });
|
|
890
|
+
if (
|
|
891
|
+
!verifierTreeIdentity ||
|
|
892
|
+
!staged?.stagedTreeIdentity ||
|
|
893
|
+
!sameWorkflowTreeIdentity(verifierTreeIdentity, postVerifierIdentity) ||
|
|
894
|
+
!sameWorkflowTreeIdentity(staged.stagedTreeIdentity, postVerifierIdentity)
|
|
895
|
+
) {
|
|
896
|
+
failureCode = "verification-tree-mismatch";
|
|
897
|
+
failureReason = "Workflow verification tree changed during verifier execution";
|
|
898
|
+
}
|
|
899
|
+
} catch (error) {
|
|
900
|
+
if (signal?.aborted) throw error;
|
|
901
|
+
failureCode = "verification-tree-unavailable";
|
|
902
|
+
failureReason = error instanceof Error ? error.message : String(error);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
if (
|
|
906
|
+
!failureReason &&
|
|
907
|
+
structured &&
|
|
908
|
+
staged?.acceptedExecutionPlanId &&
|
|
909
|
+
result.executionPlan?.id &&
|
|
910
|
+
postVerifierIdentity
|
|
911
|
+
) {
|
|
912
|
+
try {
|
|
913
|
+
const receipt = createWorkflowVerificationReceipt(structured, {
|
|
914
|
+
targetTaskId: staged.id,
|
|
915
|
+
targetTaskGeneration: staged.taskGeneration,
|
|
916
|
+
targetExecutionPlanId: staged.acceptedExecutionPlanId,
|
|
917
|
+
verifierTaskId: workItemId,
|
|
918
|
+
verifierTaskGeneration: acceptedTaskGeneration,
|
|
919
|
+
verifierExecutionPlanId: result.executionPlan.id,
|
|
920
|
+
treeIdentity: postVerifierIdentity,
|
|
921
|
+
sourceTruncated: result.truncated === true,
|
|
922
|
+
});
|
|
923
|
+
workLedger.completeVerification(workItemId, {
|
|
924
|
+
taskGeneration: acceptedTaskGeneration,
|
|
925
|
+
executionPlanId: result.executionPlan.id,
|
|
926
|
+
receipt,
|
|
927
|
+
});
|
|
928
|
+
if (receipt.decision !== "accept") {
|
|
929
|
+
const targetResult = resultsById.get(staged.id);
|
|
930
|
+
if (targetResult) {
|
|
931
|
+
targetResult.outcome = {
|
|
932
|
+
status: receipt.decision === "rework" ? "blocked" : "failed",
|
|
933
|
+
reasonCode:
|
|
934
|
+
receipt.decision === "rework"
|
|
935
|
+
? "verification-rework"
|
|
936
|
+
: "verification-rejected",
|
|
937
|
+
recoveryActions:
|
|
938
|
+
receipt.decision === "rework" ? ["replan", "verify"] : ["stop"],
|
|
939
|
+
retryable: false,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
} catch (error) {
|
|
944
|
+
failureCode = "verification-receipt-invalid";
|
|
945
|
+
failureReason = error instanceof Error ? error.message : String(error);
|
|
946
|
+
}
|
|
947
|
+
} else if (!failureReason) {
|
|
948
|
+
failureCode = "verification-receipt-invalid";
|
|
949
|
+
failureReason = "Workflow verifier did not return a current structured-v2 result";
|
|
950
|
+
}
|
|
951
|
+
if (failureReason) {
|
|
952
|
+
const reasonCode = failureCode ?? "verification-receipt-invalid";
|
|
953
|
+
result.outcome = {
|
|
954
|
+
status:
|
|
955
|
+
reasonCode === "verification-receipt-invalid" ? "contract-invalid" : "failed",
|
|
956
|
+
reasonCode,
|
|
957
|
+
recoveryActions:
|
|
958
|
+
reasonCode === "verification-receipt-invalid"
|
|
959
|
+
? ["repair-contract"]
|
|
960
|
+
: ["revalidate"],
|
|
961
|
+
retryable: false,
|
|
962
|
+
};
|
|
963
|
+
result.errorMessage = failureReason;
|
|
964
|
+
workLedger.failVerification(workItemId, reasonCode);
|
|
965
|
+
}
|
|
966
|
+
} else if (verificationTargetIds.has(workItemId) && !isResultError(result)) {
|
|
967
|
+
try {
|
|
968
|
+
const treeIdentity = await captureWorkflowTreeIdentity(target.cwd, { signal });
|
|
969
|
+
if (!result.executionPlan?.id) {
|
|
970
|
+
throw new Error("Verification-required producer has no accepted ExecutionPlan");
|
|
971
|
+
}
|
|
972
|
+
workLedger.stageForVerification(workItemId, {
|
|
973
|
+
taskGeneration: acceptedTaskGeneration,
|
|
974
|
+
executionPlanId: result.executionPlan.id,
|
|
975
|
+
artifacts: artifactsFromResult(result),
|
|
976
|
+
treeIdentity,
|
|
977
|
+
});
|
|
978
|
+
} catch (error) {
|
|
979
|
+
if (signal?.aborted) throw error;
|
|
980
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
981
|
+
result.outcome = {
|
|
982
|
+
status: "failed",
|
|
983
|
+
reasonCode: "verification-tree-unavailable",
|
|
984
|
+
recoveryActions: ["revalidate"],
|
|
985
|
+
retryable: false,
|
|
986
|
+
};
|
|
987
|
+
result.errorMessage = message;
|
|
988
|
+
settleWorkItem(workItemId, result, acceptedTaskGeneration);
|
|
989
|
+
}
|
|
990
|
+
} else {
|
|
991
|
+
settleWorkItem(workItemId, result, acceptedTaskGeneration);
|
|
992
|
+
}
|
|
769
993
|
await persistWorkLedger();
|
|
770
994
|
return result;
|
|
771
995
|
},
|
|
@@ -774,11 +998,19 @@ export async function executeSubagent(
|
|
|
774
998
|
if (batch.length === 0 || signal?.aborted) break;
|
|
775
999
|
}
|
|
776
1000
|
for (const item of workLedger.snapshot().items) {
|
|
777
|
-
if (
|
|
1001
|
+
if (
|
|
1002
|
+
item.state !== "pending" &&
|
|
1003
|
+
item.state !== "ready" &&
|
|
1004
|
+
item.state !== "awaiting-verification"
|
|
1005
|
+
) {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
778
1008
|
if (signal?.aborted) {
|
|
779
1009
|
workLedger.settle(item.id, "interrupted", "parent-aborted");
|
|
780
1010
|
} else if (deadline !== undefined && Date.now() >= deadline) {
|
|
781
1011
|
workLedger.settle(item.id, "blocked", "budget-exhausted");
|
|
1012
|
+
} else if (item.state === "awaiting-verification") {
|
|
1013
|
+
workLedger.settle(item.id, "blocked", "verification-not-completed");
|
|
782
1014
|
} else {
|
|
783
1015
|
const dependencyBlocked = item.dependencies.some(
|
|
784
1016
|
(dependency) => workLedger.get(dependency)?.state !== "completed",
|
|
@@ -793,8 +1025,26 @@ export async function executeSubagent(
|
|
|
793
1025
|
await persistWorkLedger();
|
|
794
1026
|
const results = resolvedWorkflowTasks.map((task) => {
|
|
795
1027
|
const completed = resultsById.get(task.id);
|
|
796
|
-
if (completed) return completed;
|
|
797
1028
|
const item = workLedger.get(task.id);
|
|
1029
|
+
if (completed) {
|
|
1030
|
+
if (item && item.state !== "completed" && !isResultError(completed)) {
|
|
1031
|
+
completed.outcome = {
|
|
1032
|
+
status:
|
|
1033
|
+
item.state === "needs-input"
|
|
1034
|
+
? "needs-input"
|
|
1035
|
+
: item.state === "interrupted"
|
|
1036
|
+
? "interrupted"
|
|
1037
|
+
: item.state === "failed"
|
|
1038
|
+
? "failed"
|
|
1039
|
+
: "blocked",
|
|
1040
|
+
reasonCode: item.outcomeReason ?? "verification-not-accepted",
|
|
1041
|
+
recoveryActions:
|
|
1042
|
+
item.outcomeReason === "verification-rework" ? ["replan", "verify"] : ["stop"],
|
|
1043
|
+
retryable: false,
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
return completed;
|
|
1047
|
+
}
|
|
798
1048
|
const outcomeStatus =
|
|
799
1049
|
item?.state === "interrupted"
|
|
800
1050
|
? "interrupted"
|
package/src/inspect.ts
CHANGED
|
@@ -537,6 +537,31 @@ function projectWorkflow(workflow: WorkItemLedgerSnapshot): Record<string, unkno
|
|
|
537
537
|
verified: artifact.verified,
|
|
538
538
|
})),
|
|
539
539
|
verificationAccepted: item.verificationAccepted,
|
|
540
|
+
stagedTreeIdentity: item.stagedTreeIdentity
|
|
541
|
+
? {
|
|
542
|
+
version: item.stagedTreeIdentity.version,
|
|
543
|
+
kind: item.stagedTreeIdentity.kind,
|
|
544
|
+
digest: item.stagedTreeIdentity.digest,
|
|
545
|
+
}
|
|
546
|
+
: undefined,
|
|
547
|
+
verificationReceipt: item.verificationReceipt
|
|
548
|
+
? {
|
|
549
|
+
version: item.verificationReceipt.version,
|
|
550
|
+
decision: item.verificationReceipt.decision,
|
|
551
|
+
targetTaskId: boundedPrivateText(item.verificationReceipt.targetTaskId, 256),
|
|
552
|
+
targetTaskGeneration: item.verificationReceipt.targetTaskGeneration,
|
|
553
|
+
targetExecutionPlanId: item.verificationReceipt.targetExecutionPlanId,
|
|
554
|
+
verifierTaskId: boundedPrivateText(item.verificationReceipt.verifierTaskId, 256),
|
|
555
|
+
verifierTaskGeneration: item.verificationReceipt.verifierTaskGeneration,
|
|
556
|
+
verifierExecutionPlanId: item.verificationReceipt.verifierExecutionPlanId,
|
|
557
|
+
treeIdentity: item.verificationReceipt.treeIdentity,
|
|
558
|
+
summary: boundedPrivateText(item.verificationReceipt.summary, 8 * 1024),
|
|
559
|
+
evidenceCount: item.verificationReceipt.evidence.length,
|
|
560
|
+
limitationCount: item.verificationReceipt.limitations.length,
|
|
561
|
+
createdAt: item.verificationReceipt.createdAt,
|
|
562
|
+
truncated: item.verificationReceipt.truncated,
|
|
563
|
+
}
|
|
564
|
+
: undefined,
|
|
540
565
|
outcomeReason: item.outcomeReason
|
|
541
566
|
? boundedPrivateText(item.outcomeReason, 2 * 1024)
|
|
542
567
|
: undefined,
|
|
@@ -14,6 +14,12 @@ export interface OrchestrationMetrics {
|
|
|
14
14
|
requestedTools: number;
|
|
15
15
|
effectiveRequestedTools: number;
|
|
16
16
|
permissionPrecision: number;
|
|
17
|
+
workerReportedVerification: number;
|
|
18
|
+
executorAcceptedVerification: number;
|
|
19
|
+
verificationRework: number;
|
|
20
|
+
verificationRejected: number;
|
|
21
|
+
verificationInvalid: number;
|
|
22
|
+
verificationTreeMismatch: number;
|
|
17
23
|
panelValidReviews?: number;
|
|
18
24
|
panelFailedReviews?: number;
|
|
19
25
|
panelBlockingObjections?: number;
|
|
@@ -65,6 +71,31 @@ export function calculateOrchestrationMetrics(
|
|
|
65
71
|
requestedTools,
|
|
66
72
|
effectiveRequestedTools,
|
|
67
73
|
permissionPrecision: requestedTools === 0 ? 1 : effectiveRequestedTools / requestedTools,
|
|
74
|
+
workerReportedVerification: results.filter(
|
|
75
|
+
(result, index) =>
|
|
76
|
+
!items[index]?.verifierFor &&
|
|
77
|
+
result.structuredResult?.version === "pi-subagents:result:v2" &&
|
|
78
|
+
result.structuredResult.verification.some(
|
|
79
|
+
(verification) => verification.status === "passed",
|
|
80
|
+
),
|
|
81
|
+
).length,
|
|
82
|
+
executorAcceptedVerification: items.filter((item) => item.verificationAccepted).length,
|
|
83
|
+
verificationRework: items.filter(
|
|
84
|
+
(item) => !item.verifierFor && item.verificationReceipt?.decision === "rework",
|
|
85
|
+
).length,
|
|
86
|
+
verificationRejected: items.filter(
|
|
87
|
+
(item) => !item.verifierFor && item.verificationReceipt?.decision === "reject",
|
|
88
|
+
).length,
|
|
89
|
+
verificationInvalid: items.filter(
|
|
90
|
+
(item) => !item.verifierFor && item.outcomeReason === "verification-receipt-invalid",
|
|
91
|
+
).length,
|
|
92
|
+
verificationTreeMismatch: items.filter(
|
|
93
|
+
(item) =>
|
|
94
|
+
!item.verifierFor &&
|
|
95
|
+
["verification-tree-mismatch", "verification-tree-unavailable"].includes(
|
|
96
|
+
item.outcomeReason ?? "",
|
|
97
|
+
),
|
|
98
|
+
).length,
|
|
68
99
|
...(panel
|
|
69
100
|
? {
|
|
70
101
|
panelValidReviews: panel.validReviewCount,
|
package/src/panel-execution.ts
CHANGED
|
@@ -389,7 +389,6 @@ export async function executePanel(input: PanelExecutionInput): Promise<PanelToo
|
|
|
389
389
|
},
|
|
390
390
|
]
|
|
391
391
|
: [],
|
|
392
|
-
verificationAccepted: false,
|
|
393
392
|
});
|
|
394
393
|
}
|
|
395
394
|
completed += 1;
|
|
@@ -554,7 +553,6 @@ export async function executePanel(input: PanelExecutionInput): Promise<PanelToo
|
|
|
554
553
|
workLedger.complete("synthesis", {
|
|
555
554
|
taskGeneration: synthesisWork.taskGeneration,
|
|
556
555
|
executionPlanId: synthesisResult.executionPlan?.id,
|
|
557
|
-
verificationAccepted: false,
|
|
558
556
|
});
|
|
559
557
|
}
|
|
560
558
|
await persistWork();
|
package/src/params.ts
CHANGED
|
@@ -106,7 +106,14 @@ const WorkflowTaskItem = Type.Object({
|
|
|
106
106
|
ownershipKeys: Type.Optional(Type.Array(Type.String({ maxLength: 256 }), { maxItems: 50 })),
|
|
107
107
|
acceptanceCriteria: Type.Optional(Type.Array(Type.String({ maxLength: 4096 }), { maxItems: 50 })),
|
|
108
108
|
integrationOwner: Type.Optional(Type.Boolean()),
|
|
109
|
-
verifierFor: Type.Optional(
|
|
109
|
+
verifierFor: Type.Optional(
|
|
110
|
+
Type.String({
|
|
111
|
+
minLength: 1,
|
|
112
|
+
maxLength: 256,
|
|
113
|
+
description:
|
|
114
|
+
"Target task ID for one distinct direct-dependent structured-v2 verifier. The executor gates target acceptance on a current exact-tree receipt.",
|
|
115
|
+
}),
|
|
116
|
+
),
|
|
110
117
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
|
|
111
118
|
timeoutMs: Type.Optional(TimeoutMs),
|
|
112
119
|
...TurnLimitFields,
|
package/src/subagents.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
formatAgentCatalog,
|
|
23
23
|
type SubagentSettings,
|
|
24
24
|
} from "./agents.js";
|
|
25
|
+
import { registerSubagentAutomation } from "./automation.js";
|
|
25
26
|
import { registerSubagentConfigCommand, registerSubagentConfigLifecycle } from "./config-ui.js";
|
|
26
27
|
import { registerSubagentConsult } from "./consult.js";
|
|
27
28
|
import { executeSubagent } from "./execution.js";
|
|
@@ -50,6 +51,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
50
51
|
const refreshBlockingCatalog = blockingEnabled
|
|
51
52
|
? registerBlockingSubagent(pi, () => currentSettings)
|
|
52
53
|
: () => undefined;
|
|
54
|
+
if (blockingEnabled) registerSubagentAutomation(pi, { getSettings: () => currentSettings });
|
|
53
55
|
let refreshStatefulCatalog: (catalog: string) => void = () => undefined;
|
|
54
56
|
let refreshConsultCatalog: (catalog: string) => void = () => undefined;
|
|
55
57
|
|
|
@@ -6,6 +6,14 @@ export interface VerificationRiskInput {
|
|
|
6
6
|
requiredCapabilities: string[];
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export interface WorkflowVerificationTaskProjection {
|
|
10
|
+
id: string;
|
|
11
|
+
agent: string;
|
|
12
|
+
dependsOn?: readonly string[];
|
|
13
|
+
verifierFor?: string;
|
|
14
|
+
resultFormat?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
export function requiresIndependentVerification(input: VerificationRiskInput): boolean {
|
|
10
18
|
return (
|
|
11
19
|
input.contract?.admission?.verificationRequired === true ||
|
|
@@ -15,3 +23,45 @@ export function requiresIndependentVerification(input: VerificationRiskInput): b
|
|
|
15
23
|
(input.integrationOwner && input.contract?.sideEffectPolicy !== "read-only")
|
|
16
24
|
);
|
|
17
25
|
}
|
|
26
|
+
|
|
27
|
+
export function validateWorkflowVerificationGraph(
|
|
28
|
+
tasks: readonly WorkflowVerificationTaskProjection[],
|
|
29
|
+
requiredTargetIds: ReadonlySet<string>,
|
|
30
|
+
): void {
|
|
31
|
+
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
32
|
+
const verifierByTarget = new Map<string, WorkflowVerificationTaskProjection[]>();
|
|
33
|
+
for (const task of tasks) {
|
|
34
|
+
if (!task.verifierFor) continue;
|
|
35
|
+
const target = byId.get(task.verifierFor);
|
|
36
|
+
if (!target) throw new Error(`Workflow verifier ${task.id} targets a missing task`);
|
|
37
|
+
if (target.verifierFor) {
|
|
38
|
+
throw new Error(`Workflow verifier ${task.id} cannot verify another verifier`);
|
|
39
|
+
}
|
|
40
|
+
if (target.resultFormat !== "structured-v2") {
|
|
41
|
+
throw new Error(`Workflow verification target ${target.id} must request structured-v2`);
|
|
42
|
+
}
|
|
43
|
+
if (task.dependsOn?.length !== 1 || task.dependsOn[0] !== target.id) {
|
|
44
|
+
throw new Error(`Workflow verifier ${task.id} must depend directly and only on ${target.id}`);
|
|
45
|
+
}
|
|
46
|
+
if (task.agent === target.agent) {
|
|
47
|
+
throw new Error(`Workflow verifier ${task.id} must use a distinct agent`);
|
|
48
|
+
}
|
|
49
|
+
if (task.resultFormat !== "structured-v2") {
|
|
50
|
+
throw new Error(`Workflow verifier ${task.id} must request structured-v2`);
|
|
51
|
+
}
|
|
52
|
+
const entries = verifierByTarget.get(target.id) ?? [];
|
|
53
|
+
entries.push(task);
|
|
54
|
+
verifierByTarget.set(target.id, entries);
|
|
55
|
+
}
|
|
56
|
+
for (const targetId of requiredTargetIds) {
|
|
57
|
+
const verifiers = verifierByTarget.get(targetId) ?? [];
|
|
58
|
+
if (verifiers.length !== 1) {
|
|
59
|
+
throw new Error(`Workflow task ${targetId} requires exactly one independent verifier`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const [targetId, verifiers] of verifierByTarget) {
|
|
63
|
+
if (verifiers.length !== 1) {
|
|
64
|
+
throw new Error(`Workflow task ${targetId} must have exactly one verifier`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|