@bastani/atomic 0.9.14-alpha.4 → 0.9.14-alpha.5

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 (141) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/package.json +1 -1
  4. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/subagents/src/extension/schemas.ts +5 -0
  7. package/dist/builtin/subagents/src/runs/shared/long-running-guard.ts +3 -1
  8. package/dist/builtin/subagents/src/runs/shared/progress-trend.ts +69 -0
  9. package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +12 -1
  10. package/dist/builtin/subagents/src/shared/types-results.ts +2 -0
  11. package/dist/builtin/web-access/package.json +1 -1
  12. package/dist/builtin/workflows/CHANGELOG.md +41 -1
  13. package/dist/builtin/workflows/README.md +6 -5
  14. package/dist/builtin/workflows/builtin/adversarial-verification-prompts.ts +13 -5
  15. package/dist/builtin/workflows/builtin/adversarial-verification-runner.ts +376 -89
  16. package/dist/builtin/workflows/builtin/adversarial-verification.d.ts +30 -6
  17. package/dist/builtin/workflows/builtin/adversarial-verification.ts +14 -9
  18. package/dist/builtin/workflows/builtin/generate-and-filter-prompts.ts +26 -3
  19. package/dist/builtin/workflows/builtin/generate-and-filter-runner.ts +18 -14
  20. package/dist/builtin/workflows/builtin/goal-artifacts.ts +9 -8
  21. package/dist/builtin/workflows/builtin/goal-convergence.ts +87 -0
  22. package/dist/builtin/workflows/builtin/goal-ledger.ts +4 -0
  23. package/dist/builtin/workflows/builtin/goal-prompts.ts +2 -0
  24. package/dist/builtin/workflows/builtin/goal-reducer.ts +6 -1
  25. package/dist/builtin/workflows/builtin/goal-reverify.ts +305 -0
  26. package/dist/builtin/workflows/builtin/goal-runner.ts +75 -10
  27. package/dist/builtin/workflows/builtin/goal-schemas.ts +7 -0
  28. package/dist/builtin/workflows/builtin/goal-types.ts +6 -0
  29. package/dist/builtin/workflows/builtin/loop-until-done-runner.ts +94 -6
  30. package/dist/builtin/workflows/builtin/loop-until-done.d.ts +8 -0
  31. package/dist/builtin/workflows/builtin/loop-until-done.ts +15 -0
  32. package/dist/builtin/workflows/builtin/progress-scoring.ts +230 -0
  33. package/dist/builtin/workflows/builtin/ralph-core.ts +11 -0
  34. package/dist/builtin/workflows/builtin/ralph-review-gate.ts +1 -0
  35. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +2 -0
  36. package/dist/builtin/workflows/builtin/ralph-runner.ts +60 -10
  37. package/dist/builtin/workflows/builtin/selection-math.ts +156 -0
  38. package/dist/builtin/workflows/builtin/shared-prompts.ts +5 -0
  39. package/dist/builtin/workflows/builtin/tournament-prompts.ts +57 -75
  40. package/dist/builtin/workflows/builtin/tournament-runner.ts +384 -178
  41. package/dist/builtin/workflows/builtin/tournament.d.ts +46 -17
  42. package/dist/builtin/workflows/builtin/tournament.ts +66 -32
  43. package/dist/builtin/workflows/builtin/verification-criteria.ts +330 -0
  44. package/dist/builtin/workflows/builtin/verification-prompts.ts +206 -0
  45. package/dist/builtin/workflows/builtin/verification-usage.ts +44 -0
  46. package/dist/builtin/workflows/package.json +1 -1
  47. package/dist/builtin/workflows/skills/create-spec/SKILL.md +90 -30
  48. package/dist/builtin/workflows/skills/show-me/LICENSE.txt +21 -0
  49. package/dist/builtin/workflows/skills/show-me/SKILL.md +143 -0
  50. package/dist/builtin/workflows/src/authoring/workflow.ts +8 -0
  51. package/dist/builtin/workflows/src/authoring.d.ts +1 -1
  52. package/dist/builtin/workflows/src/durable/completed-catalog.ts +5 -2
  53. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +1 -1
  54. package/dist/builtin/workflows/src/durable/resume-eligibility.ts +5 -3
  55. package/dist/builtin/workflows/src/durable/run-timing.ts +41 -10
  56. package/dist/builtin/workflows/src/durable/tool-primitive.ts +24 -2
  57. package/dist/builtin/workflows/src/engine/options.ts +1 -0
  58. package/dist/builtin/workflows/src/engine/primitives/workflow.ts +12 -3
  59. package/dist/builtin/workflows/src/engine/run-budget.ts +308 -0
  60. package/dist/builtin/workflows/src/engine/run-returned-status.ts +8 -0
  61. package/dist/builtin/workflows/src/engine/run-tool-node-lifecycle.ts +6 -0
  62. package/dist/builtin/workflows/src/engine/run.ts +124 -2
  63. package/dist/builtin/workflows/src/engine/runtime.ts +9 -0
  64. package/dist/builtin/workflows/src/extension/config-file-loader.ts +6 -0
  65. package/dist/builtin/workflows/src/extension/config-loader.ts +24 -1
  66. package/dist/builtin/workflows/src/extension/dispatcher.ts +6 -5
  67. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +2 -0
  68. package/dist/builtin/workflows/src/extension/index.bundle.mjs +2975 -843
  69. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +51 -4
  70. package/dist/builtin/workflows/src/extension/public-types.ts +3 -1
  71. package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +7 -1
  72. package/dist/builtin/workflows/src/extension/runtime.ts +22 -10
  73. package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +5 -0
  74. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +1 -0
  75. package/dist/builtin/workflows/src/extension/workflow-schema.ts +16 -0
  76. package/dist/builtin/workflows/src/extension/workflow-status-summary.ts +44 -1
  77. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +10 -1
  78. package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +21 -9
  79. package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +14 -0
  80. package/dist/builtin/workflows/src/runs/foreground/executor-lifecycle.ts +15 -4
  81. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +62 -5
  82. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +4 -0
  83. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +2 -0
  84. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +3 -1
  85. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +10 -1
  86. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +1 -0
  87. package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +1 -0
  88. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +7 -0
  89. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +7 -0
  90. package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +1 -0
  91. package/dist/builtin/workflows/src/shared/budget-meter.ts +34 -0
  92. package/dist/builtin/workflows/src/shared/budget.d.ts +67 -0
  93. package/dist/builtin/workflows/src/shared/budget.ts +127 -0
  94. package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +92 -8
  95. package/dist/builtin/workflows/src/shared/persistence-restore.ts +11 -1
  96. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +15 -3
  97. package/dist/builtin/workflows/src/shared/returned-run-status.ts +35 -2
  98. package/dist/builtin/workflows/src/shared/store-public-types.ts +4 -1
  99. package/dist/builtin/workflows/src/shared/store-run-methods.ts +8 -1
  100. package/dist/builtin/workflows/src/shared/store-stage-methods.ts +1 -0
  101. package/dist/builtin/workflows/src/shared/store-types.ts +24 -0
  102. package/dist/builtin/workflows/src/shared/types.ts +3 -0
  103. package/dist/builtin/workflows/src/shared/workflow-artifacts.ts +1 -0
  104. package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +3 -0
  105. package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +3 -0
  106. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  107. package/dist/core/atomic-guide-command.js +1 -0
  108. package/dist/core/atomic-guide-command.js.map +1 -1
  109. package/dist/core/extensions/ui-types.d.ts +13 -3
  110. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  111. package/dist/core/extensions/ui-types.js +15 -3
  112. package/dist/core/extensions/ui-types.js.map +1 -1
  113. package/dist/core/slash-commands.d.ts.map +1 -1
  114. package/dist/core/slash-commands.js +33 -3
  115. package/dist/core/slash-commands.js.map +1 -1
  116. package/dist/main-deferred-startup.d.ts.map +1 -1
  117. package/dist/main-deferred-startup.js +6 -2
  118. package/dist/main-deferred-startup.js.map +1 -1
  119. package/dist/modes/interactive/interactive-startup.js +4 -0
  120. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  121. package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
  122. package/dist/modes/interactive/interactive-tui.js +19 -1
  123. package/dist/modes/interactive/interactive-tui.js.map +1 -1
  124. package/dist/modes/interactive-engine/isolated-runtime.d.ts +7 -0
  125. package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
  126. package/dist/modes/interactive-engine/isolated-runtime.js +94 -37
  127. package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
  128. package/dist/modes/rpc/rpc-client.d.ts +1 -0
  129. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  130. package/dist/modes/rpc/rpc-client.js +15 -2
  131. package/dist/modes/rpc/rpc-client.js.map +1 -1
  132. package/dist/modes/rpc/rpc-input-scheduler.d.ts +3 -2
  133. package/dist/modes/rpc/rpc-input-scheduler.d.ts.map +1 -1
  134. package/dist/modes/rpc/rpc-input-scheduler.js +5 -2
  135. package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
  136. package/docs/extensions.md +1 -1
  137. package/docs/quickstart.md +1 -0
  138. package/docs/skills.md +4 -0
  139. package/docs/workflows.md +74 -10
  140. package/npm-shrinkwrap.json +29 -29
  141. package/package.json +2 -2
@@ -1,104 +1,391 @@
1
- import { writeFile } from "node:fs/promises";
1
+ import { readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- import { Type, type Static } from "typebox";
4
- import type { WorkflowRunContext, WorkflowSerializableValue } from "../src/shared/types.js";
5
- import { renderReducerPrompt, renderRepairPrompt, renderVerifierPrompt, renderWorkerPrompt } from "./adversarial-verification-prompts.js";
3
+ import { Type } from "typebox";
4
+ import type { WorkflowRunContext, WorkflowSerializableValue, WorkflowTaskResult } from "../src/shared/types.js";
5
+ import {
6
+ renderConsolidatorPrompt,
7
+ renderRepairPrompt,
8
+ renderWorkerPrompt,
9
+ } from "./adversarial-verification-prompts.js";
10
+ import {
11
+ build_scoring_prompt,
12
+ scoring_prompt_reads,
13
+ warm_first_fan_out,
14
+ type ScoringCandidate,
15
+ type SharedHead,
16
+ } from "./verification-prompts.js";
6
17
  import { stableArtifactRoot } from "./pattern-artifact-root.js";
18
+ import { fold_usage } from "./verification-usage.js";
19
+ import {
20
+ decide_verification,
21
+ normalize_criteria,
22
+ parse_rubric,
23
+ VERIFICATION_SCALE,
24
+ type Criterion,
25
+ type Criteria,
26
+ type CriterionScore,
27
+ type VerificationDecision,
28
+ } from "./verification-criteria.js";
7
29
 
8
- const verifierSchema = Type.Object({
9
- verdict: Type.Union([Type.Literal("pass"), Type.Literal("fail")]),
10
- evidence: Type.Array(Type.String()),
11
- blocking_findings: Type.Array(Type.String()),
30
+ export const DEFAULT_CRITERIA = {
31
+ task_fit: "The candidate satisfies the literal task.",
32
+ evidence: "Important claims cite observable evidence, and file findings cite file:line where applicable.",
33
+ completeness: "Relevant validation is executed and reported with commands run and observed output, and no blocking correctness, safety, or completeness gap remains.",
34
+ };
35
+
36
+ // V2 deliberately requires every fanned-out cell to return a schema-valid score
37
+ // after its bounded re-ask wave. A missing score is loudly Indeterminate rather
38
+ // than silently narrowing the mean, and quorum is not caller-configurable.
39
+ const QUORUM_FRACTION = 1;
40
+
41
+ const criterionScoreSchema = Type.Object({
42
+ criterion_id: Type.String(),
43
+ score: VERIFICATION_SCALE.schema,
44
+ evidence: Type.Array(Type.String()),
45
+ findings: Type.Array(Type.Object({
46
+ finding: Type.String(),
47
+ severity: Type.Union([Type.Literal("veto"), Type.Literal("blocking"), Type.Literal("note")]),
48
+ }, { additionalProperties: false })),
12
49
  }, { additionalProperties: false });
13
- const reducerSchema = Type.Object({
14
- decision: Type.Union([Type.Literal("accept"), Type.Literal("reject"), Type.Literal("repair")]),
15
- rationale: Type.String(),
16
- remaining_work: Type.Array(Type.String()),
50
+ const consolidatorSchema = Type.Object({
51
+ repair_guidance: Type.String(),
52
+ remaining_work: Type.Array(Type.String()),
17
53
  }, { additionalProperties: false });
18
54
 
19
- type VerifierDecision = Static<typeof verifierSchema>;
20
- type ReducerDecision = Static<typeof reducerSchema>;
21
- type Inputs = { readonly task: string; readonly verifier_count: number; readonly max_repairs: number } & Record<string, WorkflowSerializableValue>;
55
+ type CriterionScoreReport = {
56
+ readonly criterion_id: string;
57
+ readonly score: number;
58
+ readonly evidence: readonly string[];
59
+ readonly findings: readonly {
60
+ readonly finding: string;
61
+ readonly severity: "veto" | "blocking" | "note";
62
+ }[];
63
+ };
64
+ type ConsolidatedReport = {
65
+ readonly repair_guidance: string;
66
+ readonly remaining_work: readonly string[];
67
+ };
68
+ type Inputs = {
69
+ readonly task: string;
70
+ readonly verifier_count: number;
71
+ readonly max_repairs: number;
72
+ readonly criteria?: string | Record<string, string>;
73
+ readonly accept_mean?: number;
74
+ readonly reask_limit?: number;
75
+ };
22
76
  export type AdversarialVerificationResult = {
23
- readonly result: string;
24
- readonly approved: boolean;
25
- readonly repairs_completed: number;
26
- readonly candidate_path: string;
27
- readonly review_report_path: string;
28
- readonly verifier_artifact_paths: string[];
29
- readonly artifact_dir: string;
30
- readonly remaining_work: string[];
77
+ readonly approved: boolean;
78
+ readonly mean_score: number;
79
+ readonly score_table_path: string;
80
+ readonly repairs_completed: number;
81
+ readonly candidate_path: string;
82
+ readonly review_report_path: string;
83
+ readonly remaining_work: string[];
84
+ };
85
+
86
+ type VerificationCell = {
87
+ readonly criterion: Criterion;
88
+ readonly verifierIndex: number;
89
+ readonly name: string;
90
+ readonly artifactPath: string;
91
+ };
92
+ type ValidResult = {
93
+ readonly cell: VerificationCell;
94
+ readonly report: CriterionScoreReport;
95
+ readonly artifactPath: string;
96
+ };
97
+ type InvalidArtifact = {
98
+ readonly invalid: true;
99
+ readonly stage: string;
31
100
  };
32
101
 
33
- function structured<T extends WorkflowSerializableValue>(value: WorkflowSerializableValue | undefined, guard: (candidate: WorkflowSerializableValue) => candidate is T): T | undefined {
34
- return value !== undefined && guard(value) ? value : undefined;
35
- }
36
102
  function isRecord(value: WorkflowSerializableValue): value is Record<string, WorkflowSerializableValue> {
37
- return typeof value === "object" && value !== null && !Array.isArray(value);
103
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38
104
  }
39
- function isVerifier(value: WorkflowSerializableValue): value is VerifierDecision {
40
- return isRecord(value) && (value.verdict === "pass" || value.verdict === "fail") && Array.isArray(value.evidence) && value.evidence.every((item) => typeof item === "string") && Array.isArray(value.blocking_findings) && value.blocking_findings.every((item) => typeof item === "string");
105
+
106
+ function hasOnlyKeys(value: Record<string, WorkflowSerializableValue>, keys: readonly string[]): boolean {
107
+ const actual = Object.keys(value);
108
+ return actual.length === keys.length && keys.every((key) => key in value);
41
109
  }
42
- function isReducer(value: WorkflowSerializableValue): value is ReducerDecision {
43
- return isRecord(value) && (value.decision === "accept" || value.decision === "reject" || value.decision === "repair") && typeof value.rationale === "string" && Array.isArray(value.remaining_work) && value.remaining_work.every((item) => typeof item === "string");
110
+
111
+ function isStringArray(value: WorkflowSerializableValue | undefined): value is string[] {
112
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
44
113
  }
45
- const INVALID_VERIFIER_REPORT: VerifierDecision = {
46
- verdict: "fail",
47
- evidence: [],
48
- blocking_findings: ["Verifier stage produced no valid structured report."],
49
- };
114
+
115
+ function isStringRecord(value: WorkflowSerializableValue): value is Record<string, string> {
116
+ return isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
117
+ }
118
+
119
+ function isCriterionFinding(value: WorkflowSerializableValue): boolean {
120
+ return isRecord(value)
121
+ && hasOnlyKeys(value, ["finding", "severity"])
122
+ && typeof value.finding === "string"
123
+ && (value.severity === "veto" || value.severity === "blocking" || value.severity === "note");
124
+ }
125
+
126
+ function isCriterionScore(value: WorkflowSerializableValue | undefined, criterionId: string): value is CriterionScoreReport {
127
+ return value !== undefined
128
+ && isRecord(value)
129
+ && hasOnlyKeys(value, ["criterion_id", "score", "evidence", "findings"])
130
+ && value.criterion_id === criterionId
131
+ && typeof value.score === "number"
132
+ && Number.isInteger(value.score)
133
+ && value.score >= VERIFICATION_SCALE.min
134
+ && value.score <= VERIFICATION_SCALE.max
135
+ && isStringArray(value.evidence)
136
+ && Array.isArray(value.findings)
137
+ && value.findings.every(isCriterionFinding);
138
+ }
139
+
140
+ function isConsolidatedReport(value: WorkflowSerializableValue | undefined): value is ConsolidatedReport {
141
+ return value !== undefined
142
+ && isRecord(value)
143
+ && hasOnlyKeys(value, ["repair_guidance", "remaining_work"])
144
+ && typeof value.repair_guidance === "string"
145
+ && isStringArray(value.remaining_work);
146
+ }
147
+
148
+ function structured<T extends WorkflowSerializableValue>(
149
+ value: WorkflowSerializableValue | undefined,
150
+ guard: (candidate: WorkflowSerializableValue | undefined) => candidate is T,
151
+ ): T | undefined {
152
+ return guard(value) ? value : undefined;
153
+ }
154
+
155
+ function resolveCriteria(value: WorkflowSerializableValue | undefined): Criteria {
156
+ if (value === undefined) {
157
+ return { groundTruthNote: "", criteria: normalize_criteria(DEFAULT_CRITERIA) };
158
+ }
159
+ if (typeof value === "string") return parse_rubric(value);
160
+ if (isStringRecord(value)) return { groundTruthNote: "", criteria: normalize_criteria(value) };
161
+ throw new TypeError("criteria must be a criteria.md string or a record of criterion descriptions");
162
+ }
163
+
164
+ function renderCriteriaMarkdown(criteria: Criteria): string {
165
+ const lines = ["# Verification criteria"];
166
+ if (criteria.groundTruthNote.length > 0) lines.push("## Ground Truth Note", criteria.groundTruthNote);
167
+ lines.push("## Criteria");
168
+ for (const item of criteria.criteria) {
169
+ lines.push(`### ${item.name} {#${item.id}}`, item.description);
170
+ }
171
+ return `${lines.join("\n\n")}\n`;
172
+ }
173
+
174
+ function meanScore(scores: readonly CriterionScoreReport[]): number {
175
+ if (scores.length === 0) return 0;
176
+ return scores.reduce((total, score) => total + score.score, 0) / scores.length;
177
+ }
178
+
179
+ function toCriterionScore(report: CriterionScoreReport): CriterionScore {
180
+ return {
181
+ criterionId: report.criterion_id,
182
+ score: report.score,
183
+ evidence: report.evidence,
184
+ findings: report.findings,
185
+ };
186
+ }
187
+
188
+ function findingText(decision: Extract<VerificationDecision, { kind: "repair" }>): string[] {
189
+ return decision.findings.map((finding) => finding.finding);
190
+ }
191
+
192
+ function quorumEvidence(missing: number, invalidCount: number, expectedCount: number, reaskLimit: number): string {
193
+ return `Quorum failure: ${missing} of ${expectedCount} criterion scores remain missing after ${reaskLimit} re-ask wave(s); ${invalidCount} report attempts were invalid or missing.`;
194
+ }
195
+
196
+ function stageName(cell: VerificationCell, reaskWave: number): string {
197
+ return reaskWave === 0 ? cell.name : `${cell.name}-reask-${reaskWave}`;
198
+ }
199
+
200
+ function verifierOutputFormat(criterionId: string): string {
201
+ return `Call structured_output with criterion_id (set to ${criterionId}), score (1–20), evidence (string array), and findings containing finding and severity (veto, blocking, or note).`;
202
+ }
203
+
204
+ function scoreStep(
205
+ cell: VerificationCell,
206
+ reaskWave: number,
207
+ head: SharedHead,
208
+ criteriaPath: string,
209
+ ) {
210
+ const promptHead: SharedHead = {
211
+ ...head,
212
+ outputFormat: verifierOutputFormat(cell.criterion.id),
213
+ };
214
+ return {
215
+ name: stageName(cell, reaskWave),
216
+ prompt: build_scoring_prompt(promptHead, cell.criterion),
217
+ context: "fresh" as const,
218
+ reads: [criteriaPath, ...scoring_prompt_reads(promptHead)],
219
+ schema: criterionScoreSchema,
220
+ };
221
+ }
222
+
223
+ async function writeInvalid(path: string, stage: string): Promise<void> {
224
+ const marker: InvalidArtifact = { invalid: true, stage };
225
+ await writeFile(path, `${JSON.stringify(marker, null, 2)}\n`);
226
+ }
227
+
50
228
  export async function runAdversarialVerification(ctx: WorkflowRunContext<Inputs>): Promise<AdversarialVerificationResult> {
51
- const root = await stableArtifactRoot(ctx, "adversarial-verification");
52
- const rubricPath = join(root, "rubric.md");
53
- const candidatePath = join(root, "candidate.md");
54
- await writeFile(rubricPath, ["# Verification rubric", "- The candidate satisfies the literal task.", "- Important claims cite observable evidence.", "- Relevant validation is executed and reported with commands run and observed output.", "- File findings cite file:line evidence where applicable.", "- No blocking correctness, safety, or completeness gap remains."].join("\n"));
55
- await ctx.task("worker", { prompt: renderWorkerPrompt(ctx.inputs.task), context: "fresh", output: candidatePath, outputMode: "file-only" });
56
-
57
- let repairsCompleted = 0;
58
- let reviewReportPath!: string;
59
- let verifierArtifactPaths: string[] = [];
60
- let decision: ReducerDecision = { decision: "reject", rationale: "No valid reducer decision was produced.", remaining_work: ["Reducer did not return a valid structured decision."] };
61
- for (;;) {
62
- verifierArtifactPaths = Array.from({ length: ctx.inputs.verifier_count }, (_, index) => join(root, `verification-${repairsCompleted}-${index + 1}.json`));
63
- const reports = await ctx.parallel(verifierArtifactPaths.map((_, index) => ({
64
- name: `verifier-${repairsCompleted}-${index + 1}`,
65
- prompt: renderVerifierPrompt(ctx.inputs.task, candidatePath, rubricPath),
66
- context: "fresh" as const,
67
- reads: [candidatePath, rubricPath],
68
- schema: verifierSchema,
69
- })), { concurrency: Math.min(ctx.inputs.verifier_count, 4), failFast: false });
70
- // Verifier reports are inter-stage data: the reducer reads schema-shaped
71
- // JSON from these paths, so the runner persists the structured decisions
72
- // itself rather than routing them through the stage artifact channel.
73
- await Promise.all(verifierArtifactPaths.map(async (path, index) => {
74
- const report = structured(reports[index]?.structured, isVerifier) ?? INVALID_VERIFIER_REPORT;
75
- await writeFile(path, `${JSON.stringify(report, null, 2)}\n`);
76
- }));
77
- const validReports = reports.map((report) => structured(report.structured, isVerifier)).filter((report): report is VerifierDecision => report !== undefined);
78
- const allVerifiersPassed = validReports.length === ctx.inputs.verifier_count && validReports.every((report) => report.verdict === "pass");
79
- await writeFile(join(root, `verification-summary-${repairsCompleted}.json`), JSON.stringify(validReports, null, 2));
80
- reviewReportPath = join(root, `review-${repairsCompleted}.json`);
81
- const reduced = await ctx.task(`reducer-${repairsCompleted}`, {
82
- prompt: renderReducerPrompt(ctx.inputs.task, candidatePath, verifierArtifactPaths, repairsCompleted, ctx.inputs.max_repairs),
83
- context: "fresh", reads: [candidatePath, rubricPath, ...verifierArtifactPaths], schema: reducerSchema,
84
- });
85
- decision = structured(reduced.structured, isReducer) ?? decision;
86
- // Same inter-stage contract as the verifier reports above: the repair
87
- // stage reads this path as the reducer's structured decision.
88
- await writeFile(reviewReportPath, `${JSON.stringify(decision, null, 2)}\n`);
89
- if (decision.decision === "accept" && !allVerifiersPassed) {
90
- const remaining = validReports.flatMap((report) => report.blocking_findings);
91
- decision = repairsCompleted < ctx.inputs.max_repairs
92
- ? { decision: "repair", rationale: "Independent verification did not unanimously pass.", remaining_work: remaining }
93
- : { decision: "reject", rationale: "Independent verification did not pass before the repair bound was exhausted.", remaining_work: remaining };
94
- }
95
- if (decision.decision === "repair" && repairsCompleted >= ctx.inputs.max_repairs) {
96
- decision = { ...decision, decision: "reject", rationale: `${decision.rationale} Repair bound exhausted.` };
97
- }
98
- if (decision.decision !== "repair") break;
99
- repairsCompleted += 1;
100
- await ctx.task(`repair-${repairsCompleted}`, { prompt: renderRepairPrompt(ctx.inputs.task, candidatePath, reviewReportPath), context: "fresh", reads: [candidatePath, reviewReportPath], output: candidatePath, outputMode: "file-only" });
101
- }
102
- const approved = decision.decision === "accept";
103
- return { result: decision.rationale, approved, repairs_completed: repairsCompleted, candidate_path: candidatePath, review_report_path: reviewReportPath, verifier_artifact_paths: verifierArtifactPaths, artifact_dir: root, remaining_work: approved ? [] : decision.remaining_work };
229
+ const root = await stableArtifactRoot(ctx, "adversarial-verification");
230
+ const criteriaPath = join(root, "criteria.md");
231
+ const candidatePath = join(root, "candidate.md");
232
+ const resolvedCriteria = resolveCriteria(ctx.inputs.criteria);
233
+ await writeFile(criteriaPath, renderCriteriaMarkdown(resolvedCriteria));
234
+ await ctx.task("worker", { prompt: renderWorkerPrompt(ctx.inputs.task), context: "fresh", output: candidatePath, outputMode: "file-only" });
235
+
236
+ const verifierCount = ctx.inputs.verifier_count ?? 3;
237
+ const maxRepairs = ctx.inputs.max_repairs ?? 2;
238
+ const acceptMean = ctx.inputs.accept_mean ?? 14;
239
+ const reaskLimit = Math.max(0, Math.floor(ctx.inputs.reask_limit ?? 1));
240
+ const expectedCount = resolvedCriteria.criteria.length * verifierCount;
241
+ let repairsCompleted = 0;
242
+ let consecutiveIndeterminate = 0;
243
+ let finalDecision: VerificationDecision;
244
+ let finalMean = 0;
245
+ let scoreTablePath: string;
246
+ let reviewReportPath: string;
247
+ let remainingWork: string[] = [];
248
+
249
+ for (let round = 0; ; round += 1) {
250
+ const candidateBody = await readFile(candidatePath, "utf8");
251
+ const scoringHead: SharedHead = {
252
+ task: ctx.inputs.task,
253
+ groundTruthNote: resolvedCriteria.groundTruthNote,
254
+ candidates: [{ path: candidatePath, body: candidateBody } satisfies ScoringCandidate],
255
+ scaleAnchors: VERIFICATION_SCALE.anchors,
256
+ };
257
+ const cells: VerificationCell[] = [];
258
+ for (const criterion of resolvedCriteria.criteria) {
259
+ for (let index = 0; index < verifierCount; index += 1) {
260
+ const name = `verifier-${round}-${criterion.id}-${index + 1}`;
261
+ cells.push({
262
+ criterion,
263
+ verifierIndex: index + 1,
264
+ name,
265
+ artifactPath: join(root, `verification-${round}-${criterion.id}-${index + 1}.json`),
266
+ });
267
+ }
268
+ }
269
+
270
+ const runWave = async (
271
+ pending: readonly VerificationCell[],
272
+ reaskWave: number,
273
+ ): Promise<{ readonly valid: readonly ValidResult[]; readonly invalid: readonly VerificationCell[]; readonly results: readonly WorkflowTaskResult[] }> => {
274
+ if (pending.length === 0) return { valid: [], invalid: [], results: [] };
275
+ const reports = await warm_first_fan_out(
276
+ ctx,
277
+ pending.map((cell) => scoreStep(cell, reaskWave, scoringHead, criteriaPath)),
278
+ () => scoringHead,
279
+ { concurrency: Math.min(pending.length, 4), failFast: false },
280
+ );
281
+ const byName = new Map<string, (typeof reports)[number]>();
282
+ for (const report of reports) {
283
+ const name = report.name ?? report.stageName;
284
+ if (name !== undefined) byName.set(name, report);
285
+ }
286
+ const valid: ValidResult[] = [];
287
+ const invalid: VerificationCell[] = [];
288
+ for (const cell of pending) {
289
+ const report = byName.get(stageName(cell, reaskWave));
290
+ const artifactPath = reaskWave === 0
291
+ ? cell.artifactPath
292
+ : cell.artifactPath.replace(/\.json$/, `-reask-${reaskWave}.json`);
293
+ if (isCriterionScore(report?.structured, cell.criterion.id)) {
294
+ await writeFile(artifactPath, `${JSON.stringify(report.structured, null, 2)}\n`);
295
+ valid.push({ cell, report: report.structured, artifactPath });
296
+ } else {
297
+ await writeInvalid(artifactPath, stageName(cell, reaskWave));
298
+ invalid.push(cell);
299
+ }
300
+ }
301
+ return { valid, invalid, results: reports };
302
+ };
303
+
304
+ const roundResults: WorkflowTaskResult[] = [];
305
+ let pending = [...cells];
306
+ const validResults: ValidResult[] = [];
307
+ let invalidCount = 0;
308
+ for (let reaskWave = 0; reaskWave <= reaskLimit; reaskWave += 1) {
309
+ const wave = await runWave(pending, reaskWave);
310
+ roundResults.push(...wave.results);
311
+ validResults.push(...wave.valid);
312
+ invalidCount += wave.invalid.length;
313
+ pending = [...wave.invalid];
314
+ if (pending.length === 0) break;
315
+ }
316
+
317
+ const validReportsByCell = new Map(validResults.map((item) => [item.cell.name, item.report]));
318
+ const scoreReports = cells.flatMap((cell) => {
319
+ const report = validReportsByCell.get(cell.name);
320
+ return report === undefined ? [] : [report];
321
+ });
322
+ const scores = scoreReports.map(toCriterionScore);
323
+ const roundResult = { scores, invalidCount, expectedCount };
324
+ const decision = decide_verification(roundResult, { acceptMean, quorumFraction: QUORUM_FRACTION });
325
+ finalDecision = decision;
326
+ finalMean = meanScore(scoreReports);
327
+ scoreTablePath = join(root, `verification-summary-${round}.json`);
328
+ reviewReportPath = join(root, `review-${round}.json`);
329
+ const summary = {
330
+ scores: scoreReports,
331
+ mean: finalMean,
332
+ invalidCount,
333
+ decision,
334
+ usage: fold_usage(roundResults),
335
+ };
336
+
337
+ if (decision.kind === "accept") {
338
+ remainingWork = [];
339
+ await writeFile(scoreTablePath, `${JSON.stringify(summary, null, 2)}\n`);
340
+ await writeFile(reviewReportPath, `${JSON.stringify({ decision, remaining_work: [] }, null, 2)}\n`);
341
+ break;
342
+ }
343
+
344
+ if (decision.kind === "indeterminate") {
345
+ consecutiveIndeterminate += 1;
346
+ remainingWork = [quorumEvidence(decision.missing, invalidCount, expectedCount, reaskLimit)];
347
+ await writeFile(scoreTablePath, `${JSON.stringify(summary, null, 2)}\n`);
348
+ await writeFile(reviewReportPath, `${JSON.stringify({ decision, evidence: remainingWork, remaining_work: remainingWork }, null, 2)}\n`);
349
+ if (consecutiveIndeterminate >= 2) break;
350
+ continue;
351
+ }
352
+
353
+ consecutiveIndeterminate = 0;
354
+ const confirmedFindings = findingText(decision);
355
+ const scorePaths = validResults.map((item) => item.artifactPath);
356
+ const consolidated = await ctx.task(`consolidate-findings-${round}`, {
357
+ prompt: renderConsolidatorPrompt(ctx.inputs.task, candidatePath, scorePaths, repairsCompleted, maxRepairs),
358
+ context: "fresh",
359
+ reads: [candidatePath, criteriaPath, ...scorePaths],
360
+ schema: consolidatorSchema,
361
+ });
362
+ const fallbackRemaining = confirmedFindings.length > 0
363
+ ? confirmedFindings
364
+ : [`Mean score ${decision.mean} is below the acceptance threshold ${acceptMean}.`];
365
+ const consolidatedReport: ConsolidatedReport = structured(consolidated.structured, isConsolidatedReport)
366
+ ?? { repair_guidance: "Confirmed verifier findings require repair.", remaining_work: fallbackRemaining };
367
+ remainingWork = fallbackRemaining;
368
+ await writeFile(scoreTablePath, `${JSON.stringify(summary, null, 2)}\n`);
369
+ await writeFile(reviewReportPath, `${JSON.stringify({ ...consolidatedReport, remaining_work: remainingWork }, null, 2)}\n`);
370
+
371
+ if (repairsCompleted >= maxRepairs) break;
372
+ repairsCompleted += 1;
373
+ await ctx.task(`repair-${repairsCompleted}`, {
374
+ prompt: renderRepairPrompt(ctx.inputs.task, candidatePath, reviewReportPath),
375
+ context: "fresh",
376
+ reads: [candidatePath, reviewReportPath],
377
+ output: candidatePath,
378
+ outputMode: "file-only",
379
+ });
380
+ }
381
+
382
+ return {
383
+ approved: finalDecision.kind === "accept",
384
+ mean_score: finalMean,
385
+ score_table_path: scoreTablePath,
386
+ repairs_completed: repairsCompleted,
387
+ candidate_path: candidatePath,
388
+ review_report_path: reviewReportPath,
389
+ remaining_work: finalDecision.kind === "accept" ? [] : remainingWork,
390
+ };
104
391
  }
@@ -1,11 +1,35 @@
1
1
  import type { WorkflowDefinition, WorkflowInputValues, WorkflowOutputValues } from "../src/authoring.js";
2
- export type AdversarialVerificationInputs = WorkflowInputValues & { readonly task: string; readonly verifier_count: number; readonly max_repairs: number };
3
- export type AdversarialVerificationRunInputs = WorkflowInputValues & { readonly task: string; readonly verifier_count?: number; readonly max_repairs?: number };
2
+
3
+ export type AdversarialVerificationCriteria = string | Record<string, string>;
4
+ export type AdversarialVerificationInputs = WorkflowInputValues & {
5
+ readonly task: string;
6
+ readonly verifier_count: number;
7
+ readonly max_repairs: number;
8
+ readonly criteria: AdversarialVerificationCriteria;
9
+ readonly accept_mean: number;
10
+ readonly reask_limit: number;
11
+ };
12
+ export type AdversarialVerificationRunInputs = WorkflowInputValues & {
13
+ readonly task: string;
14
+ readonly verifier_count?: number;
15
+ readonly max_repairs?: number;
16
+ readonly criteria?: AdversarialVerificationCriteria;
17
+ readonly accept_mean?: number;
18
+ readonly reask_limit?: number;
19
+ };
4
20
  export type AdversarialVerificationOutputs = WorkflowOutputValues & {
5
- readonly result: string; readonly approved: boolean; readonly repairs_completed: number;
6
- readonly candidate_path: string; readonly review_report_path: string;
7
- readonly verifier_artifact_paths: string[]; readonly artifact_dir: string; readonly remaining_work: string[];
21
+ readonly approved: boolean;
22
+ readonly mean_score: number;
23
+ readonly score_table_path: string;
24
+ readonly repairs_completed: number;
25
+ readonly candidate_path: string;
26
+ readonly review_report_path: string;
27
+ readonly remaining_work: string[];
8
28
  };
9
- export type AdversarialVerificationDefinition = WorkflowDefinition<AdversarialVerificationInputs, AdversarialVerificationOutputs, AdversarialVerificationRunInputs>;
29
+ export type AdversarialVerificationDefinition = WorkflowDefinition<
30
+ AdversarialVerificationInputs,
31
+ AdversarialVerificationOutputs,
32
+ AdversarialVerificationRunInputs
33
+ >;
10
34
  declare const workflow: AdversarialVerificationDefinition;
11
35
  export default workflow;
@@ -1,29 +1,34 @@
1
1
  import { Type } from "typebox";
2
2
  import { workflow } from "../src/authoring/workflow.js";
3
3
  import { withSteeringPropagationContext } from "./steering-context.js";
4
- import { runAdversarialVerification } from "./adversarial-verification-runner.js";
4
+ import { DEFAULT_CRITERIA, runAdversarialVerification } from "./adversarial-verification-runner.js";
5
5
 
6
6
  export default workflow({
7
7
  name: "adversarial-verification",
8
- description: "Produce a candidate, challenge it with fresh-context rubric-based verifiers, and reduce their evidence through a bounded repair loop.",
8
+ description: "Produce a candidate, score independent per-criterion verifier reports, and apply a deterministic mean-and-veto gate with bounded repairs.",
9
9
  // The 15-minute default, stated rather than inherited: this is a per-workflow
10
10
  // product decision, so a future change to the global default must not silently
11
11
  // re-cadence a long autonomous run.
12
12
  heartbeatIntervalMinutes: 15,
13
13
  inputs: {
14
14
  task: Type.String({ description: "Task whose candidate result must be independently verified." }),
15
- verifier_count: Type.Integer({ minimum: 1, maximum: 5, default: 3, description: "Number of independent verifiers per review round." }),
15
+ verifier_count: Type.Integer({ minimum: 1, maximum: 5, default: 3, description: "Number of independent verifiers for each criterion per round." }),
16
16
  max_repairs: Type.Integer({ minimum: 0, maximum: 5, default: 2, description: "Maximum candidate repair rounds before rejection." }),
17
+ criteria: Type.Union([
18
+ Type.String(),
19
+ Type.Record(Type.String(), Type.String()),
20
+ ], { default: DEFAULT_CRITERIA, description: "Criteria record of name-to-description entries, or criteria.md markdown." }),
21
+ accept_mean: Type.Number({ default: 14, description: "Mean score required for acceptance on the 1–20 verification scale." }),
22
+ reask_limit: Type.Integer({ minimum: 0, default: 1, description: "Maximum bounded re-ask waves for invalid criterion reports." }),
17
23
  },
18
24
  outputs: {
19
- result: Type.String({ description: "Final reducer rationale." }),
20
- approved: Type.Boolean({ description: "Whether verification accepted the candidate." }),
25
+ approved: Type.Boolean({ description: "Whether the deterministic mean-and-veto gate accepted the candidate." }),
26
+ mean_score: Type.Number({ description: "Mean score of the final round's schema-valid criterion reports." }),
27
+ score_table_path: Type.String({ description: "Path to the final round per-criterion score summary." }),
21
28
  repairs_completed: Type.Integer({ description: "Number of repair rounds performed." }),
22
29
  candidate_path: Type.String({ description: "Path to the final candidate artifact." }),
23
- review_report_path: Type.String({ description: "Path to the final reducer report." }),
24
- verifier_artifact_paths: Type.Array(Type.String(), { description: "Paths to final-round verifier reports." }),
25
- artifact_dir: Type.String({ description: "Directory containing run artifacts." }),
26
- remaining_work: Type.Array(Type.String(), { description: "Unresolved blocking findings when not approved." }),
30
+ review_report_path: Type.String({ description: "Path to the final consolidated findings or quorum report." }),
31
+ remaining_work: Type.Array(Type.String(), { description: "Unresolved findings or quorum evidence when not approved." }),
27
32
  },
28
33
  run: async (ctx) => await runAdversarialVerification(withSteeringPropagationContext(ctx)),
29
34
  });
@@ -1,6 +1,14 @@
1
+ import type { ScoringCandidate, SharedHead } from "./verification-prompts.js";
2
+ import { build_scoring_prompt, scoring_prompt_reads } from "./verification-prompts.js";
3
+
1
4
  const GROUNDED_REPORTING = "Before reporting progress, audit each claim against a tool result from this session. Report only work you can point to evidence for; say so explicitly when something is unverified.";
2
5
  const READABLE_REPORT = "Lead with the outcome. Keep facts, decisions, caveats, and next steps; drop background and repetition. Use complete, readable sentences rather than compressed fragments.";
3
6
 
7
+ export interface RenderedJudgePrompt {
8
+ readonly prompt: string;
9
+ readonly reads: readonly string[];
10
+ }
11
+
4
12
  export function renderGeneratorPrompt(task: string, ordinal: number): string {
5
13
  return `<role>\nYou independently generate candidate ${ordinal}; do not imitate or assume other candidates.\n</role>\n\n<success_criteria>\nOne distinct, concrete candidate states its value, constraints, risks, and how it can be evaluated.\n</success_criteria>\n\n<stop_rules>\nStop after one self-contained, evaluable candidate; do not add alternatives.\n</stop_rules>\n\n<output_format>\nA candidate artifact with title, proposal, criteria-based rationale, risks, and evaluation evidence. ${READABLE_REPORT}\n${GROUNDED_REPORTING}\n</output_format>\n\n<objective>\n${task}\n</objective>`;
6
14
  }
@@ -9,10 +17,25 @@ export function renderFilterPrompt(task: string, candidatePaths: readonly string
9
17
  return `<artifacts>\nRead every candidate: ${candidatePaths.join(", ")}\n</artifacts>\n\n<role>\nYou deduplicate and filter independently generated candidates.\n</role>\n\n<rubric>\nFirst collapse substantively equivalent candidates. Then score fit to the task, feasibility, evidence, distinctiveness, and risk. Near-duplicates must not gain weight by repetition. Record every discarded candidate and a concrete reason.\n</rubric>\n\n<success_criteria>\nAt most ${shortlistSize} strongest distinct candidates remain, ranked by the rubric, and every discarded candidate has a concrete reason.\n</success_criteria>\n\n<stop_rules>\nStop after every candidate is shortlisted once or recorded as discarded.\n</stop_rules>\n\n<output_format>\nCall structured_output with shortlist (candidate artifact paths in ranked order) and discarded entries containing path and a concise, criteria-based reason.\n</output_format>\n\n<objective>\nSelect at most ${shortlistSize} strongest candidates for: ${task}\n</objective>`;
10
18
  }
11
19
 
12
- export function renderJudgePrompt(task: string, filterPath: string, shortlistSize: number): string {
13
- return `<artifacts>\nRead the filter report at ${filterPath} and every candidate path it references.\n</artifacts>\n\n<role>\nYou independently judge the filtered shortlist against the explicit rubric.\n</role>\n\n<rubric>\nCheck task fit, feasibility, evidence, distinctiveness, and material risk. Do not restore a duplicate merely because it is phrased differently.\n</rubric>\n\n<success_criteria>\nAt most ${shortlistSize} distinct candidate paths are ranked by rubric-grounded evidence.\n</success_criteria>\n\n<stop_rules>\nStop after evaluating every filtered candidate and ranking the qualifying paths.\n</stop_rules>\n\n<output_format>\nCall structured_output with shortlist and a concise, criteria-based rationale in complete sentences.\n</output_format>\n\n<objective>\nRank the candidates that best satisfy: ${task}\n</objective>`;
20
+ export function renderJudgePrompt(
21
+ task: string,
22
+ filterPath: string,
23
+ shortlistSize: number,
24
+ candidates: readonly ScoringCandidate[] = [],
25
+ ): RenderedJudgePrompt {
26
+ const head: SharedHead = {
27
+ task: `Rank at most ${shortlistSize} distinct candidate paths for: ${task}.`,
28
+ groundTruthNote: `Read the filter report at ${filterPath} and every candidate path it references before judging.`,
29
+ candidates,
30
+ outputFormat: "Call structured_output with shortlist (candidate paths in ranked order) and rationale (a concise, criteria-based explanation).",
31
+ };
32
+ const prompt = build_scoring_prompt(head, {
33
+ id: "filtered_shortlist",
34
+ name: "Filtered shortlist",
35
+ description: "Check task fit, feasibility, evidence, distinctiveness, and material risk; do not restore a duplicate merely because it is phrased differently.",
36
+ });
37
+ return { prompt, reads: [filterPath, ...scoring_prompt_reads(head)] };
14
38
  }
15
-
16
39
  export function renderFinalShortlistPrompt(task: string, decisionPath: string): string {
17
40
  return `<artifact>\nRead the authoritative selection at ${decisionPath}; follow its order and do not add candidates.\n</artifact>\n\n<role>\nYou present a concise, actionable final shortlist so the reader can choose the next evaluation without reading the selection session.\n</role>\n\n<success_criteria>\nEvery selected candidate appears once in authoritative order with its differentiator, evidence, tradeoffs, and recommended next evaluation.\n</success_criteria>\n\n<stop_rules>\nStop after presenting every selected candidate once; do not add or reorder candidates.\n</stop_rules>\n\n<output_format>\nRanked markdown shortlist with candidate path, differentiator, evidence, tradeoffs, and recommended next evaluation. ${READABLE_REPORT}\n${GROUNDED_REPORTING}\n</output_format>\n\n<objective>\nSummarize the selected candidates for: ${task}\n</objective>`;
18
41
  }