ultimate-pi 0.24.0 → 0.25.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/.pi/extensions/agt-prompt-guard.ts +20 -6
- package/.pi/extensions/harness-auto-compact.ts +94 -0
- package/.pi/extensions/harness-debate-tools.ts +26 -2
- package/.pi/extensions/harness-live-widget.ts +19 -2
- package/.pi/extensions/harness-plan-approval.ts +62 -19
- package/.pi/extensions/harness-plan-orchestration.ts +140 -0
- package/.pi/extensions/harness-run-context.ts +457 -48
- package/.pi/extensions/harness-web-tools.ts +1 -0
- package/.pi/extensions/policy-gate.ts +9 -0
- package/.pi/harness/agents.manifest.json +1 -1
- package/.pi/harness/docs/adrs/0056-agent-native-speed-wiring.md +26 -0
- package/.pi/harness/env.harness.template +7 -1
- package/.pi/lib/harness-auto-approve.ts +140 -0
- package/.pi/lib/harness-auto-compact-policy.ts +85 -0
- package/.pi/lib/harness-phase-telemetry.ts +7 -0
- package/.pi/lib/harness-phase-worker.ts +23 -0
- package/.pi/lib/harness-plan-fsm.ts +162 -0
- package/.pi/lib/harness-plan-route.ts +134 -0
- package/.pi/lib/harness-posthog.ts +4 -1
- package/.pi/lib/harness-remediation.ts +79 -0
- package/.pi/lib/harness-repair-brief.ts +2 -2
- package/.pi/lib/harness-review-parallel.ts +18 -0
- package/.pi/lib/harness-run-context.ts +119 -72
- package/.pi/lib/harness-spawn-budget.ts +32 -4
- package/.pi/lib/harness-spawn-topology.ts +36 -1
- package/.pi/lib/harness-subagent-precheck.ts +3 -2
- package/.pi/lib/harness-subagent-progress.ts +8 -5
- package/.pi/lib/harness-subagents-bridge.ts +14 -12
- package/.pi/lib/harness-vcc-settings.ts +36 -0
- package/.pi/lib/plan-approval-readiness.ts +9 -5
- package/.pi/lib/plan-debate-eligibility-snapshot.ts +90 -0
- package/.pi/lib/plan-debate-eligibility.ts +12 -7
- package/.pi/lib/plan-debate-focus.ts +23 -11
- package/.pi/lib/plan-debate-gate.ts +71 -29
- package/.pi/lib/plan-debate-round-status.ts +23 -8
- package/.pi/lib/plan-headless-ux.ts +598 -0
- package/.pi/lib/plan-human-gates.ts +24 -85
- package/.pi/lib/plan-messenger.ts +3 -3
- package/.pi/lib/plan-review-gate.ts +56 -0
- package/.pi/prompts/harness-abort.md +1 -0
- package/.pi/prompts/harness-auto.md +1 -1
- package/.pi/prompts/harness-clear.md +6 -6
- package/.pi/prompts/harness-plan.md +15 -2
- package/.pi/prompts/harness-review.md +2 -2
- package/.pi/scripts/harness-project-toggle.mjs +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +2 -2
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { constants } from "node:fs";
|
|
|
6
6
|
import { access, readFile } from "node:fs/promises";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { parse as parseYaml } from "yaml";
|
|
9
|
+
import { synthesizerArtifactsComplete } from "./harness-plan-route.js";
|
|
9
10
|
import {
|
|
10
11
|
isTaskClarificationReady,
|
|
11
12
|
TASK_CLARIFICATION_ARTIFACT,
|
|
@@ -213,11 +214,14 @@ export async function validatePlanApprovalReadiness(
|
|
|
213
214
|
}
|
|
214
215
|
}
|
|
215
216
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
217
|
+
const synthComplete = await synthesizerArtifactsComplete(runDir);
|
|
218
|
+
if (!synthComplete) {
|
|
219
|
+
if (!(await fileExists(join(runDir, "artifacts/decomposition.yaml")))) {
|
|
220
|
+
errors.push("missing artifacts/decomposition.yaml");
|
|
221
|
+
}
|
|
222
|
+
if (!(await fileExists(join(runDir, "artifacts/hypothesis.yaml")))) {
|
|
223
|
+
errors.push("missing artifacts/hypothesis.yaml");
|
|
224
|
+
}
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
return { ok: errors.length === 0, errors, warnings };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted plan-debate eligibility snapshot for gate pass-through.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { constants } from "node:fs";
|
|
6
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
9
|
+
import type { DebateEligibilityResult } from "./plan-debate-eligibility.js";
|
|
10
|
+
|
|
11
|
+
export const PLAN_DEBATE_ELIGIBILITY_ARTIFACT =
|
|
12
|
+
"artifacts/plan-debate-eligibility.yaml";
|
|
13
|
+
|
|
14
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
15
|
+
try {
|
|
16
|
+
await access(path, constants.R_OK);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function writePlanDebateEligibilitySnapshot(
|
|
24
|
+
runDir: string,
|
|
25
|
+
result: DebateEligibilityResult,
|
|
26
|
+
): Promise<string> {
|
|
27
|
+
const rel = PLAN_DEBATE_ELIGIBILITY_ARTIFACT;
|
|
28
|
+
const abs = join(runDir, rel);
|
|
29
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
30
|
+
const doc = {
|
|
31
|
+
schema_version: "1.0.0",
|
|
32
|
+
captured_at: new Date().toISOString(),
|
|
33
|
+
profile: result.profile,
|
|
34
|
+
required_focuses: result.required_focuses,
|
|
35
|
+
min_focus_rounds: result.review_gate_strategy.min_focus_rounds,
|
|
36
|
+
max_rounds: result.max_rounds,
|
|
37
|
+
max_exchanges_per_round: result.max_exchanges_per_round,
|
|
38
|
+
round_token_cap: result.round_token_cap,
|
|
39
|
+
debate_global_cap: result.debate_global_cap,
|
|
40
|
+
human_required: result.human_required,
|
|
41
|
+
rationale: result.rationale,
|
|
42
|
+
review_gate_strategy: result.review_gate_strategy,
|
|
43
|
+
};
|
|
44
|
+
await writeFile(abs, stringifyYaml(doc), "utf-8");
|
|
45
|
+
return rel;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function loadPlanDebateEligibilitySnapshot(
|
|
49
|
+
runDir: string,
|
|
50
|
+
): Promise<DebateEligibilityResult | null> {
|
|
51
|
+
const abs = join(runDir, PLAN_DEBATE_ELIGIBILITY_ARTIFACT);
|
|
52
|
+
if (!(await fileExists(abs))) return null;
|
|
53
|
+
try {
|
|
54
|
+
const raw = await readFile(abs, "utf-8");
|
|
55
|
+
const doc = parseYaml(raw) as Record<string, unknown>;
|
|
56
|
+
if (!doc || typeof doc !== "object") return null;
|
|
57
|
+
const strategy = doc.review_gate_strategy as
|
|
58
|
+
| DebateEligibilityResult["review_gate_strategy"]
|
|
59
|
+
| undefined;
|
|
60
|
+
if (!strategy?.mode) return null;
|
|
61
|
+
return {
|
|
62
|
+
profile: String(
|
|
63
|
+
doc.profile ?? strategy.profile ?? "standard",
|
|
64
|
+
) as DebateEligibilityResult["profile"],
|
|
65
|
+
required_focuses: (doc.required_focuses ??
|
|
66
|
+
strategy.required_focuses ??
|
|
67
|
+
[]) as DebateEligibilityResult["required_focuses"],
|
|
68
|
+
min_focus_rounds: Number(
|
|
69
|
+
doc.min_focus_rounds ?? strategy.min_focus_rounds ?? 1,
|
|
70
|
+
),
|
|
71
|
+
max_rounds: Number(doc.max_rounds ?? strategy.max_rounds ?? 12),
|
|
72
|
+
max_exchanges_per_round: Number(
|
|
73
|
+
doc.max_exchanges_per_round ?? strategy.max_exchanges_per_round ?? 3,
|
|
74
|
+
),
|
|
75
|
+
round_token_cap: Number(
|
|
76
|
+
doc.round_token_cap ?? strategy.round_token_cap ?? 8000,
|
|
77
|
+
),
|
|
78
|
+
debate_global_cap: Number(
|
|
79
|
+
doc.debate_global_cap ?? strategy.debate_global_cap ?? 80000,
|
|
80
|
+
),
|
|
81
|
+
human_required: doc.human_required === true,
|
|
82
|
+
rationale: Array.isArray(doc.rationale)
|
|
83
|
+
? doc.rationale.map((r) => String(r))
|
|
84
|
+
: [],
|
|
85
|
+
review_gate_strategy: strategy,
|
|
86
|
+
};
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -249,6 +249,16 @@ export function harnessPlanDebateEligibility(
|
|
|
249
249
|
: [...PLAN_FOCUS_AREAS];
|
|
250
250
|
|
|
251
251
|
const caps = capsForProfile(profile);
|
|
252
|
+
const reviewMode =
|
|
253
|
+
profile === "fast"
|
|
254
|
+
? ("consolidated" as const)
|
|
255
|
+
: profile === "standard"
|
|
256
|
+
? ("parallel_probes" as const)
|
|
257
|
+
: ("threaded" as const);
|
|
258
|
+
const minFocusForStrategy =
|
|
259
|
+
reviewMode === "parallel_probes" || reviewMode === "consolidated"
|
|
260
|
+
? 1
|
|
261
|
+
: caps.min_focus_rounds;
|
|
252
262
|
|
|
253
263
|
return {
|
|
254
264
|
profile,
|
|
@@ -257,15 +267,10 @@ export function harnessPlanDebateEligibility(
|
|
|
257
267
|
human_required,
|
|
258
268
|
rationale,
|
|
259
269
|
review_gate_strategy: {
|
|
260
|
-
mode:
|
|
261
|
-
profile === "fast"
|
|
262
|
-
? "consolidated"
|
|
263
|
-
: profile === "standard"
|
|
264
|
-
? "parallel_probes"
|
|
265
|
-
: "threaded",
|
|
270
|
+
mode: reviewMode,
|
|
266
271
|
profile,
|
|
267
272
|
required_focuses: [...required_focuses],
|
|
268
|
-
min_focus_rounds:
|
|
273
|
+
min_focus_rounds: minFocusForStrategy,
|
|
269
274
|
max_rounds: caps.max_rounds,
|
|
270
275
|
max_exchanges_per_round: caps.max_exchanges_per_round,
|
|
271
276
|
round_token_cap: caps.round_token_cap,
|
|
@@ -65,7 +65,7 @@ export async function getPlanFocusCoverage(
|
|
|
65
65
|
let files: string[] = [];
|
|
66
66
|
try {
|
|
67
67
|
files = (await readdir(artifactsDir)).filter((f) =>
|
|
68
|
-
/^review-round(?:-r\d+|-consolidated)\.yaml$/i.test(f),
|
|
68
|
+
/^review-round(?:-r\d+|-consolidated|-parallel-probes)\.yaml$/i.test(f),
|
|
69
69
|
);
|
|
70
70
|
} catch {
|
|
71
71
|
return {
|
|
@@ -80,11 +80,14 @@ export async function getPlanFocusCoverage(
|
|
|
80
80
|
|
|
81
81
|
for (const name of files.sort()) {
|
|
82
82
|
const consolidated = /^review-round-consolidated\.yaml$/i.test(name);
|
|
83
|
+
const parallelProbes = /^review-round-parallel-probes\.yaml$/i.test(name);
|
|
83
84
|
const m = consolidated
|
|
84
85
|
? ["review-round-consolidated.yaml", "1"]
|
|
85
|
-
:
|
|
86
|
+
: parallelProbes
|
|
87
|
+
? ["review-round-parallel-probes.yaml", "1"]
|
|
88
|
+
: /^review-round-r(\d+)\.yaml$/i.exec(name);
|
|
86
89
|
if (!m) continue;
|
|
87
|
-
const roundIndex = consolidated ? 1 : Number(m[1]);
|
|
90
|
+
const roundIndex = consolidated || parallelProbes ? 1 : Number(m[1]);
|
|
88
91
|
if (roundIndex > last_round_index) last_round_index = roundIndex;
|
|
89
92
|
const raw = await readFile(join(artifactsDir, name), "utf-8");
|
|
90
93
|
let draft: Record<string, unknown>;
|
|
@@ -151,13 +154,22 @@ export async function readDebateRoundFocus(
|
|
|
151
154
|
runDir: string,
|
|
152
155
|
roundIndex: number,
|
|
153
156
|
): Promise<PlanDebateRoundFocus | null> {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
const candidates =
|
|
158
|
+
roundIndex === 1
|
|
159
|
+
? [
|
|
160
|
+
"review-round-parallel-probes.yaml",
|
|
161
|
+
"review-round-consolidated.yaml",
|
|
162
|
+
`review-round-r${roundIndex}.yaml`,
|
|
163
|
+
]
|
|
164
|
+
: [`review-round-r${roundIndex}.yaml`];
|
|
165
|
+
for (const name of candidates) {
|
|
166
|
+
const path = join(runDir, "artifacts", name);
|
|
167
|
+
if (!(await fileExists(path))) continue;
|
|
168
|
+
try {
|
|
169
|
+
const raw = await readFile(path, "utf-8");
|
|
170
|
+
const draft = parseYaml(raw) as Record<string, unknown>;
|
|
171
|
+
return focusFromDraft(draft);
|
|
172
|
+
} catch {}
|
|
162
173
|
}
|
|
174
|
+
return null;
|
|
163
175
|
}
|
|
@@ -5,9 +5,14 @@
|
|
|
5
5
|
import { constants } from "node:fs";
|
|
6
6
|
import { access, readFile } from "node:fs/promises";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
+
import { isHarnessNonInteractive } from "./ask-user/policy.js";
|
|
8
9
|
import { capsForDebate } from "./debate-bus-core.js";
|
|
9
10
|
import { isHarnessBudgetEnforceOn } from "./harness-budget-enforce.js";
|
|
10
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
DebateEligibilityResult,
|
|
13
|
+
DebateProfile,
|
|
14
|
+
} from "./plan-debate-eligibility.js";
|
|
15
|
+
import { loadPlanDebateEligibilitySnapshot } from "./plan-debate-eligibility-snapshot.js";
|
|
11
16
|
import {
|
|
12
17
|
getPlanFocusCoverage,
|
|
13
18
|
type PlanDebateFocus,
|
|
@@ -31,9 +36,12 @@ import {
|
|
|
31
36
|
} from "./plan-messenger.js";
|
|
32
37
|
import {
|
|
33
38
|
CONSOLIDATED_REVIEW_ARTIFACT,
|
|
39
|
+
effectiveMinFocusRounds,
|
|
34
40
|
isConsolidatedReviewStrategy,
|
|
35
41
|
isParallelProbesReviewStrategy,
|
|
42
|
+
PARALLEL_PROBES_REVIEW_ARTIFACT,
|
|
36
43
|
planReviewGateStrategyFromEligibility,
|
|
44
|
+
reviewStrategyFromMessenger,
|
|
37
45
|
} from "./plan-review-gate.js";
|
|
38
46
|
|
|
39
47
|
async function fileExists(path: string): Promise<boolean> {
|
|
@@ -226,37 +234,70 @@ export async function validatePlanDebateGate(
|
|
|
226
234
|
const debatesDir = join(projectRoot, ".pi", "harness", "debates");
|
|
227
235
|
const messenger = await loadMessengerState(runDir);
|
|
228
236
|
const debateProfile = messenger?.debate_profile ?? "standard";
|
|
237
|
+
|
|
238
|
+
if (process.env.HARNESS_QA_SMOKE === "1" && isHarnessNonInteractive()) {
|
|
239
|
+
const consensusPath = join(debatesDir, `${debateId}.consensus.json`);
|
|
240
|
+
if (await fileExists(consensusPath)) {
|
|
241
|
+
try {
|
|
242
|
+
const packet = JSON.parse(await readFile(consensusPath, "utf-8")) as {
|
|
243
|
+
headless_bypass?: boolean;
|
|
244
|
+
policy_decision?: string;
|
|
245
|
+
};
|
|
246
|
+
if (
|
|
247
|
+
packet.headless_bypass === true &&
|
|
248
|
+
packet.policy_decision !== "block"
|
|
249
|
+
) {
|
|
250
|
+
const coverage = await getPlanFocusCoverage(runDir);
|
|
251
|
+
return {
|
|
252
|
+
ok: true,
|
|
253
|
+
errors: [],
|
|
254
|
+
warnings: ["QA smoke: headless debate bypass consensus accepted"],
|
|
255
|
+
debateId,
|
|
256
|
+
focus_coverage: {
|
|
257
|
+
covered: coverage.covered,
|
|
258
|
+
missing: coverage.missing,
|
|
259
|
+
last_review_gate_ready: coverage.last_review_gate_ready,
|
|
260
|
+
},
|
|
261
|
+
debate_profile: debateProfile,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
// fall through to full gate
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
229
270
|
const requiredFocuses: readonly PlanDebateFocus[] =
|
|
230
271
|
messenger?.required_focuses && messenger.required_focuses.length > 0
|
|
231
272
|
? messenger.required_focuses
|
|
232
273
|
: (["spec", "wbs", "schedule", "quality"] as const);
|
|
233
274
|
const caps = capsForDebate(debateId, debateProfile);
|
|
234
|
-
const
|
|
235
|
-
eligibility
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
275
|
+
const eligibilitySnapshot =
|
|
276
|
+
eligibility ?? (await loadPlanDebateEligibilitySnapshot(runDir));
|
|
277
|
+
const reviewStrategy = eligibilitySnapshot
|
|
278
|
+
? planReviewGateStrategyFromEligibility(eligibilitySnapshot)
|
|
279
|
+
: messenger
|
|
280
|
+
? reviewStrategyFromMessenger(
|
|
281
|
+
messenger,
|
|
282
|
+
debateProfile as DebateProfile,
|
|
283
|
+
requiredFocuses,
|
|
284
|
+
caps,
|
|
285
|
+
)
|
|
286
|
+
: {
|
|
287
|
+
mode: "threaded" as const,
|
|
288
|
+
profile: debateProfile as DebateProfile,
|
|
289
|
+
required_focuses: [...requiredFocuses],
|
|
290
|
+
min_focus_rounds: caps.min_focus_rounds,
|
|
291
|
+
max_rounds: caps.max_rounds,
|
|
292
|
+
max_exchanges_per_round: caps.max_exchanges_per_round,
|
|
293
|
+
round_token_cap: caps.round_token_cap,
|
|
294
|
+
debate_global_cap: caps.debate_global_cap,
|
|
295
|
+
rationale: [],
|
|
296
|
+
};
|
|
297
|
+
const effectiveCaps = {
|
|
298
|
+
...caps,
|
|
299
|
+
min_focus_rounds: effectiveMinFocusRounds(reviewStrategy),
|
|
300
|
+
};
|
|
260
301
|
const _consolidated = isConsolidatedReviewStrategy(reviewStrategy);
|
|
261
302
|
const _parallelProbes = isParallelProbesReviewStrategy(reviewStrategy);
|
|
262
303
|
const coverage = await getPlanFocusCoverage(runDir, { requiredFocuses });
|
|
@@ -324,7 +365,7 @@ export async function validatePlanDebateGate(
|
|
|
324
365
|
const busChecks = await collectBusAndConsensusIssues({
|
|
325
366
|
debateId,
|
|
326
367
|
debatesDir,
|
|
327
|
-
caps,
|
|
368
|
+
caps: effectiveCaps,
|
|
328
369
|
requiredFocuses,
|
|
329
370
|
coverage,
|
|
330
371
|
debateProfile,
|
|
@@ -350,7 +391,8 @@ export function isReviewRoundArtifactPath(relPath: string): boolean {
|
|
|
350
391
|
const norm = relPath.replace(/\\/g, "/");
|
|
351
392
|
return (
|
|
352
393
|
/^artifacts\/review-round-r\d+\.yaml$/i.test(norm) ||
|
|
353
|
-
norm === CONSOLIDATED_REVIEW_ARTIFACT
|
|
394
|
+
norm === CONSOLIDATED_REVIEW_ARTIFACT ||
|
|
395
|
+
norm === PARALLEL_PROBES_REVIEW_ARTIFACT
|
|
354
396
|
);
|
|
355
397
|
}
|
|
356
398
|
|
|
@@ -14,7 +14,9 @@ import { planDebateIdForRun } from "./plan-debate-id.js";
|
|
|
14
14
|
import { laneArtifactPath } from "./plan-debate-lane.js";
|
|
15
15
|
import {
|
|
16
16
|
lanesForConsolidatedRound,
|
|
17
|
+
lanesForParallelProbesRound,
|
|
17
18
|
lanesForRound,
|
|
19
|
+
PARALLEL_PROBES_REVIEW_ARTIFACT,
|
|
18
20
|
} from "./plan-debate-lanes.js";
|
|
19
21
|
import {
|
|
20
22
|
getMessengerRoundState,
|
|
@@ -53,16 +55,20 @@ export async function getPlanDebateRoundStatus(
|
|
|
53
55
|
opts?: { debate_round_focus?: PlanDebateRoundFocus },
|
|
54
56
|
): Promise<RoundStatusResult> {
|
|
55
57
|
const messengerState = await loadMessengerState(runDir);
|
|
58
|
+
const parallelProbes =
|
|
59
|
+
messengerState?.review_gate_mode === "parallel_probes" && roundIndex === 1;
|
|
56
60
|
const consolidated =
|
|
57
61
|
messengerState?.review_gate_mode === "consolidated" && roundIndex === 1;
|
|
58
62
|
const focus =
|
|
59
63
|
opts?.debate_round_focus ??
|
|
60
|
-
(consolidated ? ("all" as PlanDebateRoundFocus) : null) ??
|
|
64
|
+
(consolidated || parallelProbes ? ("all" as PlanDebateRoundFocus) : null) ??
|
|
61
65
|
(await readDebateRoundFocus(runDir, roundIndex));
|
|
62
66
|
const missing: string[] = [];
|
|
63
|
-
const laneList =
|
|
64
|
-
?
|
|
65
|
-
:
|
|
67
|
+
const laneList = parallelProbes
|
|
68
|
+
? lanesForParallelProbesRound()
|
|
69
|
+
: consolidated
|
|
70
|
+
? lanesForConsolidatedRound()
|
|
71
|
+
: lanesForRound(roundIndex, focus);
|
|
66
72
|
for (const lane of laneList) {
|
|
67
73
|
const rel = laneArtifactPath(lane, roundIndex);
|
|
68
74
|
if (!(await exists(join(runDir, rel)))) {
|
|
@@ -82,13 +88,22 @@ export async function getPlanDebateRoundStatus(
|
|
|
82
88
|
if (!dialogue.ok) {
|
|
83
89
|
missing.push(...dialogue.errors.map((e) => `messenger: ${e}`));
|
|
84
90
|
}
|
|
85
|
-
const reviewRound =
|
|
86
|
-
?
|
|
87
|
-
:
|
|
91
|
+
const reviewRound = parallelProbes
|
|
92
|
+
? PARALLEL_PROBES_REVIEW_ARTIFACT
|
|
93
|
+
: consolidated
|
|
94
|
+
? "artifacts/review-round-consolidated.yaml"
|
|
95
|
+
: `artifacts/review-round-r${roundIndex}.yaml`;
|
|
88
96
|
const reviewRoundOnDisk = await exists(join(runDir, reviewRound));
|
|
89
97
|
|
|
90
98
|
let next_tool: string | undefined;
|
|
91
|
-
if (
|
|
99
|
+
if (
|
|
100
|
+
parallelProbes &&
|
|
101
|
+
missing.some((m) => m.includes("validation-turn")) &&
|
|
102
|
+
missing.some((m) => m.includes("adversary-brief"))
|
|
103
|
+
) {
|
|
104
|
+
next_tool =
|
|
105
|
+
"subagent parallel batch: harness/planning/plan-evaluator ∥ harness/planning/plan-adversary (parallel_probes)";
|
|
106
|
+
} else if (missing.some((m) => m.includes("hypothesis-validation"))) {
|
|
92
107
|
next_tool = "subagent harness/planning/hypothesis-validator";
|
|
93
108
|
} else if (missing.some((m) => m.includes("validation-turn"))) {
|
|
94
109
|
next_tool = "subagent harness/planning/plan-evaluator";
|