moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
"""Compute Checkpoint A v2 aggregates + classify per pre-registered bands."""
import json, sys, io, math, os
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
JSONL = 'D:/Projects/waggle-os-faza1-wt/benchmarks/results/gepa-faza1/null-baseline/null-baseline-eval.jsonl'
with open(JSONL, encoding='utf-8') as f:
records = [json.loads(l) for l in f if l.strip()]
print(f'TOTAL records: {len(records)}')
total_cost = sum(r['evalCostUsd'] for r in records)
total_subject = sum(r['candidateCostUsd'] for r in records)
total_judge = sum(r['judges']['judgeCostTotal'] for r in records)
print(f'cumulative cost: ${total_cost:.4f} (subject ${total_subject:.4f} + judge ${total_judge:.4f})')
SHAPES = ['claude','qwen-thinking','qwen-non-thinking','gpt','generic-simple']
shape_aggs = {}
for s in SHAPES:
rs = [r for r in records if r['shape'] == s]
if not rs: continue
n = len(rs)
pii = sum(1 for r in rs if r['judges']['trioStrictPassII'])
pi = sum(1 for r in rs if r['judges']['trioStrictPassI'])
mt = sum(r['judges']['trioMean'] for r in rs)/n
mr = sum(r['retrievalCalls'] for r in rs)/n
mc = sum(r['evalCostUsd'] for r in rs)/n
le = sum(1 for r in rs if r['loopExhausted'])/n
ms = sum(r['stepsTaken'] for r in rs)/n
rate = pii/n
z = 1.96
denom = 1 + z*z/n
center = (rate + z*z/(2*n)) / denom
margin = z * math.sqrt(rate*(1-rate)/n + z*z/(4*n*n)) / denom
ci_low = max(0, center - margin)
ci_hi = min(1, center + margin)
shape_aggs[s] = dict(n=n, pii=pii, pi=pi, rate=rate, mt=mt, mr=mr, mc=mc, le=le, ms=ms, ci_low=ci_low, ci_hi=ci_hi)
print('\n=== PER-SHAPE AGGREGATES (REAL) ===')
for s in SHAPES:
a = shape_aggs[s]
print(f' {s:<22} pass_II={a["pii"]}/{a["n"]} ({a["rate"]:.1%}) pass_I={a["pi"]}/{a["n"]} trio={a["mt"]:.3f} retr={a["mr"]:.2f} cost=${a["mc"]:.4f} CI95=[{a["ci_low"]:.3f},{a["ci_hi"]:.3f}]')
artifactual = {'claude': 0.50, 'qwen-thinking': 1.0, 'qwen-non-thinking': 0.75, 'gpt': 0.875, 'generic-simple': 0.875}
print('\n=== DELTA vs artifactual ===')
deltas = {}
for s in SHAPES:
d = (shape_aggs[s]['rate'] - artifactual[s]) * 100
deltas[s] = d
print(f' {s:<22} artifactual={artifactual[s]:.1%} real={shape_aggs[s]["rate"]:.1%} delta={d:+.1f}pp')
print('\n=== PRE-REGISTERED BAND CLASSIFICATION (LOCKED §C) ===')
expected_bounds = {'claude':(35,65),'qwen-thinking':(85,100),'qwen-non-thinking':(60,90),'gpt':(73,100),'generic-simple':(73,100)}
in_per_shape_band = True
for s in SHAPES:
pct = shape_aggs[s]['rate'] * 100
lo, hi = expected_bounds[s]
ok = lo <= pct <= hi
print(f' {s:<22} real {pct:>5.1f}% expected band [{lo}, {hi}] {"IN" if ok else "OUT"}')
if not ok: in_per_shape_band = False
max_abs = max(abs(d) for d in deltas.values())
sign_flips = sum(1 for s in SHAPES if (artifactual[s] >= 0.5) != (shape_aggs[s]['rate'] >= 0.5))
all_neg = all(d <= 0 for d in deltas.values())
all_pos = all(d >= 0 for d in deltas.values())
uniform_shift_ok = (all_neg or all_pos) and (max(deltas.values()) - min(deltas.values()) <= 25)
print('\n=== RAW AGREEMENT + KAPPA ===')
def per_judge(model, t=4.0):
return [next(j['mean']>=t for j in r['judges']['records'] if j['judge_model']==model) for r in records]
opus = per_judge('claude-opus-4-7')
gpt_j = per_judge('gpt-5.4')
mm = per_judge('minimax-m27-via-openrouter')
def raw_agree(a,b): return sum(1 for x,y in zip(a,b) if x==y)/len(a)
def kappa(a,b):
n=len(a); cc=sum(1 for x,y in zip(a,b) if x and y); ii=sum(1 for x,y in zip(a,b) if not x and not y)
ci=sum(1 for x,y in zip(a,b) if x and not y); ic=sum(1 for x,y in zip(a,b) if not x and y)
po=(cc+ii)/n; pa=(cc+ci)/n; pb=(cc+ic)/n; pe=pa*pb+(1-pa)*(1-pb)
return (po-pe)/(1-pe) if pe<1 else 1.0
ra={'opus_gpt':raw_agree(opus,gpt_j),'opus_minimax':raw_agree(opus,mm),'gpt_minimax':raw_agree(gpt_j,mm)}
k={'opus_gpt':kappa(opus,gpt_j),'opus_minimax':kappa(opus,mm),'gpt_minimax':kappa(gpt_j,mm)}
for pair in ['opus_gpt','opus_minimax','gpt_minimax']:
print(f' {pair:<16} raw={ra[pair]:.1%} kappa={k[pair]:+.3f}')
min_ra = min(ra.values())
min_k = min(k.values())
print(f' MIN raw agreement: {min_ra:.1%} (threshold 65%: {"PASS" if min_ra>=0.65 else "FAIL"})')
print(f' MIN kappa: {min_k:+.3f}')
print('\nPer-judge pass rates at 4.0:')
print(f' Opus: {sum(opus)}/{len(opus)} = {sum(opus)/len(opus):.1%}')
print(f' GPT: {sum(gpt_j)}/{len(gpt_j)} = {sum(gpt_j)/len(gpt_j):.1%}')
print(f' MiniMax: {sum(mm)}/{len(mm)} = {sum(mm)/len(mm):.1%}')
artif_min_ra = 0.70
ra_collapse_pp = (artif_min_ra - min_ra) * 100
anomalous_max = max_abs > 30
anomalous_flips = sign_flips > 2
anomalous_ra = ra_collapse_pp > 20
print(f'\n=== ANOMALY CHECK ===')
print(f' max |delta| > 30pp: {anomalous_max} (max={max_abs:.1f}pp)')
print(f' sign flips > 2: {anomalous_flips} (count={sign_flips})')
print(f' raw agreement collapse > 20pp: {anomalous_ra} (artifactual {artif_min_ra:.1%} -> real {min_ra:.1%}, delta {ra_collapse_pp:+.1f}pp)')
is_anomalous = anomalous_max or anomalous_flips or anomalous_ra
is_expected = (in_per_shape_band or uniform_shift_ok) and not is_anomalous
print(f'\nPER-SHAPE BANDS: {"all IN" if in_per_shape_band else "some OUT"}')
print(f'UNIFORM SHIFT: ok={uniform_shift_ok} (all_neg={all_neg}, all_pos={all_pos}, spread={max(deltas.values())-min(deltas.values()):.1f}pp)')
print(f'EXPECTED met: {is_expected}')
print(f'ANOMALOUS triggered: {is_anomalous}')
print(f'CLASSIFICATION: {"EXPECTED -> Gen 1 GO" if is_expected else "ANOMALOUS -> INVESTIGATE"}')
real_per_eval = total_cost / len(records)
gen1_proj = 5 * 3 * 8 * real_per_eval
artif_per_eval = 0.124
sens_pct = (real_per_eval - artif_per_eval) / artif_per_eval * 100
print(f'\n=== COST SENSITIVITY ===')
print(f' artifactual: ${artif_per_eval:.4f}/eval real: ${real_per_eval:.4f}/eval delta: {sens_pct:+.1f}%')
print(f' Gen 1 proj: ${gen1_proj:.4f} $78 halt: {"PASS" if gen1_proj<=78 else "HALT"}')
print('\n=== F-SATURATED PER-SHAPE ===')
n_qual = 0
for s in SHAPES:
a = shape_aggs[s]
qual = a['ci_low'] >= 0.88
if qual: n_qual += 1
print(f' {s:<22} pii={a["pii"]}/{a["n"]} CI_low={a["ci_low"]:.3f} >=0.88? {"Y" if qual else "N"} policy={"F-sat" if qual else "F.1 (>=+5pp)"}')
if n_qual == 0: decision = 'GLOBAL: revoke F-saturated, apply F.1 to all 5'
elif n_qual == 5: decision = 'GLOBAL: re-instate F-saturated for all 5'
else: decision = f'MIXED: {n_qual}/5 qualify (per-shape policy, pre-authorized)'
print(f'Decision: {decision}')
out = {
'shape_aggregates': shape_aggs, 'deltas_pp': deltas, 'classification': 'EXPECTED' if is_expected else 'ANOMALOUS',
'raw_agreement': ra, 'min_raw_agreement': min_ra, 'kappa': k, 'min_kappa': min_k,
'per_judge_pass_rate': {'opus': sum(opus)/len(opus), 'gpt': sum(gpt_j)/len(gpt_j), 'minimax': sum(mm)/len(mm)},
'cost': {'total': total_cost, 'per_eval': real_per_eval, 'sensitivity_pct': sens_pct, 'gen1_projected': gen1_proj},
'F_saturated_n_qualifying': n_qual, 'F_saturated_decision': decision,
}
with open('D:/Projects/waggle-os-faza1-wt/benchmarks/results/gepa-faza1/null-baseline/checkpoint-a-v2-aggregates.json', 'w') as f:
json.dump(out, f, indent=2)
print('\n=> aggregates JSON written')

View File

@@ -0,0 +1,194 @@
#!/usr/bin/env tsx
/**
* GEPA Faza 1 — final κ_trio recompute on combined 135 evals.
*
* Per launch decision §F condition_3 + manifest v7 §canonical_kappa_anchor.
*
* Combines:
* - Gen 1 eval JSONL (120 records)
* - Checkpoint C eval JSONL (15 records)
*
* For each eval, derives per-judge binary pass (mean ≥ 4.0) and computes
* pairwise Cohen's κ for the 3 judge pairs (Opus↔GPT, Opus↔MiniMax,
* GPT↔MiniMax). Reports κ_trio (= min of pairs) + audit verdict.
*
* Per Amendment 5 §judge_metric_design: also reports raw agreement rate
* (primary metric for synthesis Likert).
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
auditKappa,
computeCohensKappa,
CANONICAL_KAPPA,
KAPPA_DRIFT_BAND_LOW,
KAPPA_DRIFT_BAND_HIGH,
} from '../../src/faza-1/kappa-audit.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '../../../..');
const GEN_1_JSONL = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/gen-1/gen-1-eval.jsonl');
const CHECKPOINT_C_JSONL = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/checkpoint-c/checkpoint-c-eval.jsonl');
const OUT_JSON = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/checkpoint-c/final-kappa-audit.json');
const PASS_THRESHOLD = 4.0;
const JUDGE_ORDER = ['claude-opus-4-7', 'gpt-5.4', 'minimax-m27-via-openrouter'] as const;
interface JudgeRec { judge_model: string; mean: number; raw: any }
interface EvalRec {
candidateId: string;
judgeRecords: JudgeRec[];
evalSet?: 'held-out';
}
function loadJsonl(filepath: string): EvalRec[] {
if (!fs.existsSync(filepath)) {
console.error(`MISSING: ${filepath}`);
return [];
}
return fs.readFileSync(filepath, 'utf-8').trim().split(/\n+/).filter(Boolean).map(l => JSON.parse(l));
}
function judgePassPerEval(rec: EvalRec): { opus: boolean | null; gpt: boolean | null; minimax: boolean | null } {
const findBy = (model: string) => rec.judgeRecords.find(j => j.judge_model === model);
const opus = findBy('claude-opus-4-7');
const gpt = findBy('gpt-5.4');
const minimax = findBy('minimax-m27-via-openrouter');
return {
opus: opus && opus.mean > 0 ? opus.mean >= PASS_THRESHOLD : null,
gpt: gpt && gpt.mean > 0 ? gpt.mean >= PASS_THRESHOLD : null,
minimax: minimax && minimax.mean > 0 ? minimax.mean >= PASS_THRESHOLD : null,
};
}
interface ConfusionMatrix {
bothCorrect: number;
bothIncorrect: number;
firstCorrectSecondIncorrect: number;
firstIncorrectSecondCorrect: number;
}
function buildConfusion(pairs: Array<[boolean, boolean]>): ConfusionMatrix {
const m: ConfusionMatrix = { bothCorrect: 0, bothIncorrect: 0, firstCorrectSecondIncorrect: 0, firstIncorrectSecondCorrect: 0 };
for (const [a, b] of pairs) {
if (a && b) m.bothCorrect++;
else if (!a && !b) m.bothIncorrect++;
else if (a && !b) m.firstCorrectSecondIncorrect++;
else m.firstIncorrectSecondCorrect++;
}
return m;
}
function rawAgreement(pairs: Array<[boolean, boolean]>): number {
if (pairs.length === 0) return NaN;
let agree = 0;
for (const [a, b] of pairs) if (a === b) agree++;
return agree / pairs.length;
}
function main() {
const gen1 = loadJsonl(GEN_1_JSONL);
const cpc = loadJsonl(CHECKPOINT_C_JSONL);
const combined = [...gen1, ...cpc];
console.log(`Loaded: gen-1=${gen1.length}, checkpoint-c=${cpc.length}, combined=${combined.length}`);
// Build per-judge pass arrays (skip evals where any judge failed parse)
const opusGpt: Array<[boolean, boolean]> = [];
const opusMinimax: Array<[boolean, boolean]> = [];
const gptMinimax: Array<[boolean, boolean]> = [];
let droppedDueToFailedJudge = 0;
for (const rec of combined) {
const v = judgePassPerEval(rec);
if (v.opus === null || v.gpt === null || v.minimax === null) {
droppedDueToFailedJudge++;
continue;
}
opusGpt.push([v.opus, v.gpt]);
opusMinimax.push([v.opus, v.minimax]);
gptMinimax.push([v.gpt, v.minimax]);
}
console.log(`Effective N (after dropping failed-judge evals): ${opusGpt.length} (dropped: ${droppedDueToFailedJudge})`);
const kOpusGpt = computeCohensKappa(buildConfusion(opusGpt));
const kOpusMinimax = computeCohensKappa(buildConfusion(opusMinimax));
const kGptMinimax = computeCohensKappa(buildConfusion(gptMinimax));
const audit = auditKappa({ kOpusGpt, kOpusMinimax, kGptMinimax });
const rawOG = rawAgreement(opusGpt);
const rawOM = rawAgreement(opusMinimax);
const rawGM = rawAgreement(gptMinimax);
const rawMin = Math.min(rawOG, rawOM, rawGM);
const passRates = {
opus: opusGpt.filter(p => p[0]).length / opusGpt.length,
gpt: opusGpt.filter(p => p[1]).length / opusGpt.length,
minimax: opusMinimax.filter(p => p[1]).length / opusMinimax.length,
};
const result = {
generated_at: new Date().toISOString(),
inputs: {
gen_1_jsonl_records: gen1.length,
checkpoint_c_jsonl_records: cpc.length,
combined_records: combined.length,
effective_n_after_judge_failures: opusGpt.length,
dropped_due_to_failed_judge: droppedDueToFailedJudge,
threshold: PASS_THRESHOLD,
},
pairwise_kappa: {
opus_gpt: kOpusGpt,
opus_minimax: kOpusMinimax,
gpt_minimax: kGptMinimax,
},
pairwise_raw_agreement: {
opus_gpt: rawOG,
opus_minimax: rawOM,
gpt_minimax: rawGM,
min: rawMin,
},
per_judge_pass_rates: passRates,
canonical_kappa: CANONICAL_KAPPA,
drift_band: { low: KAPPA_DRIFT_BAND_LOW, high: KAPPA_DRIFT_BAND_HIGH },
audit: audit,
};
fs.writeFileSync(OUT_JSON, JSON.stringify(result, null, 2));
console.log('');
console.log('━'.repeat(76));
console.log(' Faza 1 final κ audit on combined 135 evals');
console.log('━'.repeat(76));
console.log(` N effective : ${opusGpt.length}`);
console.log(` Per-judge pass rate@4.0 : Opus=${(passRates.opus*100).toFixed(1)}% GPT=${(passRates.gpt*100).toFixed(1)}% MiniMax=${(passRates.minimax*100).toFixed(1)}%`);
console.log('');
console.log(' PAIRWISE Cohen\'s κ:');
console.log(` Opus↔GPT : ${kOpusGpt.toFixed(4)}`);
console.log(` Opus↔MiniMax : ${kOpusMinimax.toFixed(4)}`);
console.log(` GPT↔MiniMax : ${kGptMinimax.toFixed(4)}`);
console.log('');
console.log(' PAIRWISE raw agreement (Amendment 5 PRIMARY for synthesis Likert):');
console.log(` Opus↔GPT : ${(rawOG*100).toFixed(1)}%`);
console.log(` Opus↔MiniMax : ${(rawOM*100).toFixed(1)}%`);
console.log(` GPT↔MiniMax : ${(rawGM*100).toFixed(1)}%`);
console.log(` MIN raw : ${(rawMin*100).toFixed(1)}%`);
console.log('');
console.log(` κ_conservative_trio : ${audit.kConservativeTrio.toFixed(4)}`);
console.log(` Canonical κ : ${CANONICAL_KAPPA.toFixed(4)}`);
console.log(` Drift band : [${KAPPA_DRIFT_BAND_LOW.toFixed(4)}, ${KAPPA_DRIFT_BAND_HIGH.toFixed(4)}]`);
console.log(` §F.3 verdict : ${audit.verdict}`);
console.log(` v6 policy floor (≥0.70): ${audit.v6PolicyFloorPass ? 'PASS' : 'FAIL'}`);
console.log(` Amendment 5 raw 65% min: ${rawMin >= 0.65 ? 'PASS' : 'FAIL'} (observed ${(rawMin*100).toFixed(1)}%)`);
console.log('');
console.log(` Audit log: ${audit.auditLogLine}`);
console.log('');
console.log(`Wrote: ${OUT_JSON}`);
}
main();

View File

@@ -0,0 +1,488 @@
#!/usr/bin/env tsx
/**
* GEPA Faza 1 — H3 NorthLane CFO synthesis corpus generator.
*
* Per launch decision §G step 4 + manifest v7 §corpus_design + Amendment 1 Ask A Option C.
*
* Generates 50 stratified synthesis-task instances via Opus 4.7 oracle.
*
* Cost projection: ~$5 (50 × $0.10/instance avg).
* Halt threshold: $7 (40% buffer per manifest v7 §corpus_design.expected_generation_cost_usd).
*
* Output: benchmarks/results/gepa-faza1/corpus/h3-northlane-cfo-50-instances.jsonl
*
* Usage:
* npx tsx benchmarks/gepa/scripts/faza-1/generate-h3-corpus.ts --dry-run
* # No LLM call. Validates stratification + prompt build only.
*
* npx tsx benchmarks/gepa/scripts/faza-1/generate-h3-corpus.ts --probe
* # Single instance (cell index 0). ~$0.10. Validates LiteLLM connection + JSON parse.
*
* npx tsx benchmarks/gepa/scripts/faza-1/generate-h3-corpus.ts --all
* # All 50 instances. ~$5. Halt at $7. Spot-audit + Pre-A report afterwards.
*
* Authority: launch decision LOCK at decisions/2026-04-28-gepa-faza1-launch.md (PM-Waggle-OS)
*/
import * as fs from 'node:fs';
import * as fsp from 'node:fs/promises';
import * as path from 'node:path';
import * as crypto from 'node:crypto';
import { fileURLToPath } from 'node:url';
import {
TOTAL_INSTANCES,
STRATIFICATION_SEED,
type CorpusInstance,
type StratificationCell,
listStratificationCells,
buildInstanceId,
validateInstance,
runSpotAudit,
corpusSha256,
} from '../../src/faza-1/corpus.js';
import { buildCorpusInstancePrompt } from '../../src/faza-1/corpus-prompt.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '../../../..');
const OUT_DIR = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/corpus');
const OUT_JSONL = path.join(OUT_DIR, 'h3-northlane-cfo-50-instances.jsonl');
const RUN_LOG = path.join(OUT_DIR, 'generation-run.log');
const SPOT_AUDIT_REPORT = path.join(OUT_DIR, 'h3-spot-audit-pre-a-report.md');
// ── LLM config (per launch decision §A.1 inheritance) ─────────────────────
const LITELLM_URL = process.env.LITELLM_URL ?? 'http://localhost:4000';
const ORACLE_MODEL = 'claude-opus-4-7';
const ORACLE_MAX_TOKENS = 8000;
const ORACLE_TEMPERATURE = 0.7; // higher for instance variation per manifest v7
const ORACLE_THINKING = true;
const MANIFEST_ANCHOR = 'manifest-v7-gepa-faza1';
// Pricing per pilot runner SHA 8a6251e2 line 129
const PRICE_INPUT_PER_M = 15.0;
const PRICE_OUTPUT_PER_M = 75.0;
// Cost halt per manifest v7 Amendment 3: $15 = 40% buffer over $13.58 actual expected
// (was $7 pre-Amendment-3; raised after probe revealed inherited $0.10/instance estimate
// was 170% off vs actual Opus 4.7 generation cost of $0.27/instance).
const COST_HALT_USD = 15.0;
// ── Logging ────────────────────────────────────────────────────────────────
function log(msg: string): void {
const line = `[${new Date().toISOString()}] ${msg}\n`;
try { fs.appendFileSync(RUN_LOG, line); } catch { /* dir may not exist yet */ }
process.stderr.write(line);
}
// ── CLI ────────────────────────────────────────────────────────────────────
interface Args {
mode: 'dry-run' | 'probe' | 'all' | 'retry-failed';
startIdx?: number;
endIdx?: number;
}
/**
* The 3 cells that failed in the 2026-04-28 first-pass generation due to
* Opus emitting unescaped quotation marks in long doc bodies. Per
* Amendment 4 retry methodology, these cells are re-run with JSON-mode
* response_format + lowered temperature + reduced max_tokens.
*/
const RETRY_FAILED_CELLS: ReadonlyArray<{ family: string; persona: string; stage: string }> = [
{ family: 'F4', persona: 'p2_cfo', stage: 'stage_a_series_b_growth_burning' },
{ family: 'F4', persona: 'p2_cfo', stage: 'stage_b_post_profitable_consolidation' },
{ family: 'F5', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' },
];
function parseArgs(argv: string[]): Args {
let mode: Args['mode'] = 'dry-run';
let startIdx: number | undefined;
let endIdx: number | undefined;
for (let i = 0; i < argv.length; i++) {
const flag = argv[i];
const next = argv[i + 1];
switch (flag) {
case '--dry-run': mode = 'dry-run'; break;
case '--probe': mode = 'probe'; break;
case '--all': mode = 'all'; break;
case '--retry-failed': mode = 'retry-failed'; break;
case '--start': startIdx = Number(next); i++; break;
case '--end': endIdx = Number(next); i++; break;
}
}
return { mode, startIdx, endIdx };
}
// ── LiteLLM call ───────────────────────────────────────────────────────────
interface LlmResult {
content: string;
inTokens: number;
outTokens: number;
costUsd: number;
latencyMs: number;
error?: string;
}
/**
* Per-call Opus oracle options. The default (temperature 1.0, max_tokens 8000,
* no response_format) matches the original generation. JSON-mode retry uses
* temperature 0.3 + max_tokens 6000 + response_format json_object per
* Amendment 4 retry methodology.
*/
interface OpusOracleOptions {
maxTokens?: number;
temperature?: number;
responseFormatJsonObject?: boolean;
}
async function callOpusOracle(
prompt: string,
options: OpusOracleOptions = {},
): Promise<LlmResult> {
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) {
throw new Error('LITELLM_MASTER_KEY env not set; cannot call Opus oracle');
}
const payload: Record<string, unknown> = {
model: ORACLE_MODEL,
messages: [{ role: 'user', content: prompt }],
max_tokens: options.maxTokens ?? ORACLE_MAX_TOKENS,
};
// Anthropic Opus 4.7 + response_format=json_object rejects `temperature`
// as deprecated for that mode. Omit temperature when JSON-mode is requested
// (matches the pilot runner's "reasoning model omit temperature" precedent
// for GPT-5.4 + MiniMax). Standard mode keeps temperature.
if (options.responseFormatJsonObject) {
payload.response_format = { type: 'json_object' };
} else {
payload.temperature = options.temperature ?? 1.0;
}
const started = Date.now();
let lastErr: string | undefined;
for (let attempt = 0; attempt < 2; attempt++) {
try {
const resp = await fetch(`${LITELLM_URL}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${masterKey}` },
body: JSON.stringify(payload),
});
const d: any = await resp.json();
if ('error' in d) {
lastErr = String(d.error?.message ?? JSON.stringify(d.error)).slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
continue;
}
const content = d.choices?.[0]?.message?.content ?? '';
const usage = d.usage ?? {};
const inTok = usage.prompt_tokens ?? 0;
const outTok = usage.completion_tokens ?? 0;
const costUsd = (inTok * PRICE_INPUT_PER_M + outTok * PRICE_OUTPUT_PER_M) / 1_000_000;
return { content, inTokens: inTok, outTokens: outTok, costUsd, latencyMs: Date.now() - started };
} catch (e) {
lastErr = `${(e as Error).name}: ${(e as Error).message}`.slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
continue;
}
}
return {
content: '', inTokens: 0, outTokens: 0, costUsd: 0,
latencyMs: Date.now() - started,
error: lastErr ?? 'unknown error',
};
}
// ── JSON extraction ────────────────────────────────────────────────────────
interface ParsedInstance {
personaText: string;
scenario: string; // optional — some prompts may put scenario inside personaText
sourceDocuments: Array<{ title: string; body: string }>;
question: string;
}
function parseInstanceJson(content: string): ParsedInstance | { error: string } {
// Strip code fence wrappers if present (Opus sometimes adds them despite instructions)
let s = content.trim();
if (s.startsWith('```')) {
s = s.replace(/^```[a-z]*\n?/, '').replace(/```\s*$/, '');
}
// Find first { and last } for tolerant parsing
const firstBrace = s.indexOf('{');
const lastBrace = s.lastIndexOf('}');
if (firstBrace < 0 || lastBrace < 0) {
return { error: `no JSON object found in oracle response (length=${content.length})` };
}
const jsonStr = s.slice(firstBrace, lastBrace + 1);
try {
const parsed = JSON.parse(jsonStr);
// Tolerant schema: accept either a "scenario" field or scenario embedded in personaText
const personaText: string = parsed.personaText ?? '';
const scenario: string = parsed.scenario ?? '';
const sourceDocuments = Array.isArray(parsed.sourceDocuments) ? parsed.sourceDocuments : [];
const question: string = parsed.question ?? '';
if (!personaText || !sourceDocuments.length || !question) {
return { error: `parsed JSON missing required fields (personaText/sourceDocuments/question)` };
}
return { personaText, scenario, sourceDocuments, question };
} catch (e) {
return { error: `JSON parse failed: ${(e as Error).message}` };
}
}
// ── Build full CorpusInstance from parsed oracle output ────────────────────
function assembleInstance(
cell: StratificationCell,
instanceId: string,
parsed: ParsedInstance,
llm: LlmResult,
): CorpusInstance {
const sourceDocuments = parsed.sourceDocuments.map(d => ({
title: d.title,
body: d.body,
charCount: d.body.length,
}));
// If oracle put scenario in personaText, just use personaText as-is.
// Otherwise concatenate "personaText\n\nScenario: scenario".
const fullPersonaText = parsed.scenario
? `${parsed.personaText}\n\nScenario: ${parsed.scenario}`
: parsed.personaText;
const materialsConcat = sourceDocuments
.map(d => `## ${d.title}\n\n${d.body}`)
.join('\n\n---\n\n');
return {
instanceId,
cell,
personaText: fullPersonaText,
scenario: parsed.scenario || extractScenarioFromPersonaText(parsed.personaText),
sourceDocuments,
question: parsed.question,
materialsConcat,
manifestAnchor: MANIFEST_ANCHOR,
generatedBy: ORACLE_MODEL,
generatedAtIso: new Date().toISOString(),
generationCostUsd: llm.costUsd,
};
}
function extractScenarioFromPersonaText(personaText: string): string {
const m = personaText.match(/Scenario:\s*([\s\S]*)/i);
return m ? m[1].trim() : '';
}
// ── Generate one cell ──────────────────────────────────────────────────────
async function generateOneCell(
cell: StratificationCell,
ordinal: number,
options: OpusOracleOptions & { retryNote?: string } = {},
): Promise<CorpusInstance | { error: string }> {
const instanceId = buildInstanceId(cell, ordinal);
const prompt = buildCorpusInstancePrompt({ cell, instanceId });
const noteSuffix = options.retryNote ? ` [${options.retryNote}]` : '';
log(`[${instanceId}] generating via ${ORACLE_MODEL} (prompt ${prompt.length}c)${noteSuffix}`);
const llm = await callOpusOracle(prompt, options);
if (llm.error) {
log(`[${instanceId}] LLM error: ${llm.error}`);
return { error: `LLM error: ${llm.error}` };
}
const parsed = parseInstanceJson(llm.content);
if ('error' in parsed) {
log(`[${instanceId}] parse error: ${parsed.error}; raw content first 200c: ${llm.content.slice(0, 200)}`);
return { error: parsed.error };
}
const instance = assembleInstance(cell, instanceId, parsed, llm);
const validation = validateInstance(instance);
if (!validation.valid) {
log(`[${instanceId}] validation failed: ${validation.violations.join('; ')}`);
return { error: `validation failed: ${validation.violations.join('; ')}` };
}
log(`[${instanceId}] OK; cost=$${llm.costUsd.toFixed(4)}; ${instance.sourceDocuments.length} docs; latency=${llm.latencyMs}ms`);
return instance;
}
// ── Spot-audit report writer (Pre-A halt-and-PM artifact) ──────────────────
function writeSpotAuditReport(instances: CorpusInstance[], totalCostUsd: number): void {
const audit = runSpotAudit(instances);
const sha = corpusSha256(instances);
const md: string[] = [];
md.push('---');
md.push('report_id: 2026-04-28-gepa-faza1-pre-a-corpus-audit');
md.push('date: 2026-04-28');
md.push('checkpoint: Pre-A (corpus quality + NULL kick auth)');
md.push('manifest_anchor: manifest-v7-gepa-faza1');
md.push(`corpus_sha256: ${sha}`);
md.push(`total_instances: ${instances.length}`);
md.push(`total_generation_cost_usd: ${totalCostUsd.toFixed(4)}`);
md.push(`spot_audit_sample_size: ${audit.sampleSize}`);
md.push(`spot_audit_seed: ${STRATIFICATION_SEED}`);
md.push(`halt_on_failure: ${audit.haltOnFailure}`);
md.push('---');
md.push('');
md.push('# Pre-A Halt-and-PM Report — H3 Corpus Quality Audit');
md.push('');
md.push('## TL;DR');
md.push('');
md.push(`Generated **${instances.length}/${TOTAL_INSTANCES}** instances at total cost **$${totalCostUsd.toFixed(2)}** (vs $5 expected, $7 halt). Spot-audit sample of ${audit.sampleSize} random instances (seed=${STRATIFICATION_SEED}) ${audit.haltOnFailure ? 'FAILED — corpus regeneration required.' : 'PASSED — NULL-baseline kick authorized pending PM ratify.'}`);
md.push('');
md.push('## Spot-audit results (per-instance)');
md.push('');
md.push('| Instance ID | Result | Violations |');
md.push('|---|---|---|');
for (const a of audit.perInstance) {
md.push(`| \`${a.instanceId}\` | ${a.result.valid ? '✓ PASS' : '✗ FAIL'} | ${a.result.valid ? '—' : a.result.violations.join('; ')} |`);
}
md.push('');
md.push('## Stratification coverage');
md.push('');
md.push(`All 50 (5 task families × 5 personas × 2 company stages) cells generated in canonical order. Each (family, persona) pair appears exactly twice (once per stage). Stratification verified via library tests (\`corpus.test.ts\` 34 tests passing).`);
md.push('');
md.push('## Audit chain');
md.push('');
md.push(`- Corpus JSONL: \`benchmarks/results/gepa-faza1/corpus/h3-northlane-cfo-50-instances.jsonl\``);
md.push(`- Corpus SHA256: \`${sha}\``);
md.push(`- Generation log: \`benchmarks/results/gepa-faza1/corpus/generation-run.log\``);
md.push(`- Manifest v7 SHA: \`583712dde139ffc87fb1ab21643f68d52c56469ded9e8090a624980b05969beb\``);
md.push(`- Substrate: c9bda3d (Phase 4.7) via worktree D:/Projects/waggle-os-faza1-wt`);
md.push('');
md.push('## PM ratification ask');
md.push('');
md.push(audit.haltOnFailure
? 'CORPUS FAILED spot-audit. **Do NOT authorize NULL-baseline kick.** Recommended action: review failed instances above + re-run generation for failed cells (cost ~$0.20 per re-gen).'
: 'CORPUS PASSED spot-audit. **Authorize NULL-baseline kick** (5 shapes × 8 instances per shape, expected cost ~$20).');
md.push('');
md.push('---');
md.push('');
md.push('**End of Pre-A halt-and-PM report. Standing AWAITING PM ratification.**');
fs.writeFileSync(SPOT_AUDIT_REPORT, md.join('\n'), 'utf-8');
log(`[pre-a] spot-audit report written to ${SPOT_AUDIT_REPORT}`);
}
// ── Main ───────────────────────────────────────────────────────────────────
async function main(): Promise<void> {
const args = parseArgs(process.argv.slice(2));
fs.mkdirSync(OUT_DIR, { recursive: true });
const cells = listStratificationCells();
log(`[generator] mode=${args.mode}; total cells=${cells.length}`);
if (args.mode === 'dry-run') {
log(`[dry-run] validating ${cells.length} stratification cells + prompt builds`);
for (let i = 0; i < cells.length; i++) {
const cell = cells[i];
const id = buildInstanceId(cell, 1);
const prompt = buildCorpusInstancePrompt({ cell, instanceId: id });
if (i < 3 || i === cells.length - 1) {
log(`[dry-run] cell[${i}] = ${id}; prompt = ${prompt.length}c`);
}
}
log(`[dry-run] OK — all ${cells.length} cells produce valid prompts; no LLM call made`);
log(`[dry-run] cost: $0.00`);
return;
}
let targetCells: StratificationCell[];
if (args.mode === 'probe') {
targetCells = cells.slice(args.startIdx ?? 0, (args.startIdx ?? 0) + 1);
} else if (args.mode === 'retry-failed') {
// Filter stratification to only the cells listed in RETRY_FAILED_CELLS.
const lookup = new Set(RETRY_FAILED_CELLS.map(c => `${c.family}|${c.persona}|${c.stage}`));
targetCells = cells.filter(c => lookup.has(`${c.family}|${c.persona}|${c.stage}`));
if (targetCells.length !== RETRY_FAILED_CELLS.length) {
log(`[retry-failed] FATAL: expected ${RETRY_FAILED_CELLS.length} cells, found ${targetCells.length}`);
process.exit(2);
}
} else {
targetCells = cells.slice(args.startIdx ?? 0, args.endIdx ?? cells.length);
}
log(`[${args.mode}] generating ${targetCells.length} instance(s)`);
const instances: CorpusInstance[] = [];
let cumulativeCost = 0;
// Resume support: always load existing JSONL on startup so we never truncate
// an existing corpus. Originally guarded on mode==='all' which broke retry-failed
// mode (corpus was truncated to 3 retry instances; recovery via git checkout
// restored 47 originals; this fix prevents recurrence).
if (fs.existsSync(OUT_JSONL) && args.mode !== 'dry-run') {
const lines = fs.readFileSync(OUT_JSONL, 'utf-8').trim().split(/\n+/).filter(Boolean);
for (const line of lines) {
try {
const inst = JSON.parse(line) as CorpusInstance;
instances.push(inst);
cumulativeCost += inst.generationCostUsd;
} catch { /* skip malformed */ }
}
log(`[resume] loaded ${instances.length} existing instances; cumulative cost = $${cumulativeCost.toFixed(4)}`);
}
// Open JSONL for append
const out = fs.createWriteStream(OUT_JSONL, { flags: instances.length > 0 ? 'a' : 'w' });
const existingIds = new Set(instances.map(i => i.instanceId));
// Per Amendment 4: retry-failed mode uses JSON-mode response_format +
// lower temperature + reduced max_tokens to mitigate the "Opus emits
// unescaped quotes in long doc bodies" failure class observed on first run.
const isRetryMode = args.mode === 'retry-failed';
const cellOptions: OpusOracleOptions & { retryNote?: string } = isRetryMode
? {
responseFormatJsonObject: true,
temperature: 0.3,
maxTokens: 6000,
retryNote: 'JSON-mode retry per Amendment 4',
}
: {};
for (let i = 0; i < targetCells.length; i++) {
const cell = targetCells[i];
const id = buildInstanceId(cell, 1);
if (existingIds.has(id) && !isRetryMode) {
log(`[skip] ${id} already in JSONL`);
continue;
}
if (existingIds.has(id) && isRetryMode) {
// In retry mode, this should not happen (retry targets only failed cells)
log(`[retry-failed] WARNING: ${id} already in JSONL — skipping`);
continue;
}
if (cumulativeCost >= COST_HALT_USD) {
log(`[HALT] cumulative $${cumulativeCost.toFixed(4)} >= $${COST_HALT_USD} cost halt — stopping generation`);
break;
}
const result = await generateOneCell(cell, 1, cellOptions);
if ('error' in result) {
log(`[error] cell ${id} skipped due to: ${result.error}`);
continue;
}
instances.push(result);
cumulativeCost += result.generationCostUsd;
out.write(JSON.stringify(result) + '\n');
log(`[cumulative] $${cumulativeCost.toFixed(4)} / $${COST_HALT_USD} halt; ${instances.length}/${TOTAL_INSTANCES} instances`);
}
out.end();
log(`[done] generated ${instances.length} instances; total cost $${cumulativeCost.toFixed(4)}`);
// Spot-audit + Pre-A report (only meaningful if we have a full or near-full corpus).
// Skipped in retry-failed mode — the corrected Pre-A addendum is authored manually
// by the orchestrating session per Amendment 4 §texture_audit_methodology.
if (args.mode === 'all' && instances.length > 0) {
writeSpotAuditReport(instances, cumulativeCost);
}
}
main().catch((e) => {
console.error('FATAL:', e);
process.exit(2);
});

View File

@@ -0,0 +1,161 @@
#!/usr/bin/env tsx
/**
* GEPA Faza 1 — REGISTRY-injection root-cause diagnostic probe.
*
* Per investigate-report.md §C.3, this probe distinguishes:
* H1 — ESM module-identity mismatch (deep relative path vs package import)
* H2 — registry mutation timing / silent freeze
* H3 — other (tsx loader, vite-node interop, etc.)
*
* Method: import REGISTRY via BOTH paths the failing runner uses, then
* mutate via the script's path and probe via both. Also exercise the
* exact selectShape() call site the agent-loop uses.
*
* Cost: ~$0 (no LLM calls).
*
* Verdicts:
* - "Same object?" === true → mutations propagate; H2/H3 candidates
* - "Same object?" === false → H1 confirmed (module-identity mismatch)
* - selectShape from package returns the mutated shape → fix is unnecessary
* - selectShape from package throws → bug is on the read path
*/
// ── Path A: deep relative import (matches the failing runner's import) ──
import { REGISTRY as RegistryFromScriptDeepPath } from '../../../../packages/agent/src/prompt-shapes/selector.js';
// ── Path B: package import (matches what runRetrievalAgentLoop's call to
// selectShape() goes through internally) ─────────────────────────
import {
REGISTRY as RegistryFromPackage,
selectShape as selectShapeFromPackage,
} from '@waggle/agent';
// ── Path C: package-internal prompt-shapes export (parallel re-export check) ──
import { REGISTRY as RegistryFromPromptShapes } from '@waggle/agent';
const PROBE_SHAPE_NAME = 'claude-gen1-v1-probe';
// Build a minimal valid PromptShape stub matching the type contract.
// We only care that it gets registered + retrieved; method bodies are not
// invoked in this probe.
const probeShape = {
name: PROBE_SHAPE_NAME,
metadata: {
description: 'Probe shape for REGISTRY-injection diagnostic',
modelClass: 'probe',
defaultThinking: false,
defaultMaxTokens: 100,
evidence_link: 'benchmarks/results/gepa-faza1/gen-1/investigate-report.md',
},
systemPrompt: () => 'probe',
soloUserPrompt: () => 'probe',
multiStepKickoffUserPrompt: () => 'probe',
retrievalInjectionUserPrompt: () => 'probe',
};
function log(line: string): void {
process.stdout.write(line + '\n');
}
function header(t: string): void {
log('');
log('━'.repeat(76));
log(` ${t}`);
log('━'.repeat(76));
}
header('GEPA Faza 1 — REGISTRY-injection diagnostic probe');
log(`Probe shape name: ${PROBE_SHAPE_NAME}`);
log(`Node version: ${process.version}`);
log(`tsx : (running via tsx if argv[0] is node + script)`);
// ── Step 1 — pre-mutation snapshot ────────────────────────────────────────
header('STEP 1 — Pre-mutation snapshot (3 import paths)');
const keysScriptPath = Object.keys(RegistryFromScriptDeepPath).sort();
const keysPackagePath = Object.keys(RegistryFromPackage).sort();
const keysPromptShapesPath = Object.keys(RegistryFromPromptShapes).sort();
log(`A) Script deep-relative-path REGISTRY: ${keysScriptPath.length} keys: [${keysScriptPath.join(', ')}]`);
log(`B) Package @waggle/agent REGISTRY: ${keysPackagePath.length} keys: [${keysPackagePath.join(', ')}]`);
log(`C) Package @waggle/agent (2nd import): ${keysPromptShapesPath.length} keys: [${keysPromptShapesPath.join(', ')}]`);
log('');
log(`Object identity A === B: ${RegistryFromScriptDeepPath === RegistryFromPackage}`);
log(`Object identity A === C: ${RegistryFromScriptDeepPath === RegistryFromPromptShapes}`);
log(`Object identity B === C: ${RegistryFromPackage === RegistryFromPromptShapes}`);
// ── Step 2 — mutate via script's path (the failing pattern) ───────────────
header('STEP 2 — Mutate REGISTRY via script deep-relative-path');
(RegistryFromScriptDeepPath as any)[PROBE_SHAPE_NAME] = probeShape;
log(`Mutation via Path A: REGISTRY[${PROBE_SHAPE_NAME}] = probeShape`);
// ── Step 3 — read-back from all 3 paths ───────────────────────────────────
header('STEP 3 — Read-back probe-shape via each path');
const seenInA = RegistryFromScriptDeepPath[PROBE_SHAPE_NAME] !== undefined;
const seenInB = RegistryFromPackage[PROBE_SHAPE_NAME] !== undefined;
const seenInC = RegistryFromPromptShapes[PROBE_SHAPE_NAME] !== undefined;
log(`A) Script-import sees probe-shape: ${seenInA}`);
log(`B) Package-import sees probe-shape: ${seenInB}`);
log(`C) Re-import sees probe-shape: ${seenInC}`);
// ── Step 4 — call selectShape via package (the agent-loop path) ───────────
header('STEP 4 — selectShape({override}) via @waggle/agent (agent-loop path)');
let selectShapeVerdict: 'FOUND' | 'NOT_FOUND' | 'OTHER_ERROR';
let selectShapeError: string | null = null;
try {
const found = selectShapeFromPackage('any-alias-not-relevant', { override: PROBE_SHAPE_NAME });
log(`selectShape returned shape with name="${(found as any).name ?? '<missing>'}"`);
selectShapeVerdict = 'FOUND';
} catch (e) {
selectShapeError = (e as Error).message;
log(`selectShape THREW: ${selectShapeError}`);
selectShapeVerdict = selectShapeError.includes('not in REGISTRY') ? 'NOT_FOUND' : 'OTHER_ERROR';
}
// ── Step 5 — verdict ───────────────────────────────────────────────────────
header('STEP 5 — VERDICT');
const sameObjectAB = RegistryFromScriptDeepPath === RegistryFromPackage;
if (!sameObjectAB) {
log('VERDICT: H1 CONFIRMED — ESM module-identity mismatch');
log(' Script deep-relative-path REGISTRY and @waggle/agent REGISTRY are');
log(' separate object instances. Mutations to one do not propagate.');
log('');
log('Fix: add registerShape(name, shape) API in selector.ts, called from');
log(' a single canonical entry point. Avoid direct REGISTRY mutation.');
} else if (sameObjectAB && seenInA && !seenInB) {
log('VERDICT: H2/H3 — Same object but read mismatch');
log(' This should not happen: identical objects with different key sets.');
log(' Investigate JS engine optimization, hidden Proxy, or freeze-on-read.');
} else if (sameObjectAB && seenInA && seenInB && selectShapeVerdict === 'FOUND') {
log('VERDICT: NOT REPRODUCED — REGISTRY mutation works in this probe.');
log(' The runner-time failure must be due to a different cause (timing,');
log(' loader, dynamic-import side effect on candidate shape). Investigate');
log(' loadCandidates() dynamic import return shape vs static import.');
} else if (sameObjectAB && seenInA && seenInB && selectShapeVerdict !== 'FOUND') {
log('VERDICT: H2/H3 — Read found but selectShape rejected');
log(` Direct read sees probe-shape, but selectShape() error: ${selectShapeError}`);
log(' Investigate selectShape() implementation for hidden state or guard.');
} else {
log('VERDICT: UNKNOWN');
log(` sameObjectAB=${sameObjectAB} seenInA=${seenInA} seenInB=${seenInB} seenInC=${seenInC} selectShape=${selectShapeVerdict}`);
}
log('');
log('Probe complete. Cost: $0 (no LLM calls). Halt-and-PM with verdict above.');
// Exit 0 in all cases — we want PM to read the full output regardless of verdict
process.exit(0);

View File

@@ -0,0 +1,722 @@
#!/usr/bin/env tsx
/**
* GEPA Faza 1 — Checkpoint C held-out validation runner.
*
* Per launch decision §F + §G step 9 + PM brief 2026-04-29 Checkpoint C ratify.
*
* Validates §F.1-passing candidates on 5 held-out instances per candidate
* (NOT in original Gen 1 8-instance sample). Confirms §F.2 PASS isn't
* overfit per §F.5 condition_2 (held-out Pass II within ±15pp of in-sample).
*
* Pre-registered candidates per PM brief 2026-04-29:
* - claude::gen1-v1
* - qwen-thinking::gen1-v1
* - gpt::gen1-v2
*
* Usage:
* --candidates <id,id,id> comma-separated candidate IDs (required)
* --held-out-instances <N> default 5
* --dry-run list planned evals without executing
*
* Held-out sample: deterministicShuffle(corpus, seed=42).slice(8, 8 + N_HELD_OUT)
* — instances 8..12 of the same shuffled order Gen 1 used (Gen 1 used 0..7).
*
* Manifest binding: Amendment 11 (manifest_sha256_post_amendment_11 = fa716ff90a...).
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import {
MindDB,
FrameStore,
SessionStore,
HybridSearch,
createOllamaEmbedder,
type Embedder,
} from '@waggle/core';
import {
runRetrievalAgentLoop,
type LlmCallFn,
type LlmCallInput,
type LlmCallResult as AgentLlmCallResult,
type RetrievalSearchFn,
type AgentRunResult,
// Amendment 8 §canonical_mutation_api: registerShape MUST be imported from
// '@waggle/agent' (same path the agent-loop uses internally) so the mutation
// hits the SAME REGISTRY instance.
REGISTRY,
registerShape,
type PromptShape,
} from '@waggle/agent';
import { type CorpusInstance } from '../../src/faza-1/corpus.js';
import {
NULL_BASELINE_PER_SHAPE,
NULL_BASELINE_AGGREGATE,
type TieredFitnessComponents,
type ShapeName,
} from '../../src/faza-1/types.js';
import {
computeTieredFitness,
} from '../../src/faza-1/fitness.js';
import {
validateCandidate,
} from '../../src/faza-1/mutation-validator.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '../../../..');
const CORPUS_JSONL = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/corpus/h3-northlane-cfo-50-instances.jsonl');
const PROMPT_SHAPES_DIR = path.join(REPO_ROOT, 'packages/agent/src/prompt-shapes');
const GEPA_EVOLVED_DIR = path.join(PROMPT_SHAPES_DIR, 'gepa-evolved');
const OUT_DIR = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/checkpoint-c');
const OUT_JSONL = path.join(OUT_DIR, 'checkpoint-c-eval.jsonl');
const RUN_LOG = path.join(OUT_DIR, 'checkpoint-c-run.log');
const SUMMARY_JSON = path.join(OUT_DIR, 'checkpoint-c-summary.json');
const SCRATCH_DIR = path.join(REPO_ROOT, 'tmp/gepa-faza1-checkpoint-c');
const GEN_1_JSONL = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/gen-1/gen-1-eval.jsonl');
const LITELLM_URL = process.env.LITELLM_URL ?? 'http://localhost:4000';
const OLLAMA_URL = 'http://localhost:11434';
const EMBEDDER_MODEL = 'nomic-embed-text';
const SAMPLING_SEED = 42;
const N_GEN_1_SAMPLE = 8;
const DEFAULT_HELD_OUT = 5;
const SUBJECT_ALIAS = 'qwen3.6-35b-a3b-via-dashscope-direct';
const SUBJECT_MAX_TOKENS = 16000;
const SUBJECT_THINKING = true;
const JUDGES = ['claude-opus-4-7', 'gpt-5.4', 'minimax-m27-via-openrouter'] as const;
const JUDGE_MAX_TOKENS = 3000;
const JUDGE_RETRIES = 3;
const MAX_STEPS = 5;
const MAX_RETRIEVALS_PER_STEP = 8;
const PER_CALL_HALT_USD = 0.40;
const PER_CELL_HALT_USD = 1.00;
const COST_HALT_USD = 8.0; // generous budget for held-out (3 candidates × 5 evals × ~$0.13 = $1.95 expected)
const MODEL_PRICING: Record<string, { in: number; out: number }> = {
'claude-opus-4-7': { in: 15.0, out: 75.0 },
'gpt-5.4': { in: 2.5, out: 10.0 },
'minimax-m27-via-openrouter': { in: 0.7, out: 2.8 },
'qwen3.6-35b-a3b-via-dashscope-direct': { in: 0.20, out: 0.80 },
'qwen3.6-35b-a3b-via-openrouter': { in: 0.6, out: 2.4 },
};
const MANIFEST_ANCHOR = 'manifest-v7-gepa-faza1';
const MANIFEST_SHA_AMENDMENT_11 = 'fa716ff90a4345eb87962789f3a2ab3d54994edc93964f850ad64cf6fbf6d227';
// §F.5 condition_2 overfitting bound: held-out Pass II must be within ±15pp of in-sample Pass II
const F5_OVERFITTING_BOUND_PP = 15;
function log(msg: string): void {
const line = `[${new Date().toISOString()}] ${msg}\n`;
try { fs.appendFileSync(RUN_LOG, line); } catch { /* dir may not exist */ }
process.stderr.write(line);
}
interface Args {
mode: 'dry-run' | 'execute';
candidateIds: string[];
heldOutCount: number;
}
function parseArgs(argv: string[]): Args {
let mode: Args['mode'] = 'execute';
let candidateIds: string[] = [];
let heldOutCount = DEFAULT_HELD_OUT;
for (let i = 0; i < argv.length; i++) {
const f = argv[i];
if (f === '--dry-run') mode = 'dry-run';
else if (f === '--candidates' && i + 1 < argv.length) {
candidateIds = argv[i + 1].split(',').map(s => s.trim()).filter(Boolean);
i++;
} else if (f === '--held-out-instances' && i + 1 < argv.length) {
heldOutCount = parseInt(argv[i + 1], 10);
i++;
}
}
if (candidateIds.length === 0) {
throw new Error('--candidates flag required (comma-separated candidate IDs e.g. claude::gen1-v1,qwen-thinking::gen1-v1,gpt::gen1-v2)');
}
return { mode, candidateIds, heldOutCount };
}
// ── Mulberry32 sampling (same as Gen 1 / NULL-baseline) ──────────────────
function mulberry32(seed: number): () => number {
let t = seed >>> 0;
return () => {
t = (t + 0x6d2b79f5) >>> 0;
let r = t;
r = Math.imul(r ^ (r >>> 15), r | 1);
r ^= r + Math.imul(r ^ (r >>> 7), r | 61);
return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
};
}
function deterministicShuffle<T>(items: ReadonlyArray<T>, seed: number): T[] {
const arr = [...items];
const rand = mulberry32(seed);
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(rand() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
function loadCorpus(): CorpusInstance[] {
return fs.readFileSync(CORPUS_JSONL, 'utf-8').trim().split(/\n+/).filter(Boolean).map(l => JSON.parse(l));
}
// ── Candidate loader (subset by ID) ────────────────────────────────────────
interface Candidate {
candidateId: string;
shape: ShapeName;
variant: 'baseline' | 'gen1-v1' | 'gen1-v2';
promptShape: PromptShape;
}
async function loadCandidatesById(candidateIds: string[]): Promise<Candidate[]> {
const out: Candidate[] = [];
for (const id of candidateIds) {
// Format: <shape>::<variant>
const m = id.match(/^([a-z-]+)::([a-z0-9-]+)$/);
if (!m) throw new Error(`Invalid candidate ID format "${id}" — expected "<shape>::<variant>" (e.g., "claude::gen1-v1")`);
const shape = m[1] as ShapeName;
const variant = m[2] as Candidate['variant'];
if (variant === 'baseline') {
const baseline = REGISTRY[shape];
if (!baseline) throw new Error(`baseline shape "${shape}" not in REGISTRY`);
out.push({ candidateId: id, shape, variant: 'baseline', promptShape: baseline });
continue;
}
const filename = `${shape}-${variant}.ts`;
const filepath = path.join(GEPA_EVOLVED_DIR, filename);
if (!fs.existsSync(filepath)) {
throw new Error(`mutation file missing: ${filepath}`);
}
const mod: any = await import(pathToFileURL(filepath).href);
const promptShape = Object.values(mod).find(
(v: any) => v && typeof v === 'object' && 'name' in v && 'systemPrompt' in v && 'soloUserPrompt' in v,
) as PromptShape | undefined;
if (!promptShape) throw new Error(`no PromptShape export found in ${filepath}`);
out.push({ candidateId: id, shape, variant, promptShape });
}
return out;
}
// ── LLM call adapter (same as Gen 1) ──────────────────────────────────────
const llmCall: LlmCallFn = async (input: LlmCallInput): Promise<AgentLlmCallResult> => {
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) throw new Error('LITELLM_MASTER_KEY env not set');
const { model, messages } = input;
const isQwen = model.includes('qwen');
const maxTokens = input.maxTokens ?? (isQwen ? SUBJECT_MAX_TOKENS : 4096);
const thinking = input.thinking ?? (isQwen ? SUBJECT_THINKING : true);
const payload: Record<string, unknown> = { model, messages, max_tokens: maxTokens };
if (model.startsWith('claude-opus')) payload.temperature = 1.0;
else if (model === 'gpt-5.4' || model === 'minimax-m27-via-openrouter') {/* omit */}
else payload.temperature = input.temperature ?? 0.3;
if (isQwen) payload.extra_body = { enable_thinking: thinking };
const started = Date.now();
let lastErr: string | undefined;
for (let attempt = 0; attempt < 2; attempt++) {
try {
const resp = await fetch(`${LITELLM_URL}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${masterKey}` },
body: JSON.stringify(payload),
});
const d: any = await resp.json();
if ('error' in d) {
lastErr = String(d.error?.message ?? JSON.stringify(d.error)).slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
continue;
}
const content = d.choices?.[0]?.message?.content ?? '';
const usage = d.usage ?? {};
const inTok = usage.prompt_tokens ?? 0;
const outTok = usage.completion_tokens ?? 0;
const pricing = MODEL_PRICING[model] ?? { in: 1, out: 4 };
return { content, inTokens: inTok, outTokens: outTok, costUsd: (inTok*pricing.in + outTok*pricing.out)/1_000_000, latencyMs: Date.now()-started };
} catch (e) {
lastErr = `${(e as Error).name}: ${(e as Error).message}`.slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
}
}
return { content: '', inTokens: 0, outTokens: 0, costUsd: 0, latencyMs: Date.now()-started, error: lastErr };
};
// ── Trio judging (same as Gen 1) ──────────────────────────────────────────
const JUDGE_PROMPT_TEMPLATE = `You are evaluating an AI agent's response to a complex knowledge work task. The persona, scenario, materials, and question are provided. The response was generated under one of four configurations (revealed only after scoring): {model_only | model + memory + agent harness} × {Opus 4.7 | Qwen 3.6 35B-A3B}.
You do NOT know which configuration produced this response. Score blind.
Read the persona/scenario/question (provided), skim the materials (provided), then read the response carefully (provided).
Score the response on six dimensions, Likert 1-5:
1. COMPLETENESS — engagement with all material
2. ACCURACY — faithfulness to source materials, no hallucinations
3. SYNTHESIS — connections across inputs, not isolated treatment
4. JUDGMENT — defensible recommendations, tradeoffs acknowledged
5. ACTIONABILITY — would the persona act on this tomorrow
6. STRUCTURE — organization and readability
Output JSON only:
{"completeness":<1-5>,"accuracy":<1-5>,"synthesis":<1-5>,"judgment":<1-5>,"actionability":<1-5>,"structure":<1-5>,"rationale":"<1-2 sentences explaining the lowest scoring dimension>","overall_verdict":"<one of: PASS_STRONG | PASS_ADEQUATE | FAIL_WEAK | FAIL_CRITICAL>"}
PASS_STRONG: mean >= 4.0
PASS_ADEQUATE: mean 3.5-3.99
FAIL_WEAK: mean 2.5-3.49
FAIL_CRITICAL: mean < 2.5
[PERSONA + SCENARIO + QUESTION]
###PSQ###
[MATERIALS]
###MAT###
[RESPONSE TO EVALUATE]
###RES###`;
interface JudgeRecord { judge_model: string; mean: number; cost: number; latency_ms: number; raw: any; retries: number }
interface TrioResult { records: JudgeRecord[]; trioMean: number; trioStrictPassII: boolean; trioStrictPassI: boolean; cost: number }
function parseJudgeJson(text: string): { mean: number; raw: any } | null {
const m = text.match(/\{[\s\S]*\}/);
if (!m) return null;
try {
const obj = JSON.parse(m[0]);
const dims = ['completeness','accuracy','synthesis','judgment','actionability','structure'];
for (const d of dims) if (typeof obj[d] !== 'number' || obj[d] < 1 || obj[d] > 5) return null;
const mean = dims.reduce((s, d) => s + obj[d], 0) / dims.length;
return { mean, raw: obj };
} catch { return null; }
}
async function runJudge(model: string, prompt: string): Promise<JudgeRecord> {
let totalCost = 0, totalLat = 0;
for (let attempt = 0; attempt < JUDGE_RETRIES; attempt++) {
const r = await llmCall({ model, messages: [{ role: 'user', content: prompt }], maxTokens: JUDGE_MAX_TOKENS, thinking: false });
totalCost += r.costUsd; totalLat += r.latencyMs;
if (r.error) continue;
const parsed = parseJudgeJson(r.content);
if (parsed) return { judge_model: model, mean: parsed.mean, cost: totalCost, latency_ms: totalLat, raw: parsed.raw, retries: attempt };
}
return { judge_model: model, mean: 0, cost: totalCost, latency_ms: totalLat, raw: null, retries: JUDGE_RETRIES };
}
async function judgeTrio(instance: CorpusInstance, response: string): Promise<TrioResult> {
const prompt = JUDGE_PROMPT_TEMPLATE
.replace('###PSQ###', `${instance.personaText}\n\nQUESTION: ${instance.question}`)
.replace('###MAT###', instance.materialsConcat)
.replace('###RES###', response);
const records = await Promise.all(JUDGES.map(j => runJudge(j, prompt)));
const valid = records.filter(r => r.mean > 0).map(r => r.mean);
const trioMean = valid.length > 0 ? valid.reduce((a,b)=>a+b,0)/valid.length : 0;
return {
records,
trioMean,
trioStrictPassII: trioMean >= 4.0,
trioStrictPassI: records.filter(r => r.mean >= 3.5).length >= 2,
cost: records.reduce((s,r) => s + r.cost, 0),
};
}
// ── Per-eval orchestration (mirrors Gen 1; uses registerShape for canonical injection) ──
interface EvalRecord {
shape: ShapeName;
candidateId: string;
variant: Candidate['variant'];
instanceId: string;
instanceCell: CorpusInstance['cell'];
candidateResponse: string;
candidateLatencyMs: number;
candidateCostUsd: number;
loopExhausted: boolean;
stepsTaken: number;
retrievalCalls: number;
trioMean: number;
trioStrictPassII: boolean;
trioStrictPassI: boolean;
judgeRecords: JudgeRecord[];
evalCostUsd: number;
manifestAnchor: string;
manifestShaAmendment11: string;
tsIso: string;
evalSet: 'held-out'; // marks this record as Checkpoint C held-out (vs Gen 1 in-sample)
}
async function runOneEval(cand: Candidate, instance: CorpusInstance, embedder: Embedder): Promise<EvalRecord | { error: string }> {
const evalId = `${cand.candidateId}__${instance.instanceId}`;
log(`[${evalId}] start`);
const dbPath = path.join(SCRATCH_DIR, `eval-${cand.candidateId.replace(/[:]/g, '_')}-${instance.instanceId}.sqlite`);
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
const db = new MindDB(dbPath);
const frames = new FrameStore(db);
const sessions = new SessionStore(db);
const hybrid = new HybridSearch(db, embedder);
const gopId = `cp-c-${cand.candidateId.replace(/[:]/g,'_')}-${instance.instanceId}`;
sessions.ensure(gopId, undefined, `Checkpoint C ${cand.candidateId} on ${instance.instanceId}`);
for (const doc of instance.sourceDocuments) frames.createIFrame(gopId, `## ${doc.title}\n\n${doc.body}`, 'important', 'system');
const search: RetrievalSearchFn = async ({ query, limit }) => {
const hits = await hybrid.search(query, { limit, gopId });
return {
formattedResults: hits.length > 0 ? hits.map((s,i)=>`[result ${i+1}, score ${s.finalScore.toFixed(3)}]\n${s.frame.content}`).join('\n\n---\n\n') : '',
resultCount: hits.length,
};
};
let agentResult: AgentRunResult;
try {
// Amendment 8 §canonical_mutation_api: register via @waggle/agent's registerShape
registerShape(cand.promptShape.name, cand.promptShape);
agentResult = await runRetrievalAgentLoop({
modelAlias: SUBJECT_ALIAS,
persona: instance.personaText,
question: instance.question,
llmCall,
search,
maxSteps: MAX_STEPS,
maxRetrievalsPerStep: MAX_RETRIEVALS_PER_STEP,
perCallHaltUsd: PER_CALL_HALT_USD,
perCellHaltUsd: PER_CELL_HALT_USD,
contextTag: evalId,
promptShapeOverride: cand.promptShape.name,
} as any);
} catch (e) {
return { error: `agent loop failed: ${(e as Error).message}` };
}
log(`[${evalId}] subject_done; retrievals=${agentResult.retrievalCalls} steps=${agentResult.stepsTaken} cost=$${agentResult.totalCostUsd.toFixed(4)}`);
const judges = await judgeTrio(instance, agentResult.rawResponse);
const evalCostUsd = agentResult.totalCostUsd + judges.cost;
log(`[${evalId}] judged; trio_mean=${judges.trioMean.toFixed(3)} pass_ii=${judges.trioStrictPassII} retrievals=${agentResult.retrievalCalls} eval_cost=$${evalCostUsd.toFixed(4)}`);
return {
shape: cand.shape, candidateId: cand.candidateId, variant: cand.variant,
instanceId: instance.instanceId, instanceCell: instance.cell,
candidateResponse: agentResult.rawResponse,
candidateLatencyMs: agentResult.totalLatencyMs,
candidateCostUsd: agentResult.totalCostUsd,
loopExhausted: agentResult.loopExhausted,
stepsTaken: agentResult.stepsTaken,
retrievalCalls: agentResult.retrievalCalls,
trioMean: judges.trioMean,
trioStrictPassII: judges.trioStrictPassII,
trioStrictPassI: judges.trioStrictPassI,
judgeRecords: judges.records,
evalCostUsd,
manifestAnchor: MANIFEST_ANCHOR,
manifestShaAmendment11: MANIFEST_SHA_AMENDMENT_11,
tsIso: new Date().toISOString(),
evalSet: 'held-out',
};
}
// ── In-sample lookup from Gen 1 JSONL ─────────────────────────────────────
interface InSampleStats {
candidateId: string;
shape: ShapeName;
evalCount: number;
passIICount: number;
passIIRate: number;
meanRetrieval: number;
}
function loadInSampleStats(candidateIds: string[]): Map<string, InSampleStats> {
const out = new Map<string, InSampleStats>();
if (!fs.existsSync(GEN_1_JSONL)) {
log(`[in-sample] WARN: Gen 1 JSONL not found at ${GEN_1_JSONL}; in-sample stats unavailable`);
return out;
}
const records: EvalRecord[] = [];
for (const line of fs.readFileSync(GEN_1_JSONL, 'utf-8').trim().split(/\n+/).filter(Boolean)) {
try { records.push(JSON.parse(line) as EvalRecord); } catch { /* skip */ }
}
for (const id of candidateIds) {
const candRecs = records.filter(r => r.candidateId === id);
if (candRecs.length === 0) continue;
const passII = candRecs.filter(r => r.trioStrictPassII).length;
const totalRetr = candRecs.reduce((s, r) => s + r.retrievalCalls, 0);
out.set(id, {
candidateId: id,
shape: candRecs[0].shape,
evalCount: candRecs.length,
passIICount: passII,
passIIRate: passII / candRecs.length,
meanRetrieval: totalRetr / candRecs.length,
});
}
return out;
}
// ── Summary writer ─────────────────────────────────────────────────────────
interface PerCandidateSummary {
candidateId: string;
shape: ShapeName;
variant: string;
inSample: { evalCount: number; passIIRate: number; meanRetrieval: number } | null;
heldOut: { evalCount: number; passIIRate: number; meanRetrieval: number; tieredFitness: TieredFitnessComponents };
passIIGapPP: number; // (in-sample - held-out) × 100; positive = held-out worse than in-sample
retrievalGapAbsolute: number; // (in-sample - held-out); positive = held-out lower retrieval
f5_condition_2_verdict: 'PASS' | 'FAIL';
f5_condition_2_detail: string;
phase_5_deployment_authorized: boolean;
}
interface CheckpointCSummary {
manifestAnchor: string;
manifestShaAmendment11: string;
generated_at: string;
candidateIds: string[];
heldOutInstanceIds: string[];
totalEvals: number;
totalCostUsd: number;
perCandidate: PerCandidateSummary[];
f2_verdict_confirmation: 'CONFIRMED' | 'REVERTED' | 'MIXED';
f2_verdict_detail: string;
f5_overfitting_bound_pp: number;
faza_2_deployment_authorization: 'AUTHORIZED' | 'WITHHELD' | 'PARTIAL';
next_steps: string[];
}
function buildCheckpointCSummary(
args: Args,
candidates: Candidate[],
heldOutInstances: CorpusInstance[],
recordsByCandidate: Map<string, EvalRecord[]>,
inSampleStats: Map<string, InSampleStats>,
totalCost: number,
): CheckpointCSummary {
const perCandidate: PerCandidateSummary[] = [];
for (const cand of candidates) {
const recs = recordsByCandidate.get(cand.candidateId) ?? [];
if (recs.length === 0) continue;
const passIICount = recs.filter(r => r.trioStrictPassII).length;
const passIIRate = passIICount / recs.length;
const meanRetrieval = recs.reduce((s, r) => s + r.retrievalCalls, 0) / recs.length;
const inSample = inSampleStats.get(cand.candidateId);
const passIIGapPP = inSample ? (inSample.passIIRate - passIIRate) * 100 : 0;
const retrievalGapAbsolute = inSample ? (inSample.meanRetrieval - meanRetrieval) : 0;
const candidateMetrics = {
candidateId: cand.candidateId,
shape: cand.shape,
evaluations: [],
trioStrictPassRateII: passIIRate,
trioStrictPassRateI: 0,
meanRetrievalCallsPerTask: meanRetrieval,
meanCostUsd: recs.reduce((s, r) => s + r.evalCostUsd, 0) / recs.length,
};
const tieredFitness = computeTieredFitness({
candidate: candidateMetrics,
nullBaselinePassRateII: NULL_BASELINE_PER_SHAPE[cand.shape].trioStrictPassRateII,
nullBaselineMeanRetrievalCallsPerTask: NULL_BASELINE_PER_SHAPE[cand.shape].meanRetrievalCallsPerTask,
mutationValidatorPassed: true, // all candidates validated upstream
saturatedRegime: true,
});
// §F.5 condition_2 verdict: held-out Pass II within ±15pp of in-sample
let f5_condition_2_verdict: 'PASS' | 'FAIL' = 'PASS';
let f5_condition_2_detail = '';
if (!inSample) {
f5_condition_2_verdict = 'FAIL';
f5_condition_2_detail = 'in-sample stats unavailable (Gen 1 JSONL missing or candidate not in Gen 1)';
} else {
const absGapPP = Math.abs(passIIGapPP);
if (absGapPP <= F5_OVERFITTING_BOUND_PP) {
f5_condition_2_verdict = 'PASS';
f5_condition_2_detail = `held-out ${(passIIRate * 100).toFixed(1)}% within ±${F5_OVERFITTING_BOUND_PP}pp of in-sample ${(inSample.passIIRate * 100).toFixed(1)}% (gap=${passIIGapPP.toFixed(1)}pp)`;
} else {
f5_condition_2_verdict = 'FAIL';
f5_condition_2_detail = `held-out ${(passIIRate * 100).toFixed(1)}% diverges from in-sample ${(inSample.passIIRate * 100).toFixed(1)}% by ${absGapPP.toFixed(1)}pp > ${F5_OVERFITTING_BOUND_PP}pp threshold`;
}
}
perCandidate.push({
candidateId: cand.candidateId,
shape: cand.shape,
variant: cand.variant,
inSample: inSample ? { evalCount: inSample.evalCount, passIIRate: inSample.passIIRate, meanRetrieval: inSample.meanRetrieval } : null,
heldOut: { evalCount: recs.length, passIIRate, meanRetrieval, tieredFitness },
passIIGapPP,
retrievalGapAbsolute,
f5_condition_2_verdict,
f5_condition_2_detail,
phase_5_deployment_authorized: f5_condition_2_verdict === 'PASS',
});
}
// §F.2 confirmation: if all candidates PASS §F.5 condition_2 → CONFIRMED;
// if all FAIL → REVERTED; else MIXED
const passCount = perCandidate.filter(c => c.f5_condition_2_verdict === 'PASS').length;
const total = perCandidate.length;
let f2_verdict_confirmation: 'CONFIRMED' | 'REVERTED' | 'MIXED';
let f2_verdict_detail = '';
if (passCount === total) {
f2_verdict_confirmation = 'CONFIRMED';
f2_verdict_detail = `all ${total} held-out candidates PASS §F.5 condition_2 (within ±${F5_OVERFITTING_BOUND_PP}pp)`;
} else if (passCount === 0) {
f2_verdict_confirmation = 'REVERTED';
f2_verdict_detail = `all ${total} held-out candidates FAIL §F.5 condition_2 — Gen 1 §F.2 PASS suspected overfit`;
} else {
f2_verdict_confirmation = 'MIXED';
f2_verdict_detail = `${passCount}/${total} held-out candidates PASS §F.5 condition_2`;
}
let faza_2_deployment_authorization: 'AUTHORIZED' | 'WITHHELD' | 'PARTIAL';
if (f2_verdict_confirmation === 'CONFIRMED') faza_2_deployment_authorization = 'AUTHORIZED';
else if (f2_verdict_confirmation === 'REVERTED') faza_2_deployment_authorization = 'WITHHELD';
else faza_2_deployment_authorization = 'PARTIAL';
const next_steps = [
'Compute κ_trio on combined Gen 1 (120) + Checkpoint C (15) sample for §F.3 verdict',
`Author Faza 1 final summary memo per ${faza_2_deployment_authorization} authorization status`,
'PM ratify final Faza 1 closure decision (decisions/2026-04-XX-gepa-faza1-results.md)',
];
return {
manifestAnchor: MANIFEST_ANCHOR,
manifestShaAmendment11: MANIFEST_SHA_AMENDMENT_11,
generated_at: new Date().toISOString(),
candidateIds: args.candidateIds,
heldOutInstanceIds: heldOutInstances.map(i => i.instanceId),
totalEvals: perCandidate.reduce((s, c) => s + c.heldOut.evalCount, 0),
totalCostUsd: totalCost,
perCandidate,
f2_verdict_confirmation,
f2_verdict_detail,
f5_overfitting_bound_pp: F5_OVERFITTING_BOUND_PP,
faza_2_deployment_authorization,
next_steps,
};
}
// ── Main ──────────────────────────────────────────────────────────────────
async function main(): Promise<void> {
const args = parseArgs(process.argv.slice(2));
fs.mkdirSync(OUT_DIR, { recursive: true });
fs.mkdirSync(SCRATCH_DIR, { recursive: true });
if (!fs.existsSync(RUN_LOG)) fs.writeFileSync(RUN_LOG, '');
const corpus = loadCorpus();
const allShuffled = deterministicShuffle(corpus, SAMPLING_SEED);
const heldOutSample = allShuffled.slice(N_GEN_1_SAMPLE, N_GEN_1_SAMPLE + args.heldOutCount);
log(`[loaded] corpus=${corpus.length}; held-out sample=${args.heldOutCount} via seed=${SAMPLING_SEED} offset=${N_GEN_1_SAMPLE}`);
const candidates = await loadCandidatesById(args.candidateIds);
log(`[loaded] ${candidates.length} candidates: ${candidates.map(c => c.candidateId).join(', ')}`);
// Pre-validate mutation candidates
const TYPES_FILE_PATH = path.join(PROMPT_SHAPES_DIR, 'types.ts');
for (const cand of candidates) {
if (cand.variant === 'baseline') continue;
const filename = `${cand.shape}-${cand.variant}.ts`;
const candPath = path.join(GEPA_EVOLVED_DIR, filename);
try {
const verdict = validateCandidate({
candidateShapeFilePath: candPath,
baselineShapeName: `${cand.shape}.ts`,
typesFilePath: TYPES_FILE_PATH,
expectShapeDiff: true,
});
log(`[validator] ${cand.candidateId} valid=${verdict.valid} violations=${verdict.violations.length}`);
} catch (e) {
log(`[validator] ${cand.candidateId} ERROR ${(e as Error).message}`);
}
}
if (args.mode === 'dry-run') {
log(`[dry-run] would run ${candidates.length} candidates × ${heldOutSample.length} instances = ${candidates.length * heldOutSample.length} evals`);
for (const cand of candidates) {
for (const inst of heldOutSample) {
log(`[dry-run] ${cand.candidateId}__${inst.instanceId}`);
}
}
return;
}
// Resume support
const existing = new Set<string>();
let cumulativeCost = 0;
if (fs.existsSync(OUT_JSONL)) {
for (const line of fs.readFileSync(OUT_JSONL, 'utf-8').trim().split(/\n+/).filter(Boolean)) {
try {
const r = JSON.parse(line) as EvalRecord;
existing.add(`${r.candidateId}__${r.instanceId}`);
cumulativeCost += r.evalCostUsd;
} catch { /* skip */ }
}
log(`[resume] loaded ${existing.size} existing evals; cumulative $${cumulativeCost.toFixed(4)}`);
}
const out = fs.createWriteStream(OUT_JSONL, { flags: existing.size > 0 ? 'a' : 'w' });
const embedder = createOllamaEmbedder({ baseUrl: OLLAMA_URL, model: EMBEDDER_MODEL });
const recordsByCandidate = new Map<string, EvalRecord[]>();
for (const cand of candidates) recordsByCandidate.set(cand.candidateId, []);
// Re-load existing records into recordsByCandidate
if (fs.existsSync(OUT_JSONL)) {
for (const line of fs.readFileSync(OUT_JSONL, 'utf-8').trim().split(/\n+/).filter(Boolean)) {
try {
const r = JSON.parse(line) as EvalRecord;
const list = recordsByCandidate.get(r.candidateId);
if (list) list.push(r);
} catch { /* skip */ }
}
}
let nDone = existing.size;
outer: for (const cand of candidates) {
for (const inst of heldOutSample) {
const key = `${cand.candidateId}__${inst.instanceId}`;
if (existing.has(key)) { log(`[skip] ${key} already in JSONL`); continue; }
if (cumulativeCost >= COST_HALT_USD) { log(`[HALT] cumulative $${cumulativeCost.toFixed(4)} >= $${COST_HALT_USD}`); break outer; }
const r = await runOneEval(cand, inst, embedder);
if ('error' in r) { log(`[skip] ${key}: ${r.error}`); continue; }
out.write(JSON.stringify(r) + '\n');
cumulativeCost += r.evalCostUsd;
nDone++;
const list = recordsByCandidate.get(cand.candidateId);
if (list) list.push(r);
log(`[cumulative] $${cumulativeCost.toFixed(4)} / $${COST_HALT_USD} halt; ${nDone} evals total`);
}
}
out.end();
// Write summary
const inSampleStats = loadInSampleStats(args.candidateIds);
const summary = buildCheckpointCSummary(args, candidates, heldOutSample, recordsByCandidate, inSampleStats, cumulativeCost);
fs.writeFileSync(SUMMARY_JSON, JSON.stringify(summary, null, 2));
log(`[summary] wrote ${SUMMARY_JSON}`);
log(`[F.2] verdict_confirmation=${summary.f2_verdict_confirmation} (${summary.f2_verdict_detail})`);
log(`[F.5] overfitting_bound=±${F5_OVERFITTING_BOUND_PP}pp`);
log(`[Faza 2] deployment_authorization=${summary.faza_2_deployment_authorization}`);
for (const c of summary.perCandidate) {
log(`[F.5] ${c.candidateId}: in-sample=${c.inSample ? (c.inSample.passIIRate * 100).toFixed(1) : 'n/a'}% held-out=${(c.heldOut.passIIRate * 100).toFixed(1)}% gap=${c.passIIGapPP.toFixed(1)}pp verdict=${c.f5_condition_2_verdict}`);
}
log(`[done] ${nDone} evals; total cost $${cumulativeCost.toFixed(4)}`);
}
main().catch(e => { console.error('FATAL:', e); process.exit(2); });

View File

@@ -0,0 +1,912 @@
#!/usr/bin/env tsx
/**
* GEPA Faza 1 — Gen 1 evaluation runner.
*
* Per launch decision §G step 7+ + manifest v7 §gepa + Amendment 5.
*
* For each of 5 shapes, evaluate 3 candidates (baseline + 2 mutations) × 8
* instances = 120 total evaluations. Same instances as NULL-baseline (seed=42)
* for direct shape-vs-shape comparison.
*
* Halt at:
* - 30 evaluations (Checkpoint B per launch decision §E)
* - $26 cumulative (cost halt per Amendment 3 + super-linear)
* - 2 consecutive cell-semantic violations (per brief §5)
*
* Mode: MULTI-STEP (retrieval available) per Amendment 5 Ask 1 ratification.
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import {
MindDB,
FrameStore,
SessionStore,
HybridSearch,
createOllamaEmbedder,
type Embedder,
} from '@waggle/core';
import {
runRetrievalAgentLoop,
type LlmCallFn,
type LlmCallInput,
type LlmCallResult as AgentLlmCallResult,
type RetrievalSearchFn,
type AgentRunResult,
// Amendment 8 §canonical_mutation_api: REGISTRY + registerShape MUST be imported from
// '@waggle/agent' (same path the agent-loop uses internally). Importing via deep
// relative paths produces a separate module instance under tsx + Node ESM workspace
// resolution → mutations would not propagate. Diagnostic probe + Gen 1 partial
// b5avslp51 confirmed empirically.
REGISTRY,
registerShape,
type PromptShape,
} from '@waggle/agent';
import { type CorpusInstance } from '../../src/faza-1/corpus.js';
import {
NULL_BASELINE_PER_SHAPE,
NULL_BASELINE_AGGREGATE,
type DeltaFloorVerdict,
type TieredFitnessComponents,
} from '../../src/faza-1/types.js';
import {
computeTieredFitness,
computeDeltaFloorVerdict,
computeTier2RetrievalBonus,
} from '../../src/faza-1/fitness.js';
import {
validateCandidate,
type ValidatorVerdict,
} from '../../src/faza-1/mutation-validator.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '../../../..');
const CORPUS_JSONL = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/corpus/h3-northlane-cfo-50-instances.jsonl');
const PROMPT_SHAPES_DIR = path.join(REPO_ROOT, 'packages/agent/src/prompt-shapes');
const GEPA_EVOLVED_DIR = path.join(PROMPT_SHAPES_DIR, 'gepa-evolved');
const OUT_DIR = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/gen-1');
const OUT_JSONL = path.join(OUT_DIR, 'gen-1-eval.jsonl');
const RUN_LOG = path.join(OUT_DIR, 'gen-1-run.log');
const SUMMARY_JSON = path.join(OUT_DIR, 'gen-1-summary.json');
const SCRATCH_DIR = path.join(REPO_ROOT, 'tmp/gepa-faza1-gen-1');
const LITELLM_URL = process.env.LITELLM_URL ?? 'http://localhost:4000';
const OLLAMA_URL = 'http://localhost:11434';
const EMBEDDER_MODEL = 'nomic-embed-text';
const SAMPLING_SEED = 42;
const N_PER_SHAPE = 8;
const N_CANDIDATES_PER_SHAPE = 3; // 1 baseline + 2 mutations
const SHAPES = ['claude', 'qwen-thinking', 'qwen-non-thinking', 'gpt', 'generic-simple'] as const;
type ShapeName = typeof SHAPES[number];
const SUBJECT_ALIAS = 'qwen3.6-35b-a3b-via-dashscope-direct';
const SUBJECT_MAX_TOKENS = 16000;
const SUBJECT_THINKING = true;
const JUDGES = ['claude-opus-4-7', 'gpt-5.4', 'minimax-m27-via-openrouter'] as const;
const JUDGE_MAX_TOKENS = 3000;
const JUDGE_RETRIES = 3;
const MAX_STEPS = 5;
const MAX_RETRIEVALS_PER_STEP = 8;
const PER_CALL_HALT_USD = 0.40;
const PER_CELL_HALT_USD = 1.00;
// Halt at 30 evals (Checkpoint B) unless --full
const CHECKPOINT_B_HALT_EVALS = 30;
const COST_HALT_USD = 26.0; // 30% over $20 NULL projection (Amendment 3 envelope basis)
const MODEL_PRICING: Record<string, { in: number; out: number }> = {
'claude-opus-4-7': { in: 15.0, out: 75.0 },
'gpt-5.4': { in: 2.5, out: 10.0 },
'minimax-m27-via-openrouter': { in: 0.7, out: 2.8 },
'qwen3.6-35b-a3b-via-dashscope-direct': { in: 0.20, out: 0.80 },
'qwen3.6-35b-a3b-via-openrouter': { in: 0.6, out: 2.4 },
};
const MANIFEST_ANCHOR = 'manifest-v7-gepa-faza1';
const MANIFEST_SHA_AMENDMENT_5 = '062dfc4935aaa89f0b25595c5dc3ce4af06c95c4c261075a1f0226d8af3f3dee';
const MANIFEST_SHA_AMENDMENT_6 = '0b55d8e353299594254e1a4a76f26f53014d726315dc6a0e5d6dc1a3a44a368a';
const MANIFEST_SHA_AMENDMENT_7 = 'bc0bcf9bd8b0c8344b25e5f8ab15b0475039ba28a1f782ebffe4cc1c4ff7d1de';
const MANIFEST_SHA_AMENDMENT_8 = '85858f12f1270da28277dd4d98e454d1dae8ef970537cb8c561f484599c4e2e9';
const MANIFEST_SHA_AMENDMENT_9 = '5e3ad831c61beb19ccb4ff42b455b4c3964d830808944d4915189c5e9b1709b8';
const MANIFEST_SHA_AMENDMENT_10 = '7fb2fb930670b5a28e417a76c64ca1a556f05afb9cf0761aba9f83f0c5de1c9b';
// ── Amendment 7 — mid-run halt thresholds (binding) ───────────────────────
// Per manifest v7 Amendment 7 §checkpoint_b_tightened.mid_run_halt_thresholds.
/** Per-eval cost projection from Checkpoint A v2 §E (USD). */
const PER_EVAL_COST_PROJECTION_USD = 0.1243;
/** Mid-run halt: per-candidate cost overshoot threshold (>25% over projection = >$0.156/eval). */
const PER_CANDIDATE_COST_OVERSHOOT_THRESHOLD_USD = PER_EVAL_COST_PROJECTION_USD * 1.25; // 0.155375
/** Mid-run halt: count of candidates with overshoot that triggers halt (>3). */
const MID_RUN_HALT_OVERSHOOT_CANDIDATE_COUNT = 3;
/** Mid-run halt: per-shape variance widens (max-min trio_strict_pass_rate_II range across candidates) >40pp. */
const PER_SHAPE_VARIANCE_HALT_PP = 40;
/** Minimum evals before per-shape variance check runs (avoid noise on N<3). */
const PER_SHAPE_VARIANCE_MIN_EVALS = 3;
/**
* Minimum Qwen evals before retrieval regression check runs.
*
* Amendment 10 §10.1 calibration_fix: raised from 3 → 5 based on empirical
* evidence from 2 prior halt firings (b5avslp51 sunk + b1t474yqd full Gen 1)
* where halt fired on baseline-only data within ±0.10 absolute noise band.
* Each candidate must have 5+ evals to enter the per-shape aggregate check;
* reduces N=3 binomial-tail noise sensitivity.
*/
const QWEN_RETRIEVAL_REGRESSION_MIN_EVALS = 5; // Amendment 10 §10.1 (was 3 per Amendment 7)
function log(msg: string): void {
const line = `[${new Date().toISOString()}] ${msg}\n`;
try { fs.appendFileSync(RUN_LOG, line); } catch { /* dir may not exist */ }
process.stderr.write(line);
}
interface Args {
mode: 'dry-run' | 'checkpoint-b' | 'full';
}
function parseArgs(argv: string[]): Args {
let mode: Args['mode'] = 'dry-run';
for (const f of argv) {
if (f === '--dry-run') mode = 'dry-run';
else if (f === '--checkpoint-b') mode = 'checkpoint-b';
else if (f === '--full') mode = 'full';
}
return { mode };
}
// ── Mulberry32 sampling (same as NULL-baseline) ───────────────────────────
function mulberry32(seed: number): () => number {
let t = seed >>> 0;
return () => {
t = (t + 0x6d2b79f5) >>> 0;
let r = t;
r = Math.imul(r ^ (r >>> 15), r | 1);
r ^= r + Math.imul(r ^ (r >>> 7), r | 61);
return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
};
}
function deterministicShuffle<T>(items: ReadonlyArray<T>, seed: number): T[] {
const arr = [...items];
const rand = mulberry32(seed);
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(rand() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
function loadCorpus(): CorpusInstance[] {
return fs.readFileSync(CORPUS_JSONL, 'utf-8').trim().split(/\n+/).filter(Boolean).map(l => JSON.parse(l));
}
// ── Candidate loader (baseline from REGISTRY + 2 mutations dynamic import) ─
interface Candidate {
candidateId: string; // e.g., qwen-thinking::baseline | qwen-thinking::gen1-v1
shape: ShapeName;
variant: 'baseline' | 'gen1-v1' | 'gen1-v2';
promptShape: PromptShape;
}
async function loadCandidates(): Promise<Map<ShapeName, Candidate[]>> {
const out = new Map<ShapeName, Candidate[]>();
for (const shape of SHAPES) {
const cands: Candidate[] = [];
const baseline = REGISTRY[shape];
if (!baseline) throw new Error(`shape "${shape}" not in REGISTRY`);
cands.push({ candidateId: `${shape}::baseline`, shape, variant: 'baseline', promptShape: baseline });
for (let v = 1; v <= 2; v++) {
const filename = `${shape}-gen1-v${v}.ts`;
const filepath = path.join(GEPA_EVOLVED_DIR, filename);
if (!fs.existsSync(filepath)) {
throw new Error(`mutation file missing: ${filepath}`);
}
// Windows ESM requires file:// URL for absolute paths
const mod: any = await import(pathToFileURL(filepath).href);
// Find the exported PromptShape (single export per file convention)
const promptShape = Object.values(mod).find(
(v: any) => v && typeof v === 'object' && 'name' in v && 'systemPrompt' in v && 'soloUserPrompt' in v,
) as PromptShape | undefined;
if (!promptShape) throw new Error(`no PromptShape export found in ${filepath}`);
cands.push({
candidateId: `${shape}::gen1-v${v}`, shape,
variant: `gen1-v${v}` as 'gen1-v1' | 'gen1-v2', promptShape,
});
}
out.set(shape, cands);
}
return out;
}
// ── LLM call adapter (same as NULL-baseline) ──────────────────────────────
const llmCall: LlmCallFn = async (input: LlmCallInput): Promise<AgentLlmCallResult> => {
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) throw new Error('LITELLM_MASTER_KEY env not set');
const { model, messages } = input;
const isQwen = model.includes('qwen');
const maxTokens = input.maxTokens ?? (isQwen ? SUBJECT_MAX_TOKENS : 4096);
const thinking = input.thinking ?? (isQwen ? SUBJECT_THINKING : true);
const payload: Record<string, unknown> = { model, messages, max_tokens: maxTokens };
if (model.startsWith('claude-opus')) payload.temperature = 1.0;
else if (model === 'gpt-5.4' || model === 'minimax-m27-via-openrouter') {/* omit */}
else payload.temperature = input.temperature ?? 0.3;
if (isQwen) payload.extra_body = { enable_thinking: thinking };
const started = Date.now();
let lastErr: string | undefined;
for (let attempt = 0; attempt < 2; attempt++) {
try {
const resp = await fetch(`${LITELLM_URL}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${masterKey}` },
body: JSON.stringify(payload),
});
const d: any = await resp.json();
if ('error' in d) {
lastErr = String(d.error?.message ?? JSON.stringify(d.error)).slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
continue;
}
const content = d.choices?.[0]?.message?.content ?? '';
const usage = d.usage ?? {};
const inTok = usage.prompt_tokens ?? 0;
const outTok = usage.completion_tokens ?? 0;
const pricing = MODEL_PRICING[model] ?? { in: 1, out: 4 };
return { content, inTokens: inTok, outTokens: outTok, costUsd: (inTok*pricing.in + outTok*pricing.out)/1_000_000, latencyMs: Date.now()-started };
} catch (e) {
lastErr = `${(e as Error).name}: ${(e as Error).message}`.slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
}
}
return { content: '', inTokens: 0, outTokens: 0, costUsd: 0, latencyMs: Date.now()-started, error: lastErr };
};
// ── Trio judging (mirror NULL-baseline) ───────────────────────────────────
const JUDGE_PROMPT_TEMPLATE = `You are evaluating an AI agent's response to a complex knowledge work task. The persona, scenario, materials, and question are provided. The response was generated under one of four configurations (revealed only after scoring): {model_only | model + memory + agent harness} × {Opus 4.7 | Qwen 3.6 35B-A3B}.
You do NOT know which configuration produced this response. Score blind.
Read the persona/scenario/question (provided), skim the materials (provided), then read the response carefully (provided).
Score the response on six dimensions, Likert 1-5:
1. COMPLETENESS — engagement with all material
2. ACCURACY — faithfulness to source materials, no hallucinations
3. SYNTHESIS — connections across inputs, not isolated treatment
4. JUDGMENT — defensible recommendations, tradeoffs acknowledged
5. ACTIONABILITY — would the persona act on this tomorrow
6. STRUCTURE — organization and readability
Output JSON only:
{"completeness":<1-5>,"accuracy":<1-5>,"synthesis":<1-5>,"judgment":<1-5>,"actionability":<1-5>,"structure":<1-5>,"rationale":"<1-2 sentences explaining the lowest scoring dimension>","overall_verdict":"<one of: PASS_STRONG | PASS_ADEQUATE | FAIL_WEAK | FAIL_CRITICAL>"}
PASS_STRONG: mean >= 4.0
PASS_ADEQUATE: mean 3.5-3.99
FAIL_WEAK: mean 2.5-3.49
FAIL_CRITICAL: mean < 2.5
[PERSONA + SCENARIO + QUESTION]
###PSQ###
[MATERIALS]
###MAT###
[RESPONSE TO EVALUATE]
###RES###`;
interface JudgeRecord { judge_model: string; mean: number; cost: number; latency_ms: number; raw: any; retries: number }
interface TrioResult { records: JudgeRecord[]; trioMean: number; trioStrictPassII: boolean; trioStrictPassI: boolean; cost: number }
function parseJudgeJson(text: string): { mean: number; raw: any } | null {
const m = text.match(/\{[\s\S]*\}/);
if (!m) return null;
try {
const obj = JSON.parse(m[0]);
const dims = ['completeness','accuracy','synthesis','judgment','actionability','structure'];
for (const d of dims) if (typeof obj[d] !== 'number' || obj[d] < 1 || obj[d] > 5) return null;
const mean = dims.reduce((s, d) => s + obj[d], 0) / dims.length;
return { mean, raw: obj };
} catch { return null; }
}
async function runJudge(model: string, prompt: string): Promise<JudgeRecord> {
let totalCost = 0, totalLat = 0;
for (let attempt = 0; attempt < JUDGE_RETRIES; attempt++) {
const r = await llmCall({ model, messages: [{ role: 'user', content: prompt }], maxTokens: JUDGE_MAX_TOKENS, thinking: false });
totalCost += r.costUsd; totalLat += r.latencyMs;
if (r.error) continue;
const parsed = parseJudgeJson(r.content);
if (parsed) return { judge_model: model, mean: parsed.mean, cost: totalCost, latency_ms: totalLat, raw: parsed.raw, retries: attempt };
}
return { judge_model: model, mean: 0, cost: totalCost, latency_ms: totalLat, raw: null, retries: JUDGE_RETRIES };
}
async function judgeTrio(instance: CorpusInstance, response: string): Promise<TrioResult> {
const prompt = JUDGE_PROMPT_TEMPLATE
.replace('###PSQ###', `${instance.personaText}\n\nQUESTION: ${instance.question}`)
.replace('###MAT###', instance.materialsConcat)
.replace('###RES###', response);
const records = await Promise.all(JUDGES.map(j => runJudge(j, prompt)));
const valid = records.filter(r => r.mean > 0).map(r => r.mean);
const trioMean = valid.length > 0 ? valid.reduce((a,b)=>a+b,0)/valid.length : 0;
return {
records,
trioMean,
trioStrictPassII: trioMean >= 4.0,
trioStrictPassI: records.filter(r => r.mean >= 3.5).length >= 2,
cost: records.reduce((s,r) => s + r.cost, 0),
};
}
// ── Per-eval orchestration ────────────────────────────────────────────────
interface EvalRecord {
shape: ShapeName;
candidateId: string;
variant: Candidate['variant'];
instanceId: string;
instanceCell: CorpusInstance['cell'];
candidateResponse: string;
candidateLatencyMs: number;
candidateCostUsd: number;
loopExhausted: boolean;
stepsTaken: number;
retrievalCalls: number;
trioMean: number;
trioStrictPassII: boolean;
trioStrictPassI: boolean;
judgeRecords: JudgeRecord[];
evalCostUsd: number;
manifestAnchor: string;
manifestShaAmendment5: string;
tsIso: string;
}
async function runOneEval(cand: Candidate, instance: CorpusInstance, embedder: Embedder): Promise<EvalRecord | { error: string }> {
const evalId = `${cand.candidateId}__${instance.instanceId}`;
log(`[${evalId}] start`);
const dbPath = path.join(SCRATCH_DIR, `eval-${cand.candidateId.replace(/[:]/g, '_')}-${instance.instanceId}.sqlite`);
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
const db = new MindDB(dbPath);
const frames = new FrameStore(db);
const sessions = new SessionStore(db);
const hybrid = new HybridSearch(db, embedder);
const gopId = `gen1-${cand.candidateId.replace(/[:]/g,'_')}-${instance.instanceId}`;
sessions.ensure(gopId, undefined, `Gen 1 ${cand.candidateId} on ${instance.instanceId}`);
for (const doc of instance.sourceDocuments) frames.createIFrame(gopId, `## ${doc.title}\n\n${doc.body}`, 'important', 'system');
const search: RetrievalSearchFn = async ({ query, limit }) => {
const hits = await hybrid.search(query, { limit, gopId });
return {
formattedResults: hits.length > 0 ? hits.map((s,i)=>`[result ${i+1}, score ${s.finalScore.toFixed(3)}]\n${s.frame.content}`).join('\n\n---\n\n') : '',
resultCount: hits.length,
};
};
// Inject the candidate's prompt shape via custom orchestration: we use runRetrievalAgentLoop
// with the candidate's modelAlias + prompt-shape. The agent loop internally selects shape via
// selector; we override by passing the candidate's shape directly. Since runRetrievalAgentLoop
// uses selectShape internally, we override REGISTRY at runtime by name match.
// For Faza 1 simplicity, we register candidate as override under its unique name:
let agentResult: AgentRunResult;
try {
// Amendment 8 §canonical_mutation_api: register the candidate via the sanctioned
// mutation path. registerShape() is imported from '@waggle/agent' so it mutates
// the SAME REGISTRY instance the agent-loop's selectShape() reads from. Direct
// (REGISTRY as any)[name] = shape is forbidden post-Amendment-8 (would mutate a
// separate module instance under tsx + Node ESM workspace resolution).
registerShape(cand.promptShape.name, cand.promptShape);
agentResult = await runRetrievalAgentLoop({
modelAlias: SUBJECT_ALIAS,
persona: instance.personaText,
question: instance.question,
llmCall,
search,
maxSteps: MAX_STEPS,
maxRetrievalsPerStep: MAX_RETRIEVALS_PER_STEP,
perCallHaltUsd: PER_CALL_HALT_USD,
perCellHaltUsd: PER_CELL_HALT_USD,
contextTag: evalId,
promptShapeOverride: cand.promptShape.name, // if supported
} as any);
} catch (e) {
return { error: `agent loop failed: ${(e as Error).message}` };
}
log(`[${evalId}] subject_done; retrievals=${agentResult.retrievalCalls} steps=${agentResult.stepsTaken} cost=$${agentResult.totalCostUsd.toFixed(4)}`);
const judges = await judgeTrio(instance, agentResult.rawResponse);
const evalCostUsd = agentResult.totalCostUsd + judges.cost;
log(`[${evalId}] judged; trio_mean=${judges.trioMean.toFixed(3)} pass_ii=${judges.trioStrictPassII} retrievals=${agentResult.retrievalCalls} eval_cost=$${evalCostUsd.toFixed(4)}`);
return {
shape: cand.shape, candidateId: cand.candidateId, variant: cand.variant,
instanceId: instance.instanceId, instanceCell: instance.cell,
candidateResponse: agentResult.rawResponse,
candidateLatencyMs: agentResult.totalLatencyMs,
candidateCostUsd: agentResult.totalCostUsd,
loopExhausted: agentResult.loopExhausted,
stepsTaken: agentResult.stepsTaken,
retrievalCalls: agentResult.retrievalCalls,
trioMean: judges.trioMean,
trioStrictPassII: judges.trioStrictPassII,
trioStrictPassI: judges.trioStrictPassI,
judgeRecords: judges.records,
evalCostUsd,
manifestAnchor: MANIFEST_ANCHOR,
manifestShaAmendment5: MANIFEST_SHA_AMENDMENT_5,
tsIso: new Date().toISOString(),
};
}
// ── Main ──────────────────────────────────────────────────────────────────
// ── Amendment 7 — per-candidate accumulator ───────────────────────────────
interface CandidateAcc {
candidateId: string;
shape: ShapeName;
variant: 'baseline' | 'gen1-v1' | 'gen1-v2';
evalCount: number;
passIICount: number; // count of trioStrictPassII = true
totalCostUsd: number;
totalRetrievalCalls: number;
trioMeans: number[]; // per-eval trioMean for variance + audit
retrievalCalls: number[]; // per-eval retrieval calls for audit
mutationValidatorPassed: boolean; // computed from validateCandidate at startup
}
function makeCandidateAcc(cand: Candidate, validatorPassed: boolean): CandidateAcc {
return {
candidateId: cand.candidateId,
shape: cand.shape,
variant: cand.variant,
evalCount: 0,
passIICount: 0,
totalCostUsd: 0,
totalRetrievalCalls: 0,
trioMeans: [],
retrievalCalls: [],
mutationValidatorPassed: validatorPassed,
};
}
function ingestEvalIntoAcc(acc: CandidateAcc, r: EvalRecord): void {
acc.evalCount++;
if (r.trioStrictPassII) acc.passIICount++;
acc.totalCostUsd += r.evalCostUsd;
acc.totalRetrievalCalls += r.retrievalCalls;
acc.trioMeans.push(r.trioMean);
acc.retrievalCalls.push(r.retrievalCalls);
}
function accMeanCostPerEval(acc: CandidateAcc): number {
return acc.evalCount > 0 ? acc.totalCostUsd / acc.evalCount : 0;
}
function accPassRateII(acc: CandidateAcc): number {
return acc.evalCount > 0 ? acc.passIICount / acc.evalCount : 0;
}
function accMeanRetrievalCallsPerTask(acc: CandidateAcc): number {
return acc.evalCount > 0 ? acc.totalRetrievalCalls / acc.evalCount : 0;
}
// ── Amendment 7 — mid-run halt check (binding) ────────────────────────────
interface MidRunHaltCheckResult {
shouldHalt: boolean;
reason: string | null;
}
function checkMidRunHalts(accs: Map<string, CandidateAcc>): MidRunHaltCheckResult {
// Threshold A — per-candidate cost overshoot >25% on >3 candidates
let overshootCount = 0;
const overshootCandidates: string[] = [];
for (const acc of accs.values()) {
if (acc.evalCount === 0) continue;
if (accMeanCostPerEval(acc) > PER_CANDIDATE_COST_OVERSHOOT_THRESHOLD_USD) {
overshootCount++;
overshootCandidates.push(`${acc.candidateId}=$${accMeanCostPerEval(acc).toFixed(4)}/eval`);
}
}
if (overshootCount > MID_RUN_HALT_OVERSHOOT_CANDIDATE_COUNT) {
return {
shouldHalt: true,
reason: `Amendment 7 §checkpoint_b_tightened.per_candidate_cost_overshoot: ${overshootCount} candidates >$${PER_CANDIDATE_COST_OVERSHOOT_THRESHOLD_USD.toFixed(4)}/eval (threshold >${MID_RUN_HALT_OVERSHOOT_CANDIDATE_COUNT}); offenders=[${overshootCandidates.join(', ')}]`,
};
}
// Threshold B — per-shape variance widens >40pp range (max-min trio_strict_pass_rate_II) on any shape
for (const shape of SHAPES) {
const shapeAccs = [...accs.values()].filter(a => a.shape === shape && a.evalCount >= PER_SHAPE_VARIANCE_MIN_EVALS);
if (shapeAccs.length < 2) continue;
const passRates = shapeAccs.map(accPassRateII);
const max = Math.max(...passRates);
const min = Math.min(...passRates);
const rangePP = (max - min) * 100;
if (rangePP > PER_SHAPE_VARIANCE_HALT_PP) {
return {
shouldHalt: true,
reason: `Amendment 7 §checkpoint_b_tightened.per_shape_variance_widens: shape=${shape} range=${rangePP.toFixed(1)}pp > ${PER_SHAPE_VARIANCE_HALT_PP}pp; rates=${passRates.map(r => r.toFixed(2)).join(',')}`,
};
}
}
// Threshold C — Qwen-targeted retrieval engagement drops below per-shape NULL baseline
//
// Amendment 10 §10.1 mutation_execution_gate (binding): halt only fires when at
// least one mutation candidate has been evaluated for this shape. Baseline-only
// data does NOT trigger halt. This matches Amendment 9 §qwen_evolution_verdict_capture
// .mid_run_halt_binding intent that the halt represents direction_2 verdict
// (mutations regress retrieval), not baseline-running stochastic variance.
// Empirical basis: 2 prior halt firings (b5avslp51 + b1t474yqd) on baseline-only
// data within ±0.10 absolute noise band were both per-Amendment-9 NOT direction_2
// verdicts.
for (const shape of ['qwen-thinking', 'qwen-non-thinking'] as const) {
const allShapeAccs = [...accs.values()].filter(a => a.shape === shape);
// Amendment 11 §11.1 second_order_calibration_patch (binding):
// mutation_execution_gate threshold tightened from ≥1 eval to ≥MIN_EVALS evals.
// Halt only fires when at least one mutation candidate (variant !== 'baseline')
// has STATISTICALLY MEANINGFUL sample size (≥QWEN_RETRIEVAL_REGRESSION_MIN_EVALS=5
// evals). This guarantees the mutation IS in the per-shape aggregate (not
// excluded by the individual-candidate MIN_EVALS filter), eliminating the
// second-order false-positive class where halt fired on baseline-only
// aggregate while gate was mechanically met by a single mutation eval.
//
// Per Amendment 11 §11.2 terminal_calibration_clause (BINDING): if halt
// fires AGAIN with this calibration ACTIVE, that IS Phase 4.5 direction_2
// verdict. No further calibration patches; escalate to Option C (Amendment 12
// interface refactor).
const hasStatisticallyMeaningfulMutationForShape = allShapeAccs.some(
a => a.variant !== 'baseline' && a.evalCount >= QWEN_RETRIEVAL_REGRESSION_MIN_EVALS,
);
if (!hasStatisticallyMeaningfulMutationForShape) continue;
const baseline = NULL_BASELINE_PER_SHAPE[shape].meanRetrievalCallsPerTask;
const shapeAccs = allShapeAccs.filter(a => a.evalCount >= QWEN_RETRIEVAL_REGRESSION_MIN_EVALS);
if (shapeAccs.length === 0) continue;
const totalRetr = shapeAccs.reduce((s, a) => s + a.totalRetrievalCalls, 0);
const totalEvals = shapeAccs.reduce((s, a) => s + a.evalCount, 0);
if (totalEvals === 0) continue;
const aggMean = totalRetr / totalEvals;
if (aggMean < baseline) {
return {
shouldHalt: true,
reason: `Amendment 7 §checkpoint_b_tightened.qwen_retrieval_engagement_regression (post Amendment 11 §11.1 second_order_calibration_patch): shape=${shape} mean=${aggMean.toFixed(3)} < NULL baseline ${baseline.toFixed(3)} (n=${totalEvals}; ≥1 mutation candidate with ≥${QWEN_RETRIEVAL_REGRESSION_MIN_EVALS} evals evaluated for shape; per Amendment 11 §11.2 terminal_calibration_clause, this IS Phase 4.5 direction_2 verdict)`,
};
}
}
return { shouldHalt: false, reason: null };
}
// ── Amendment 7 — Checkpoint B summary writer (binding extensions) ────────
interface PerCandidateTierBreakdown {
candidateId: string;
shape: ShapeName;
variant: string;
evalCount: number;
trioStrictPassRateII: number;
meanRetrievalCallsPerTask: number;
meanEvalCostUsd: number;
costOvershoot: boolean;
tieredFitness: TieredFitnessComponents;
}
interface CheckpointBSummary {
manifestAnchor: string;
manifestShaAmendment7: string;
generated_at: string;
mode: string;
totalEvals: number;
totalCostUsd: number;
haltReason: string | null;
perCandidateTierBreakdown: PerCandidateTierBreakdown[];
retrievalEngagementDeltasPerQwenShape: {
'qwen-thinking': { nullBaselineMean: number; gen1PartialMean: number | null; deltaAbsolute: number | null };
'qwen-non-thinking': { nullBaselineMean: number; gen1PartialMean: number | null; deltaAbsolute: number | null };
};
cellSemanticAnchorInvarianceCountPerCandidate: Record<string, number>;
preRegisteredDeltaFloorVerdict: DeltaFloorVerdict;
midRunHaltsBindingThresholds: {
perCandidateCostOvershoot: { threshold: number; candidatesOvershoot: number };
perShapeVariance: { thresholdPP: number; maxRangeObservedPP: number };
qwenRetrievalRegression: { triggered: boolean; details: string };
};
}
function buildCheckpointBSummary(
args: ReturnType<typeof parseArgs>,
accs: Map<string, CandidateAcc>,
totalEvals: number,
totalCostUsd: number,
haltReason: string | null,
): CheckpointBSummary {
const perCandidate: PerCandidateTierBreakdown[] = [];
for (const acc of accs.values()) {
if (acc.evalCount === 0) continue;
const passRate = accPassRateII(acc);
const meanRetr = accMeanRetrievalCallsPerTask(acc);
const meanCost = accMeanCostPerEval(acc);
const candidateMetrics = {
candidateId: acc.candidateId,
shape: acc.shape,
evaluations: [],
trioStrictPassRateII: passRate,
trioStrictPassRateI: 0, // not tracked here; reported in JSONL
meanRetrievalCallsPerTask: meanRetr,
meanCostUsd: meanCost,
};
const tieredFitness = computeTieredFitness({
candidate: candidateMetrics,
nullBaselinePassRateII: NULL_BASELINE_PER_SHAPE[acc.shape].trioStrictPassRateII,
nullBaselineMeanRetrievalCallsPerTask: NULL_BASELINE_PER_SHAPE[acc.shape].meanRetrievalCallsPerTask,
mutationValidatorPassed: acc.mutationValidatorPassed,
saturatedRegime: true, // 5/5 shapes ≥75% per Checkpoint A v2 §B.2
});
perCandidate.push({
candidateId: acc.candidateId,
shape: acc.shape,
variant: acc.variant,
evalCount: acc.evalCount,
trioStrictPassRateII: passRate,
meanRetrievalCallsPerTask: meanRetr,
meanEvalCostUsd: meanCost,
costOvershoot: meanCost > PER_CANDIDATE_COST_OVERSHOOT_THRESHOLD_USD,
tieredFitness,
});
}
// Aggregate Tier 1: mean trio_strict_pass_rate_II across all evals
const totalEvalsAcc = perCandidate.reduce((s, c) => s + c.evalCount, 0);
const aggregateTrioStrictPassRateII =
totalEvalsAcc > 0
? perCandidate.reduce((s, c) => s + c.trioStrictPassRateII * c.evalCount, 0) / totalEvalsAcc
: 0;
// Per-shape Qwen retrieval means (across that shape's candidates)
function qwenShapeAggregate(shape: 'qwen-thinking' | 'qwen-non-thinking'):
{ gen1PartialMean: number | null; deltaAbsolute: number | null } {
const shapeAccs = [...accs.values()].filter(a => a.shape === shape && a.evalCount > 0);
if (shapeAccs.length === 0) return { gen1PartialMean: null, deltaAbsolute: null };
const totalRetr = shapeAccs.reduce((s, a) => s + a.totalRetrievalCalls, 0);
const totalEvalsLocal = shapeAccs.reduce((s, a) => s + a.evalCount, 0);
const mean = totalEvalsLocal > 0 ? totalRetr / totalEvalsLocal : null;
const baseline = NULL_BASELINE_PER_SHAPE[shape].meanRetrievalCallsPerTask;
return { gen1PartialMean: mean, deltaAbsolute: mean === null ? null : mean - baseline };
}
const qwenThinkingAgg = qwenShapeAggregate('qwen-thinking');
const qwenNonThinkingAgg = qwenShapeAggregate('qwen-non-thinking');
const qwenShapeRetrievalMeans: Partial<Record<ShapeName, number>> = {};
if (qwenThinkingAgg.gen1PartialMean !== null) qwenShapeRetrievalMeans['qwen-thinking'] = qwenThinkingAgg.gen1PartialMean;
if (qwenNonThinkingAgg.gen1PartialMean !== null) qwenShapeRetrievalMeans['qwen-non-thinking'] = qwenNonThinkingAgg.gen1PartialMean;
// Aggregate Tier 2 bonus across Qwen-targeted candidates (mean across qwen candidates with data)
const qwenCandidates = perCandidate.filter(c => c.shape === 'qwen-thinking' || c.shape === 'qwen-non-thinking');
const qwenAggregateTier2Bonus =
qwenCandidates.length > 0
? qwenCandidates.reduce((s, c) => s + c.tieredFitness.tier2RetrievalBonus, 0) / qwenCandidates.length
: 0;
const deltaFloorVerdict = computeDeltaFloorVerdict({
aggregateTrioStrictPassRateII,
aggregateNullBaselinePassRateII: NULL_BASELINE_AGGREGATE.trioStrictPassRateII,
qwenShapeRetrievalMeans,
qwenShapeNullBaselineRetrievalMeans: {
'qwen-thinking': NULL_BASELINE_PER_SHAPE['qwen-thinking'].meanRetrievalCallsPerTask,
'qwen-non-thinking': NULL_BASELINE_PER_SHAPE['qwen-non-thinking'].meanRetrievalCallsPerTask,
},
qwenAggregateTier2Bonus,
});
// Per-shape variance maxRange snapshot
let maxRangeObservedPP = 0;
for (const shape of SHAPES) {
const shapeAccs = perCandidate.filter(c => c.shape === shape);
if (shapeAccs.length < 2) continue;
const rates = shapeAccs.map(c => c.trioStrictPassRateII);
const range = (Math.max(...rates) - Math.min(...rates)) * 100;
if (range > maxRangeObservedPP) maxRangeObservedPP = range;
}
const overshootCount = perCandidate.filter(c => c.costOvershoot).length;
// Qwen retrieval regression check (binary informational; halt logic in checkMidRunHalts)
let qwenRegressionDetails = 'no_regression';
let qwenRegressionTriggered = false;
for (const shape of ['qwen-thinking', 'qwen-non-thinking'] as const) {
const agg = shape === 'qwen-thinking' ? qwenThinkingAgg : qwenNonThinkingAgg;
if (agg.gen1PartialMean !== null && agg.gen1PartialMean < NULL_BASELINE_PER_SHAPE[shape].meanRetrievalCallsPerTask) {
qwenRegressionTriggered = true;
qwenRegressionDetails = `${shape} mean=${agg.gen1PartialMean.toFixed(3)} < NULL ${NULL_BASELINE_PER_SHAPE[shape].meanRetrievalCallsPerTask}`;
break;
}
}
const cellSemanticAnchorInvarianceCountPerCandidate: Record<string, number> = {};
for (const c of perCandidate) {
cellSemanticAnchorInvarianceCountPerCandidate[c.candidateId] = c.tieredFitness.cellSemanticAnchorInvarianceCount;
}
return {
manifestAnchor: MANIFEST_ANCHOR,
manifestShaAmendment7: MANIFEST_SHA_AMENDMENT_7,
generated_at: new Date().toISOString(),
mode: args.mode,
totalEvals,
totalCostUsd,
haltReason,
perCandidateTierBreakdown: perCandidate,
retrievalEngagementDeltasPerQwenShape: {
'qwen-thinking': {
nullBaselineMean: NULL_BASELINE_PER_SHAPE['qwen-thinking'].meanRetrievalCallsPerTask,
gen1PartialMean: qwenThinkingAgg.gen1PartialMean,
deltaAbsolute: qwenThinkingAgg.deltaAbsolute,
},
'qwen-non-thinking': {
nullBaselineMean: NULL_BASELINE_PER_SHAPE['qwen-non-thinking'].meanRetrievalCallsPerTask,
gen1PartialMean: qwenNonThinkingAgg.gen1PartialMean,
deltaAbsolute: qwenNonThinkingAgg.deltaAbsolute,
},
},
cellSemanticAnchorInvarianceCountPerCandidate,
preRegisteredDeltaFloorVerdict: deltaFloorVerdict,
midRunHaltsBindingThresholds: {
perCandidateCostOvershoot: {
threshold: PER_CANDIDATE_COST_OVERSHOOT_THRESHOLD_USD,
candidatesOvershoot: overshootCount,
},
perShapeVariance: {
thresholdPP: PER_SHAPE_VARIANCE_HALT_PP,
maxRangeObservedPP,
},
qwenRetrievalRegression: {
triggered: qwenRegressionTriggered,
details: qwenRegressionDetails,
},
},
};
}
// ── Main ──────────────────────────────────────────────────────────────────
async function main(): Promise<void> {
const args = parseArgs(process.argv.slice(2));
fs.mkdirSync(OUT_DIR, { recursive: true });
fs.mkdirSync(SCRATCH_DIR, { recursive: true });
if (!fs.existsSync(RUN_LOG)) fs.writeFileSync(RUN_LOG, '');
const corpus = loadCorpus();
const sample = deterministicShuffle(corpus, SAMPLING_SEED).slice(0, N_PER_SHAPE);
log(`[loaded] corpus=${corpus.length}; sample=${N_PER_SHAPE} via seed=${SAMPLING_SEED}`);
const candidates = await loadCandidates();
log(`[loaded] ${SHAPES.length} shapes × ${N_CANDIDATES_PER_SHAPE} candidates each`);
// Amendment 7 — pre-validate all candidates against cell-semantic anchors (Tier 3 input)
const candidateValidatorVerdicts = new Map<string, ValidatorVerdict | null>();
const TYPES_FILE_PATH = path.join(PROMPT_SHAPES_DIR, 'types.ts');
for (const shape of SHAPES) {
for (const cand of candidates.get(shape)!) {
if (cand.variant === 'baseline') {
// Baselines pass by definition (they ARE the pinned shape file)
candidateValidatorVerdicts.set(cand.candidateId, null); // null = baseline (Tier 3 = 0.10 by anchor invariance)
continue;
}
const filename = `${shape}-${cand.variant}.ts`;
const candPath = path.join(GEPA_EVOLVED_DIR, filename);
try {
const verdict = validateCandidate({
candidateShapeFilePath: candPath,
baselineShapeName: `${shape}.ts`,
typesFilePath: TYPES_FILE_PATH,
expectShapeDiff: true,
});
candidateValidatorVerdicts.set(cand.candidateId, verdict);
log(`[validator] ${cand.candidateId} valid=${verdict.valid} violations=${verdict.violations.length}`);
} catch (e) {
log(`[validator] ${cand.candidateId} ERROR ${(e as Error).message}`);
candidateValidatorVerdicts.set(cand.candidateId, null);
}
}
}
if (args.mode === 'dry-run') {
log(`[dry-run] would run 5×3×8 = 120 evals (or halt at 30 = Checkpoint B)`);
for (const shape of SHAPES) {
for (const cand of candidates.get(shape)!) {
log(`[dry-run] candidate=${cand.candidateId} variant=${cand.variant} shape.name=${cand.promptShape.name}`);
}
}
return;
}
// Resume support
const existing = new Set<string>();
let cumulativeCost = 0;
// Amendment 7 — per-candidate accumulator (rebuilt from JSONL on resume)
const accs = new Map<string, CandidateAcc>();
for (const shape of SHAPES) {
for (const cand of candidates.get(shape)!) {
const verdict = candidateValidatorVerdicts.get(cand.candidateId);
// Baselines: validatorPassed = true (anchor invariant by definition).
// Mutations: validatorPassed = verdict.valid (or false if validator threw).
const validatorPassed = cand.variant === 'baseline' ? true : verdict?.valid ?? false;
accs.set(cand.candidateId, makeCandidateAcc(cand, validatorPassed));
}
}
if (fs.existsSync(OUT_JSONL)) {
for (const line of fs.readFileSync(OUT_JSONL, 'utf-8').trim().split(/\n+/).filter(Boolean)) {
try {
const r = JSON.parse(line) as EvalRecord;
existing.add(`${r.candidateId}__${r.instanceId}`);
cumulativeCost += r.evalCostUsd;
const acc = accs.get(r.candidateId);
if (acc) ingestEvalIntoAcc(acc, r);
} catch { /* skip */ }
}
log(`[resume] loaded ${existing.size} existing evals; cumulative $${cumulativeCost.toFixed(4)}`);
}
const out = fs.createWriteStream(OUT_JSONL, { flags: existing.size > 0 ? 'a' : 'w' });
const embedder = createOllamaEmbedder({ baseUrl: OLLAMA_URL, model: EMBEDDER_MODEL });
const haltAt = args.mode === 'checkpoint-b' ? CHECKPOINT_B_HALT_EVALS : 120;
log(`[mode=${args.mode}] target eval count: ${haltAt}`);
let nDone = existing.size;
let amendment7HaltReason: string | null = null;
outer: for (const shape of SHAPES) {
for (const cand of candidates.get(shape)!) {
for (const inst of sample) {
const key = `${cand.candidateId}__${inst.instanceId}`;
if (existing.has(key)) { log(`[skip] ${key} already in JSONL`); continue; }
if (nDone >= haltAt) { log(`[HALT] reached ${haltAt} evals (Checkpoint B)`); break outer; }
if (cumulativeCost >= COST_HALT_USD) { log(`[HALT] cumulative $${cumulativeCost.toFixed(4)} >= $${COST_HALT_USD}`); break outer; }
const r = await runOneEval(cand, inst, embedder);
if ('error' in r) { log(`[skip] ${key}: ${r.error}`); continue; }
out.write(JSON.stringify(r) + '\n');
cumulativeCost += r.evalCostUsd;
nDone++;
// Amendment 7 — update accumulator + check mid-run halts
const acc = accs.get(cand.candidateId);
if (acc) ingestEvalIntoAcc(acc, r);
const haltCheck = checkMidRunHalts(accs);
if (haltCheck.shouldHalt) {
amendment7HaltReason = haltCheck.reason;
log(`[HALT-A7] ${haltCheck.reason}`);
break outer;
}
log(`[cumulative] $${cumulativeCost.toFixed(4)} / $${COST_HALT_USD} halt; ${nDone} evals total`);
}
}
}
out.end();
// Amendment 7 — write Checkpoint B summary (binding extension per §checkpoint_b_tightened.report_extensions)
const summary = buildCheckpointBSummary(args, accs, nDone, cumulativeCost, amendment7HaltReason);
fs.writeFileSync(SUMMARY_JSON, JSON.stringify(summary, null, 2));
log(`[summary] wrote ${SUMMARY_JSON}`);
log(`[delta-floor] verdict=${summary.preRegisteredDeltaFloorVerdict.overallVerdict}`);
log(`[delta-floor] threshold_1_aggregate_tier_1=${summary.preRegisteredDeltaFloorVerdict.threshold1AggregateTier1} (value=${summary.preRegisteredDeltaFloorVerdict.threshold1ValuePP.toFixed(2)}pp)`);
log(`[delta-floor] threshold_2_qwen_retrieval_absolute=${summary.preRegisteredDeltaFloorVerdict.threshold2QwenRetrievalAbsolute} (max_delta=${summary.preRegisteredDeltaFloorVerdict.threshold2MaxDeltaAbsolute.toFixed(3)})`);
log(`[delta-floor] threshold_3_compound_tier_1_plus_tier_2=${summary.preRegisteredDeltaFloorVerdict.threshold3CompoundTier1PlusTier2} (tier1=${summary.preRegisteredDeltaFloorVerdict.threshold3Tier1ValuePP.toFixed(2)}pp tier2_agg=${summary.preRegisteredDeltaFloorVerdict.threshold3Tier2Aggregate.toFixed(3)})`);
log(`[done] ${nDone} evals; total cost $${cumulativeCost.toFixed(4)}; halt_reason=${amendment7HaltReason ?? 'none (Checkpoint B reached or completed)'}`);
}
main().catch(e => { console.error('FATAL:', e); process.exit(2); });

View File

@@ -0,0 +1,438 @@
#!/usr/bin/env tsx
/**
* GEPA Faza 1 — mutation oracle runner.
*
* Per launch decision §G step 7 + manifest v7 §gepa.mutation_oracle +
* §mutation_oracle_design + Amendment 2 §4 (forked Qwen vs non-Qwen templates).
*
* For each of 5 shapes, generate 2 mutation candidates via Opus 4.7. Each
* candidate is validated via mutation-validator.ts (cell-semantic preservation).
* Output: 10 candidate files at packages/agent/src/prompt-shapes/gepa-evolved/<shape>-gen1-v<N>.ts
*
* Approach:
* - Use JSON-mode response_format (Amendment 4 lesson) for reliable parsing
* - Opus outputs JSON with the 5 method body strings + new evidence_link
* - Runner assembles TS file from fixed template (preserves cell semantics by construction)
* - Validator confirms types.ts + MULTI_STEP_ACTION_CONTRACT SHAs unchanged
*
* Cost projection: 10 calls × ~$0.15 = ~$1.50
*
* Failure handling per brief §5: 2 consecutive invalid mutations from oracle
* → halt-and-PM.
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import { validateCandidate, type ValidatorVerdict } from '../../src/faza-1/mutation-validator.js';
import { classifyShape, type TemplateClass } from '../../src/faza-1/mutation-oracle-fork.js';
import { type ShapeName, QWEN_TARGETED_SHAPES } from '../../src/faza-1/types.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '../../../..');
const PROMPT_SHAPES_DIR = path.join(REPO_ROOT, 'packages/agent/src/prompt-shapes');
const TYPES_FILE = path.join(PROMPT_SHAPES_DIR, 'types.ts');
const GEPA_EVOLVED_DIR = path.join(PROMPT_SHAPES_DIR, 'gepa-evolved');
const ORACLE_LOG = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/gen-1/mutation-oracle-run.log');
const OUT_MANIFEST = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/gen-1/mutation-oracle-manifest.json');
const LITELLM_URL = process.env.LITELLM_URL ?? 'http://localhost:4000';
const ORACLE_MODEL = 'claude-opus-4-7';
const ORACLE_MAX_TOKENS = 8000;
const N_MUTATIONS_PER_SHAPE = 2;
const SHAPES: ShapeName[] = ['claude', 'qwen-thinking', 'qwen-non-thinking', 'gpt', 'generic-simple'];
const PHASE_4_3_FAILURE_MODES = `Top T2 categories from Phase 4.3 verdict (decisions/2026-04-28-phase-4-3-rescore-delta-report.md):
1. unsupported-specifics (10 of 26 T2 hits): hallucinated specifics or overreach beyond materials
2. missed / didn't-consider / shallow (9 of 26 T2 hits): incomplete coverage of source documents
3. conflation / weak-synthesis (5 of 26 T2 hits): risks blended together rather than separated
4. wrong-entity / off-topic (sparse): minor framing errors
Phase 4.5 retrieval-engagement signal: Qwen retrieves 1.33×/task vs Opus 2.33×/task on byte-identical tool surface. H3 corpus NULL-baseline replicated this (mean retrievals = 1.05).`;
// ── Logging ────────────────────────────────────────────────────────────────
function log(msg: string): void {
const line = `[${new Date().toISOString()}] ${msg}\n`;
try { fs.appendFileSync(ORACLE_LOG, line); } catch { /* dir may not exist yet */ }
process.stderr.write(line);
}
// ── Baseline shape inspection (extract metadata for prompt) ────────────────
interface BaselineShapeMetadata {
description: string;
modelClass: string;
defaultThinking: boolean;
defaultMaxTokens: number;
shapeFileContent: string; // full file text
}
function loadBaselineShape(shapeName: ShapeName): BaselineShapeMetadata {
const shapeFile = path.join(PROMPT_SHAPES_DIR, `${shapeName}.ts`);
const content = fs.readFileSync(shapeFile, 'utf-8');
const description = content.match(/description: '([^']+)'/)?.[1] ?? '';
const modelClass = content.match(/modelClass: '([^']+)'/)?.[1] ?? shapeName;
const defaultThinking = content.match(/defaultThinking: (true|false|undefined)/)?.[1] === 'true';
const defaultMaxTokens = Number(content.match(/defaultMaxTokens: (\d+)/)?.[1] ?? 4096);
return { description, modelClass, defaultThinking, defaultMaxTokens, shapeFileContent: content };
}
// ── Oracle prompt (JSON-mode) ──────────────────────────────────────────────
function buildOraclePrompt(shapeName: ShapeName, baseline: BaselineShapeMetadata, mutationIdx: number): string {
const cls: TemplateClass = classifyShape(shapeName);
const isQwen = QWEN_TARGETED_SHAPES.has(shapeName);
const qwenGuidance = `For Qwen-targeted shape mutation:
- Emphasize multi-turn retrieval over single-shot retrieval. Phrase like "Continue retrieving until you have evidence from at least 2 distinct queries before finalizing."
- Add anti-premature-finalization scaffolding. Phrase like "Before finalizing, ask: what gap in evidence remains? Issue another retrieval if any gap exists."
- Encourage iterative refinement of retrieval queries based on prior turn results.
- Goal: push mean retrieval_calls per task from current 1.0 baseline toward >= 1.5 (escape Amendment 2 penalty zone) and ideally >= 2.0 (Opus parity proxy).`;
const nonQwenGuidance = `For non-Qwen shape mutation:
- Standard mutation guidance per brief §3.3 — evolve reasoning scaffold, planning step structure, chain-of-thought triggers.
- Restructure implicit reasoning prompts (e.g., "think step by step" variants, planning bullets).
- Refine where the model is prompted to articulate reasoning before producing output.
- Improve multi-step task decomposition explicitness.`;
return `You are an expert prompt engineer. Generate ONE mutated variant of the prompt-shape below, evolving reasoning scaffold + retrieval-engagement guidance while preserving cell semantics.
## Target shape
- Name: ${shapeName}
- Class: ${cls}
- Mutation variant index: ${mutationIdx} (you are generating mutation #${mutationIdx} of 2 for this shape)
## Baseline metadata (LOCKED — do NOT change these)
- description: ${baseline.description}
- modelClass: ${baseline.modelClass}
- defaultThinking: ${baseline.defaultThinking}
- defaultMaxTokens: ${baseline.defaultMaxTokens}
## Phase 4.3 + Phase 4.5 failure modes to address
${PHASE_4_3_FAILURE_MODES}
## Mutation guidance
${isQwen ? qwenGuidance : nonQwenGuidance}
## Cell semantic boundaries (LOCKED — violation = REJECTED candidate)
You may NOT modify:
- The MULTI_STEP_ACTION_CONTRACT constant (lives in types.ts; bytes are SHA-pinned)
- The JSON action contract format ({"action": "retrieve" | "finalize", ...})
- Task framing (persona/question/materials section labels)
- Imports block
- Locked metadata fields above
You MAY modify:
- The 5 method bodies (string-building only): systemPromptSolo, systemPromptMultiStep, soloUserPrompt, multiStepKickoffUserPrompt, retrievalInjectionUserPrompt
- The evidence_link metadata (you MUST update to point to GEPA Gen 1 results: "benchmarks/results/gepa-faza1/gen-1/mutation-oracle-run.log + Phase 4.5 + Amendment 2 §3 retrieval-engagement bonus")
## Baseline shape file (your input)
\`\`\`typescript
${baseline.shapeFileContent}
\`\`\`
## Your output: JSON object
Output a single JSON object with these fields. Each method body field should be a TypeScript expression that evaluates to a string (the prompt text). Use the same approach as the baseline (e.g., array.join('\\n')). Variable references like \${persona}, \${question}, \${maxSteps}, \${maxRetrievalsPerStep}, \${input.persona}, etc. must be preserved verbatim where the baseline used them.
\`\`\`json
{
"evidenceLink": "<updated evidence_link string referencing Gen 1 results + Phase 4.5/Amendment 2>",
"systemPromptSolo": "<TypeScript expression returning system prompt for !isMultiStep — typically a join of strings or template literal; reference {persona}>",
"systemPromptMultiStep": "<TypeScript expression returning system prompt for isMultiStep — must reference MULTI_STEP_ACTION_CONTRACT verbatim, {persona}, {question}, {maxSteps}, {maxRetrievalsPerStep}>",
"soloUserPrompt": "<TypeScript expression returning user prompt with {input.persona}, {input.materials}, {input.question}>",
"multiStepKickoffUserPrompt": "<TypeScript expression returning kickoff user message; baseline uses 'Begin. Output your first action JSON now.' — your variant should request engagement scaffolding for Qwen shapes>",
"retrievalInjectionUserPrompt": "<TypeScript expression returning retrieval-injection user message with {input.query}, {input.resultCount}, {input.results}>"
}
\`\`\`
CRITICAL: each field's value must be a STRING containing valid TypeScript code that, when wrapped in \`return (\${value})\`, would compile + return a string. The simplest valid pattern is template literals or .join('\\n') over an array of strings.
Output ONLY the JSON object. No prose. No code fences.`;
}
// ── LLM call (JSON-mode, no temperature per Amendment 4 lesson) ────────────
interface OracleCallResult {
content: string;
inTokens: number;
outTokens: number;
costUsd: number;
latencyMs: number;
error?: string;
}
async function callOpusOracle(prompt: string): Promise<OracleCallResult> {
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) throw new Error('LITELLM_MASTER_KEY env not set');
const payload = {
model: ORACLE_MODEL,
messages: [{ role: 'user', content: prompt }],
max_tokens: ORACLE_MAX_TOKENS,
response_format: { type: 'json_object' },
// temperature omitted per Amendment 4 (Anthropic deprecates with JSON mode)
};
const started = Date.now();
let lastErr: string | undefined;
for (let attempt = 0; attempt < 2; attempt++) {
try {
const resp = await fetch(`${LITELLM_URL}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${masterKey}` },
body: JSON.stringify(payload),
});
const d: any = await resp.json();
if ('error' in d) {
lastErr = String(d.error?.message ?? JSON.stringify(d.error)).slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
continue;
}
const content = d.choices?.[0]?.message?.content ?? '';
const usage = d.usage ?? {};
const inTok = usage.prompt_tokens ?? 0;
const outTok = usage.completion_tokens ?? 0;
const costUsd = (inTok * 15.0 + outTok * 75.0) / 1_000_000;
return { content, inTokens: inTok, outTokens: outTok, costUsd, latencyMs: Date.now() - started };
} catch (e) {
lastErr = `${(e as Error).name}: ${(e as Error).message}`.slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
}
}
return { content: '', inTokens: 0, outTokens: 0, costUsd: 0, latencyMs: Date.now() - started, error: lastErr };
}
// ── JSON parsing + TS file assembly ────────────────────────────────────────
interface MutationFields {
evidenceLink: string;
systemPromptSolo: string;
systemPromptMultiStep: string;
soloUserPrompt: string;
multiStepKickoffUserPrompt: string;
retrievalInjectionUserPrompt: string;
}
function parseOracleOutput(content: string): MutationFields | { error: string } {
let s = content.trim();
if (s.startsWith('```')) s = s.replace(/^```[a-z]*\n?/, '').replace(/```\s*$/, '');
const firstBrace = s.indexOf('{');
const lastBrace = s.lastIndexOf('}');
if (firstBrace < 0 || lastBrace < 0) {
return { error: `no JSON object found in output (length=${content.length})` };
}
try {
const obj = JSON.parse(s.slice(firstBrace, lastBrace + 1));
const required = ['evidenceLink', 'systemPromptSolo', 'systemPromptMultiStep', 'soloUserPrompt', 'multiStepKickoffUserPrompt', 'retrievalInjectionUserPrompt'];
for (const k of required) {
if (typeof obj[k] !== 'string' || obj[k].length === 0) {
return { error: `field "${k}" missing or empty` };
}
}
return obj as MutationFields;
} catch (e) {
return { error: `JSON parse failed: ${(e as Error).message}` };
}
}
function buildShapeFile(shapeName: ShapeName, baseline: BaselineShapeMetadata, fields: MutationFields, mutationIdx: number): string {
const exportName = `${shapeName.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}Gen1V${mutationIdx}Shape`;
const tsName = `${shapeName}-gen1-v${mutationIdx}`;
return `/**
* GEPA Faza 1 — Gen 1 mutation #${mutationIdx} of ${shapeName}.
*
* Generated by Opus 4.7 mutation oracle per Amendment 2 §4 forked template
* (${classifyShape(shapeName)} branch). Cell-semantic boundaries preserved
* via mutation-validator.ts SHA pins.
*
* Evidence: ${fields.evidenceLink}
*
* Baseline anchor: packages/agent/src/prompt-shapes/${shapeName}.ts (manifest v7
* §gepa.mutation_validator.baseline_shape_shas[${shapeName}.ts]).
*/
import {
type PromptShape,
type SystemPromptInput,
type SoloUserPromptInput,
type MultiStepKickoffInput,
type RetrievalInjectionInput,
MULTI_STEP_ACTION_CONTRACT,
} from '../types.js';
export const ${exportName}: PromptShape = {
name: '${tsName}',
metadata: {
description: '${baseline.description.replace(/'/g, "\\'")}',
modelClass: '${baseline.modelClass}',
evidence_link: ${JSON.stringify(fields.evidenceLink)},
defaultThinking: ${baseline.defaultThinking},
defaultMaxTokens: ${baseline.defaultMaxTokens},
},
systemPrompt(input: SystemPromptInput): string {
const { persona, question, isMultiStep, maxSteps = 5, maxRetrievalsPerStep = 8 } = input;
if (!isMultiStep) {
return ${fields.systemPromptSolo};
}
return ${fields.systemPromptMultiStep};
},
soloUserPrompt(input: SoloUserPromptInput): string {
return ${fields.soloUserPrompt};
},
multiStepKickoffUserPrompt(_input: MultiStepKickoffInput): string {
return ${fields.multiStepKickoffUserPrompt};
},
retrievalInjectionUserPrompt(input: RetrievalInjectionInput): string {
return ${fields.retrievalInjectionUserPrompt};
},
};
`;
}
// ── Validate via runtime sanity (compile check) + structural check ─────────
function validateAssembledFile(filepath: string, baselineShapeName: ShapeName): { valid: boolean; reason?: string } {
// Check file parses as TypeScript by attempting a require-style import
// For Faza 1 simplicity, just check that the file:
// 1. Imports from types.js (mutation-validator's import preservation check)
// 2. Contains the required locked metadata fields
// 3. Exports a single PromptShape
// 4. Doesn't break the cell-semantic boundary (types.ts/MULTI_STEP_ACTION_CONTRACT SHAs unchanged)
// The mutation-validator.ts does the SHA checks; we use it via validateCandidate.
const verdict: ValidatorVerdict = validateCandidate({
candidateShapeFilePath: filepath,
baselineShapeName: `${baselineShapeName}.ts` as any,
typesFilePath: TYPES_FILE,
expectShapeDiff: true,
});
if (!verdict.valid) {
return { valid: false, reason: `validator violations: ${verdict.violations.map(v => `${v.category}: ${v.detail}`).join('; ')}` };
}
return { valid: true };
}
// ── Main: generate 10 mutations ────────────────────────────────────────────
interface MutationManifestEntry {
shape: ShapeName;
mutationIdx: number;
filename: string;
costUsd: number;
latencyMs: number;
tsName: string;
validatorVerdict: 'valid' | 'invalid_after_retry';
error?: string;
}
async function main(): Promise<void> {
fs.mkdirSync(GEPA_EVOLVED_DIR, { recursive: true });
fs.mkdirSync(path.dirname(ORACLE_LOG), { recursive: true });
if (!fs.existsSync(ORACLE_LOG)) fs.writeFileSync(ORACLE_LOG, '');
log(`[start] mutation oracle for ${SHAPES.length} shapes × ${N_MUTATIONS_PER_SHAPE} mutations`);
const manifest: MutationManifestEntry[] = [];
let cumulativeCost = 0;
let consecutiveInvalidGlobal = 0;
for (const shapeName of SHAPES) {
const baseline = loadBaselineShape(shapeName);
log(`[${shapeName}] baseline loaded; modelClass=${baseline.modelClass} description="${baseline.description.slice(0, 60)}..."`);
for (let mutIdx = 1; mutIdx <= N_MUTATIONS_PER_SHAPE; mutIdx++) {
const tsName = `${shapeName}-gen1-v${mutIdx}`;
const outFile = path.join(GEPA_EVOLVED_DIR, `${tsName}.ts`);
if (fs.existsSync(outFile)) {
log(`[${tsName}] already exists; skipping`);
manifest.push({ shape: shapeName, mutationIdx: mutIdx, filename: outFile, costUsd: 0, latencyMs: 0, tsName, validatorVerdict: 'valid' });
continue;
}
// Try once; if invalid, retry once with structural feedback. After 2 fails → mark invalid_after_retry.
let valid = false;
let totalCost = 0;
let totalLatency = 0;
let errMsg: string | undefined;
for (let attempt = 0; attempt < 2 && !valid; attempt++) {
const prompt = buildOraclePrompt(shapeName, baseline, mutIdx);
log(`[${tsName}] oracle call attempt ${attempt + 1}; prompt_len=${prompt.length}c`);
const llm = await callOpusOracle(prompt);
totalCost += llm.costUsd;
totalLatency += llm.latencyMs;
if (llm.error) {
errMsg = `LLM error: ${llm.error}`;
log(`[${tsName}] ${errMsg}`);
continue;
}
const parsed = parseOracleOutput(llm.content);
if ('error' in parsed) {
errMsg = `parse error: ${parsed.error}`;
log(`[${tsName}] ${errMsg}; first 200c: ${llm.content.slice(0, 200)}`);
continue;
}
const tsContent = buildShapeFile(shapeName, baseline, parsed, mutIdx);
fs.writeFileSync(outFile, tsContent, 'utf-8');
const v = validateAssembledFile(outFile, shapeName);
if (v.valid) {
valid = true;
log(`[${tsName}] OK; cost=$${llm.costUsd.toFixed(4)}; latency=${llm.latencyMs}ms; file=${path.basename(outFile)}`);
} else {
errMsg = `validator failed: ${v.reason}`;
log(`[${tsName}] ${errMsg}`);
fs.unlinkSync(outFile);
}
}
cumulativeCost += totalCost;
manifest.push({
shape: shapeName, mutationIdx: mutIdx, filename: outFile,
costUsd: totalCost, latencyMs: totalLatency, tsName,
validatorVerdict: valid ? 'valid' : 'invalid_after_retry',
error: valid ? undefined : errMsg,
});
if (valid) {
consecutiveInvalidGlobal = 0;
} else {
consecutiveInvalidGlobal++;
if (consecutiveInvalidGlobal >= 2) {
log(`[HALT] 2 consecutive invalid mutations from oracle (per brief §5) — stopping`);
break;
}
}
}
if (consecutiveInvalidGlobal >= 2) break;
}
fs.writeFileSync(OUT_MANIFEST, JSON.stringify({
totalMutationsAttempted: manifest.length,
totalValid: manifest.filter(m => m.validatorVerdict === 'valid').length,
totalInvalid: manifest.filter(m => m.validatorVerdict !== 'valid').length,
cumulativeCostUsd: +cumulativeCost.toFixed(6),
entries: manifest,
completedAtIso: new Date().toISOString(),
}, null, 2));
log(`[done] ${manifest.length} mutations attempted; ${manifest.filter(m => m.validatorVerdict === 'valid').length} valid; cumulative $${cumulativeCost.toFixed(4)}`);
}
main().catch(e => { console.error('FATAL:', e); process.exit(2); });

View File

@@ -0,0 +1,662 @@
#!/usr/bin/env tsx
/**
* GEPA Faza 1 — NULL-baseline runner.
*
* Per launch decision §G step 6 + §F + §A.5/A.7/A.10.
*
* Per Amendment 4 + binding texture-audit verdict, the corpus is 50/50 (PM
* ratified). NULL-baseline measures each of 5 baseline prompt-shapes against
* 8 stratified instances from the corpus to establish per-shape trio_strict_pass
* baseline rate.
*
* --------------------------------------------------------------------------
* MULTI-STEP MODE (vs solo) — design rationale
* --------------------------------------------------------------------------
*
* Brief §2 says "Cell scope Faza 1: H3 only" and pilot Cell C labels H3 as
* "Qwen solo". HOWEVER, Amendment 2 §3 retrieval_engagement_bonus only makes
* sense if a retrieval tool is present (in solo mode, retrieval_calls always
* = 0 → bonus pinned at -0.05 → fitness function cannot discriminate
* candidates). Amendment 2 §6 Phase 5 forward-record acceptance criteria
* (engagement parity ≥ Opus + score parity narrowed by ≥0.30 H4 trio_mean
* delta) explicitly invoke retrieval-mode metrics.
*
* Resolution (BINDING for this runner): NULL-baseline runs in MULTI-STEP
* mode with retrieval tool available. The "H3 cell" in Faza 1 GEPA context
* means "Qwen-targeted evaluation with retrieval available", not pilot Cell
* C strict "Qwen solo". This reconciles brief §2 with Amendment 2 + Phase 5
* forward record, mirrors Phase 4.5 empirical setup (Cells B/D had
* retrieval), and makes Amendment 2 fitness function meaningful.
*
* Documented in Checkpoint A halt-and-PM report for PM ratification or pivot.
*
* --------------------------------------------------------------------------
* Cost projection (PM ratified ~$20):
* --------------------------------------------------------------------------
*
* 5 shapes × 8 instances × ($0.50/eval avg) ≈ $20 expected
* - Subject: Qwen 3.6 35B-A3B (DashScope direct, ~$0.001/call × 2-3 calls)
* - Trio judges: Opus 4.7 + GPT-5.4 + MiniMax M2.7 × ($0.05/call avg) = $0.15/eval
* - Per pilot 2026-04-26 cost average = $0.47/cell
*
* Halt threshold (per launch decision §D + Amendment 3): if cumulative > $26
* (30% over $20 expected), halt-and-PM per super-linear sub-rule.
*
* --------------------------------------------------------------------------
* Sampling design:
* --------------------------------------------------------------------------
*
* Same 8 instances across all 5 shapes (controlled comparison; trio_mean delta
* is purely shape-attributable). Deterministic Mulberry32 with seed=42, then
* take first 8 of shuffled corpus. Held-out 5 = next 5 (instances 9-13)
* after the 8 — kept for Faza 1 §F.4 held-out validation.
*
* --------------------------------------------------------------------------
* Usage:
* --------------------------------------------------------------------------
*
* npx tsx benchmarks/gepa/scripts/faza-1/run-null-baseline.ts --dry-run
* # No LLM call. Validates sampling + shape resolution + substrate setup.
*
* npx tsx benchmarks/gepa/scripts/faza-1/run-null-baseline.ts --probe
* # Single (shape=qwen-thinking, instance=0). ~$0.50. Validates round-trip.
*
* npx tsx benchmarks/gepa/scripts/faza-1/run-null-baseline.ts --all
* # Full 5×8 = 40 evaluations. ~$20 expected, $26 halt.
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
MindDB,
FrameStore,
SessionStore,
HybridSearch,
createOllamaEmbedder,
type Embedder,
} from '@waggle/core';
import {
runRetrievalAgentLoop,
type LlmCallFn,
type LlmCallInput,
type LlmCallResult as AgentLlmCallResult,
type RetrievalSearchFn,
type AgentRunResult,
} from '@waggle/agent';
import { REGISTRY, selectShape } from '../../../../packages/agent/src/prompt-shapes/selector.js';
import { type PromptShape } from '../../../../packages/agent/src/prompt-shapes/types.js';
import { type CorpusInstance } from '../../src/faza-1/corpus.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '../../../..');
const CORPUS_JSONL = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/corpus/h3-northlane-cfo-50-instances.jsonl');
const OUT_DIR = path.join(REPO_ROOT, 'benchmarks/results/gepa-faza1/null-baseline');
const OUT_JSONL = path.join(OUT_DIR, 'null-baseline-eval.jsonl');
const RUN_LOG = path.join(OUT_DIR, 'null-baseline-run.log');
const SUMMARY_JSON = path.join(OUT_DIR, 'null-baseline-summary.json');
const SCRATCH_DIR = path.join(REPO_ROOT, 'tmp/gepa-faza1-null-baseline');
const LITELLM_URL = process.env.LITELLM_URL ?? 'http://localhost:4000';
const OLLAMA_URL = 'http://localhost:11434';
const EMBEDDER_MODEL = 'nomic-embed-text';
const SAMPLING_SEED = 42;
const N_PER_SHAPE = 8;
// Shapes in fixed evaluation order (matches manifest v7 §gepa.shape_scope.targets)
const SHAPES = ['claude', 'qwen-thinking', 'qwen-non-thinking', 'gpt', 'generic-simple'] as const;
type ShapeName = typeof SHAPES[number];
// Subject inheritance per manifest v7 §subject (= pilot 2026-04-26 runner SHA 8a6251e2)
const SUBJECT_ALIAS = 'qwen3.6-35b-a3b-via-dashscope-direct';
const SUBJECT_MAX_TOKENS = 16000;
const SUBJECT_THINKING = true;
// Judges inheritance per manifest v7 §judges (= pilot runner line 626)
const JUDGES = ['claude-opus-4-7', 'gpt-5.4', 'minimax-m27-via-openrouter'] as const;
const JUDGE_MAX_TOKENS = 3000;
const JUDGE_RETRIES = 3;
// Multi-step orchestration
const MAX_STEPS = 5;
const MAX_RETRIEVALS_PER_STEP = 8;
const PER_CALL_HALT_USD = 0.40;
const PER_CELL_HALT_USD = 1.00;
// Cost halt per launch decision §D (super-linear sub-rule per A.7)
const COST_HALT_USD = 26.0; // 30% over $20 expected
// Pricing (per pilot runner line 128-133)
const MODEL_PRICING: Record<string, { in: number; out: number }> = {
'claude-opus-4-7': { in: 15.0, out: 75.0 },
'gpt-5.4': { in: 2.5, out: 10.0 },
'minimax-m27-via-openrouter': { in: 0.7, out: 2.8 },
'qwen3.6-35b-a3b-via-dashscope-direct': { in: 0.20, out: 0.80 },
'qwen3.6-35b-a3b-via-openrouter': { in: 0.6, out: 2.4 },
};
const MANIFEST_ANCHOR = 'manifest-v7-gepa-faza1';
const MANIFEST_SHA_AMENDMENT_4 = '1f7a6d6fa01403f6c8d6855893adbfa5e82898a81b7583cfa55628e5eba60196';
// ── Logging ────────────────────────────────────────────────────────────────
function log(msg: string): void {
const line = `[${new Date().toISOString()}] ${msg}\n`;
try { fs.appendFileSync(RUN_LOG, line); } catch { /* dir not yet created */ }
process.stderr.write(line);
}
// ── CLI ────────────────────────────────────────────────────────────────────
interface Args {
mode: 'dry-run' | 'probe' | 'all';
probeShape?: ShapeName;
probeInstanceIdx?: number;
}
function parseArgs(argv: string[]): Args {
let mode: Args['mode'] = 'dry-run';
let probeShape: ShapeName | undefined;
let probeInstanceIdx: number | undefined;
for (let i = 0; i < argv.length; i++) {
const flag = argv[i];
const next = argv[i + 1];
switch (flag) {
case '--dry-run': mode = 'dry-run'; break;
case '--probe': mode = 'probe'; break;
case '--all': mode = 'all'; break;
case '--probe-shape': probeShape = next as ShapeName; i++; break;
case '--probe-instance': probeInstanceIdx = Number(next); i++; break;
}
}
return { mode, probeShape, probeInstanceIdx };
}
// ── Deterministic sampling (Mulberry32, seed=42) ───────────────────────────
function mulberry32(seed: number): () => number {
let t = seed >>> 0;
return () => {
t = (t + 0x6d2b79f5) >>> 0;
let r = t;
r = Math.imul(r ^ (r >>> 15), r | 1);
r ^= r + Math.imul(r ^ (r >>> 7), r | 61);
return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
};
}
function deterministicShuffle<T>(items: ReadonlyArray<T>, seed: number): T[] {
const arr = [...items];
const rand = mulberry32(seed);
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(rand() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
function loadCorpus(): CorpusInstance[] {
const text = fs.readFileSync(CORPUS_JSONL, 'utf-8');
return text.trim().split(/\n+/).filter(Boolean).map(l => JSON.parse(l) as CorpusInstance);
}
/**
* Sample N=8 instances deterministically. Same set across all shapes (controlled
* comparison). Held-out 5 are the next 5 after the sample (instances 9-13).
*/
function sampleInstances(corpus: CorpusInstance[], n: number, seed: number): CorpusInstance[] {
const shuffled = deterministicShuffle(corpus, seed);
return shuffled.slice(0, n);
}
// ── LiteLLM call adapter ──────────────────────────────────────────────────
const llmCall: LlmCallFn = async (input: LlmCallInput): Promise<AgentLlmCallResult> => {
const masterKey = process.env.LITELLM_MASTER_KEY;
if (!masterKey) throw new Error('LITELLM_MASTER_KEY env not set');
const { model, messages } = input;
const isQwen = model.includes('qwen');
const maxTokens = input.maxTokens ?? (isQwen ? SUBJECT_MAX_TOKENS : 4096);
const thinking = input.thinking ?? (isQwen ? SUBJECT_THINKING : true);
const payload: Record<string, unknown> = { model, messages, max_tokens: maxTokens };
if (model.startsWith('claude-opus')) {
payload.temperature = 1.0;
} else if (model === 'gpt-5.4' || model === 'minimax-m27-via-openrouter') {
// omit temperature — reasoning-model defaults
} else {
payload.temperature = input.temperature ?? 0.3;
}
if (isQwen) {
payload.extra_body = { enable_thinking: thinking };
}
const started = Date.now();
let lastErr: string | undefined;
for (let attempt = 0; attempt < 2; attempt++) {
try {
const resp = await fetch(`${LITELLM_URL}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${masterKey}` },
body: JSON.stringify(payload),
});
const d: any = await resp.json();
if ('error' in d) {
lastErr = String(d.error?.message ?? JSON.stringify(d.error)).slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
continue;
}
const content = d.choices?.[0]?.message?.content ?? '';
const usage = d.usage ?? {};
const inTok = usage.prompt_tokens ?? 0;
const outTok = usage.completion_tokens ?? 0;
const pricing = MODEL_PRICING[model] ?? { in: 1, out: 4 };
const costUsd = (inTok * pricing.in + outTok * pricing.out) / 1_000_000;
return { content, inTokens: inTok, outTokens: outTok, costUsd, latencyMs: Date.now() - started };
} catch (e) {
lastErr = `${(e as Error).name}: ${(e as Error).message}`.slice(0, 200);
if (attempt < 1) await new Promise(r => setTimeout(r, 1500));
continue;
}
}
return { content: '', inTokens: 0, outTokens: 0, costUsd: 0, latencyMs: Date.now() - started, error: lastErr ?? 'unknown error' };
};
// ── Trio judging (mirrors pilot 2026-04-26 runner line 545+) ───────────────
const JUDGE_PROMPT_TEMPLATE = `You are evaluating an AI agent's response to a complex knowledge work task. The persona, scenario, materials, and question are provided. The response was generated under one of four configurations (revealed only after scoring): {model_only | model + memory + agent harness} × {Opus 4.7 | Qwen 3.6 35B-A3B}.
You do NOT know which configuration produced this response. Score blind.
Read the persona/scenario/question (provided), skim the materials (provided), then read the response carefully (provided).
Score the response on six dimensions, Likert 1-5:
1. COMPLETENESS — engagement with all material
2. ACCURACY — faithfulness to source materials, no hallucinations
3. SYNTHESIS — connections across inputs, not isolated treatment
4. JUDGMENT — defensible recommendations, tradeoffs acknowledged
5. ACTIONABILITY — would the persona act on this tomorrow
6. STRUCTURE — organization and readability
Output JSON only:
{
"completeness": <1-5>,
"accuracy": <1-5>,
"synthesis": <1-5>,
"judgment": <1-5>,
"actionability": <1-5>,
"structure": <1-5>,
"rationale": "<1-2 sentences explaining the lowest scoring dimension>",
"overall_verdict": "<one of: PASS_STRONG | PASS_ADEQUATE | FAIL_WEAK | FAIL_CRITICAL>"
}
PASS_STRONG: mean >= 4.0
PASS_ADEQUATE: mean 3.5-3.99
FAIL_WEAK: mean 2.5-3.49
FAIL_CRITICAL: mean < 2.5
[PERSONA + SCENARIO + QUESTION]
###PERSONA_SCENARIO_QUESTION###
[MATERIALS]
###MATERIALS###
[RESPONSE TO EVALUATE]
###RESPONSE###`;
interface JudgeVerdict {
completeness: number;
accuracy: number;
synthesis: number;
judgment: number;
actionability: number;
structure: number;
rationale: string;
overall_verdict: string;
mean: number;
}
interface JudgeRecord extends JudgeVerdict {
judge_model: string;
judge_cost_usd: number;
judge_latency_ms: number;
judge_retries: number;
}
const ZERO_VERDICT: JudgeVerdict = {
completeness: 0, accuracy: 0, synthesis: 0, judgment: 0, actionability: 0, structure: 0,
rationale: '__JUDGE_FAILED__', overall_verdict: 'FAIL_CRITICAL', mean: 0,
};
function parseJudgeJson(text: string): JudgeVerdict | null {
const m = text.match(/\{[\s\S]*\}/);
if (!m) return null;
try {
const obj = JSON.parse(m[0]);
const dims = ['completeness', 'accuracy', 'synthesis', 'judgment', 'actionability', 'structure'] as const;
for (const d of dims) {
if (typeof obj[d] !== 'number' || obj[d] < 1 || obj[d] > 5) return null;
}
const mean = dims.reduce((s, d) => s + obj[d], 0) / dims.length;
return {
completeness: obj.completeness, accuracy: obj.accuracy, synthesis: obj.synthesis,
judgment: obj.judgment, actionability: obj.actionability, structure: obj.structure,
rationale: typeof obj.rationale === 'string' ? obj.rationale : '',
overall_verdict: typeof obj.overall_verdict === 'string' ? obj.overall_verdict : '',
mean,
};
} catch { return null; }
}
function buildJudgePrompt(instance: CorpusInstance, response: string): string {
return JUDGE_PROMPT_TEMPLATE
.replace('###PERSONA_SCENARIO_QUESTION###', `${instance.personaText}\n\nQUESTION: ${instance.question}`)
.replace('###MATERIALS###', instance.materialsConcat)
.replace('###RESPONSE###', response);
}
async function runJudge(judgeModel: string, prompt: string): Promise<JudgeRecord> {
let lastErr = '';
let totalCost = 0, totalLatency = 0;
for (let attempt = 0; attempt < JUDGE_RETRIES; attempt++) {
const r = await llmCall({ model: judgeModel, messages: [{ role: 'user', content: prompt }], maxTokens: JUDGE_MAX_TOKENS, thinking: false });
totalCost += r.costUsd; totalLatency += r.latencyMs;
if (r.error) { lastErr = `attempt ${attempt + 1}: ${r.error}`; continue; }
const parsed = parseJudgeJson(r.content);
if (parsed) {
return { ...parsed, judge_model: judgeModel, judge_cost_usd: totalCost, judge_latency_ms: totalLatency, judge_retries: attempt };
}
lastErr = `attempt ${attempt + 1}: malformed JSON: ${r.content.slice(0, 100)}`;
}
log(`[judge ${judgeModel}] FAILED after ${JUDGE_RETRIES}: ${lastErr}`);
return { ...ZERO_VERDICT, judge_model: judgeModel, judge_cost_usd: totalCost, judge_latency_ms: totalLatency, judge_retries: JUDGE_RETRIES, rationale: `__JUDGE_FAILED__: ${lastErr}` };
}
interface TrioResult {
records: JudgeRecord[];
trioMean: number;
trioStrictPassII: boolean; // op (ii) — trio_mean >= 4.0
trioStrictPassI: boolean; // op (i) — >=2 of 3 judges with mean >= 3.5
judgeCostTotal: number;
}
async function judgeTrio(instance: CorpusInstance, response: string): Promise<TrioResult> {
const prompt = buildJudgePrompt(instance, response);
const records = await Promise.all(JUDGES.map(j => runJudge(j, prompt)));
const validMeans = records.filter(r => r.mean > 0).map(r => r.mean);
const trioMean = validMeans.length > 0 ? validMeans.reduce((s, m) => s + m, 0) / validMeans.length : 0;
const trioStrictPassII = trioMean >= 4.0;
const trioStrictPassI = records.filter(r => r.mean >= 3.5).length >= 2;
const judgeCostTotal = records.reduce((s, r) => s + r.judge_cost_usd, 0);
return { records, trioMean, trioStrictPassII, trioStrictPassI, judgeCostTotal };
}
// ── Per-eval orchestration: run shape × instance via multi-step ───────────
interface EvalRecord {
shape: ShapeName;
instanceId: string;
instanceCell: CorpusInstance['cell'];
candidateResponse: string;
candidateLatencyMs: number;
candidateTokensIn: number;
candidateTokensOut: number;
candidateCostUsd: number;
loopExhausted: boolean;
stepsTaken: number;
retrievalCalls: number;
judges: { records: JudgeRecord[]; trioMean: number; trioStrictPassII: boolean; trioStrictPassI: boolean; judgeCostTotal: number };
evalCostUsd: number;
manifestAnchor: string;
manifestShaAmendment4: string;
tsIso: string;
}
async function runOneEval(shape: PromptShape, instance: CorpusInstance, embedder: Embedder): Promise<EvalRecord | { error: string }> {
const evalId = `${shape.name}__${instance.instanceId}`;
log(`[${evalId}] start`);
// Per-eval SQLite + HybridSearch substrate (per pilot runner pattern)
const dbPath = path.join(SCRATCH_DIR, `eval-${shape.name}-${instance.instanceId}.sqlite`);
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
const db = new MindDB(dbPath);
const frames = new FrameStore(db);
const sessions = new SessionStore(db);
const hybrid = new HybridSearch(db, embedder);
const gopId = `gepa-faza1-null-${shape.name}-${instance.instanceId}`;
sessions.ensure(gopId, undefined, `NULL-baseline ${shape.name} on ${instance.instanceId}`);
// Ingest source documents
for (const doc of instance.sourceDocuments) {
const content = `## ${doc.title}\n\n${doc.body}`;
frames.createIFrame(gopId, content, 'important', 'system');
}
log(`[${evalId}] ingested ${instance.sourceDocuments.length} frames`);
// Retrieval adapter
const searchAdapter: RetrievalSearchFn = async ({ query, limit }) => {
const hits = await hybrid.search(query, { limit, gopId });
const formatted = hits.length > 0
? hits.map((sr, i) => `[result ${i + 1}, score ${sr.finalScore.toFixed(3)}]\n${sr.frame.content}`).join('\n\n---\n\n')
: '';
return { formattedResults: formatted, resultCount: hits.length };
};
// Run multi-step retrieval agent loop.
// CRITICAL: pass promptShapeOverride to actually apply the per-shape prompt.
// Without this, runRetrievalAgentLoop calls selectShape(modelAlias) which always
// resolves to 'qwen-thinking' for Qwen subject (per config), making the runner's
// shape parameter unused. This bug was discovered post-Checkpoint-A and corrected
// per PM Option A ratification + manifest v7 Amendment 6.
let agentResult: AgentRunResult;
try {
agentResult = await runRetrievalAgentLoop({
modelAlias: SUBJECT_ALIAS,
persona: instance.personaText,
question: instance.question,
llmCall,
search: searchAdapter,
maxSteps: MAX_STEPS,
maxRetrievalsPerStep: MAX_RETRIEVALS_PER_STEP,
perCallHaltUsd: PER_CALL_HALT_USD,
perCellHaltUsd: PER_CELL_HALT_USD,
contextTag: evalId,
promptShapeOverride: shape.name, // bug fix per Amendment 6
} as any);
} catch (e) {
const msg = `agent loop failed: ${(e as Error).message}`;
log(`[${evalId}] ${msg}`);
return { error: msg };
}
if (agentResult.errors.length > 0) {
log(`[${evalId}] agent errors: ${agentResult.errors.join('; ').slice(0, 200)}`);
}
log(`[${evalId}] subject_done; retrievals=${agentResult.retrievalCalls} steps=${agentResult.stepsTaken} cost=$${agentResult.totalCostUsd.toFixed(4)} loop_exhausted=${agentResult.loopExhausted}`);
// Judge response
const judges = await judgeTrio(instance, agentResult.rawResponse);
const evalCostUsd = agentResult.totalCostUsd + judges.judgeCostTotal;
log(`[${evalId}] judged; trio_mean=${judges.trioMean.toFixed(3)} pass_ii=${judges.trioStrictPassII} pass_i=${judges.trioStrictPassI} eval_cost=$${evalCostUsd.toFixed(4)}`);
return {
shape: shape.name as ShapeName,
instanceId: instance.instanceId,
instanceCell: instance.cell,
candidateResponse: agentResult.rawResponse,
candidateLatencyMs: agentResult.totalLatencyMs,
candidateTokensIn: agentResult.totalTokensIn,
candidateTokensOut: agentResult.totalTokensOut,
candidateCostUsd: agentResult.totalCostUsd,
loopExhausted: agentResult.loopExhausted,
stepsTaken: agentResult.stepsTaken,
retrievalCalls: agentResult.retrievalCalls,
judges,
evalCostUsd,
manifestAnchor: MANIFEST_ANCHOR,
manifestShaAmendment4: MANIFEST_SHA_AMENDMENT_4,
tsIso: new Date().toISOString(),
};
}
// ── Aggregate per-shape metrics + κ across batch ──────────────────────────
interface ShapeAggregate {
shape: ShapeName;
nEvals: number;
trioStrictPassRateII: number; // op (ii) primary
trioStrictPassRateI: number; // op (i) supplementary
meanRetrievalCallsPerTask: number;
meanCandidateCostUsd: number;
meanJudgeCostUsd: number;
meanEvalCostUsd: number;
totalEvalCostUsd: number;
loopExhaustedRate: number;
meanStepsTaken: number;
}
function aggregatePerShape(records: EvalRecord[]): ShapeAggregate[] {
const byShape = new Map<ShapeName, EvalRecord[]>();
for (const r of records) {
if (!byShape.has(r.shape)) byShape.set(r.shape, []);
byShape.get(r.shape)!.push(r);
}
const out: ShapeAggregate[] = [];
for (const shape of SHAPES) {
const rs = byShape.get(shape) ?? [];
if (rs.length === 0) continue;
const passII = rs.filter(r => r.judges.trioStrictPassII).length;
const passI = rs.filter(r => r.judges.trioStrictPassI).length;
const meanRetr = rs.reduce((s, r) => s + r.retrievalCalls, 0) / rs.length;
const meanCandCost = rs.reduce((s, r) => s + r.candidateCostUsd, 0) / rs.length;
const meanJudgeCost = rs.reduce((s, r) => s + r.judges.judgeCostTotal, 0) / rs.length;
const meanEvalCost = rs.reduce((s, r) => s + r.evalCostUsd, 0) / rs.length;
const totalEvalCost = rs.reduce((s, r) => s + r.evalCostUsd, 0);
const exhaustedRate = rs.filter(r => r.loopExhausted).length / rs.length;
const meanSteps = rs.reduce((s, r) => s + r.stepsTaken, 0) / rs.length;
out.push({
shape, nEvals: rs.length,
trioStrictPassRateII: passII / rs.length,
trioStrictPassRateI: passI / rs.length,
meanRetrievalCallsPerTask: meanRetr,
meanCandidateCostUsd: meanCandCost,
meanJudgeCostUsd: meanJudgeCost,
meanEvalCostUsd: meanEvalCost,
totalEvalCostUsd: totalEvalCost,
loopExhaustedRate: exhaustedRate,
meanStepsTaken: meanSteps,
});
}
return out;
}
// ── Main ───────────────────────────────────────────────────────────────────
async function main(): Promise<void> {
const args = parseArgs(process.argv.slice(2));
fs.mkdirSync(OUT_DIR, { recursive: true });
fs.mkdirSync(SCRATCH_DIR, { recursive: true });
if (!fs.existsSync(RUN_LOG)) fs.writeFileSync(RUN_LOG, '');
const corpus = loadCorpus();
log(`[loaded] corpus = ${corpus.length} instances from ${CORPUS_JSONL}`);
const sample = sampleInstances(corpus, N_PER_SHAPE, SAMPLING_SEED);
log(`[sampled] N=${N_PER_SHAPE} via seed=${SAMPLING_SEED}: ${sample.map(i => i.instanceId).join(', ')}`);
if (args.mode === 'dry-run') {
log(`[dry-run] would run ${SHAPES.length} shapes × ${N_PER_SHAPE} instances = ${SHAPES.length * N_PER_SHAPE} evals`);
for (const shapeName of SHAPES) {
const shape = REGISTRY[shapeName];
const sysPrompt = shape.systemPrompt({ persona: sample[0].personaText, question: sample[0].question, isMultiStep: true });
log(`[dry-run] shape=${shapeName} systemPrompt(multi-step)=${sysPrompt.length}c`);
}
log(`[dry-run] OK; no LLM call; cost: $0.00`);
return;
}
// Resume support: skip evals already in JSONL
const existing = new Set<string>();
let cumulativeCost = 0;
let allRecords: EvalRecord[] = [];
if (fs.existsSync(OUT_JSONL)) {
for (const line of fs.readFileSync(OUT_JSONL, 'utf-8').trim().split(/\n+/).filter(Boolean)) {
try {
const r = JSON.parse(line) as EvalRecord;
const key = `${r.shape}__${r.instanceId}`;
existing.add(key);
cumulativeCost += r.evalCostUsd;
allRecords.push(r);
} catch { /* skip */ }
}
log(`[resume] loaded ${existing.size} existing evals; cumulative cost $${cumulativeCost.toFixed(4)}`);
}
const out = fs.createWriteStream(OUT_JSONL, { flags: existing.size > 0 ? 'a' : 'w' });
const embedder = createOllamaEmbedder({ baseUrl: OLLAMA_URL, model: EMBEDDER_MODEL });
const targetShapes = args.mode === 'probe' ? [args.probeShape ?? 'qwen-thinking'] : SHAPES;
const targetInstances = args.mode === 'probe'
? [sample[args.probeInstanceIdx ?? 0]]
: sample;
for (const shapeName of targetShapes) {
const shape = REGISTRY[shapeName];
if (!shape) { log(`[error] shape "${shapeName}" not in REGISTRY`); continue; }
for (const instance of targetInstances) {
const key = `${shapeName}__${instance.instanceId}`;
if (existing.has(key)) {
log(`[skip] ${key} already in JSONL`);
continue;
}
if (cumulativeCost >= COST_HALT_USD) {
log(`[HALT] cumulative $${cumulativeCost.toFixed(4)} >= $${COST_HALT_USD} cost halt — stopping`);
break;
}
const result = await runOneEval(shape, instance, embedder);
if ('error' in result) {
log(`[skip] ${key} due to: ${result.error}`);
continue;
}
out.write(JSON.stringify(result) + '\n');
cumulativeCost += result.evalCostUsd;
allRecords.push(result);
log(`[cumulative] $${cumulativeCost.toFixed(4)} / $${COST_HALT_USD} halt; ${allRecords.length} evals total`);
}
}
out.end();
// Aggregate + summary
const aggregates = aggregatePerShape(allRecords);
const summary = {
manifestAnchor: MANIFEST_ANCHOR,
manifestShaAmendment4: MANIFEST_SHA_AMENDMENT_4,
samplingSeed: SAMPLING_SEED,
nPerShape: N_PER_SHAPE,
sampledInstanceIds: sample.map(i => i.instanceId),
totalEvals: allRecords.length,
totalCostUsd: +cumulativeCost.toFixed(6),
perShape: aggregates,
completedAtIso: new Date().toISOString(),
};
fs.writeFileSync(SUMMARY_JSON, JSON.stringify(summary, null, 2), 'utf-8');
log(`[done] ${allRecords.length} evals; total cost $${cumulativeCost.toFixed(4)}; summary written`);
}
main().catch(e => {
console.error('FATAL:', e);
process.exit(2);
});