@bastani/atomic 0.9.14-alpha.3 → 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.
- package/CHANGELOG.md +23 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/extension/schemas.ts +5 -0
- package/dist/builtin/subagents/src/runs/shared/long-running-guard.ts +3 -1
- package/dist/builtin/subagents/src/runs/shared/progress-trend.ts +69 -0
- package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +12 -1
- package/dist/builtin/subagents/src/shared/types-results.ts +2 -0
- package/dist/builtin/web-access/CHANGELOG.md +10 -0
- package/dist/builtin/web-access/README.md +3 -6
- package/dist/builtin/web-access/index.ts +1 -2
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/web-access/web-search-config.ts +2 -8
- package/dist/builtin/web-access/web-search-tool.ts +2 -7
- package/dist/builtin/web-access/web-search-workflow.ts +10 -0
- package/dist/builtin/workflows/CHANGELOG.md +46 -0
- package/dist/builtin/workflows/README.md +6 -5
- package/dist/builtin/workflows/builtin/adversarial-verification-prompts.ts +13 -5
- package/dist/builtin/workflows/builtin/adversarial-verification-runner.ts +376 -89
- package/dist/builtin/workflows/builtin/adversarial-verification.d.ts +30 -6
- package/dist/builtin/workflows/builtin/adversarial-verification.ts +14 -9
- package/dist/builtin/workflows/builtin/generate-and-filter-prompts.ts +26 -3
- package/dist/builtin/workflows/builtin/generate-and-filter-runner.ts +18 -14
- package/dist/builtin/workflows/builtin/goal-artifacts.ts +9 -8
- package/dist/builtin/workflows/builtin/goal-convergence.ts +87 -0
- package/dist/builtin/workflows/builtin/goal-ledger.ts +4 -0
- package/dist/builtin/workflows/builtin/goal-prompts.ts +2 -0
- package/dist/builtin/workflows/builtin/goal-reducer.ts +6 -1
- package/dist/builtin/workflows/builtin/goal-reverify.ts +305 -0
- package/dist/builtin/workflows/builtin/goal-runner.ts +75 -10
- package/dist/builtin/workflows/builtin/goal-schemas.ts +7 -0
- package/dist/builtin/workflows/builtin/goal-types.ts +6 -0
- package/dist/builtin/workflows/builtin/loop-until-done-runner.ts +94 -6
- package/dist/builtin/workflows/builtin/loop-until-done.d.ts +8 -0
- package/dist/builtin/workflows/builtin/loop-until-done.ts +15 -0
- package/dist/builtin/workflows/builtin/progress-scoring.ts +230 -0
- package/dist/builtin/workflows/builtin/ralph-core.ts +11 -0
- package/dist/builtin/workflows/builtin/ralph-review-gate.ts +1 -0
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph-runner.ts +60 -10
- package/dist/builtin/workflows/builtin/selection-math.ts +156 -0
- package/dist/builtin/workflows/builtin/shared-prompts.ts +5 -0
- package/dist/builtin/workflows/builtin/tournament-prompts.ts +57 -75
- package/dist/builtin/workflows/builtin/tournament-runner.ts +384 -178
- package/dist/builtin/workflows/builtin/tournament.d.ts +46 -17
- package/dist/builtin/workflows/builtin/tournament.ts +66 -32
- package/dist/builtin/workflows/builtin/verification-criteria.ts +330 -0
- package/dist/builtin/workflows/builtin/verification-prompts.ts +206 -0
- package/dist/builtin/workflows/builtin/verification-usage.ts +44 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +90 -30
- package/dist/builtin/workflows/skills/show-me/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/show-me/SKILL.md +143 -0
- package/dist/builtin/workflows/src/authoring/workflow.ts +8 -0
- package/dist/builtin/workflows/src/authoring.d.ts +1 -1
- package/dist/builtin/workflows/src/durable/completed-catalog.ts +5 -2
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +1 -1
- package/dist/builtin/workflows/src/durable/resume-eligibility.ts +5 -3
- package/dist/builtin/workflows/src/durable/run-timing.ts +41 -10
- package/dist/builtin/workflows/src/durable/tool-primitive.ts +24 -2
- package/dist/builtin/workflows/src/engine/options.ts +1 -0
- package/dist/builtin/workflows/src/engine/primitives/workflow.ts +12 -3
- package/dist/builtin/workflows/src/engine/run-budget.ts +308 -0
- package/dist/builtin/workflows/src/engine/run-returned-status.ts +8 -0
- package/dist/builtin/workflows/src/engine/run-tool-node-lifecycle.ts +6 -0
- package/dist/builtin/workflows/src/engine/run.ts +124 -2
- package/dist/builtin/workflows/src/engine/runtime.ts +9 -0
- package/dist/builtin/workflows/src/extension/config-file-loader.ts +6 -0
- package/dist/builtin/workflows/src/extension/config-loader.ts +24 -1
- package/dist/builtin/workflows/src/extension/dispatcher.ts +6 -5
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +2 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +3033 -872
- package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +51 -4
- package/dist/builtin/workflows/src/extension/public-types.ts +3 -1
- package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +7 -1
- package/dist/builtin/workflows/src/extension/runtime.ts +22 -10
- package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +5 -0
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +1 -0
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +16 -0
- package/dist/builtin/workflows/src/extension/workflow-status-summary.ts +44 -1
- package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +10 -1
- package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +21 -9
- package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +14 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-lifecycle.ts +15 -4
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +62 -5
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +4 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +2 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +3 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +10 -1
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +1 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +1 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +7 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +7 -0
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +1 -0
- package/dist/builtin/workflows/src/shared/budget-meter.ts +34 -0
- package/dist/builtin/workflows/src/shared/budget.d.ts +67 -0
- package/dist/builtin/workflows/src/shared/budget.ts +127 -0
- package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +92 -8
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +11 -1
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +15 -3
- package/dist/builtin/workflows/src/shared/returned-run-status.ts +35 -2
- package/dist/builtin/workflows/src/shared/store-public-types.ts +4 -1
- package/dist/builtin/workflows/src/shared/store-run-methods.ts +8 -1
- package/dist/builtin/workflows/src/shared/store-stage-methods.ts +1 -0
- package/dist/builtin/workflows/src/shared/store-types.ts +24 -0
- package/dist/builtin/workflows/src/shared/types.ts +3 -0
- package/dist/builtin/workflows/src/shared/workflow-artifacts.ts +1 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +3 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +3 -0
- package/dist/builtin/workflows/src/tui/graph-theme.ts +11 -0
- package/dist/builtin/workflows/src/tui/graph-view-render.ts +19 -10
- package/dist/builtin/workflows/src/tui/tool-detail.ts +45 -26
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +1 -0
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/extensions/ui-types.d.ts +13 -3
- package/dist/core/extensions/ui-types.d.ts.map +1 -1
- package/dist/core/extensions/ui-types.js +15 -3
- package/dist/core/extensions/ui-types.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +33 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/main-deferred-startup.d.ts.map +1 -1
- package/dist/main-deferred-startup.js +6 -2
- package/dist/main-deferred-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +4 -0
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-tui.js +19 -1
- package/dist/modes/interactive/interactive-tui.js.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.d.ts +7 -0
- package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +94 -37
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +1 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +15 -2
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.d.ts +3 -2
- package/dist/modes/rpc/rpc-input-scheduler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.js +5 -2
- package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
- package/docs/extensions.md +1 -1
- package/docs/quickstart.md +1 -0
- package/docs/skills.md +4 -0
- package/docs/workflows.md +75 -11
- package/npm-shrinkwrap.json +29 -29
- package/package.json +2 -2
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import type { WorkflowParallelOptions, WorkflowTaskOptions, WorkflowTaskResult, WorkflowTaskStep } from "../src/shared/types.js";
|
|
3
|
+
import { fold_usage } from "./verification-usage.js";
|
|
4
|
+
import {
|
|
5
|
+
convergence_escalation_evidence,
|
|
6
|
+
record_convergence,
|
|
7
|
+
type ConvergenceEntry,
|
|
8
|
+
} from "./goal-convergence.js";
|
|
3
9
|
import { orchestratorModelConfig, reviewerModelConfig } from "./goal-models.js";
|
|
4
10
|
import {
|
|
5
11
|
DEFAULT_BLOCKER_THRESHOLD,
|
|
@@ -22,6 +28,8 @@ import {
|
|
|
22
28
|
reviewDecisionToRecord,
|
|
23
29
|
} from "./goal-review.js";
|
|
24
30
|
import { reviewerFailureText } from "./review-convergence.js";
|
|
31
|
+
import { consolidateFindingsBatch } from "./review-convergence.js";
|
|
32
|
+
import { reverify_consolidated_batch } from "./goal-reverify.js";
|
|
25
33
|
import {
|
|
26
34
|
renderForkedGoalOrchestratorPrompt,
|
|
27
35
|
renderGoalOrchestratorPrompt,
|
|
@@ -79,11 +87,13 @@ function reviewerExecutionFailedDecision(input: {
|
|
|
79
87
|
readonly reviewQuorum: number;
|
|
80
88
|
readonly reviews: readonly ReviewRecord[];
|
|
81
89
|
readonly reason: string;
|
|
90
|
+
readonly convergence?: readonly ConvergenceEntry[];
|
|
82
91
|
}): ReducerDecision {
|
|
92
|
+
const evidence = convergence_escalation_evidence(input.convergence ?? []);
|
|
83
93
|
return {
|
|
84
94
|
turn: input.turn,
|
|
85
95
|
decision: "needs_human",
|
|
86
|
-
reason: input.reason,
|
|
96
|
+
reason: [input.reason, ...evidence].join("\n"),
|
|
87
97
|
complete_votes: input.reviews.filter((review) => review.decision === "complete").length,
|
|
88
98
|
review_quorum: input.reviewQuorum,
|
|
89
99
|
parsed: input.reviews.every((review) => review.parsed),
|
|
@@ -150,7 +160,9 @@ export async function runGoalWorkflow(ctx: GoalRunnerContext, options: GoalWorkf
|
|
|
150
160
|
});
|
|
151
161
|
} catch (err) {
|
|
152
162
|
const message = err instanceof Error ? err.message : String(err);
|
|
153
|
-
|
|
163
|
+
const baseReason = `Orchestrator failed before producing a receipt: ${message}`;
|
|
164
|
+
terminalRemainingWork = baseReason;
|
|
165
|
+
const reason = [baseReason, ...convergence_escalation_evidence(ledger.convergence ?? [])].join("\n");
|
|
154
166
|
latestReviews = [];
|
|
155
167
|
latestReviewArtifactPaths = [];
|
|
156
168
|
latestReviewReportPath = undefined;
|
|
@@ -159,7 +171,7 @@ export async function runGoalWorkflow(ctx: GoalRunnerContext, options: GoalWorkf
|
|
|
159
171
|
ledger.decisions.push({
|
|
160
172
|
turn,
|
|
161
173
|
decision: "needs_human",
|
|
162
|
-
reason
|
|
174
|
+
reason,
|
|
163
175
|
complete_votes: 0,
|
|
164
176
|
review_quorum: reviewQuorum,
|
|
165
177
|
parsed: false,
|
|
@@ -167,9 +179,9 @@ export async function runGoalWorkflow(ctx: GoalRunnerContext, options: GoalWorkf
|
|
|
167
179
|
stopReviewLoop: false,
|
|
168
180
|
nextAction: "needs_human",
|
|
169
181
|
finalActionRemaining: false,
|
|
170
|
-
diagnostics: [
|
|
182
|
+
diagnostics: [baseReason],
|
|
171
183
|
});
|
|
172
|
-
appendLifecycleEvent(ledger, "status_decided",
|
|
184
|
+
appendLifecycleEvent(ledger, "status_decided", reason, turn);
|
|
173
185
|
await writeGoalLedger(ledgerPath, ledger);
|
|
174
186
|
break;
|
|
175
187
|
}
|
|
@@ -269,10 +281,60 @@ export async function runGoalWorkflow(ctx: GoalRunnerContext, options: GoalWorkf
|
|
|
269
281
|
);
|
|
270
282
|
return record;
|
|
271
283
|
}));
|
|
272
|
-
|
|
284
|
+
const consolidatedFindings = consolidateFindingsBatch(
|
|
285
|
+
latestReviews.map((review) => ({
|
|
286
|
+
reviewer: review.reviewer,
|
|
287
|
+
findings: review.findings,
|
|
288
|
+
})),
|
|
289
|
+
);
|
|
290
|
+
const roundProducedDecisions = latestReviews.some((review) => review.parsed);
|
|
291
|
+
const reverifyResults: WorkflowTaskResult[] = [];
|
|
292
|
+
const reverifyContext = {
|
|
293
|
+
task: async (name: string, taskOptions: WorkflowTaskOptions): Promise<WorkflowTaskResult> => {
|
|
294
|
+
const result = await ctx.task(name, taskOptions);
|
|
295
|
+
reverifyResults.push(result);
|
|
296
|
+
return result;
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
const reverified = await reverify_consolidated_batch(reverifyContext, {
|
|
300
|
+
batch: consolidatedFindings,
|
|
301
|
+
context: {
|
|
302
|
+
objective,
|
|
303
|
+
candidateRefs: [ledgerPath, orchestratorReceiptPath],
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
latestReviewReportPath = await writeReviewRoundArtifact(
|
|
307
|
+
artifactDir,
|
|
308
|
+
latestReviews,
|
|
309
|
+
reverified.batch,
|
|
310
|
+
reverified.audits,
|
|
311
|
+
);
|
|
312
|
+
if (reverified.audits.length > 0) {
|
|
313
|
+
ledger.reverification ??= [];
|
|
314
|
+
ledger.reverification.push(...reverified.audits);
|
|
315
|
+
}
|
|
316
|
+
const findings = latestReviews.flatMap((review) => review.findings);
|
|
317
|
+
const traceability = latestReviews.flatMap((review) => review.requirements_traceability);
|
|
318
|
+
ledger.convergence ??= [];
|
|
319
|
+
// A thrown reviewer batch or an all-unparsed reviewer batch produced no
|
|
320
|
+
// decisions, so recording a zero-blocker round would fabricate progress
|
|
321
|
+
// and can suppress the escalation evidence on the very escalation it triggers.
|
|
322
|
+
if (!reviewerBatchFailed && roundProducedDecisions) {
|
|
323
|
+
ledger.convergence.push(record_convergence({
|
|
324
|
+
unresolvedBlockingCount: reverified.batch.filter((entry) => entry.blocking).length,
|
|
325
|
+
meanFindingConfidence: findings.length === 0
|
|
326
|
+
? null
|
|
327
|
+
: findings.reduce((total, finding) => total + finding.confidence_score, 0) / findings.length,
|
|
328
|
+
fractionProven: traceability.length === 0
|
|
329
|
+
? 0
|
|
330
|
+
: traceability.filter((entry) => entry.status === "proven").length / traceability.length,
|
|
331
|
+
demotions: reverified.audits.filter((audit) => audit.verdict === "demoted").length,
|
|
332
|
+
usage: fold_usage([orchestrator, ...reviewResults, ...reverifyResults]),
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
ledger.reviews.push(...latestReviews);
|
|
273
336
|
// Consolidated round artifact leads so the next orchestrator turn plans the full findings batch first.
|
|
274
337
|
latestReviewArtifactPaths = [latestReviewReportPath, ...latestReviews.map((review) => review.artifact_path)];
|
|
275
|
-
ledger.reviews.push(...latestReviews);
|
|
276
338
|
appendLifecycleEvent(
|
|
277
339
|
ledger,
|
|
278
340
|
"reviews_recorded",
|
|
@@ -282,14 +344,16 @@ export async function runGoalWorkflow(ctx: GoalRunnerContext, options: GoalWorkf
|
|
|
282
344
|
if (reviewerBatchFailed) {
|
|
283
345
|
terminalRemainingWork = collectRemainingWork(latestReviews);
|
|
284
346
|
const reason = `Reviewer execution failed before quorum could be established. Remaining work: ${terminalRemainingWork}`;
|
|
285
|
-
|
|
347
|
+
const decision = reviewerExecutionFailedDecision({
|
|
286
348
|
turn,
|
|
287
349
|
reviewQuorum,
|
|
288
350
|
reviews: latestReviews,
|
|
289
351
|
reason,
|
|
290
|
-
|
|
352
|
+
convergence: ledger.convergence,
|
|
353
|
+
});
|
|
354
|
+
ledger.decisions.push(decision);
|
|
291
355
|
ledger.status = "needs_human";
|
|
292
|
-
appendLifecycleEvent(ledger, "status_decided", reason, turn);
|
|
356
|
+
appendLifecycleEvent(ledger, "status_decided", decision.reason, turn);
|
|
293
357
|
await writeGoalLedger(ledgerPath, ledger);
|
|
294
358
|
break;
|
|
295
359
|
}
|
|
@@ -300,6 +364,7 @@ export async function runGoalWorkflow(ctx: GoalRunnerContext, options: GoalWorkf
|
|
|
300
364
|
reviewQuorum,
|
|
301
365
|
blockerThreshold,
|
|
302
366
|
nextActionOnComplete: createPr ? "pull-request" : "finish",
|
|
367
|
+
convergence: ledger.convergence,
|
|
303
368
|
});
|
|
304
369
|
if (reducerOutcome.blockerObservation !== undefined) {
|
|
305
370
|
ledger.blockers.push(reducerOutcome.blockerObservation);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { VERIFICATION_SCALE } from "./verification-criteria.js";
|
|
2
3
|
|
|
3
4
|
const reviewFindingSchema = Type.Object(
|
|
4
5
|
{
|
|
@@ -74,6 +75,12 @@ export const reviewDecisionSchema = Type.Object(
|
|
|
74
75
|
receipt_assessment: Type.String(),
|
|
75
76
|
verification_remaining: Type.String(),
|
|
76
77
|
stop_review_loop: Type.Boolean(),
|
|
78
|
+
criterion_scores: Type.Optional(
|
|
79
|
+
Type.Array(Type.Object({
|
|
80
|
+
criterion_id: Type.String(),
|
|
81
|
+
score: VERIFICATION_SCALE.schema,
|
|
82
|
+
}, { additionalProperties: false })),
|
|
83
|
+
),
|
|
77
84
|
reviewer_error: Type.Optional(
|
|
78
85
|
Type.Union([Type.Null(), reviewerErrorSchema]),
|
|
79
86
|
),
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ReviewConvergenceSummary } from "./review-convergence.js";
|
|
2
|
+
import type { ReverifyAuditEntry } from "./goal-reverify.js";
|
|
3
|
+
import type { ConvergenceEntry } from "./goal-convergence.js";
|
|
2
4
|
|
|
3
5
|
export const DEFAULT_MAX_TURNS = 10;
|
|
4
6
|
// Goal Runner runs three independent reviewer personas; two approvals form a majority.
|
|
@@ -55,6 +57,7 @@ export type ReviewerError = {
|
|
|
55
57
|
|
|
56
58
|
export type ReviewDecision = {
|
|
57
59
|
readonly findings: readonly ReviewFinding[];
|
|
60
|
+
readonly criterion_scores?: readonly { readonly criterion_id: string; readonly score: number }[];
|
|
58
61
|
readonly overall_correctness: "patch is correct" | "patch is incorrect";
|
|
59
62
|
readonly overall_explanation: string;
|
|
60
63
|
readonly overall_confidence_score: number;
|
|
@@ -123,6 +126,9 @@ export type GoalLedger = {
|
|
|
123
126
|
blockers: BlockerObservation[];
|
|
124
127
|
decisions: ReducerDecision[];
|
|
125
128
|
lifecycle: GoalLifecycleEvent[];
|
|
129
|
+
/** V9 audit records; the original findings remain in `reviews`. */
|
|
130
|
+
reverification?: ReverifyAuditEntry<ReviewFinding>[];
|
|
131
|
+
convergence?: ConvergenceEntry[];
|
|
126
132
|
};
|
|
127
133
|
|
|
128
134
|
export type ReducerOutcome = {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
WorkflowSerializableValue,
|
|
8
8
|
WorkflowTaskResult,
|
|
9
9
|
} from "../src/shared/types.js";
|
|
10
|
+
import { classify_trend, score_progress, type Trend } from "./progress-scoring.js";
|
|
10
11
|
import {
|
|
11
12
|
renderCompletionPrompt,
|
|
12
13
|
renderEvaluationPrompt,
|
|
@@ -14,6 +15,8 @@ import {
|
|
|
14
15
|
} from "./loop-until-done-prompts.js";
|
|
15
16
|
import { stableArtifactRoot } from "./pattern-artifact-root.js";
|
|
16
17
|
|
|
18
|
+
const PROGRESS_DISCLAIMER = "Progress scores are a monitoring signal; VOC separation +0.079; never authoritative.";
|
|
19
|
+
|
|
17
20
|
const evaluationSchema = Type.Object({
|
|
18
21
|
done: Type.Boolean(),
|
|
19
22
|
summary: Type.String(),
|
|
@@ -26,6 +29,8 @@ const evaluationSchema = Type.Object({
|
|
|
26
29
|
type LoopInputs = {
|
|
27
30
|
readonly prompt: string;
|
|
28
31
|
readonly max_iterations: number;
|
|
32
|
+
readonly progress_scoring?: boolean;
|
|
33
|
+
readonly progress_repeats?: number;
|
|
29
34
|
} & Record<string, WorkflowSerializableValue>;
|
|
30
35
|
|
|
31
36
|
type Evaluation = {
|
|
@@ -36,6 +41,12 @@ type Evaluation = {
|
|
|
36
41
|
readonly validationEvidence: readonly string[];
|
|
37
42
|
readonly remainingWork: string;
|
|
38
43
|
};
|
|
44
|
+
type LedgerProgress = {
|
|
45
|
+
readonly score: number;
|
|
46
|
+
readonly perRepeat: (number | null)[][];
|
|
47
|
+
readonly trend: Trend;
|
|
48
|
+
readonly window: number;
|
|
49
|
+
};
|
|
39
50
|
type LedgerEntry = {
|
|
40
51
|
readonly iteration: number;
|
|
41
52
|
readonly artifact_path: string;
|
|
@@ -46,7 +57,57 @@ type LedgerEntry = {
|
|
|
46
57
|
readonly validation_evidence: readonly string[];
|
|
47
58
|
readonly done: boolean;
|
|
48
59
|
readonly remaining_work: string;
|
|
60
|
+
readonly progress?: LedgerProgress;
|
|
49
61
|
};
|
|
62
|
+
|
|
63
|
+
function progressCurve(entries: readonly LedgerEntry[]): number[] {
|
|
64
|
+
return entries.flatMap((entry) => (entry.progress === undefined ? [] : [entry.progress.score]));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function progressReport(entries: readonly LedgerEntry[]): { curve: number[]; trend: Trend } {
|
|
68
|
+
const curve = progressCurve(entries);
|
|
69
|
+
return { curve, trend: classify_trend(curve).trend };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function formatProgressReport(report: { curve: readonly number[]; trend: Trend }): string {
|
|
73
|
+
return [
|
|
74
|
+
`Progress curve: ${JSON.stringify(report.curve)}`,
|
|
75
|
+
`Final trend: ${report.trend}`,
|
|
76
|
+
PROGRESS_DISCLAIMER,
|
|
77
|
+
].join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function repeatCount(input: LoopInputs): number {
|
|
81
|
+
const repeats = input.progress_repeats;
|
|
82
|
+
return typeof repeats === "number" && Number.isInteger(repeats) && repeats > 0 ? repeats : 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function scoreIteration(
|
|
86
|
+
ctx: WorkflowRunContext<LoopInputs>,
|
|
87
|
+
task: string,
|
|
88
|
+
entries: readonly LedgerEntry[],
|
|
89
|
+
repeats: number,
|
|
90
|
+
): Promise<LedgerProgress | undefined> {
|
|
91
|
+
try {
|
|
92
|
+
const curve = await score_progress(ctx, {
|
|
93
|
+
problem: task,
|
|
94
|
+
steps: entries.map((entry) => entry.summary),
|
|
95
|
+
checkpoints: [entries.length],
|
|
96
|
+
repeats,
|
|
97
|
+
});
|
|
98
|
+
const score = curve.scores[0];
|
|
99
|
+
if (score === null || score === undefined) return undefined;
|
|
100
|
+
const trend = classify_trend([...progressCurve(entries), score]);
|
|
101
|
+
return {
|
|
102
|
+
score,
|
|
103
|
+
perRepeat: curve.perRepeat,
|
|
104
|
+
trend: trend.trend,
|
|
105
|
+
window: trend.evidence.window,
|
|
106
|
+
};
|
|
107
|
+
} catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
50
111
|
function serializableObject(
|
|
51
112
|
value: WorkflowSerializableValue | undefined,
|
|
52
113
|
): WorkflowSerializableObject | undefined {
|
|
@@ -79,14 +140,23 @@ function evaluationFrom(result: WorkflowTaskResult): Evaluation {
|
|
|
79
140
|
return { done, summary, newFindings, failures, validationEvidence, remainingWork };
|
|
80
141
|
}
|
|
81
142
|
|
|
82
|
-
async function writeLedger(
|
|
83
|
-
|
|
143
|
+
async function writeLedger(
|
|
144
|
+
path: string,
|
|
145
|
+
task: string,
|
|
146
|
+
maxIterations: number,
|
|
147
|
+
status: string,
|
|
148
|
+
entries: readonly LedgerEntry[],
|
|
149
|
+
): Promise<void> {
|
|
150
|
+
const report = progressReport(entries);
|
|
84
151
|
await writeFile(path, `${JSON.stringify({
|
|
85
152
|
task,
|
|
86
153
|
max_iterations: maxIterations,
|
|
87
154
|
status,
|
|
88
155
|
iterations_completed: entries.length,
|
|
89
156
|
entries,
|
|
157
|
+
progress_curve: report.curve,
|
|
158
|
+
final_trend: report.trend,
|
|
159
|
+
progress_disclaimer: PROGRESS_DISCLAIMER,
|
|
90
160
|
}, null, 2)}\n`);
|
|
91
161
|
}
|
|
92
162
|
|
|
@@ -135,7 +205,7 @@ export async function runLoopUntilDone(ctx: WorkflowRunContext<LoopInputs>) {
|
|
|
135
205
|
// runner persists the structured decision itself so evaluation-N.json
|
|
136
206
|
// stays schema-shaped JSON rather than stage prose.
|
|
137
207
|
await writeFile(evaluationPath, `${JSON.stringify(evaluator.structured, null, 2)}\n`);
|
|
138
|
-
|
|
208
|
+
const entry: LedgerEntry = {
|
|
139
209
|
iteration,
|
|
140
210
|
artifact_path: iterationPath,
|
|
141
211
|
evaluation_artifact_path: evaluationPath,
|
|
@@ -145,7 +215,12 @@ export async function runLoopUntilDone(ctx: WorkflowRunContext<LoopInputs>) {
|
|
|
145
215
|
validation_evidence: decision.validationEvidence,
|
|
146
216
|
done: decision.done,
|
|
147
217
|
remaining_work: decision.remainingWork,
|
|
148
|
-
}
|
|
218
|
+
};
|
|
219
|
+
entries.push(entry);
|
|
220
|
+
if (ctx.inputs.progress_scoring !== false) {
|
|
221
|
+
const progress = await scoreIteration(ctx, ctx.inputs.prompt, entries, repeatCount(ctx.inputs));
|
|
222
|
+
if (progress !== undefined) entries[entries.length - 1] = { ...entry, progress };
|
|
223
|
+
}
|
|
149
224
|
await writeLedger(
|
|
150
225
|
ledgerPath,
|
|
151
226
|
ctx.inputs.prompt,
|
|
@@ -154,6 +229,7 @@ export async function runLoopUntilDone(ctx: WorkflowRunContext<LoopInputs>) {
|
|
|
154
229
|
entries,
|
|
155
230
|
);
|
|
156
231
|
if (decision.done) {
|
|
232
|
+
const report = progressReport(entries);
|
|
157
233
|
const resultPath = join(artifactDir, "result.md");
|
|
158
234
|
const final = await ctx.task("completion-summary", {
|
|
159
235
|
prompt: renderCompletionPrompt({ task: ctx.inputs.prompt, ledgerPath, iterationPath }),
|
|
@@ -164,8 +240,9 @@ export async function runLoopUntilDone(ctx: WorkflowRunContext<LoopInputs>) {
|
|
|
164
240
|
// `result_path` below carries it for callers that want the contents.
|
|
165
241
|
outputMode: "file-only",
|
|
166
242
|
});
|
|
243
|
+
const result = `${final.text ? `${final.text.trimEnd()}\n\n` : ""}${formatProgressReport(report)}\n`;
|
|
167
244
|
return {
|
|
168
|
-
result
|
|
245
|
+
result,
|
|
169
246
|
status: "complete" as const,
|
|
170
247
|
iterations_completed: iteration,
|
|
171
248
|
ledger_path: ledgerPath,
|
|
@@ -174,14 +251,22 @@ export async function runLoopUntilDone(ctx: WorkflowRunContext<LoopInputs>) {
|
|
|
174
251
|
result_path: resultPath,
|
|
175
252
|
remaining_work: "",
|
|
176
253
|
artifact_dir: artifactDir,
|
|
254
|
+
progress_curve: report.curve,
|
|
255
|
+
final_trend: report.trend,
|
|
256
|
+
progress_disclaimer: PROGRESS_DISCLAIMER,
|
|
177
257
|
};
|
|
178
258
|
}
|
|
179
259
|
}
|
|
180
260
|
|
|
181
261
|
const last = entries.at(-1)!;
|
|
262
|
+
const report = progressReport(entries);
|
|
182
263
|
await writeLedger(ledgerPath, ctx.inputs.prompt, ctx.inputs.max_iterations, "failed", entries);
|
|
264
|
+
const result = [
|
|
265
|
+
`Iteration limit exhausted after ${ctx.inputs.max_iterations} iterations. Inspect ${ledgerPath}.`,
|
|
266
|
+
formatProgressReport(report),
|
|
267
|
+
].join("\n\n");
|
|
183
268
|
return {
|
|
184
|
-
result
|
|
269
|
+
result,
|
|
185
270
|
status: "failed" as const,
|
|
186
271
|
iterations_completed: ctx.inputs.max_iterations,
|
|
187
272
|
ledger_path: ledgerPath,
|
|
@@ -190,5 +275,8 @@ export async function runLoopUntilDone(ctx: WorkflowRunContext<LoopInputs>) {
|
|
|
190
275
|
result_path: ledgerPath,
|
|
191
276
|
remaining_work: last.remaining_work,
|
|
192
277
|
artifact_dir: artifactDir,
|
|
278
|
+
progress_curve: report.curve,
|
|
279
|
+
final_trend: report.trend,
|
|
280
|
+
progress_disclaimer: PROGRESS_DISCLAIMER,
|
|
193
281
|
};
|
|
194
282
|
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import type { WorkflowDefinition, WorkflowInputValues, WorkflowOutputValues } from "../src/authoring.js";
|
|
2
2
|
|
|
3
3
|
export type LoopUntilDoneWorkflowStatus = "complete" | "failed";
|
|
4
|
+
export type LoopUntilDoneProgressTrend = "rising" | "flat" | "regressing";
|
|
4
5
|
|
|
5
6
|
export type LoopUntilDoneWorkflowInputs = WorkflowInputValues & {
|
|
6
7
|
readonly prompt: string;
|
|
7
8
|
readonly max_iterations: number;
|
|
9
|
+
readonly progress_scoring: boolean;
|
|
10
|
+
readonly progress_repeats: number;
|
|
8
11
|
};
|
|
9
12
|
|
|
10
13
|
export type LoopUntilDoneWorkflowRunInputs = WorkflowInputValues & {
|
|
11
14
|
readonly prompt: string;
|
|
12
15
|
readonly max_iterations?: number;
|
|
16
|
+
readonly progress_scoring?: boolean;
|
|
17
|
+
readonly progress_repeats?: number;
|
|
13
18
|
};
|
|
14
19
|
|
|
15
20
|
export type LoopUntilDoneWorkflowOutputs = WorkflowOutputValues & {
|
|
@@ -22,6 +27,9 @@ export type LoopUntilDoneWorkflowOutputs = WorkflowOutputValues & {
|
|
|
22
27
|
readonly result_path: string;
|
|
23
28
|
readonly remaining_work: string;
|
|
24
29
|
readonly artifact_dir: string;
|
|
30
|
+
readonly progress_curve: number[];
|
|
31
|
+
readonly final_trend: LoopUntilDoneProgressTrend;
|
|
32
|
+
readonly progress_disclaimer: string;
|
|
25
33
|
};
|
|
26
34
|
|
|
27
35
|
export type LoopUntilDoneWorkflowDefinition = WorkflowDefinition<
|
|
@@ -18,6 +18,15 @@ export default workflow({
|
|
|
18
18
|
default: 5,
|
|
19
19
|
description: "Maximum work/evaluation iterations before returning an inspectable failed status (1-20).",
|
|
20
20
|
}),
|
|
21
|
+
progress_scoring: Type.Boolean({
|
|
22
|
+
default: true,
|
|
23
|
+
description: "Enable advisory progress scoring after each completed iteration.",
|
|
24
|
+
}),
|
|
25
|
+
progress_repeats: Type.Integer({
|
|
26
|
+
minimum: 1,
|
|
27
|
+
default: 1,
|
|
28
|
+
description: "Number of advisory progress-scoring repeats per iteration.",
|
|
29
|
+
}),
|
|
21
30
|
},
|
|
22
31
|
outputs: {
|
|
23
32
|
result: Type.String({ description: "Compact reference to the evidence-backed completion report, or the deterministic exhaustion report; read `result_path` for the full report." }),
|
|
@@ -31,6 +40,12 @@ export default workflow({
|
|
|
31
40
|
result_path: Type.String({ description: "Path to the final report, or the ledger on exhausted failure." }),
|
|
32
41
|
remaining_work: Type.String({ description: "Actionable remaining work; empty only after proven completion." }),
|
|
33
42
|
artifact_dir: Type.String({ description: "Run-specific directory containing loop artifacts." }),
|
|
43
|
+
progress_curve: Type.Array(Type.Number(), { description: "Advisory progress scores in iteration order." }),
|
|
44
|
+
final_trend: Type.Union(
|
|
45
|
+
[Type.Literal("rising"), Type.Literal("flat"), Type.Literal("regressing")],
|
|
46
|
+
{ description: "Final advisory trend classification; never a stop decision." },
|
|
47
|
+
),
|
|
48
|
+
progress_disclaimer: Type.String({ description: "Calibration disclaimer for the advisory progress signal." }),
|
|
34
49
|
},
|
|
35
50
|
run: async (ctx) => await runLoopUntilDone(withSteeringPropagationContext(ctx)),
|
|
36
51
|
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observation-grounded trajectory scoring and pure trend evidence.
|
|
3
|
+
*
|
|
4
|
+
* Progress is a monitoring magnitude only. The prompt asks about the supplied
|
|
5
|
+
* current state, and the trend result carries evidence without an action.
|
|
6
|
+
*/
|
|
7
|
+
import { Type } from "typebox";
|
|
8
|
+
import type { WorkflowRunContext, WorkflowSerializableObject, WorkflowSerializableValue } from "../src/shared/types.js";
|
|
9
|
+
import { VERIFICATION_SCALE } from "./verification-criteria.js";
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_TREND_WINDOW = 3;
|
|
12
|
+
export const DEFAULT_RISE_DELTA = 1.5;
|
|
13
|
+
export const DEFAULT_FALL_DELTA = -1.5;
|
|
14
|
+
|
|
15
|
+
const DEFAULT_REPEATS = 1;
|
|
16
|
+
const CALIBRATION_RULES = [
|
|
17
|
+
"Trust observed output, not the agent's narration.",
|
|
18
|
+
"Effort and step count are NOT progress.",
|
|
19
|
+
'Agent declarations of success ("done!", "all tests pass") are ZERO evidence.',
|
|
20
|
+
"Scores may plateau or fall; wrong approaches plateau, and regressions decrease.",
|
|
21
|
+
] as const;
|
|
22
|
+
const PROGRESS_SCALE_ORIENTATION = `${VERIFICATION_SCALE.min} = certainly would not satisfy the acceptance criteria … ${VERIFICATION_SCALE.max} = verified satisfaction with observed output`;
|
|
23
|
+
|
|
24
|
+
export interface ProgressPromptInput {
|
|
25
|
+
readonly problem: string;
|
|
26
|
+
readonly steps: readonly string[];
|
|
27
|
+
readonly checkpoints: readonly number[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ProgressScoreInput {
|
|
31
|
+
readonly problem: string;
|
|
32
|
+
readonly steps: readonly string[];
|
|
33
|
+
readonly checkpoints?: readonly number[];
|
|
34
|
+
readonly repeats?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type ProgressScoringContext = Pick<WorkflowRunContext, "task">;
|
|
38
|
+
|
|
39
|
+
export type ProgressCurve = {
|
|
40
|
+
checkpoints: number[];
|
|
41
|
+
scores: (number | null)[];
|
|
42
|
+
perRepeat: (number | null)[][];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type TrendConfig = {
|
|
46
|
+
window?: number;
|
|
47
|
+
riseDelta?: number;
|
|
48
|
+
fallDelta?: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type Trend = "rising" | "flat" | "regressing";
|
|
52
|
+
|
|
53
|
+
export type TrendResult = {
|
|
54
|
+
trend: Trend;
|
|
55
|
+
evidence: {
|
|
56
|
+
series: readonly number[];
|
|
57
|
+
window: number;
|
|
58
|
+
delta: number;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const progressSchema = Type.Object({
|
|
63
|
+
scores: Type.Array(Type.Object({
|
|
64
|
+
checkpoint: Type.Integer({ minimum: 1 }),
|
|
65
|
+
score: VERIFICATION_SCALE.schema,
|
|
66
|
+
}, { additionalProperties: false })),
|
|
67
|
+
}, { additionalProperties: false });
|
|
68
|
+
|
|
69
|
+
type ProgressStructuredOutput = {
|
|
70
|
+
readonly scores: readonly WorkflowSerializableValue[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function isRecord(value: WorkflowSerializableValue | undefined): value is WorkflowSerializableObject {
|
|
74
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isProgressStructuredOutput(value: WorkflowSerializableValue | undefined): value is ProgressStructuredOutput {
|
|
78
|
+
if (!isRecord(value) || Object.keys(value).length !== 1 || !Object.hasOwn(value, "scores")) return false;
|
|
79
|
+
return Array.isArray(value.scores);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function nullScores(checkpointCount: number): (number | null)[] {
|
|
83
|
+
return Array.from({ length: checkpointCount }, () => null);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function defaultCheckpoints(stepCount: number): number[] {
|
|
87
|
+
const result: number[] = [];
|
|
88
|
+
for (let checkpoint = 2; checkpoint <= stepCount - 1; checkpoint += 1) result.push(checkpoint);
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function validateCheckpoints(checkpoints: readonly number[], stepCount: number): void {
|
|
93
|
+
for (const checkpoint of checkpoints) {
|
|
94
|
+
if (!Number.isInteger(checkpoint) || checkpoint < 1 || checkpoint > stepCount) {
|
|
95
|
+
throw new RangeError(`Progress checkpoint ${checkpoint} is outside the step range 1..${stepCount}.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function scoresFromStructured(
|
|
101
|
+
value: WorkflowSerializableValue | undefined,
|
|
102
|
+
checkpoints: readonly number[],
|
|
103
|
+
): (number | null)[] {
|
|
104
|
+
if (!isProgressStructuredOutput(value)) return nullScores(checkpoints.length);
|
|
105
|
+
const requested = new Set(checkpoints);
|
|
106
|
+
const seen = new Set<number>();
|
|
107
|
+
const scoresByCheckpoint = new Map<number, number>();
|
|
108
|
+
for (const candidate of value.scores) {
|
|
109
|
+
if (!isRecord(candidate) || Object.keys(candidate).length !== 2 ||
|
|
110
|
+
!Object.hasOwn(candidate, "checkpoint") || !Object.hasOwn(candidate, "score")) continue;
|
|
111
|
+
const checkpoint = candidate.checkpoint;
|
|
112
|
+
const score = candidate.score;
|
|
113
|
+
if (typeof checkpoint !== "number" || !Number.isInteger(checkpoint) || seen.has(checkpoint)) continue;
|
|
114
|
+
if (!requested.has(checkpoint) || typeof score !== "number" || !Number.isInteger(score) ||
|
|
115
|
+
score < VERIFICATION_SCALE.min || score > VERIFICATION_SCALE.max) continue;
|
|
116
|
+
seen.add(checkpoint);
|
|
117
|
+
scoresByCheckpoint.set(checkpoint, score);
|
|
118
|
+
}
|
|
119
|
+
return checkpoints.map((checkpoint) => scoresByCheckpoint.get(checkpoint) ?? null);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Score all requested checkpoints in one structured stage call for one repeat.
|
|
124
|
+
* A failed or malformed call is an invalid repeat and leaves every checkpoint
|
|
125
|
+
* in that repeat null.
|
|
126
|
+
*/
|
|
127
|
+
async function scoreRepeat(
|
|
128
|
+
ctx: ProgressScoringContext,
|
|
129
|
+
input: ProgressScoreInput,
|
|
130
|
+
checkpoints: readonly number[],
|
|
131
|
+
repeat: number,
|
|
132
|
+
): Promise<(number | null)[]> {
|
|
133
|
+
try {
|
|
134
|
+
const result = await ctx.task(`progress-score-${repeat + 1}`, {
|
|
135
|
+
prompt: build_progress_prompt({ ...input, checkpoints }),
|
|
136
|
+
context: "fresh",
|
|
137
|
+
schema: progressSchema,
|
|
138
|
+
});
|
|
139
|
+
return scoresFromStructured(result.structured, checkpoints);
|
|
140
|
+
} catch {
|
|
141
|
+
return nullScores(checkpoints.length);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Score every requested checkpoint once per repeat, returning null-safe means. */
|
|
146
|
+
export async function score_progress(
|
|
147
|
+
ctx: ProgressScoringContext,
|
|
148
|
+
input: ProgressScoreInput,
|
|
149
|
+
): Promise<ProgressCurve> {
|
|
150
|
+
if (input.steps.length === 0) throw new RangeError("Progress scoring requires a non-empty step prefix.");
|
|
151
|
+
const checkpoints = Array.from(input.checkpoints ?? defaultCheckpoints(input.steps.length));
|
|
152
|
+
validateCheckpoints(checkpoints, input.steps.length);
|
|
153
|
+
const repeats = input.repeats ?? DEFAULT_REPEATS;
|
|
154
|
+
const perRepeat: (number | null)[][] = [];
|
|
155
|
+
for (let repeat = 0; repeat < repeats; repeat += 1) {
|
|
156
|
+
perRepeat.push(await scoreRepeat(ctx, input, checkpoints, repeat));
|
|
157
|
+
}
|
|
158
|
+
const scores = checkpoints.map((_, checkpointIndex) => {
|
|
159
|
+
const valid = perRepeat
|
|
160
|
+
.map((repeat) => repeat[checkpointIndex])
|
|
161
|
+
.filter((score): score is number => score !== null);
|
|
162
|
+
if (valid.length === 0) return null;
|
|
163
|
+
return valid.reduce((total, score) => total + score, 0) / valid.length;
|
|
164
|
+
});
|
|
165
|
+
return { checkpoints, scores, perRepeat };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Build a progress prompt with V3's shared-head/varying-tail layout. Steps are
|
|
170
|
+
* part of the cacheable head; only the requested checkpoint list varies at the
|
|
171
|
+
* tail.
|
|
172
|
+
*/
|
|
173
|
+
export function build_progress_prompt(input: ProgressPromptInput): string {
|
|
174
|
+
const numberedSteps = input.steps.map((step, index) => `${index + 1}. ${step}`);
|
|
175
|
+
const sharedHead = [
|
|
176
|
+
"<progress_head>",
|
|
177
|
+
"<problem>",
|
|
178
|
+
input.problem,
|
|
179
|
+
"</problem>",
|
|
180
|
+
"<steps>",
|
|
181
|
+
...numberedSteps,
|
|
182
|
+
"</steps>",
|
|
183
|
+
"<calibration>",
|
|
184
|
+
...CALIBRATION_RULES,
|
|
185
|
+
"</calibration>",
|
|
186
|
+
"<scale>",
|
|
187
|
+
`Use VERIFICATION_SCALE ${VERIFICATION_SCALE.min}..${VERIFICATION_SCALE.max} to answer: would the CURRENT state satisfy the acceptance criteria?`,
|
|
188
|
+
PROGRESS_SCALE_ORIENTATION,
|
|
189
|
+
"Score only the supplied current state from observed output.",
|
|
190
|
+
"</scale>",
|
|
191
|
+
"</progress_head>",
|
|
192
|
+
].join("\n");
|
|
193
|
+
const checkpointTail = [
|
|
194
|
+
"<checkpoints>",
|
|
195
|
+
"Score each listed 1-indexed checkpoint.",
|
|
196
|
+
...input.checkpoints.map((checkpoint) => `- ${checkpoint}`),
|
|
197
|
+
"</checkpoints>",
|
|
198
|
+
"<output_format>",
|
|
199
|
+
`Return structured_output with scores: [{ checkpoint, score }], using integer scores from ${VERIFICATION_SCALE.min} through ${VERIFICATION_SCALE.max}.`,
|
|
200
|
+
"</output_format>",
|
|
201
|
+
].join("\n");
|
|
202
|
+
return `${sharedHead}\n\n${checkpointTail}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function mean(values: readonly number[]): number {
|
|
206
|
+
if (values.length === 0) return 0;
|
|
207
|
+
return values.reduce((total, value) => total + value, 0) / values.length;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Classify a score series using a deterministic hysteresis delta. The trailing
|
|
212
|
+
* 2*window values are split into equal leading/trailing halves; an odd sample
|
|
213
|
+
* drops its middle value. Thresholds are inclusive at +riseDelta and -fallDelta.
|
|
214
|
+
*/
|
|
215
|
+
export function classify_trend(series: readonly number[], config: TrendConfig = {}): TrendResult {
|
|
216
|
+
const window = config.window ?? DEFAULT_TREND_WINDOW;
|
|
217
|
+
const riseDelta = config.riseDelta ?? DEFAULT_RISE_DELTA;
|
|
218
|
+
const fallDelta = config.fallDelta ?? DEFAULT_FALL_DELTA;
|
|
219
|
+
if (series.length < window + 1) {
|
|
220
|
+
return { trend: "flat", evidence: { series, window, delta: 0 } };
|
|
221
|
+
}
|
|
222
|
+
const sample = series.slice(-2 * window);
|
|
223
|
+
const usableLength = sample.length % 2 === 1 ? sample.length - 1 : sample.length;
|
|
224
|
+
const halfLength = usableLength / 2;
|
|
225
|
+
const leading = sample.slice(0, halfLength);
|
|
226
|
+
const trailing = sample.slice(sample.length - halfLength);
|
|
227
|
+
const delta = halfLength === 0 ? 0 : mean(trailing) - mean(leading);
|
|
228
|
+
const trend: Trend = delta >= riseDelta ? "rising" : delta <= fallDelta ? "regressing" : "flat";
|
|
229
|
+
return { trend, evidence: { series, window, delta } };
|
|
230
|
+
}
|