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

117
benchmarks/gepa/README.md Normal file
View File

@@ -0,0 +1,117 @@
# GEPA — Tier 2 Prompt-Shapes Evolution
GEPA (Agrawal et al. — Genetic Evolutionary Prompt Adaptation) harness for
evolving the per-model prompt-shape templates in `packages/agent/src/prompt-shapes/`.
**Faza 1 (current):** proof-of-concept pilot. $100 cap. H3 cell only. 5 shapes ×
3 candidates × 2 generations × N=8 evaluations.
**Faza 2 (gated on Faza 1 PASS):** expansion to H2 + H4 cells, scale N to 20+,
3-5 generations, separate ratification + brief.
## Authority chain
- PM brief: `D:/Projects/PM-Waggle-OS/briefs/2026-04-28-cc4-gepa-tier2-evolution-faza1-brief.md`
- Pre-flight report: `D:/Projects/PM-Waggle-OS/briefs/2026-04-28-cc4-faza1-preflight-report.md`
- Amendment 1: `D:/Projects/PM-Waggle-OS/briefs/2026-04-28-cc4-faza1-amendment-1.md`
- Amendment 2: `D:/Projects/PM-Waggle-OS/briefs/2026-04-28-cc4-faza1-amendment-2.md`
- Launch decision (LOCK): `D:/Projects/PM-Waggle-OS/decisions/2026-04-28-gepa-faza1-launch.md`
- Manifest v7 (Amendment 2 supplemented): `benchmarks/preregistration/manifest-v7-gepa-faza1.yaml`
- SHA: `583712dde139ffc87fb1ab21643f68d52c56469ded9e8090a624980b05969beb`
## Substrate freeze
- Branch: `feature/c3-v3-wrapper`
- Commit: `c9bda3d6dd4c0a4f715e09f3757a96d01ff01cd7` (Phase 4.7)
- Worktree: `D:/Projects/waggle-os-faza1-wt` (detached HEAD c9bda3d, race-condition guarded)
## Module map (`src/faza-1/`)
| Module | Purpose | Key references |
|---|---|---|
| `types.ts` | Shared types + `QWEN_TARGETED_SHAPES` / `NON_QWEN_SHAPES` partition | Amendment 2 §3 |
| `fitness.ts` | Per-shape fitness function with retrieval engagement bonus fork | Amendment 2 §3 bands |
| `acceptance.ts` | §F + §F.5 verdict per candidate (false-positive guard) | Launch decision §F |
| `mutation-validator.ts` | Cell-semantic preservation audit (boundary SHAs) | Launch decision §A.4 + §B |
| `kappa-audit.ts` | Drift band detection vs canonical 0.7878 ± 0.05 | Launch decision §F.3 |
| `cost-tracker.ts` | Super-linear governance + halt triggers | Launch decision §A.7 + §D |
| `selection.ts` | Top-1-per-shape + run-aggregate §F.2 | Launch decision §F.2 |
| `mutation-oracle-fork.ts` | Qwen vs non-Qwen template routing | Amendment 2 §4 |
## Boundary anchors (LOCKED at substrate c9bda3d)
| Anchor | SHA-256 | Bytes |
|---|---|---|
| `packages/agent/src/prompt-shapes/types.ts` (whole file) | `1a9fa329e4b66ed9f0abe8bc22cbbf0124e0c879e1e78ec806d557cab25bc94d` | — |
| `MULTI_STEP_ACTION_CONTRACT` constant body | `70a1701dfa126f8dc1df9c116f0a8469da005821ecadc59d9b8f348568e755ba` | 252 |
## Baseline shape SHAs (LOCKED)
| Shape | SHA-256 |
|---|---|
| `claude.ts` | `cbaf0c37b067b025a1fe97f2feeec11fae4070a8b3fcfaad1da8775dda451cc0` |
| `qwen-thinking.ts` | `848a4e4917baa5c7bbcc3bb35fb8cb4b4ac8f0ab537243f14cbef3a99197aacb` |
| `qwen-non-thinking.ts` | `35be379be9a8caafc2c419e32da5f63f92fc83f6f6d70d9df76029c1e8584572` |
| `gpt.ts` | `5dc6d750d52a68feb9d37ad8384b2bcd59d70962066122ff086b0e5888413576` |
| `generic-simple.ts` | `81189817f560e26a69394248d8bd9089cae72c7d40825323e2b7407e36026172` |
## Per-shape fitness fork (Amendment 2 §3)
```
Qwen-targeted (qwen-thinking, qwen-non-thinking):
fitness = trio_strict_pass_rate + retrieval_engagement_bonus cost_penalty
retrieval_engagement_bonus =
+0.05 if mean retrieval_calls per task ≥ 2.0 (Opus parity proxy)
0.00 if mean retrieval_calls per task ∈ [1.5, 2.0)
0.05 if mean retrieval_calls per task < 1.5 (Qwen baseline penalty)
Non-Qwen (claude, gpt, generic-simple):
fitness = trio_strict_pass_rate cost_penalty
```
## §F.5 false-positive guard (Amendment 2 §5)
If best Qwen-shape candidate achieves `+5pp trio_strict delta WITHOUT closing
retrieval engagement gap (mean retrieval_calls < 1.5)`, candidate is REJECTED
as false-positive evolution.
## Tests
110 tests across 7 files (`tests/faza-1/`). Run via `npm test` from repo root
(vitest auto-discovers `benchmarks/*/tests/**/*.test.ts`).
| Test file | Tests |
|---|---|
| `fitness.test.ts` | 30 (5 mandatory boundary cases + 5 routing + cost penalty + invariants) |
| `acceptance.test.ts` | 17 (§F.5 mandatory FAIL + PASS-path + boundaries) |
| `mutation-validator.test.ts` | 19 (SHA boundary + baseline + metadata + imports + Gen 1 mutation) |
| `kappa-audit.test.ts` | 22 (drift band + Cohen's κ + audit log + canonical anchor) |
| `cost-tracker.test.ts` | 23 (HARD_CAP + INTERNAL_HALT + SUPER_LINEAR + audit cadence) |
| `selection.test.ts` | (top-1 + Qwen retrieval ranking + §F.2 boundaries + missing-baseline errors) |
| `mutation-oracle-fork.test.ts` | (shape classification + template paths + placeholder substitution) |
## Halt-and-PM checkpoints
| # | Checkpoint | Cumulative | Trigger |
|---|---|---|---|
| 1 | Pre-A | ~$5 | Post 50-instance corpus + 5-instance spot-audit |
| 2 | A | ~$25 | Post NULL-baseline 5 shapes × 8 instances |
| 3 | B | ~$50-65 | Mid-Gen 1 (after 30 evaluations) |
| 4 | C | ~$100 | Post held-out validation, Faza 1 verdict |
See launch decision §E for binding details.
## Acceptance criteria summary
Per launch decision §F (binding) — all 4 conditions must hold AND no §F.5 trigger:
1. Best GEPA candidate beats NULL-baseline by ≥+5pp on `trio_strict_pass` rate
(`trio_mean ≥ 4.0` per Ask B); for Qwen-targeted shapes, additionally
`mean retrieval_calls per task ≥ 1.7` (50% gap closure).
2. ≥3/5 shapes show positive trio_strict delta vs NULL-baseline.
3. Trio judge κ remains within `±0.05` of canonical `0.7878`
(drift band `[0.7378, 0.8378]`).
4. Zero cell semantic violations detected by `mutation-validator`.
5. **§F.5 false-positive guard:** Qwen candidate with `+5pp trio_strict delta`
AND `mean retrieval_calls < 1.5` → REJECTED.

View File

@@ -0,0 +1,54 @@
# GEPA Mutation Oracle Prompt — Non-Qwen (Claude / GPT / Generic-Simple)
**Template class:** ###TEMPLATE_CLASS###
**Shape under mutation:** ###SHAPE_NAME###
---
You are an expert prompt engineer. Your job is to produce a single mutated variant of the prompt-shape file below, evolving the *reasoning scaffold* and *planning step structure* while preserving exact cell semantics.
## Context (non-Qwen branch)
This shape (###SHAPE_NAME###) does **not** exhibit the retrieval-engagement gap that Phase 4.5 surfaced for Qwen. Standard mutation guidance per brief §3.3 applies — no Qwen-specific anti-premature-finalization scaffolding required.
Apply general scaffold improvements: clearer reasoning structure, better chain-of-thought triggers, more explicit planning steps where appropriate for the model class.
## Failure modes from Phase 4.3 (top-3 T2 categories for this shape)
###FAILURE_MODE_SUMMARY###
## Mutation guidance (binding)
Modify only:
- `systemPrompt(input)` method body (string-building only)
- `soloUserPrompt(input)` method body
- `multiStepKickoffUserPrompt(input)` method body
- `retrievalInjectionUserPrompt(input)` method body
- `metadata.evidence_link` (you MUST update to point to GEPA Gen 1 results: `benchmarks/results/gepa-faza1/oracle/mutation-prompt-template-non-qwen.md`)
Standard mutation directions:
1. **Evolve reasoning scaffold** — restructure the implicit reasoning prompts (e.g., "think step by step" variants, planning bullets)
2. **Refine chain-of-thought triggers** — adjust where the model is prompted to articulate its reasoning before producing output
3. **Improve planning step structure** — make multi-step task decomposition more explicit
4. **Preserve all cell semantic boundaries** — see prohibitions below
## Prohibited modifications (cell semantic LOCK)
- **DO NOT modify** `MULTI_STEP_ACTION_CONTRACT` — this constant lives in `packages/agent/src/prompt-shapes/types.ts` and its bytes are SHA-pinned
- **DO NOT modify** `metadata.{description,modelClass,defaultThinking,defaultMaxTokens}`
- **DO NOT modify** the imports block
- **DO NOT modify** the file structure (must remain a TypeScript module with a single PromptShape export)
- **DO NOT modify** the JSON action contract
- **DO NOT modify** task framing (persona/question/materials section labels)
Mutations violating any of the above will be REJECTED by the mutation validator. Two consecutive invalid mutations trigger halt-and-PM per brief §5.
## Baseline shape file (the input to your mutation)
```typescript
###BASELINE_SHAPE_CONTENT###
```
## Output format
Output ONLY the mutated TypeScript file content. No prose, no commentary, no code fences. The file should be a drop-in replacement for the baseline shape file.

View File

@@ -0,0 +1,64 @@
# GEPA Mutation Oracle Prompt — Qwen-Targeted Shape
**Template class:** ###TEMPLATE_CLASS###
**Shape under mutation:** ###SHAPE_NAME###
---
You are an expert prompt engineer. Your job is to produce a single mutated variant of the prompt-shape file below, evolving the *reasoning scaffold* and *retrieval-engagement guidance* while preserving exact cell semantics.
## Empirical context (binding)
Phase 4.5 audit empirically established that Qwen 3.6 35B-A3B (under both `thinking-on` and `thinking-off` configurations) exhibits a **retrieval-engagement gap** vs Opus 4.7 on the same `MULTI_STEP_ACTION_CONTRACT` tool surface:
- Qwen retrieves **1.33 times per task** on average
- Opus retrieves **2.33 times per task** on average
- Qwen finalizes prematurely; Opus exhausts maxSteps in 67% of retrieval runs
- Resulting H4 trio_mean delta: Qwen scores **0.65 below** Opus on every task
The mutation surface (prompt-shape body) is the lever to address this. **Your mutation must encourage more aggressive multi-turn retrieval engagement on Qwen.**
## Failure modes from Phase 4.3 (top-3 T2 categories for this shape)
###FAILURE_MODE_SUMMARY###
## Mutation guidance (binding)
Modify only:
- `systemPrompt(input)` method body (string-building only)
- `soloUserPrompt(input)` method body
- `multiStepKickoffUserPrompt(input)` method body
- `retrievalInjectionUserPrompt(input)` method body
- `metadata.evidence_link` (you MUST update to point to GEPA Gen 1 results: `benchmarks/results/gepa-faza1/oracle/mutation-prompt-template-qwen.md`)
Apply Qwen-specific scaffolding:
1. **Emphasize multi-turn retrieval** over single-shot retrieval — make repeated retrieval feel mandatory, not optional
2. **Discourage premature finalization** with explicit phrasing like:
- "Continue retrieving until you have evidence from at least 2 distinct queries before finalizing"
- "Single retrieval is rarely sufficient for synthesis tasks"
3. **Encourage iterative refinement** — each retrieval query should build on prior turn's results
4. **Add anti-premature-finalization scaffolding** in the multiStepKickoff or systemPrompt:
- "Before finalizing, ask: what gap in evidence remains? Issue another retrieval if any gap exists."
- "A complete answer requires triangulation from multiple retrievals"
5. **Preserve all cell semantic boundaries** — see prohibitions below
## Prohibited modifications (cell semantic LOCK)
- **DO NOT modify** `MULTI_STEP_ACTION_CONTRACT` — this constant lives in `packages/agent/src/prompt-shapes/types.ts` and its bytes are SHA-pinned
- **DO NOT modify** `metadata.{description,modelClass,defaultThinking,defaultMaxTokens}`
- **DO NOT modify** the imports block
- **DO NOT modify** the file structure (must remain a TypeScript module with a single PromptShape export)
- **DO NOT modify** the JSON action contract (the `{"action": "retrieve", ...}` / `{"action": "finalize", ...}` shape)
- **DO NOT modify** task framing (persona/question/materials section labels — the template rendering structure)
Mutations that violate any of the above will be REJECTED by the mutation validator (`mutation-validator.ts`), consume one of two retry tolerances, and trigger halt-and-PM if exceeded twice consecutively per brief §5.
## Baseline shape file (the input to your mutation)
```typescript
###BASELINE_SHAPE_CONTENT###
```
## Output format
Output ONLY the mutated TypeScript file content. No prose, no commentary, no code fences. The file should be a drop-in replacement for the baseline shape file.

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);
});

View File

@@ -0,0 +1,154 @@
/**
* GEPA Faza 1 — acceptance validator.
*
* Per launch decision §F (4 must-hold conditions) + §F.5 (Amendment 2 §5
* false-positive evolution guard).
*
* Per-candidate verdict logic:
*
* §F condition 1 (third update — Amendment 2 §5):
* "Best GEPA candidate per shape beats NULL-baseline by ≥+5pp on
* trio_strict_pass rate (trio_mean ≥ 4.0). For Qwen-targeted shapes,
* additionally: best candidate must have mean retrieval_calls per task
* ≥ 1.7 (engagement gap closed by ≥50% relative to Qwen baseline 1.33)."
*
* §F.5 (NEW per Amendment 2 — false-positive guard):
* "If best Qwen-shape candidate achieves +5pp trio_strict delta WITHOUT
* closing retrieval engagement gap (mean retrieval_calls < 1.5), this
* signals false-positive evolution. Result: candidate REJECTED, shape
* marked FAIL even if other criteria pass."
*
* Conditions §F.2/§F.3/§F.4 are evaluated at the run-aggregate level (across
* shapes / κ stability across all evaluations / mutation validator log) and
* are NOT checked per-candidate here. See selection.ts + run-orchestrator
* for those.
*/
import {
type AcceptanceInputs,
type AcceptanceVerdict,
QWEN_TARGETED_SHAPES,
} from './types.js';
/** §F condition 1 trio_strict delta threshold (percentage points). */
export const TRIO_STRICT_DELTA_THRESHOLD_PP = 5;
/**
* §F condition 1 Qwen-only retrieval engagement floor (mean retrieval_calls
* per task). 1.7 = 50% gap closure between Qwen baseline 1.33 and Opus
* parity 2.33 per Amendment 2 §5.
*/
export const QWEN_RETRIEVAL_ENGAGEMENT_FLOOR = 1.7;
/**
* §F.5 false-positive guard threshold (mean retrieval_calls per task).
* If a Qwen candidate achieves trio_strict delta but stays below this floor,
* it is REJECTED as false-positive evolution per Amendment 2 §5.
*/
export const QWEN_FALSE_POSITIVE_RETRIEVAL_FLOOR = 1.5;
/**
* Floating-point tolerance for threshold comparisons. 1e-9 is well below
* any signal magnitude in the +/-0.05 fitness band (which is itself ~9
* orders of magnitude larger). Required because IEEE 754 makes
* `(0.25 - 0.20) * 100 = 4.999999999999999` rather than exact 5.0.
*/
const EPSILON = 1e-9;
/**
* Compute acceptance verdict for a single candidate per launch decision §F + §F.5.
*
* Caller is responsible for §F.2 (≥3/5 shapes positive delta), §F.3 (κ stability),
* and §F.4 (zero cell semantic violations) at the run-aggregate level.
*/
export function evaluateCandidate(inputs: AcceptanceInputs): AcceptanceVerdict {
const { candidate, baselineTrioStrictPassRateII } = inputs;
// Compute delta in percentage points (scale 0..100)
const trioStrictDeltaPP =
(candidate.trioStrictPassRateII - baselineTrioStrictPassRateII) * 100;
const isQwenTargeted = QWEN_TARGETED_SHAPES.has(candidate.shape);
// §F.5 false-positive guard — applies ONLY to Qwen-targeted shapes
// and ONLY when trio_strict delta meets the +5pp threshold.
// Per Amendment 2 §5: if delta ≥ +5pp AND retrieval_calls < 1.5 → REJECT.
// EPSILON tolerance handles IEEE 754 precision on exact-boundary deltas.
let condition5FalsePositiveGuardTriggered = false;
if (
isQwenTargeted &&
trioStrictDeltaPP >= TRIO_STRICT_DELTA_THRESHOLD_PP - EPSILON &&
candidate.meanRetrievalCallsPerTask < QWEN_FALSE_POSITIVE_RETRIEVAL_FLOOR - EPSILON
) {
condition5FalsePositiveGuardTriggered = true;
}
// §F condition 1 — trio_strict delta + (Qwen only) retrieval engagement floor
let condition1Pass = trioStrictDeltaPP >= TRIO_STRICT_DELTA_THRESHOLD_PP - EPSILON;
if (condition1Pass && isQwenTargeted) {
// Qwen sub-criterion: mean retrieval_calls ≥ 1.7
if (candidate.meanRetrievalCallsPerTask < QWEN_RETRIEVAL_ENGAGEMENT_FLOOR - EPSILON) {
condition1Pass = false;
}
}
// Overall acceptance: condition 1 must pass AND §F.5 must not trigger
const accepted = condition1Pass && !condition5FalsePositiveGuardTriggered;
// Reason string for audit log
const reason = buildReason({
candidate,
trioStrictDeltaPP,
condition1Pass,
condition5FalsePositiveGuardTriggered,
isQwenTargeted,
accepted,
});
return {
condition1Pass,
condition5FalsePositiveGuardTriggered,
accepted,
reason,
trioStrictDeltaPP,
};
}
interface BuildReasonInputs {
candidate: { shape: string; meanRetrievalCallsPerTask: number };
trioStrictDeltaPP: number;
condition1Pass: boolean;
condition5FalsePositiveGuardTriggered: boolean;
isQwenTargeted: boolean;
accepted: boolean;
}
function buildReason(i: BuildReasonInputs): string {
const parts: string[] = [];
parts.push(`shape=${i.candidate.shape}`);
parts.push(`trio_strict_delta=${i.trioStrictDeltaPP.toFixed(2)}pp`);
parts.push(`mean_retrieval_calls=${i.candidate.meanRetrievalCallsPerTask.toFixed(2)}`);
if (i.condition5FalsePositiveGuardTriggered) {
parts.push(
`REJECTED §F.5 false-positive guard: delta ≥ +${TRIO_STRICT_DELTA_THRESHOLD_PP}pp AND retrieval < ${QWEN_FALSE_POSITIVE_RETRIEVAL_FLOOR}`,
);
} else if (!i.condition1Pass) {
// Use same EPSILON tolerance as condition1Pass evaluation to keep root-cause
// attribution consistent with the gating logic on exact-boundary deltas.
if (i.trioStrictDeltaPP < TRIO_STRICT_DELTA_THRESHOLD_PP - EPSILON) {
parts.push(
`FAIL §F.1 trio_strict delta: ${i.trioStrictDeltaPP.toFixed(2)}pp < ${TRIO_STRICT_DELTA_THRESHOLD_PP}pp`,
);
} else if (i.isQwenTargeted) {
parts.push(
`FAIL §F.1 Qwen retrieval floor: ${i.candidate.meanRetrievalCallsPerTask.toFixed(2)} < ${QWEN_RETRIEVAL_ENGAGEMENT_FLOOR}`,
);
}
} else {
parts.push(`PASS §F.1${i.isQwenTargeted ? ' (Qwen retrieval floor met)' : ''}`);
}
parts.push(`accepted=${i.accepted}`);
return parts.join(' | ');
}

View File

@@ -0,0 +1,114 @@
/**
* GEPA Faza 1 — Opus 4.7 oracle prompt for synthesizing one H3 corpus instance.
*
* Per Amendment 1 Ask A Option C sub-ask 4 (instance generation methodology):
* "Opus 4.7 generates task scaffold (persona + scenario + 6-7 source document specs);
* PM does NOT review each instance pre-NULL-baseline. Instead: CC-2 spot-audits
* 5 random instances pre NULL-baseline kick (3% sample)."
*
* The oracle is instructed to produce a JSON object that maps directly onto
* the CorpusInstance shape (minus computed fields like materialsConcat, charCount).
*/
import { type StratificationCell, type CorpusInstance, TASK_FAMILY_DESCRIPTORS } from './corpus.js';
/** Persona descriptor — short label + role context for prompt template. */
const PERSONA_DESCRIPTORS: Record<string, { label: string; roleContext: string }> = {
p1_founder_ceo: {
label: 'Founder/CEO',
roleContext: 'company founder + chief executive; focused on growth, fundraising, vision',
},
p2_cfo: {
label: 'CFO',
roleContext: 'chief financial officer; focused on burn, runway, capital efficiency',
},
p3_coo: {
label: 'COO',
roleContext: 'chief operating officer; focused on operational efficiency, hiring, process',
},
p4_vp_finance: {
label: 'VP Finance',
roleContext: 'reports to CFO; focused on FP&A, financial reporting, budget management',
},
p5_independent_director: {
label: 'Independent Director',
roleContext: 'board-level oversight; focused on governance, strategic risk, executive accountability',
},
};
/** Company-stage descriptor — narrative framing for Opus to ground each instance. */
const COMPANY_STAGE_DESCRIPTORS: Record<string, { label: string; financial: string; pressure: string }> = {
stage_a_series_b_growth_burning: {
label: 'Series B growth-stage, burning capital',
financial: 'recently raised Series B ~$30M; burning $1-1.5M/month; 12-18 months runway; ARR $10-20M',
pressure: 'investor pressure to demonstrate capital efficiency; competitive displacement risk; talent retention concerns',
},
stage_b_post_profitable_consolidation: {
label: 'Post-profitable, consolidation phase',
financial: 'reached profitability 2-4 quarters ago; $40-80M ARR; 15-25% operating margin; 24+ months runway',
pressure: 'Wall Street scrutiny on growth deceleration; M&A integration challenges; complacency risk',
},
};
export interface BuildCorpusInstancePromptInputs {
cell: StratificationCell;
instanceId: string;
/** Domain anchor — ensures all 50 instances stay in NorthLane CFO synthesis territory. */
domainAnchor?: string;
}
/** Build the Opus 4.7 generation prompt for one stratification cell. */
export function buildCorpusInstancePrompt(inputs: BuildCorpusInstancePromptInputs): string {
const { cell, instanceId } = inputs;
const familyDesc = TASK_FAMILY_DESCRIPTORS[cell.family];
const personaDesc = PERSONA_DESCRIPTORS[cell.persona];
const stageDesc = COMPANY_STAGE_DESCRIPTORS[cell.stage];
return `You are generating one synthesis-task instance for a benchmark corpus.
The corpus targets B2B SaaS knowledge work in the spirit of "NorthLane CFO" pilot tasks: a complex business situation requiring multi-document synthesis to produce a structured business deliverable (memo, action plan, decision recommendation).
## Stratification cell for this instance
- Instance ID: \`${instanceId}\`
- Task family: \`${cell.family}\`${familyDesc.label}
- Task family description: ${familyDesc.label.replace(/_/g, ' ')}; mirror pattern: ${familyDesc.mirrorPilotTask ?? 'NEW family (no pilot mirror)'}
- Persona: \`${cell.persona}\`${personaDesc.label} (${personaDesc.roleContext})
- Company stage: \`${cell.stage}\`${stageDesc.label}
- Financial state: ${stageDesc.financial}
- Pressure dynamics: ${stageDesc.pressure}
## Required output (JSON object only, no prose, no code fences)
\`\`\`json
{
"personaText": "Persona: <2-3 sentence persona description grounding the model in role + company>\\n\\nScenario: <2-4 sentence scenario hook setting up why this synthesis is needed now>",
"sourceDocuments": [
{ "title": "DOC 1 — <Doc name>", "body": "<markdown content, 600-1200 chars>" },
{ "title": "DOC 2 — <Doc name>", "body": "<...>" },
...
(exactly ${familyDesc.docsPerInstance} docs total)
],
"question": "<open-ended question, 100-400 chars, that requires synthesizing across multiple docs to answer>"
}
\`\`\`
## Generation constraints (binding)
1. **Domain anchor:** All ${familyDesc.docsPerInstance} source documents must be plausible artifacts a ${personaDesc.label} would receive in a B2B SaaS company at the ${stageDesc.label} phase.
2. **Document diversity:** Each doc should be a different artifact type (P&L summary, pipeline review, churn analysis, eng velocity report, marketing dashboard, board notes, competitor intel, customer interview notes, OKR report, runway model, etc.). No two docs should be the same artifact type.
3. **Synthesis requirement:** The question must be answerable ONLY by triangulating across multiple docs. A single-doc answer should be insufficient. Specifically, the question should require:
- Identifying tradeoffs across docs
- Synthesizing competing positions
- Producing a structured deliverable (memo, action plan, or recommendation with justification)
4. **Persona consistency:** The persona's role + company-stage pressures must be evident in BOTH the scenario framing AND the question phrasing. A founder-CEO question reads differently than an independent-director question.
5. **Doc body realism:** Use realistic numbers (revenue figures, percentages, dates). Cite specific people by role title (e.g., "VP Sales", not "John Smith"). Include both hard data + qualitative commentary in each doc.
6. **JSON format:** Output ONLY a single valid JSON object. No prose before or after. No code fence markers. The first character must be \`{\` and the last character must be \`}\`.
## Output begins below
`;
}

View File

@@ -0,0 +1,311 @@
/**
* GEPA Faza 1 — H3 corpus data definitions, stratification, validation,
* and spot-audit sampler.
*
* Per Amendment 1 Ask A Option C + manifest v7 §corpus_design:
* - 50 instances total
* - 5 task families × 5 personas × 2 company stages = 50 cells
* - NorthLane CFO synthesis domain (preserves Phase 4.3 anchor)
* - Each instance: ≥6 source documents, 6-dim Likert rubric
* - Stratified sampling: deterministic, seed=42
* - Generated via Opus 4.7 oracle
*
* Pre-A halt-and-PM checkpoint: spot-audit 5 random instances.
*/
import * as crypto from 'node:crypto';
/** 5 task families per manifest v7 §corpus_design.task_families. */
export type TaskFamily = 'F1' | 'F2' | 'F3' | 'F4' | 'F5';
/** 5 persona codes per manifest v7 §corpus_design.persona_axis. */
export type PersonaCode = 'p1_founder_ceo' | 'p2_cfo' | 'p3_coo' | 'p4_vp_finance' | 'p5_independent_director';
/** 2 company stages per manifest v7 §corpus_design.company_stage_axis. */
export type CompanyStage = 'stage_a_series_b_growth_burning' | 'stage_b_post_profitable_consolidation';
/** All 5 task families in canonical order. */
export const TASK_FAMILIES: ReadonlyArray<TaskFamily> = ['F1', 'F2', 'F3', 'F4', 'F5'];
/** All 5 personas in canonical order. */
export const PERSONAS: ReadonlyArray<PersonaCode> = [
'p1_founder_ceo',
'p2_cfo',
'p3_coo',
'p4_vp_finance',
'p5_independent_director',
];
/** All 2 company stages in canonical order. */
export const COMPANY_STAGES: ReadonlyArray<CompanyStage> = [
'stage_a_series_b_growth_burning',
'stage_b_post_profitable_consolidation',
];
/** Total instances per manifest v7 §corpus_design.total_instances. */
export const TOTAL_INSTANCES = 50;
/** Required source document count floor per manifest v7. */
export const DOCS_PER_INSTANCE_MIN = 6;
/** Required source document count ceiling per manifest v7. */
export const DOCS_PER_INSTANCE_MAX = 8;
/** Spot-audit sample size per manifest v7 §corpus_design.spot_audit. */
export const SPOT_AUDIT_SAMPLE_SIZE = 5;
/** Deterministic stratification seed per manifest v7 §corpus_design.stratified_sampling.seed. */
export const STRATIFICATION_SEED = 42;
/** Per-instance human-readable task family descriptor. */
export const TASK_FAMILY_DESCRIPTORS: Record<TaskFamily, {
label: string;
mirrorPilotTask: string | null;
docsPerInstance: number;
promptTemplate: string;
}> = {
F1: {
label: 'strategic_synthesis',
mirrorPilotTask: 'task-1',
docsPerInstance: 7,
promptTemplate: 'Identify the 3 most critical risks for {company} in {period} and propose action plan for each.',
},
F2: {
label: 'cross_thread_coordination',
mirrorPilotTask: 'task-2',
docsPerInstance: 6,
promptTemplate: 'Reconcile conflicting positions from {stakeholders} and propose unified approach.',
},
F3: {
label: 'decision_support',
mirrorPilotTask: 'task-3',
docsPerInstance: 6,
promptTemplate: 'Recommend {decision} based on materials; justify, address counter-arguments.',
},
F4: {
label: 'investor_communications',
mirrorPilotTask: null, // NEW family
docsPerInstance: 6,
promptTemplate: 'Draft Q{n} investor update covering {metrics} + addressing {concerns}.',
},
F5: {
label: 'scenario_planning',
mirrorPilotTask: null, // NEW family
docsPerInstance: 7,
promptTemplate: 'Compare {n} scenarios for {decision_area}; recommend hedging strategy.',
},
};
/** Stratification cell — one of 50 unique combinations. */
export interface StratificationCell {
family: TaskFamily;
persona: PersonaCode;
stage: CompanyStage;
}
/** A single source document inside a corpus instance. */
export interface SourceDoc {
title: string;
body: string;
charCount: number;
}
/** A single corpus instance ready for evaluation by NULL-baseline / GEPA candidates. */
export interface CorpusInstance {
/** Stable instance ID: h3-{family}-{persona}-{stage}-{ordinal}. */
instanceId: string;
cell: StratificationCell;
personaText: string;
scenario: string;
sourceDocuments: SourceDoc[];
question: string;
/** Aggregated materials block (joined source docs) — consumer convenience. */
materialsConcat: string;
manifestAnchor: string;
generatedBy: string;
generatedAtIso: string;
generationCostUsd: number;
}
// ───────────────────────────────────────────────────────────────────────────
// Stratification — deterministic enumeration of 50 cells
// ───────────────────────────────────────────────────────────────────────────
/**
* Enumerate all 50 stratification cells in canonical (family-major, persona-mid,
* stage-minor) order. Deterministic given the constant arrays above.
*
* Yield order: F1×p1×stage_a, F1×p1×stage_b, F1×p2×stage_a, ..., F5×p5×stage_b.
*/
export function* iterateStratificationCells(): Generator<StratificationCell> {
for (const family of TASK_FAMILIES) {
for (const persona of PERSONAS) {
for (const stage of COMPANY_STAGES) {
yield { family, persona, stage };
}
}
}
}
/** Materialize all 50 cells as an array. */
export function listStratificationCells(): StratificationCell[] {
return Array.from(iterateStratificationCells());
}
/** Build a stable instance ID from a cell + ordinal (1-based within cell). */
export function buildInstanceId(cell: StratificationCell, ordinal: number = 1): string {
return `h3-${cell.family}-${cell.persona}-${cell.stage}-${String(ordinal).padStart(3, '0')}`;
}
// ───────────────────────────────────────────────────────────────────────────
// Validation — check instance shape against manifest v7 quality floor
// ───────────────────────────────────────────────────────────────────────────
export interface InstanceValidationResult {
valid: boolean;
violations: string[];
}
/** Per manifest v7 §corpus_design.per_instance_quality_floor + spot_audit dimensions. */
export function validateInstance(instance: CorpusInstance): InstanceValidationResult {
const violations: string[] = [];
if (instance.sourceDocuments.length < DOCS_PER_INSTANCE_MIN) {
violations.push(`docs count ${instance.sourceDocuments.length} < ${DOCS_PER_INSTANCE_MIN} min`);
}
if (instance.sourceDocuments.length > DOCS_PER_INSTANCE_MAX) {
violations.push(`docs count ${instance.sourceDocuments.length} > ${DOCS_PER_INSTANCE_MAX} max`);
}
// Length bounds loosened post-probe (2026-04-28) — Opus generates naturally
// richer personas/scenarios than the hand-crafted pilot baseline. The intent
// of these bounds is to catch broken/empty output, not to police verbosity.
const personaLen = instance.personaText.length;
if (personaLen < 100 || personaLen > 1500) {
violations.push(`persona length ${personaLen} outside [100, 1500] range`);
}
// Scenario may be empty if oracle embedded it in personaText (handled by
// assembleInstance — extractScenarioFromPersonaText). Skip length floor in
// that case but still cap upper bound.
const scenarioLen = instance.scenario.length;
if (scenarioLen > 0 && (scenarioLen < 100 || scenarioLen > 1500)) {
violations.push(`scenario length ${scenarioLen} outside [100, 1500] range (or 0 if embedded in personaText)`);
}
const questionLen = instance.question.length;
if (questionLen < 100 || questionLen > 800) {
violations.push(`question length ${questionLen} outside [100, 800] range`);
}
for (const doc of instance.sourceDocuments) {
if (doc.body.length < 400 || doc.body.length > 3000) {
violations.push(`doc "${doc.title}" body length ${doc.body.length} outside [400, 3000] range`);
}
if (doc.body.length !== doc.charCount) {
violations.push(`doc "${doc.title}" charCount ${doc.charCount} != actual body length ${doc.body.length}`);
}
}
if (!instance.instanceId.startsWith('h3-')) {
violations.push(`instanceId "${instance.instanceId}" missing h3- prefix`);
}
return {
valid: violations.length === 0,
violations,
};
}
// ───────────────────────────────────────────────────────────────────────────
// Spot-audit sampler — deterministic random selection of N instances
// ───────────────────────────────────────────────────────────────────────────
/**
* Mulberry32 PRNG — deterministic, fast, 32-bit. Same seed produces same
* output across runs and platforms.
*/
function mulberry32(seed: number): () => number {
let t = seed >>> 0;
return function () {
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;
};
}
/**
* Deterministic Fisher-Yates shuffle producing a sample of `sampleSize` from
* the input array, seeded by `seed`. Reproducible across runs.
*/
export function deterministicSample<T>(
items: ReadonlyArray<T>,
sampleSize: number,
seed: number = STRATIFICATION_SEED,
): T[] {
if (sampleSize >= items.length) {
return [...items];
}
const arr = [...items];
const rand = mulberry32(seed);
// Partial Fisher-Yates: only need first `sampleSize` swapped to front.
for (let i = 0; i < sampleSize; i++) {
const j = i + Math.floor(rand() * (arr.length - i));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr.slice(0, sampleSize);
}
/** Select 5 random instances per manifest v7 §corpus_design.spot_audit. */
export function selectSpotAuditSample(instances: ReadonlyArray<CorpusInstance>): CorpusInstance[] {
return deterministicSample(instances, SPOT_AUDIT_SAMPLE_SIZE);
}
// ───────────────────────────────────────────────────────────────────────────
// Aggregate spot-audit verdict
// ───────────────────────────────────────────────────────────────────────────
export interface SpotAuditReport {
sampleSize: number;
perInstance: Array<{ instanceId: string; result: InstanceValidationResult }>;
/** Per manifest v7 §corpus_design.spot_audit.halt_on: any 1 of 5 fails → corpus regeneration. */
haltOnFailure: boolean;
haltReason?: string;
}
/** Run validation across spot-audit sample + return aggregate verdict. */
export function runSpotAudit(instances: ReadonlyArray<CorpusInstance>): SpotAuditReport {
const sample = selectSpotAuditSample(instances);
const perInstance = sample.map(inst => ({
instanceId: inst.instanceId,
result: validateInstance(inst),
}));
const failed = perInstance.filter(p => !p.result.valid);
return {
sampleSize: sample.length,
perInstance,
haltOnFailure: failed.length > 0,
haltReason: failed.length > 0
? `${failed.length}/${sample.length} spot-audit instances failed validation: ${failed.map(f => f.instanceId).join(', ')}`
: undefined,
};
}
// ───────────────────────────────────────────────────────────────────────────
// Audit-chain helpers
// ───────────────────────────────────────────────────────────────────────────
/** Compute SHA-256 of a JSON-serialized corpus (for manifest v7 audit pinning). */
export function corpusSha256(instances: ReadonlyArray<CorpusInstance>): string {
const canonical = instances.map(inst => ({
instanceId: inst.instanceId,
cell: inst.cell,
materialsLength: inst.materialsConcat.length,
questionLength: inst.question.length,
docCount: inst.sourceDocuments.length,
}));
const json = JSON.stringify(canonical);
return crypto.createHash('sha256').update(json).digest('hex');
}

View File

@@ -0,0 +1,133 @@
/**
* GEPA Faza 1 — cost governance + super-linear projection tracker.
*
* Per launch decision §A.7 + manifest v7 §cost_governance.
*
* Halt triggers (any single triggers immediate halt + PM ratify):
* - Cumulative spend > $80 (internal halt)
* - Cumulative spend > $100 (hard cap)
* - Mid-run actual exceeds projection by >30% (super-linear sub-rule per brief §6.7)
*
* Audit cadence: every 20 evaluations.
*
* Cost projection methodology: 1.5× baseline token count per candidate
* (encodes mutation overhead since GEPA candidates may grow prompts).
*/
/** Hard cap (immediate halt on breach). */
export const HARD_CAP_USD = 100.00;
/** Internal halt threshold (halt + PM ratify before proceeding). */
export const INTERNAL_HALT_USD = 80.00;
/** Super-linear projection multiplier per brief §6.7. */
export const SUPER_LINEAR_MULTIPLIER = 1.5;
/** Mid-run halt threshold: actual exceeds projection by this fraction. */
export const SUPER_LINEAR_OVERAGE_THRESHOLD = 0.30;
/** Audit cadence (every N evaluations). */
export const AUDIT_CADENCE_EVAL_COUNT = 20;
export type HaltReason =
| 'NONE'
| 'INTERNAL_HALT_USD_BREACH'
| 'HARD_CAP_USD_BREACH'
| 'SUPER_LINEAR_PROJECTION_BREACH';
export interface CostTrackerState {
cumulativeUsd: number;
evaluationCount: number;
/** Per-evaluation projection used for super-linear check (1.5× baseline median). */
projectionPerEvalUsd: number;
}
export interface HaltCheckResult {
haltReason: HaltReason;
cumulativeUsd: number;
expectedAtThisCount: number;
overageFraction: number;
message: string;
}
/** Create a new cost tracker state with the per-evaluation projection. */
export function createCostTracker(baselineMedianCostPerEvalUsd: number): CostTrackerState {
return {
cumulativeUsd: 0,
evaluationCount: 0,
projectionPerEvalUsd: baselineMedianCostPerEvalUsd * SUPER_LINEAR_MULTIPLIER,
};
}
/**
* Record an evaluation cost and return updated state (immutable update).
* Per coding-style.md: never mutate, always return new copy.
*/
export function recordEvaluation(state: CostTrackerState, evalCostUsd: number): CostTrackerState {
return {
...state,
cumulativeUsd: state.cumulativeUsd + evalCostUsd,
evaluationCount: state.evaluationCount + 1,
};
}
/**
* Check halt triggers against current state.
*
* Returns NONE if all checks pass; otherwise returns the first triggered
* halt reason with diagnostic context.
*
* Order of precedence (most severe first):
* 1. HARD_CAP_USD_BREACH ($100 breach)
* 2. INTERNAL_HALT_USD_BREACH ($80 breach)
* 3. SUPER_LINEAR_PROJECTION_BREACH (actual > 1.30 × expected at current eval count)
*/
export function checkHaltTriggers(state: CostTrackerState): HaltCheckResult {
const expectedAtThisCount = state.projectionPerEvalUsd * state.evaluationCount;
const overageFraction =
expectedAtThisCount > 0 ? (state.cumulativeUsd - expectedAtThisCount) / expectedAtThisCount : 0;
if (state.cumulativeUsd > HARD_CAP_USD) {
return {
haltReason: 'HARD_CAP_USD_BREACH',
cumulativeUsd: state.cumulativeUsd,
expectedAtThisCount,
overageFraction,
message: `HARD CAP BREACH: $${state.cumulativeUsd.toFixed(2)} > $${HARD_CAP_USD} cap`,
};
}
if (state.cumulativeUsd > INTERNAL_HALT_USD) {
return {
haltReason: 'INTERNAL_HALT_USD_BREACH',
cumulativeUsd: state.cumulativeUsd,
expectedAtThisCount,
overageFraction,
message: `INTERNAL HALT: $${state.cumulativeUsd.toFixed(2)} > $${INTERNAL_HALT_USD} internal halt — PM ratify before proceeding`,
};
}
// Super-linear check requires at least 1 eval to have meaningful expected value.
if (state.evaluationCount > 0 && overageFraction > SUPER_LINEAR_OVERAGE_THRESHOLD) {
return {
haltReason: 'SUPER_LINEAR_PROJECTION_BREACH',
cumulativeUsd: state.cumulativeUsd,
expectedAtThisCount,
overageFraction,
message: `SUPER-LINEAR BREACH: actual $${state.cumulativeUsd.toFixed(2)} exceeds expected $${expectedAtThisCount.toFixed(2)} by ${(overageFraction * 100).toFixed(1)}% (>${(SUPER_LINEAR_OVERAGE_THRESHOLD * 100).toFixed(0)}% threshold)`,
};
}
return {
haltReason: 'NONE',
cumulativeUsd: state.cumulativeUsd,
expectedAtThisCount,
overageFraction,
message: `OK: $${state.cumulativeUsd.toFixed(2)} cumulative; expected $${expectedAtThisCount.toFixed(2)}; ${(overageFraction * 100).toFixed(1)}% overage`,
};
}
/** Whether the current eval count is on an audit cadence boundary. */
export function shouldAudit(state: CostTrackerState): boolean {
return state.evaluationCount > 0 && state.evaluationCount % AUDIT_CADENCE_EVAL_COUNT === 0;
}

View File

@@ -0,0 +1,298 @@
/**
* GEPA Faza 1 — per-shape fitness function.
*
* Per Amendment 2 §3 (manifest v7 §metric_operationalization.per_shape_fitness_formula
* + §metric_operationalization.retrieval_engagement_bonus).
*
* Phase 4.5 empirical signal: Qwen retrieves 1.33×/task vs Opus 2.33×/task.
* H4 score gap mechanistically traces to under-engagement. Mutation surface
* (prompt-shape body) is the lever to address this on Qwen-targeted shapes.
*
* Fitness function forks by shape class:
* Qwen-targeted: fitness = trio_strict_pass_rate + retrieval_engagement_bonus cost_penalty
* Non-Qwen: fitness = trio_strict_pass_rate cost_penalty
*
* Retrieval engagement bonus bands (Qwen-targeted shapes only):
* +0.05 if mean retrieval_calls per task ≥ 2.0 (Opus parity proxy)
* 0.00 if mean retrieval_calls per task ∈ [1.5, 2.0)
* 0.05 if mean retrieval_calls per task < 1.5 (Qwen baseline penalty)
*/
import {
type FitnessInputs,
type FitnessComponents,
type ShapeName,
type TieredFitnessInputs,
type TieredFitnessComponents,
type DeltaFloorInputs,
type DeltaFloorVerdict,
QWEN_TARGETED_SHAPES,
} from './types.js';
/** Cost penalty coefficient per brief §3.1 — 0.5pp per $0.10 above baseline median. */
const COST_PENALTY_PP_PER_DOLLAR_10C = 0.5;
/** Retrieval engagement thresholds per Amendment 2 §3 bands. */
export const RETRIEVAL_ENGAGEMENT_BANDS = {
/** ≥ this → +0.05 bonus (Opus parity proxy). */
upperThreshold: 2.0,
/** ≥ this and < upper → 0.00. */
lowerThreshold: 1.5,
/** < lower → 0.05 (Qwen baseline penalty). */
bonusPlus: 0.05,
bonusZero: 0.0,
bonusMinus: -0.05,
} as const;
/**
* Compute the retrieval engagement bonus for a Qwen-targeted shape.
*
* Returns 0.0 for non-Qwen shapes (callers should branch on shape class
* before calling this if they need to distinguish; alternatively use
* `computeFitness` which handles routing).
*
* Boundary semantics (binding per Amendment 2 §8 test cases):
* 1.49 → 0.05
* 1.50 → 0.00 (exact lower threshold = zero band)
* 1.99 → 0.00
* 2.00 → +0.05 (exact upper threshold = bonus band)
* 2.50 → +0.05
*/
export function computeRetrievalEngagementBonus(
shape: ShapeName,
meanRetrievalCallsPerTask: number,
): number {
if (!QWEN_TARGETED_SHAPES.has(shape)) {
return 0.0;
}
if (meanRetrievalCallsPerTask >= RETRIEVAL_ENGAGEMENT_BANDS.upperThreshold) {
return RETRIEVAL_ENGAGEMENT_BANDS.bonusPlus;
}
if (meanRetrievalCallsPerTask >= RETRIEVAL_ENGAGEMENT_BANDS.lowerThreshold) {
return RETRIEVAL_ENGAGEMENT_BANDS.bonusZero;
}
return RETRIEVAL_ENGAGEMENT_BANDS.bonusMinus;
}
/**
* Compute the cost penalty per brief §3.1.
*
* If the candidate's mean cost is at or below the per-shape baseline median,
* penalty is 0. Otherwise, penalty = 0.5pp per $0.10 of overage, encoded as
* a positive decimal (caller subtracts from fitness).
*/
export function computeCostPenalty(
candidateMeanCostUsd: number,
baselineMedianCostUsd: number,
): number {
const overageUsd = candidateMeanCostUsd - baselineMedianCostUsd;
if (overageUsd <= 0) {
return 0.0;
}
// 0.5 pp per $0.10 → 5 pp per $1.00 → encoded as 0.05 per $1.00 → 0.005 per $0.10
const penaltyDecimal = (overageUsd / 0.10) * (COST_PENALTY_PP_PER_DOLLAR_10C / 100);
return penaltyDecimal;
}
/**
* Compute the per-shape fitness for a candidate.
*
* Routes by shape class:
* - Qwen-targeted (qwen-thinking, qwen-non-thinking): includes retrieval engagement bonus
* - Non-Qwen (claude, gpt, generic-simple): no retrieval engagement weighting
*
* Returns full FitnessComponents for downstream audit + reporting per
* launch decision §A.9 binding compliance requirement.
*/
export function computeFitness(inputs: FitnessInputs): FitnessComponents {
const { candidate, baselineMedianCostUsd } = inputs;
const trioStrictPassRateII = candidate.trioStrictPassRateII;
const retrievalEngagementApplied = QWEN_TARGETED_SHAPES.has(candidate.shape);
const retrievalEngagementBonus = retrievalEngagementApplied
? computeRetrievalEngagementBonus(candidate.shape, candidate.meanRetrievalCallsPerTask)
: 0.0;
const costPenalty = computeCostPenalty(candidate.meanCostUsd, baselineMedianCostUsd);
const fitness = trioStrictPassRateII + retrievalEngagementBonus - costPenalty;
return {
trioStrictPassRateII,
retrievalEngagementBonus,
costPenalty,
fitness,
retrievalEngagementApplied,
};
}
// ── Amendment 7 — tiered fitness function ─────────────────────────────────
/** Tier 2 cap per Amendment 7 §fitness_function_tiered.tier_2 (max bonus). */
export const TIER_2_BONUS_CAP = 0.25;
/** Tier 2 weight per percentage point of retrieval engagement above NULL baseline. */
export const TIER_2_BONUS_PER_PP = 0.05;
/** Tier 3 binary bonus when all 7 cell-semantic anchors invariant. */
export const TIER_3_BONUS_FULL_INVARIANCE = 0.10;
/** Total cell-semantic anchor count (types.ts + MULTI_STEP_ACTION_CONTRACT + 5 baseline shapes). */
export const TIER_3_ANCHOR_COUNT_FULL = 7;
/**
* Compute Tier 2 retrieval engagement bonus per Amendment 7 §fitness_function_tiered.tier_2.
*
* Continuous formula (supersedes Amendment 2 band bonus for tiered ranking):
* bonus = clamp(0.05 × delta_pp, 0, 0.25)
* where delta_pp = (candidate_mean - baseline_mean) × 100
*
* Cap reached at +5pp absolute increase in mean retrieval calls per task.
* Floor 0 (no negative bonus from Tier 2 — negative-band penalty handled
* by Amendment 2 §F.5 false-positive guard separately).
*
* Always returns 0 for non-Qwen shapes.
*/
export function computeTier2RetrievalBonus(
shape: ShapeName,
candidateMeanRetrievalCallsPerTask: number,
baselineMeanRetrievalCallsPerTask: number,
): number {
if (!QWEN_TARGETED_SHAPES.has(shape)) return 0;
const deltaAbsolute = candidateMeanRetrievalCallsPerTask - baselineMeanRetrievalCallsPerTask;
if (deltaAbsolute <= 0) return 0;
const deltaPP = deltaAbsolute * 100;
return Math.min(TIER_2_BONUS_PER_PP * deltaPP, TIER_2_BONUS_CAP);
}
/**
* Compute the per-shape tiered fitness per Amendment 7 §fitness_function_tiered.
*
* Routes tier roles by saturation:
* - In saturated regime (≥4/5 shapes have NULL pass rate ≥75%):
* PRIMARY differentiator = Tier 2 (Qwen-targeted retrieval engagement)
* SECONDARY differentiator = Tier 3 (cell-semantic anchor invariance)
* TIE-BREAKER = Tier 1 (NULL pass rate delta)
* aggregateSaturatedRegime = tier_2 + tier_3
* - Outside saturated regime: legacy Amendment 2 form (computeFitness) applies
* and aggregateSaturatedRegime is reported but not the canonical fitness.
*
* Tier 1 also serves as the §F.1 acceptance gate (≥+5pp) — UNCHANGED from
* Amendment 5 launch decision §F.1.
*/
export function computeTieredFitness(inputs: TieredFitnessInputs): TieredFitnessComponents {
const {
candidate,
nullBaselinePassRateII,
nullBaselineMeanRetrievalCallsPerTask,
mutationValidatorPassed,
saturatedRegime,
} = inputs;
// Tier 1 — NULL pass rate delta (signed pp)
const tier1DeltaPP = (candidate.trioStrictPassRateII - nullBaselinePassRateII) * 100;
// Tier 2 — continuous retrieval engagement bonus (Qwen-targeted only)
const tier2RetrievalBonus = computeTier2RetrievalBonus(
candidate.shape,
candidate.meanRetrievalCallsPerTask,
nullBaselineMeanRetrievalCallsPerTask,
);
// Tier 3 — binary cell-semantic anchor invariance bonus
const tier3CellSemanticInvarianceBonus = mutationValidatorPassed
? TIER_3_BONUS_FULL_INVARIANCE
: 0;
// For Gen 1 candidates the mutation_validator gives binary (valid|invalid);
// count semantic: pass = full 7-anchor invariance, fail = 0.
const cellSemanticAnchorInvarianceCount = mutationValidatorPassed ? TIER_3_ANCHOR_COUNT_FULL : 0;
// Aggregate fitness in saturated regime: tier_2 + tier_3 only
const aggregateSaturatedRegime = tier2RetrievalBonus + tier3CellSemanticInvarianceBonus;
return {
tier1DeltaPP,
tier2RetrievalBonus,
tier3CellSemanticInvarianceBonus,
aggregateSaturatedRegime,
saturatedRegimeApplied: saturatedRegime,
cellSemanticAnchorInvarianceCount,
};
}
// ── Amendment 7 — pre-registered Δ-floor verdict ──────────────────────────
/** Δ-floor thresholds per Amendment 7 §gen_1_pre_registered_delta_floor. */
export const DELTA_FLOOR_THRESHOLDS = {
/** Threshold 1: aggregate Tier 1 delta in pp (≥+3pp passes). */
threshold1AggregateTier1PP: 3,
/** Threshold 2: max Qwen retrieval engagement delta absolute (≥+0.10 passes). */
threshold2QwenRetrievalAbsolute: 0.10,
/** Threshold 3a: aggregate Tier 1 delta in pp (≥0pp). */
threshold3Tier1MinPP: 0,
/** Threshold 3b: aggregate Tier 2 bonus across Qwen-targeted candidates (≥0.05). */
threshold3Tier2MinBonus: 0.05,
} as const;
/** Float tolerance for exact-boundary threshold comparisons (consistent with acceptance.ts EPSILON). */
const DELTA_FLOOR_EPSILON = 1e-9;
/**
* Compute the Δ-floor verdict per Amendment 7 §gen_1_pre_registered_delta_floor.
*
* Three OR-gated thresholds:
* 1. aggregate Tier 1 delta ≥ +3pp absolute (loosened from §F.1 ≥+5pp)
* 2. max Qwen-shape retrieval engagement delta ≥ +0.10 absolute above per-shape NULL baseline
* 3. (aggregate Tier 1 delta ≥ 0pp) AND (aggregate Tier 2 bonus ≥ 0.05)
*
* If ANY ONE passes → PROCEED (continue past Checkpoint B subject to PM ratify).
* If ALL THREE fail → HALT_INVESTIGATE (file Investigate report).
*
* Per Amendment 7 §3.2: "If Gen 1 fails ALL three thresholds → HALT before Gen 2,
* file Investigate report. If Gen 1 passes any one → proceed to Gen 2."
*/
export function computeDeltaFloorVerdict(inputs: DeltaFloorInputs): DeltaFloorVerdict {
// Threshold 1 — aggregate Tier 1 delta
const threshold1ValuePP =
(inputs.aggregateTrioStrictPassRateII - inputs.aggregateNullBaselinePassRateII) * 100;
const threshold1Pass =
threshold1ValuePP >= DELTA_FLOOR_THRESHOLDS.threshold1AggregateTier1PP - DELTA_FLOOR_EPSILON;
// Threshold 2 — max Qwen retrieval engagement delta absolute
let threshold2MaxDeltaAbsolute: number | null = null;
for (const [shapeKey, candidateMean] of Object.entries(inputs.qwenShapeRetrievalMeans)) {
if (candidateMean === undefined) continue;
const baselineMean = inputs.qwenShapeNullBaselineRetrievalMeans[shapeKey as ShapeName];
if (baselineMean === undefined) continue;
const delta = candidateMean - baselineMean;
if (threshold2MaxDeltaAbsolute === null || delta > threshold2MaxDeltaAbsolute) {
threshold2MaxDeltaAbsolute = delta;
}
}
// No Qwen data: report 0 delta (informative neutral); threshold2 fails since 0 < 0.10
if (threshold2MaxDeltaAbsolute === null) threshold2MaxDeltaAbsolute = 0;
const threshold2Pass =
threshold2MaxDeltaAbsolute >=
DELTA_FLOOR_THRESHOLDS.threshold2QwenRetrievalAbsolute - DELTA_FLOOR_EPSILON;
// Threshold 3 — compound Tier 1 ≥0 AND Tier 2 ≥0.05
const threshold3Tier1ValuePP = threshold1ValuePP;
const threshold3Tier1Pass =
threshold3Tier1ValuePP >= DELTA_FLOOR_THRESHOLDS.threshold3Tier1MinPP - DELTA_FLOOR_EPSILON;
const threshold3Tier2Pass =
inputs.qwenAggregateTier2Bonus >=
DELTA_FLOOR_THRESHOLDS.threshold3Tier2MinBonus - DELTA_FLOOR_EPSILON;
const threshold3Pass = threshold3Tier1Pass && threshold3Tier2Pass;
const overallVerdict: 'PROCEED' | 'HALT_INVESTIGATE' =
threshold1Pass || threshold2Pass || threshold3Pass ? 'PROCEED' : 'HALT_INVESTIGATE';
return {
threshold1AggregateTier1: threshold1Pass ? 'PASS' : 'FAIL',
threshold1ValuePP,
threshold2QwenRetrievalAbsolute: threshold2Pass ? 'PASS' : 'FAIL',
threshold2MaxDeltaAbsolute,
threshold3CompoundTier1PlusTier2: threshold3Pass ? 'PASS' : 'FAIL',
threshold3Tier1ValuePP,
threshold3Tier2Aggregate: inputs.qwenAggregateTier2Bonus,
overallVerdict,
};
}

View File

@@ -0,0 +1,24 @@
/**
* GEPA Faza 1 — public API surface.
*
* Per launch decision §G + manifest v7 §gepa.
*
* Module map:
* - types shared types + shape-class partitions
* - fitness per-shape fitness function (Amendment 2 §3 fork)
* - acceptance §F + §F.5 verdict per candidate
* - mutation-validator cell-semantic preservation audit (boundary SHAs)
* - kappa-audit drift band detection vs canonical 0.7878 ± 0.05
* - cost-tracker super-linear governance + halt triggers
* - selection top-1-per-shape + run-aggregate verdict
* - mutation-oracle-fork Qwen vs non-Qwen template routing (Amendment 2 §4)
*/
export * from './types.js';
export * from './fitness.js';
export * from './acceptance.js';
export * from './mutation-validator.js';
export * from './kappa-audit.js';
export * from './cost-tracker.js';
export * from './selection.js';
export * from './mutation-oracle-fork.js';

View File

@@ -0,0 +1,141 @@
/**
* GEPA Faza 1 — κ drift audit utility.
*
* Per launch decision §F.3 + manifest v7 §canonical_kappa_anchor +
* §faza_1_acceptance.condition_3.
*
* Canonical κ value: 0.7877758913412564 (rounds to 0.7878).
* Source: benchmarks/calibration/v6-kappa-recal/_summary-v6-kappa.json
* (SHA 657d4490bab28d35cf8a9c3ccea8a6b79e92835d700155184e51f3900836684c)
* Drift threshold: ±0.05 (drift band [0.7378, 0.8378])
*
* Per κ_recalibration_v6 success_criteria + monitoring_runtime in manifest v6 §5.4:
* - PASS: κ_trio ≥ 0.70
* - BORDERLINE: κ_trio ∈ [0.60, 0.70]
* - FAIL: κ_trio < 0.60
*
* Faza 1-specific drift acceptance (tighter than v6 baseline policy):
* - PASS: κ_trio ∈ [0.7378, 0.8378] (canonical ±0.05)
* - DRIFT_LOW: κ_trio < 0.7378
* - DRIFT_HIGH: κ_trio > 0.8378 (also a fail signal — judge ensemble drifted upward)
*/
/** Canonical κ baseline anchor pinned per manifest v7. */
export const CANONICAL_KAPPA = 0.7877758913412564;
/** Drift threshold (per brief §4 condition 3). */
export const KAPPA_DRIFT_THRESHOLD = 0.05;
/** Drift band lower bound (canonical threshold). */
export const KAPPA_DRIFT_BAND_LOW = CANONICAL_KAPPA - KAPPA_DRIFT_THRESHOLD;
/** Drift band upper bound (canonical + threshold). */
export const KAPPA_DRIFT_BAND_HIGH = CANONICAL_KAPPA + KAPPA_DRIFT_THRESHOLD;
/** v6 policy floor for absolute κ pass (kept for cross-validation reporting). */
export const V6_KAPPA_POLICY_FLOOR_PASS = 0.70;
/** v6 borderline lower bound. */
export const V6_KAPPA_POLICY_FLOOR_BORDERLINE = 0.60;
export type KappaVerdict =
| 'PASS_WITHIN_DRIFT_BAND'
| 'DRIFT_LOW_BELOW_BAND'
| 'DRIFT_HIGH_ABOVE_BAND';
export interface KappaPairwise {
/** Cohen's κ between Opus and GPT verdict streams. */
kOpusGpt: number;
/** Cohen's κ between Opus and MiniMax verdict streams. */
kOpusMinimax: number;
/** Cohen's κ between GPT and MiniMax verdict streams. */
kGptMinimax: number;
}
export interface KappaAuditResult {
/** Conservative trio κ = min of three pairwise κ values (per manifest v6 §5.4). */
kConservativeTrio: number;
/** Pairwise components (carried through for audit log). */
pairwise: KappaPairwise;
/** Drift band verdict per Faza 1 §F.3 acceptance. */
verdict: KappaVerdict;
/** Absolute drift from canonical (positive = above, negative = below). */
driftFromCanonical: number;
/** Whether the κ_conservative_trio passes v6 policy floor (≥ 0.70). */
v6PolicyFloorPass: boolean;
/** Audit log line for inclusion in checkpoint reports. */
auditLogLine: string;
}
/** Compute κ audit verdict from three pairwise Cohen's κ values. */
export function auditKappa(pairwise: KappaPairwise): KappaAuditResult {
const kConservativeTrio = Math.min(
pairwise.kOpusGpt,
pairwise.kOpusMinimax,
pairwise.kGptMinimax,
);
const driftFromCanonical = kConservativeTrio - CANONICAL_KAPPA;
let verdict: KappaVerdict;
if (kConservativeTrio < KAPPA_DRIFT_BAND_LOW) {
verdict = 'DRIFT_LOW_BELOW_BAND';
} else if (kConservativeTrio > KAPPA_DRIFT_BAND_HIGH) {
verdict = 'DRIFT_HIGH_ABOVE_BAND';
} else {
verdict = 'PASS_WITHIN_DRIFT_BAND';
}
const v6PolicyFloorPass = kConservativeTrio >= V6_KAPPA_POLICY_FLOOR_PASS;
const auditLogLine = [
`κ_conservative_trio=${kConservativeTrio.toFixed(4)}`,
`canonical=${CANONICAL_KAPPA.toFixed(4)}`,
`drift=${driftFromCanonical >= 0 ? '+' : ''}${driftFromCanonical.toFixed(4)}`,
`verdict=${verdict}`,
`v6_policy_floor=${v6PolicyFloorPass ? 'PASS' : 'FAIL'}`,
].join(' | ');
return {
kConservativeTrio,
pairwise,
verdict,
driftFromCanonical,
v6PolicyFloorPass,
auditLogLine,
};
}
/**
* Compute Cohen's κ from a 2×2 confusion matrix.
*
* Convenience helper for callers that have raw verdict pair counts (the κ
* recalibration script computes these for Phase 1 baseline; tests and
* inline audits during Faza 1 reproduce the computation here).
*
* Returns NaN if total observations is zero (caller must handle).
*/
export function computeCohensKappa(confusion: {
bothCorrect: number;
bothIncorrect: number;
firstCorrectSecondIncorrect: number;
firstIncorrectSecondCorrect: number;
}): number {
const { bothCorrect, bothIncorrect, firstCorrectSecondIncorrect, firstIncorrectSecondCorrect } = confusion;
const total = bothCorrect + bothIncorrect + firstCorrectSecondIncorrect + firstIncorrectSecondCorrect;
if (total === 0) return NaN;
const observedAgreement = (bothCorrect + bothIncorrect) / total;
// Marginal probabilities for "correct" verdict per rater.
const firstCorrectMarginal = (bothCorrect + firstCorrectSecondIncorrect) / total;
const secondCorrectMarginal = (bothCorrect + firstIncorrectSecondCorrect) / total;
const expectedAgreement =
firstCorrectMarginal * secondCorrectMarginal +
(1 - firstCorrectMarginal) * (1 - secondCorrectMarginal);
if (expectedAgreement === 1) return 1.0; // perfect base rate, no variance → return κ=1 by convention
return (observedAgreement - expectedAgreement) / (1 - expectedAgreement);
}

View File

@@ -0,0 +1,85 @@
/**
* GEPA Faza 1 — mutation oracle fork (Qwen vs non-Qwen template routing).
*
* Per Amendment 2 §4 + manifest v7 §mutation_oracle_design.
*
* Phase 4.5 finding requires Qwen-specific scaffolding to address mechanistic
* under-engagement; uniform mutation guidance would not target the empirical
* gap. Forked oracle prompts ensure Qwen mutations explore the engagement-
* bonus reward landscape while non-Qwen mutations explore the broader scaffold
* space.
*
* This module is responsible only for:
* 1. Selecting the right template path per shape class
* 2. Loading template content from disk
* 3. Substituting baseline shape body + failure mode summary into template
*
* The actual LLM oracle call lives in the run orchestrator (out of scaffold scope).
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { type ShapeName, QWEN_TARGETED_SHAPES } from './types.js';
export type TemplateClass = 'qwen' | 'non-qwen';
/** Per-shape template class routing. */
export function classifyShape(shape: ShapeName): TemplateClass {
return QWEN_TARGETED_SHAPES.has(shape) ? 'qwen' : 'non-qwen';
}
/** Resolve template path for a given shape class. */
export function templatePathForShape(
shape: ShapeName,
oracleDir: string = path.resolve(__dirname, '../../oracle/faza-1'),
): string {
const cls = classifyShape(shape);
const filename =
cls === 'qwen'
? 'mutation-prompt-template-qwen.md'
: 'mutation-prompt-template-non-qwen.md';
return path.join(oracleDir, filename);
}
/** Load raw template content for a given shape. */
export function loadTemplate(
shape: ShapeName,
oracleDir?: string,
): string {
const templatePath = templatePathForShape(shape, oracleDir);
return fs.readFileSync(templatePath, 'utf-8');
}
/** Inputs for assembling the final oracle prompt. */
export interface OraclePromptInputs {
shape: ShapeName;
/** Baseline shape file content (the candidate to mutate). */
baselineShapeContent: string;
/**
* Failure mode summary from Phase 4.3 verdict (top-3 T2 failures for this shape).
* Per brief §3.3 mutation oracle prompt.
*/
failureModeSummary: string;
oracleDir?: string;
}
/**
* Build the complete oracle prompt by template substitution.
*
* Templates use these placeholders (must appear verbatim in template files):
* ###BASELINE_SHAPE_CONTENT###
* ###FAILURE_MODE_SUMMARY###
* ###SHAPE_NAME###
* ###TEMPLATE_CLASS###
*/
export function buildOraclePrompt(inputs: OraclePromptInputs): string {
const template = loadTemplate(inputs.shape, inputs.oracleDir);
const cls = classifyShape(inputs.shape);
return template
.replace(/###BASELINE_SHAPE_CONTENT###/g, inputs.baselineShapeContent)
.replace(/###FAILURE_MODE_SUMMARY###/g, inputs.failureModeSummary)
.replace(/###SHAPE_NAME###/g, inputs.shape)
.replace(/###TEMPLATE_CLASS###/g, cls);
}

View File

@@ -0,0 +1,194 @@
/**
* GEPA Faza 1 — mutation validator (cell-semantic preservation audit).
*
* Per launch decision §A.4 (binding) + manifest v7 §gepa.mutation_validator.
*
* Boundary anchor: MULTI_STEP_ACTION_CONTRACT constant in
* packages/agent/src/prompt-shapes/types.ts.
* - whole-file SHA: 1a9fa329e4b66ed9f0abe8bc22cbbf0124e0c879e1e78ec806d557cab25bc94d
* - constant body bytes SHA: 70a1701dfa126f8dc1df9c116f0a8469da005821ecadc59d9b8f348568e755ba (252 bytes)
*
* Any GEPA candidate that produces non-zero diff against either anchor → INVALID.
*
* Allowed diff targets (per manifest v7 §gepa.mutation_validator.valid_diff_targets):
* - shape_file.systemPrompt method body (string-building only)
* - shape_file.soloUserPrompt method body
* - shape_file.multiStepKickoffUserPrompt method body
* - shape_file.retrievalInjectionUserPrompt method body
* - shape_file.metadata.evidence_link (MUST update to point to GEPA Gen 1 results)
*
* Invalid diff targets (LOCKED):
* - types.ts (entire file)
* - selector.ts (entire file)
* - index.ts (entire file)
* - shape_file.metadata.{description,modelClass,defaultThinking,defaultMaxTokens}
* - shape_file.imports
* - MULTI_STEP_ACTION_CONTRACT bytes
*/
import * as crypto from 'node:crypto';
import * as fs from 'node:fs';
/**
* Cell-semantic boundary SHAs pinned at substrate anchor commit c9bda3d.
* These are the byte-level invariants that any GEPA candidate must preserve.
*/
export const BOUNDARY_SHAS = {
/** Whole types.ts file SHA (must equal this exactly post-mutation). */
typesFile: '1a9fa329e4b66ed9f0abe8bc22cbbf0124e0c879e1e78ec806d557cab25bc94d',
/** MULTI_STEP_ACTION_CONTRACT constant body bytes SHA (252 bytes). */
multiStepActionContract: '70a1701dfa126f8dc1df9c116f0a8469da005821ecadc59d9b8f348568e755ba',
} as const;
/** Per-shape baseline SHAs pinned at substrate anchor commit c9bda3d. */
export const BASELINE_SHAPE_SHAS: Readonly<Record<string, string>> = {
'claude.ts': 'cbaf0c37b067b025a1fe97f2feeec11fae4070a8b3fcfaad1da8775dda451cc0',
'qwen-thinking.ts': '848a4e4917baa5c7bbcc3bb35fb8cb4b4ac8f0ab537243f14cbef3a99197aacb',
'qwen-non-thinking.ts': '35be379be9a8caafc2c419e32da5f63f92fc83f6f6d70d9df76029c1e8584572',
'gpt.ts': '5dc6d750d52a68feb9d37ad8384b2bcd59d70962066122ff086b0e5888413576',
'generic-simple.ts': '81189817f560e26a69394248d8bd9089cae72c7d40825323e2b7407e36026172',
} as const;
/** Validator verdict for a single candidate. */
export interface ValidatorVerdict {
valid: boolean;
/** List of violations (empty if valid). Each violation is a structured reason. */
violations: ValidationViolation[];
/** SHA of the candidate shape file (computed by validator). */
candidateShapeFileSha: string;
/** SHA of the cell-semantic types.ts file at validation time. */
typesFileSha: string;
/** SHA of the MULTI_STEP_ACTION_CONTRACT bytes at validation time. */
multiStepActionContractSha: string;
}
export interface ValidationViolation {
category:
| 'types_file_modified'
| 'multi_step_action_contract_modified'
| 'shape_file_unchanged_from_baseline'
| 'shape_file_metadata_locked_field_modified'
| 'shape_file_imports_modified';
severity: 'invalid';
detail: string;
}
/** Compute SHA-256 of a string's UTF-8 bytes. */
export function sha256(bytes: string | Buffer): string {
const buf = typeof bytes === 'string' ? Buffer.from(bytes, 'utf-8') : bytes;
return crypto.createHash('sha256').update(buf).digest('hex');
}
/** Compute SHA-256 of a file's bytes. */
export function sha256File(filepath: string): string {
return sha256(fs.readFileSync(filepath));
}
/**
* Extract the bytes of MULTI_STEP_ACTION_CONTRACT from a types.ts file content.
* Returns null if the constant is not found (which itself indicates a violation).
*/
export function extractMultiStepActionContractBytes(typesFileContent: string): string | null {
// Match the template literal body between backticks.
const match = typesFileContent.match(/export const MULTI_STEP_ACTION_CONTRACT = `([^`]+)`/);
return match ? match[1] : null;
}
/** Inputs for validating a GEPA candidate against cell-semantic boundaries. */
export interface ValidatorInputs {
/** Path to the candidate shape file (e.g., gepa-evolved/qwen-thinking-gen1-v0.ts). */
candidateShapeFilePath: string;
/** Shape file basename for baseline lookup (e.g., 'qwen-thinking.ts'). */
baselineShapeName: keyof typeof BASELINE_SHAPE_SHAS;
/** Path to the types.ts file (cell-semantic boundary anchor). */
typesFilePath: string;
/** Whether to require shape file to differ from baseline (true for Gen 1 mutations). */
expectShapeDiff: boolean;
}
/**
* Validate a GEPA candidate against the cell-semantic preservation invariants.
*
* Returns INVALID with structured violations if any boundary anchor is breached,
* or if a Gen 1 mutation produces zero diff vs baseline (violates the
* "every mutation must change something" implicit contract — Gen 0 baselines
* use expectShapeDiff=false).
*/
export function validateCandidate(inputs: ValidatorInputs): ValidatorVerdict {
const violations: ValidationViolation[] = [];
const typesFileSha = sha256File(inputs.typesFilePath);
const typesContent = fs.readFileSync(inputs.typesFilePath, 'utf-8');
const contractBytes = extractMultiStepActionContractBytes(typesContent);
const multiStepActionContractSha = contractBytes ? sha256(contractBytes) : '';
const candidateShapeFileSha = sha256File(inputs.candidateShapeFilePath);
if (typesFileSha !== BOUNDARY_SHAS.typesFile) {
violations.push({
category: 'types_file_modified',
severity: 'invalid',
detail: `types.ts SHA ${typesFileSha} != pinned baseline ${BOUNDARY_SHAS.typesFile}`,
});
}
if (multiStepActionContractSha !== BOUNDARY_SHAS.multiStepActionContract) {
violations.push({
category: 'multi_step_action_contract_modified',
severity: 'invalid',
detail:
contractBytes === null
? 'MULTI_STEP_ACTION_CONTRACT constant not found in types.ts'
: `MULTI_STEP_ACTION_CONTRACT bytes SHA ${multiStepActionContractSha} != pinned baseline ${BOUNDARY_SHAS.multiStepActionContract}`,
});
}
const baselineSha = BASELINE_SHAPE_SHAS[inputs.baselineShapeName];
if (inputs.expectShapeDiff && candidateShapeFileSha === baselineSha) {
violations.push({
category: 'shape_file_unchanged_from_baseline',
severity: 'invalid',
detail: `Gen 1 mutation expected to differ from baseline ${inputs.baselineShapeName} (SHA ${baselineSha}) but candidate produced identical bytes`,
});
}
// Optional shallow check: locked metadata fields must remain in the shape file.
// GEPA mutations are allowed to update evidence_link only.
const candidateContent = fs.readFileSync(inputs.candidateShapeFilePath, 'utf-8');
const lockedMetadataFields = [
'description:',
'modelClass:',
'defaultThinking:',
'defaultMaxTokens:',
];
for (const field of lockedMetadataFields) {
if (!candidateContent.includes(field)) {
violations.push({
category: 'shape_file_metadata_locked_field_modified',
severity: 'invalid',
detail: `Locked metadata field "${field}" missing from candidate shape file`,
});
}
}
// Check that imports block is still present (GEPA mutations cannot add/remove imports).
// Accept either './types.js' (same-dir candidate) or '../types.js' (gepa-evolved/ subdir
// candidate per manifest v7 §gepa.shape_scope.target_path) — the invariant is that
// candidates must import from types.js, not the specific relative path.
const importsTypes = candidateContent.includes("from './types.js'") ||
candidateContent.includes("from '../types.js'");
if (!importsTypes) {
violations.push({
category: 'shape_file_imports_modified',
severity: 'invalid',
detail: `Required import from "types.js" (either "./" or "../" relative) missing from candidate shape file`,
});
}
return {
valid: violations.length === 0,
violations,
candidateShapeFileSha,
typesFileSha,
multiStepActionContractSha,
};
}

View File

@@ -0,0 +1,128 @@
/**
* GEPA Faza 1 — top-1-per-shape selection.
*
* Per brief §2 selection metric ("best-per-shape-per-cell, ne aggregate") +
* launch decision §F + §F.5 acceptance.
*
* Selection algorithm:
* 1. For each shape, compute fitness for all candidates (per-shape fitness fork)
* 2. Per shape, select candidate with highest fitness as "best"
* 3. Apply acceptance verdict to each best-per-shape candidate
* 4. Report shape-level + run-aggregate verdict
*
* Run-aggregate §F conditions:
* §F.2: ≥3/5 shapes show positive trio_strict delta vs NULL-baseline
*
* Per-candidate §F.1 + §F.5 already evaluated by acceptance.evaluateCandidate.
*/
import { computeFitness } from './fitness.js';
import { evaluateCandidate } from './acceptance.js';
import {
type CandidateMetrics,
type FitnessComponents,
type AcceptanceVerdict,
type ShapeName,
} from './types.js';
/** Per-shape selection result. */
export interface ShapeSelectionResult {
shape: ShapeName;
bestCandidate: CandidateMetrics;
bestFitness: FitnessComponents;
acceptance: AcceptanceVerdict;
/** All candidates evaluated for this shape (for audit log). */
allCandidatesRanked: Array<{ candidate: CandidateMetrics; fitness: FitnessComponents }>;
}
/** Aggregate run verdict per launch decision §F.2. */
export interface RunAggregateVerdict {
/** Number of shapes with positive trio_strict delta vs NULL-baseline. */
shapesWithPositiveDelta: number;
/** Total shapes evaluated. */
totalShapes: number;
/** §F.2 condition: ≥3/5 shapes show positive delta. */
condition2Pass: boolean;
/** Number of shapes where best candidate was ACCEPTED (passes §F.1 + not §F.5). */
shapesAccepted: number;
}
/** Full selection report — per shape + run aggregate. */
export interface SelectionReport {
perShape: ShapeSelectionResult[];
runAggregate: RunAggregateVerdict;
}
/** Inputs: per-shape candidates + per-shape NULL-baseline metrics + cost baselines. */
export interface SelectionInputs {
/** All candidates grouped by shape. */
candidatesPerShape: Map<ShapeName, CandidateMetrics[]>;
/** NULL-baseline trio_strict_pass_rate (op. ii) per shape. */
baselineTrioStrictPassRateII: Map<ShapeName, number>;
/** NULL-baseline median cost (USD per evaluation) per shape. */
baselineMedianCostUsd: Map<ShapeName, number>;
}
/**
* Run top-1-per-shape selection + apply acceptance verdicts.
*
* Returns a complete selection report including per-shape results and the
* run-aggregate verdict.
*
* Throws if a shape in candidatesPerShape lacks a corresponding baseline entry.
*/
export function runSelection(inputs: SelectionInputs): SelectionReport {
const perShape: ShapeSelectionResult[] = [];
for (const [shape, candidates] of inputs.candidatesPerShape.entries()) {
if (candidates.length === 0) {
continue; // skip shapes with no candidates
}
const baselineRate = inputs.baselineTrioStrictPassRateII.get(shape);
if (baselineRate === undefined) {
throw new Error(`runSelection: missing baseline trio_strict rate for shape "${shape}"`);
}
const baselineCost = inputs.baselineMedianCostUsd.get(shape);
if (baselineCost === undefined) {
throw new Error(`runSelection: missing baseline median cost for shape "${shape}"`);
}
// Compute fitness for all candidates of this shape
const ranked = candidates
.map(candidate => ({
candidate,
fitness: computeFitness({ candidate, baselineMedianCostUsd: baselineCost }),
}))
.sort((a, b) => b.fitness.fitness - a.fitness.fitness);
const top = ranked[0];
const acceptance = evaluateCandidate({
candidate: top.candidate,
baselineTrioStrictPassRateII: baselineRate,
});
perShape.push({
shape,
bestCandidate: top.candidate,
bestFitness: top.fitness,
acceptance,
allCandidatesRanked: ranked,
});
}
const shapesWithPositiveDelta = perShape.filter(s => s.acceptance.trioStrictDeltaPP > 0).length;
const shapesAccepted = perShape.filter(s => s.acceptance.accepted).length;
const totalShapes = perShape.length;
const condition2Pass = shapesWithPositiveDelta >= 3;
return {
perShape,
runAggregate: {
shapesWithPositiveDelta,
totalShapes,
condition2Pass,
shapesAccepted,
},
};
}

View File

@@ -0,0 +1,321 @@
/**
* GEPA Faza 1 — shared type definitions.
*
* Per manifest v7 (SHA 583712dde139ffc87fb1ab21643f68d52c56469ded9e8090a624980b05969beb,
* Amendment 2 supplemented) and launch decision §A inherited rules.
*
* Cell semantic boundary linchpin: MULTI_STEP_ACTION_CONTRACT in
* packages/agent/src/prompt-shapes/types.ts (SHA 70a1701d...).
*
* DO NOT modify this file as part of GEPA candidate evolution. This file is
* scaffold-only; the mutation surface is the prompt-shape body methods in
* packages/agent/src/prompt-shapes/{claude,qwen-thinking,qwen-non-thinking,gpt,generic-simple}.ts.
*/
/** The 5 shape names targeted by Faza 1 GEPA evolution. */
export type ShapeName =
| 'claude'
| 'qwen-thinking'
| 'qwen-non-thinking'
| 'gpt'
| 'generic-simple';
/**
* Shapes that get retrieval-engagement weighting per Amendment 2 §3.
* Phase 4.5 finding: only Qwen exhibits the under-engagement gap.
*/
export const QWEN_TARGETED_SHAPES: ReadonlySet<ShapeName> = new Set<ShapeName>([
'qwen-thinking',
'qwen-non-thinking',
]);
/**
* Shapes that use baseline fitness (no retrieval-engagement weight).
* Per Amendment 2 §3: these shapes don't have the gap, so applying the
* bonus uniformly would distort their fitness measurement.
*/
export const NON_QWEN_SHAPES: ReadonlySet<ShapeName> = new Set<ShapeName>([
'claude',
'gpt',
'generic-simple',
]);
/** Per-task evaluation result for one candidate × one instance. */
export interface EvaluationResult {
/** Stable instance identifier (e.g., from corpus generator). */
instanceId: string;
/** Trio judge mean (per pilot runner line 654 — arithmetic mean of valid judge means). */
trioMean: number;
/**
* Trio strict pass per metric_operationalization (ii) — primary acceptance.
* `trioMean >= 4.0` per Amendment 1 Ask B ratification.
*/
trioStrictPassII: boolean;
/**
* Trio strict pass per metric_operationalization (i) — supplementary.
* `>= 2 of 3 judges with judge.mean >= 3.5` per pilot runner line 657.
* Reported in parallel for cross-validation against pilot baseline.
*/
trioStrictPassI: boolean;
/** Number of retrieve actions issued during this evaluation (existing telemetry). */
retrievalCalls: number;
/** Subject + judge cumulative cost (USD) for this evaluation. */
costUsd: number;
}
/** Aggregated per-candidate metrics across N=8 evaluations (per shape). */
export interface CandidateMetrics {
/** Stable candidate identifier (e.g., shape name + generation + variant). */
candidateId: string;
/** Which shape this candidate belongs to. */
shape: ShapeName;
/** All N=8 evaluation results for this candidate. */
evaluations: EvaluationResult[];
/** Pass rate per operationalization (ii) — primary. Range [0, 1]. */
trioStrictPassRateII: number;
/** Pass rate per operationalization (i) — supplementary. Range [0, 1]. */
trioStrictPassRateI: number;
/** Mean retrieval_calls across evaluations (per task). */
meanRetrievalCallsPerTask: number;
/** Mean cost (USD) across evaluations. */
meanCostUsd: number;
}
/**
* Per-shape fitness components per Amendment 2 §3.
*
* For Qwen-targeted shapes:
* fitness = trio_strict_pass_rate + retrieval_engagement_bonus - cost_penalty
*
* For non-Qwen shapes:
* fitness = trio_strict_pass_rate - cost_penalty
* (retrievalEngagementBonus is always 0 for non-Qwen — not added to fitness)
*/
export interface FitnessComponents {
/** trio_strict_pass_rate per operationalization (ii). Range [0, 1]. */
trioStrictPassRateII: number;
/**
* Retrieval engagement bonus per Amendment 2 §3 bands:
* +0.05 if mean retrieval_calls per task >= 2.0
* 0.00 if mean retrieval_calls per task in [1.5, 2.0)
* -0.05 if mean retrieval_calls per task < 1.5
* Always 0.0 for non-Qwen shapes (these shapes don't have the gap).
*/
retrievalEngagementBonus: number;
/**
* Cost penalty per brief §3.1 — 0.5pp per $0.10 above per-shape baseline median.
* Encoded as decimal (e.g., 0.005 = 0.5pp).
*/
costPenalty: number;
/** Aggregate fitness (sum of above with sign convention: bonus +, penalty ). */
fitness: number;
/**
* Whether retrieval engagement was applied (true for Qwen-targeted shapes,
* false otherwise). Useful for downstream auditing + report generation.
*/
retrievalEngagementApplied: boolean;
}
/** Inputs for the per-shape fitness function. */
export interface FitnessInputs {
/** Aggregated metrics for this candidate. */
candidate: CandidateMetrics;
/**
* Per-shape baseline median cost (USD per evaluation). Used for cost penalty
* computation. Typically the NULL-baseline median for the same shape.
*/
baselineMedianCostUsd: number;
}
/** Acceptance verdict for a single candidate per §F + §F.5 of launch decision. */
export interface AcceptanceVerdict {
/** Whether candidate passes §F condition 1 (trio_strict delta + Qwen retrieval floor). */
condition1Pass: boolean;
/**
* §F.5 false-positive guard — REJECTED if Qwen candidate has +5pp trio delta
* but mean retrieval_calls < 1.5 (Amendment 2 §5).
*/
condition5FalsePositiveGuardTriggered: boolean;
/** Overall acceptance for this candidate (must pass condition 1 AND not trigger §F.5). */
accepted: boolean;
/** Detailed reason string for audit log. */
reason: string;
/**
* Computed delta vs NULL-baseline trio_strict_pass_rate (percentage points).
* Positive = improvement.
*/
trioStrictDeltaPP: number;
}
/** Inputs for the acceptance validator. */
export interface AcceptanceInputs {
/** Candidate under evaluation. */
candidate: CandidateMetrics;
/** NULL-baseline trio_strict_pass_rate (op. (ii)) for the same shape. */
baselineTrioStrictPassRateII: number;
}
// ── Amendment 7 — tiered fitness + Δ-floor types ───────────────────────────
/**
* Per-shape NULL-baseline anchors pinned from Checkpoint A v2 §B.2 (manifest
* v7 Amendment 6 binding SHA 0b55d8e353...).
*
* These anchor:
* - Tier 1 baseline (NULL pass rate per shape)
* - Tier 2 baseline (NULL retrieval engagement per shape)
* - §F.1 acceptance gate ≥+5pp delta basis
* - Δ-floor threshold 2 per-shape comparison
*
* Source: real per-shape data from re-run NULL-baseline post Amendment 6
* promptShapeOverride bug fix (run bhe0zwi91, 40/40 evals, 2026-04-28).
*/
export const NULL_BASELINE_PER_SHAPE: Readonly<
Record<ShapeName, { trioStrictPassRateII: number; meanRetrievalCallsPerTask: number }>
> = {
claude: { trioStrictPassRateII: 0.875, meanRetrievalCallsPerTask: 1.12 },
'qwen-thinking': { trioStrictPassRateII: 0.875, meanRetrievalCallsPerTask: 1.12 },
'qwen-non-thinking': { trioStrictPassRateII: 1.000, meanRetrievalCallsPerTask: 1.25 },
gpt: { trioStrictPassRateII: 0.750, meanRetrievalCallsPerTask: 1.00 },
'generic-simple': { trioStrictPassRateII: 0.875, meanRetrievalCallsPerTask: 1.12 },
} as const;
/** NULL-baseline aggregate across all 5 shapes (Checkpoint A v2 §B.2). */
export const NULL_BASELINE_AGGREGATE = {
trioStrictPassRateII: 0.875, // 35/40
meanRetrievalCallsPerTask: 1.12,
} as const;
/**
* Tiered fitness components per Amendment 7 §fitness_function_tiered.
*
* Saturated regime (NULL pass rate ≥75% for ≥4/5 shapes — current Faza 1 state)
* makes Tier 1 (NULL delta) noise-bound on N=8 binomial. Tier 2 + Tier 3 act as
* primary differentiators; Tier 1 retained as TIE-BREAKER + acceptance gate.
*/
export interface TieredFitnessComponents {
/**
* Tier 1 — NULL pass rate delta (signed, percentage points).
* Acceptance gate threshold: ≥+5pp (launch decision §F.1, UNCHANGED).
* Role in saturated regime: TIE_BREAKER (noise-bound at N=8).
*/
tier1DeltaPP: number;
/**
* Tier 2 — continuous retrieval engagement bonus (Qwen-targeted only).
* Formula: clamp(0.05 × delta_pp, 0, 0.25) where delta_pp = (candidate baseline) × 100.
* Always 0 for non-Qwen shapes.
* Role in saturated regime: PRIMARY differentiator for Qwen-targeted shapes.
*/
tier2RetrievalBonus: number;
/**
* Tier 3 — binary cell-semantic anchor invariance bonus.
* 0.10 if all 7 anchors invariant (mutation-validator passed); 0 otherwise.
* Role in saturated regime: SECONDARY differentiator (substrate-preservation proxy).
*/
tier3CellSemanticInvarianceBonus: number;
/**
* Aggregate fitness in saturated regime: tier_2 + tier_3.
* Tier 1 reserved as tie-breaker (NOT in this aggregate).
* Cost penalty per Amendment 2 NOT in tiered ranking aggregate (supplementary diagnostic).
*/
aggregateSaturatedRegime: number;
/** Whether saturated regime applies (per Amendment 7 §fitness_function_tiered.saturated_regime_definition). */
saturatedRegimeApplied: boolean;
/** Cell-semantic anchor invariance count (0..7) for audit reporting. */
cellSemanticAnchorInvarianceCount: number;
}
/** Inputs for tiered fitness. */
export interface TieredFitnessInputs {
/** Aggregated metrics for this candidate. */
candidate: CandidateMetrics;
/** Per-shape NULL-baseline pass rate (op. ii). Typically NULL_BASELINE_PER_SHAPE[shape].trioStrictPassRateII. */
nullBaselinePassRateII: number;
/** Per-shape NULL-baseline mean retrieval calls per task. Typically NULL_BASELINE_PER_SHAPE[shape].meanRetrievalCallsPerTask. */
nullBaselineMeanRetrievalCallsPerTask: number;
/** Whether the candidate's mutation-validator verdict was VALID (all 7 anchors invariant). */
mutationValidatorPassed: boolean;
/**
* Whether the saturated regime applies (≥4/5 shapes have NULL pass rate ≥75%).
* Caller computes from Checkpoint A v2 data; default true for Faza 1.
*/
saturatedRegime: boolean;
}
/**
* Δ-floor verdict per Amendment 7 §gen_1_pre_registered_delta_floor.
* Pre-registered Gen 1 floor for "evolution worked at all" — looser than
* §F.1 acceptance gate. Three OR-gated thresholds.
*/
export interface DeltaFloorVerdict {
/** Threshold 1: aggregate Tier 1 delta ≥+3pp. */
threshold1AggregateTier1: 'PASS' | 'FAIL';
/** Aggregate Tier 1 delta value (pp, signed) — for audit. */
threshold1ValuePP: number;
/** Threshold 2: max Qwen-shape retrieval engagement delta ≥+0.10 absolute. */
threshold2QwenRetrievalAbsolute: 'PASS' | 'FAIL';
/** Max delta across Qwen shapes (absolute) — for audit. */
threshold2MaxDeltaAbsolute: number;
/** Threshold 3: (Tier 1 ≥0pp) AND (Tier 2 ≥0.05). */
threshold3CompoundTier1PlusTier2: 'PASS' | 'FAIL';
/** Aggregate Tier 1 delta (pp, signed) — for audit. */
threshold3Tier1ValuePP: number;
/** Aggregate Tier 2 bonus across Qwen-targeted candidates — for audit. */
threshold3Tier2Aggregate: number;
/** Overall verdict: PROCEED if ANY threshold passes; HALT_INVESTIGATE if ALL fail. */
overallVerdict: 'PROCEED' | 'HALT_INVESTIGATE';
}
/** Inputs for Δ-floor verdict computation. */
export interface DeltaFloorInputs {
/** Aggregate trio_strict_pass_rate_II across all evaluated candidates × all evals (in saturated regime, mean across 30 Checkpoint B evals). */
aggregateTrioStrictPassRateII: number;
/** Aggregate NULL-baseline pass rate (e.g., 0.875 = NULL_BASELINE_AGGREGATE.trioStrictPassRateII). */
aggregateNullBaselinePassRateII: number;
/**
* Per-shape mean retrieval calls per task for Qwen-targeted candidates.
* Map: shape → meanRetrievalCallsPerTask. Empty entries treated as no-data (skipped).
*/
qwenShapeRetrievalMeans: Partial<Record<ShapeName, number>>;
/** Per-shape NULL baseline retrieval means (typically NULL_BASELINE_PER_SHAPE[shape].meanRetrievalCallsPerTask). */
qwenShapeNullBaselineRetrievalMeans: Partial<Record<ShapeName, number>>;
/** Aggregate Tier 2 retrieval engagement bonus across Qwen-targeted candidates (mean). */
qwenAggregateTier2Bonus: number;
}

View File

@@ -0,0 +1,60 @@
# Faza 1 closed — quarantined tests
**Quarantine date:** 2026-04-29 (Phase 5 §0 preflight)
**Authority:** PM ratification — Phase 5 deployment §0 preflight 3-ask response, Ask #1 Option 1
**Branch:** `phase-5-deployment-v2`
**Quarantine commit:** see git log this directory
**Vitest exclude:** `**/__faza1-closed/**` added to `vitest.config.ts` exclude list — these files are skipped from collection (no longer load-bearing for current verification).
---
## Quarantined files
| File | Reason |
|---|---|
| `mutation-validator.test.ts` | Pins baseline shape SHAs at Faza 1 substrate freeze `c9bda3d`; `phase-5-deployment-v2` Opcija C inheritance chain doesn't reach `c9bda3d`. PM Ask #1 Option 1 ratified 2026-04-29. |
| `registry-injection.test.ts` | Asserts Amendment 8 H1 failure mode (deep-relative-path REGISTRY ≠ package REGISTRY) reproduces under Node 22.19.0; current Node ESM resolver deduplicates the paths so the assertion fails. CC extended quarantine 2026-04-29 same session — flagged in §0 preflight evidence + commit message; PM advised. |
## Why these tests live here
Tests in this directory pin substrate SHAs against the Faza 1 substrate freeze head `c9bda3d` (Phase 4.7 HEAD on `feature/c3-v3-wrapper`, per `benchmarks/preregistration/manifest-v7-gepa-faza1.yaml` `substrate_freeze_head`). They were load-bearing during Faza 1 GEPA evolution runs (executed inside isolated worktree `D:/Projects/waggle-os-faza1-wt` rooted at `c9bda3d`) to enforce the cell-semantic boundary invariant — the mutation oracle must not modify baseline shape file content during Gen 1+ candidate generation.
`registry-injection.test.ts` documents a SECOND class of Faza 1 closed-work artifact: tests that intentionally assert a buggy state reproduces (so the bug stays detectable if someone "fixes" the canonical path back). Once Amendment 8 fixed H1 via the canonical `registerShape` API + Node ESM resolver dedup behavior changed in subsequent versions, these documentation tests can no longer pass — but their failure carries no Phase 5 substrate signal. The H1 fix is verified independently via `selectShape` + `registerShape` integration tests in the agent suite.
After Faza 1 closure (`6bc2089` — Checkpoint C closure decision `decisions/2026-04-29-gepa-faza1-results.md`), the branch architecture decision **Opcija C** (`decisions/2026-04-30-branch-architecture-opcija-c.md`) determined that:
- `phase-5-deployment-v2` inherits `gepa-faza-1` baseline (= `6bc2089`)
- `gepa-faza-1` parent chain reaches `origin/main` (`5ec069e`), NOT `c9bda3d`
- `c9bda3d` is on the divergent `feature/c3-v3-wrapper` branch (CC-1 Phase 4 work)
- Therefore the SHAs of baseline shape files on `phase-5-deployment-v2` reflect `origin/main` content, not the `c9bda3d` content these tests pin
Running these tests on `phase-5-deployment-v2` produces 14 failures with no Phase 5 substrate signal — the failures are a scope-leakage artifact of post-closure test continuation under Opcija C inheritance, not a bug in either Phase 5 substrate or Faza 1 evolution invariants.
Faza 1 closure verdict §F.4 already documents `105/105 anchor invariance checks PASS` during in-worktree execution + `15/15 held-out anchor checks PASS` during Checkpoint C — the cell-semantic boundary discipline was verified and binding throughout Faza 1.
## What this quarantine does and does NOT mean
- **Does NOT mean** Faza 1 substrate discipline was wrong or the test was buggy.
- **Does NOT mean** baseline shape files have been modified.
- **Does mean** the SHAs the test pins to (`c9bda3d` substrate snapshot) are not reachable from `phase-5-deployment-v2` HEAD without integration sprint work.
- **Does mean** the test is no longer load-bearing for Phase 5 deployment substrate verification (REGISTRY API + registerShape canonical path + gen1-v1 shape definitions are verified independently via Phase 5 §0.1 substrate readiness grep).
## Reactivation conditions
These tests should be re-activated (moved back out of `__faza1-closed/`) when ANY of the following holds:
1. **Post-Phase-5 production-stable integration sprint** (per Opcija C §5) merges `feature/c3-v3-wrapper` into the deployment lineage. Re-pin the test SHAs to the integrated substrate snapshot before re-activating.
2. **Future Faza N evolution sprints** that re-establish substrate freeze inside an isolated worktree. Re-activate the tests inside that worktree's branch context, not on the deployment branch.
3. **Substrate boundary regression suspected** — re-pin SHAs to the current deployment branch HEAD content and re-activate as a drift detector for that specific branch.
Forbidden: simply blanking the SHA pins to silence the test. Replacement pins must be anchored to a documented substrate snapshot with audit-traceable origin.
## Audit trail
| Anchor | Path |
|---|---|
| Faza 1 closure decision | `D:/Projects/PM-Waggle-OS/decisions/2026-04-29-gepa-faza1-results.md` |
| Branch architecture (Opcija C) | `D:/Projects/PM-Waggle-OS/decisions/2026-04-30-branch-architecture-opcija-c.md` |
| Phase 5 brief LOCKED | `D:/Projects/PM-Waggle-OS/briefs/2026-04-29-phase-5-deployment-brief-v1.md` |
| §0 preflight evidence | `D:/Projects/waggle-os/gepa-phase-5/preflight-evidence.md` |
| Quarantine ratification | PM 3-ask response 2026-04-29, Ask #1 Option 1 |

View File

@@ -0,0 +1,254 @@
/**
* GEPA Faza 1 — mutation validator tests.
*
* Coverage targets:
* - SHA boundary checks (types.ts + MULTI_STEP_ACTION_CONTRACT bytes)
* - Pinned baseline shape SHAs (5 shapes)
* - Locked metadata field detection
* - Imports preservation check
* - Gen 1 mutation must differ from baseline
* - Gen 0 baseline (expectShapeDiff=false) accepts identity
*
* Validation against actual substrate-pinned files (the worktree at c9bda3d).
*/
import { describe, expect, it } from 'vitest';
import * as path from 'node:path';
import * as fs from 'node:fs';
import * as os from 'node:os';
import {
BOUNDARY_SHAS,
BASELINE_SHAPE_SHAS,
sha256,
sha256File,
extractMultiStepActionContractBytes,
validateCandidate,
} from '../../src/faza-1/mutation-validator.js';
const WORKTREE_ROOT = path.resolve(__dirname, '../../../../');
const PROMPT_SHAPES_DIR = path.join(WORKTREE_ROOT, 'packages/agent/src/prompt-shapes');
const TYPES_FILE = path.join(PROMPT_SHAPES_DIR, 'types.ts');
// ───────────────────────────────────────────────────────────────────────────
// SHA primitive tests
// ───────────────────────────────────────────────────────────────────────────
describe('sha256 primitive', () => {
it('computes deterministic SHA-256 of utf-8 string', () => {
expect(sha256('hello')).toBe(
'2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
);
});
it('computes SHA-256 of buffer', () => {
expect(sha256(Buffer.from('hello'))).toBe(
'2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
);
});
it('returns different SHAs for different inputs', () => {
expect(sha256('a')).not.toBe(sha256('b'));
});
});
// ───────────────────────────────────────────────────────────────────────────
// Boundary anchor SHAs match actual substrate at c9bda3d
// ───────────────────────────────────────────────────────────────────────────
describe('boundary anchor SHAs match substrate at c9bda3d', () => {
it('types.ts whole-file SHA matches pinned baseline', () => {
const actual = sha256File(TYPES_FILE);
expect(actual).toBe(BOUNDARY_SHAS.typesFile);
});
it('MULTI_STEP_ACTION_CONTRACT bytes SHA matches pinned baseline', () => {
const content = fs.readFileSync(TYPES_FILE, 'utf-8');
const bytes = extractMultiStepActionContractBytes(content);
expect(bytes).not.toBeNull();
expect(sha256(bytes!)).toBe(BOUNDARY_SHAS.multiStepActionContract);
});
it('extractMultiStepActionContractBytes captures the 252-byte constant', () => {
const content = fs.readFileSync(TYPES_FILE, 'utf-8');
const bytes = extractMultiStepActionContractBytes(content);
expect(bytes).not.toBeNull();
expect(Buffer.from(bytes!, 'utf-8').length).toBe(252);
expect(bytes!).toContain('Output exactly ONE JSON object on its own line');
});
it('extractMultiStepActionContractBytes returns null when constant absent', () => {
expect(extractMultiStepActionContractBytes('export const SOMETHING_ELSE = 42;')).toBeNull();
});
});
describe('all 5 baseline shape SHAs match substrate at c9bda3d', () => {
for (const shapeName of Object.keys(BASELINE_SHAPE_SHAS) as Array<keyof typeof BASELINE_SHAPE_SHAS>) {
it(`baseline ${shapeName} SHA matches pinned`, () => {
const filepath = path.join(PROMPT_SHAPES_DIR, shapeName);
const actual = sha256File(filepath);
expect(actual).toBe(BASELINE_SHAPE_SHAS[shapeName]);
});
}
});
// ───────────────────────────────────────────────────────────────────────────
// validateCandidate end-to-end
// ───────────────────────────────────────────────────────────────────────────
describe('validateCandidate — Gen 0 (baseline) acceptance', () => {
it('Gen 0 baseline shape passes validation with expectShapeDiff=false', () => {
const verdict = validateCandidate({
candidateShapeFilePath: path.join(PROMPT_SHAPES_DIR, 'qwen-thinking.ts'),
baselineShapeName: 'qwen-thinking.ts',
typesFilePath: TYPES_FILE,
expectShapeDiff: false,
});
expect(verdict.valid).toBe(true);
expect(verdict.violations).toHaveLength(0);
expect(verdict.candidateShapeFileSha).toBe(BASELINE_SHAPE_SHAS['qwen-thinking.ts']);
});
it('Gen 0 baseline FAILS validation if expectShapeDiff=true (identity violation)', () => {
const verdict = validateCandidate({
candidateShapeFilePath: path.join(PROMPT_SHAPES_DIR, 'qwen-thinking.ts'),
baselineShapeName: 'qwen-thinking.ts',
typesFilePath: TYPES_FILE,
expectShapeDiff: true,
});
expect(verdict.valid).toBe(false);
expect(verdict.violations.some(v => v.category === 'shape_file_unchanged_from_baseline')).toBe(true);
});
});
describe('validateCandidate — types.ts boundary violation detection', () => {
it('FAILS if types.ts SHA differs from pinned baseline', () => {
const tmpTypes = path.join(os.tmpdir(), `types-modified-${Date.now()}.ts`);
const original = fs.readFileSync(TYPES_FILE, 'utf-8');
fs.writeFileSync(tmpTypes, original + '\n// MODIFIED\n');
try {
const verdict = validateCandidate({
candidateShapeFilePath: path.join(PROMPT_SHAPES_DIR, 'qwen-thinking.ts'),
baselineShapeName: 'qwen-thinking.ts',
typesFilePath: tmpTypes,
expectShapeDiff: false,
});
expect(verdict.valid).toBe(false);
expect(verdict.violations.some(v => v.category === 'types_file_modified')).toBe(true);
} finally {
fs.unlinkSync(tmpTypes);
}
});
it('FAILS if MULTI_STEP_ACTION_CONTRACT bytes are modified', () => {
const tmpTypes = path.join(os.tmpdir(), `types-contract-modified-${Date.now()}.ts`);
const original = fs.readFileSync(TYPES_FILE, 'utf-8');
const modified = original.replace(
'Output exactly ONE JSON object',
'Output exactly TWO JSON objects', // single-byte tweak in the contract
);
fs.writeFileSync(tmpTypes, modified);
try {
const verdict = validateCandidate({
candidateShapeFilePath: path.join(PROMPT_SHAPES_DIR, 'qwen-thinking.ts'),
baselineShapeName: 'qwen-thinking.ts',
typesFilePath: tmpTypes,
expectShapeDiff: false,
});
expect(verdict.valid).toBe(false);
expect(verdict.violations.some(v => v.category === 'multi_step_action_contract_modified')).toBe(true);
} finally {
fs.unlinkSync(tmpTypes);
}
});
});
describe('validateCandidate — shape file metadata + imports validation', () => {
it('FAILS if locked metadata field is missing from candidate', () => {
const tmpShape = path.join(os.tmpdir(), `qwen-thinking-bad-${Date.now()}.ts`);
const original = fs.readFileSync(path.join(PROMPT_SHAPES_DIR, 'qwen-thinking.ts'), 'utf-8');
// Strip out modelClass: line entirely. Use \r?\n to handle both LF and CRLF
// line endings — git on Windows may check out files with CRLF.
const stripped = original.replace(/modelClass:.*\r?\n/, '');
expect(stripped).not.toContain('modelClass:'); // sanity: stripping actually worked
fs.writeFileSync(tmpShape, stripped);
try {
const verdict = validateCandidate({
candidateShapeFilePath: tmpShape,
baselineShapeName: 'qwen-thinking.ts',
typesFilePath: TYPES_FILE,
expectShapeDiff: true,
});
expect(verdict.valid).toBe(false);
expect(verdict.violations.some(v => v.category === 'shape_file_metadata_locked_field_modified')).toBe(true);
} finally {
fs.unlinkSync(tmpShape);
}
});
it('FAILS if imports block is removed', () => {
const tmpShape = path.join(os.tmpdir(), `qwen-thinking-noimport-${Date.now()}.ts`);
const original = fs.readFileSync(path.join(PROMPT_SHAPES_DIR, 'qwen-thinking.ts'), 'utf-8');
const stripped = original.replace(/from '\.\/types\.js'/, "from './SOMETHING_ELSE.js'");
fs.writeFileSync(tmpShape, stripped);
try {
const verdict = validateCandidate({
candidateShapeFilePath: tmpShape,
baselineShapeName: 'qwen-thinking.ts',
typesFilePath: TYPES_FILE,
expectShapeDiff: true,
});
expect(verdict.valid).toBe(false);
expect(verdict.violations.some(v => v.category === 'shape_file_imports_modified')).toBe(true);
} finally {
fs.unlinkSync(tmpShape);
}
});
it('PASSES if candidate uses ../types.js (gepa-evolved/ subdir convention)', () => {
const tmpShape = path.join(os.tmpdir(), `qwen-thinking-subdir-${Date.now()}.ts`);
const original = fs.readFileSync(path.join(PROMPT_SHAPES_DIR, 'qwen-thinking.ts'), 'utf-8');
// Simulate gepa-evolved/ subdir candidate: ./types.js → ../types.js, body mutation
const subdirImport = original
.replace(/from '\.\/types\.js'/, "from '../types.js'")
.replace('Answer the question precisely', 'Answer the question precisely (mutation)');
fs.writeFileSync(tmpShape, subdirImport);
try {
const verdict = validateCandidate({
candidateShapeFilePath: tmpShape,
baselineShapeName: 'qwen-thinking.ts',
typesFilePath: TYPES_FILE,
expectShapeDiff: true,
});
expect(verdict.valid).toBe(true);
expect(verdict.violations.some(v => v.category === 'shape_file_imports_modified')).toBe(false);
} finally {
fs.unlinkSync(tmpShape);
}
});
});
describe('validateCandidate — accepts valid Gen 1 mutation', () => {
it('Gen 1 candidate with body-only mutation passes validation', () => {
const tmpShape = path.join(os.tmpdir(), `qwen-thinking-mutation-${Date.now()}.ts`);
const original = fs.readFileSync(path.join(PROMPT_SHAPES_DIR, 'qwen-thinking.ts'), 'utf-8');
// Realistic-shape mutation: change a string in the body, keep imports + metadata + structure
const mutated = original.replace(
'Answer the question precisely and substantively',
'Answer the question precisely, substantively, and with explicit retrieval',
);
expect(mutated).not.toBe(original); // sanity: mutation actually changed bytes
fs.writeFileSync(tmpShape, mutated);
try {
const verdict = validateCandidate({
candidateShapeFilePath: tmpShape,
baselineShapeName: 'qwen-thinking.ts',
typesFilePath: TYPES_FILE,
expectShapeDiff: true,
});
expect(verdict.valid).toBe(true);
expect(verdict.candidateShapeFileSha).not.toBe(BASELINE_SHAPE_SHAS['qwen-thinking.ts']);
} finally {
fs.unlinkSync(tmpShape);
}
});
});

View File

@@ -0,0 +1,154 @@
/**
* GEPA Faza 1 — REGISTRY-injection cross-module-boundary regression test.
*
* Per manifest v7 Amendment 8 §registry_invariant_test.
*
* Documents the H1 failure mode discovered via Gen 1 partial run b5avslp51 +
* diagnostic probe (benchmarks/gepa/scripts/faza-1/probe-registry-injection.ts):
*
* Under tsx + Node ESM with workspace path resolution, importing REGISTRY
* via a deep relative path produces a SEPARATE module instance from
* importing via the package path '@waggle/agent'. Mutations to one
* instance do NOT propagate to the other.
*
* This test asserts BOTH:
* (a) the failure mode (direct deep-path mutation does NOT propagate
* to package-import REGISTRY), so the bug class stays detectable
* if someone "fixes" the canonical path back to a deep import; AND
* (b) registerShape() via '@waggle/agent' DOES propagate to the
* agent-loop's view of REGISTRY (selectShape sees the new shape).
*
* If this test ever fails on (a) it means the underlying ESM resolver
* dedup-logic changed; if it ever fails on (b) it means registerShape
* was broken or its export path was changed.
*/
import { describe, expect, it } from 'vitest';
// Path A — deep relative import (the failing pattern from b5avslp51)
import { REGISTRY as RegistryFromScriptDeepPath } from '../../../../packages/agent/src/prompt-shapes/selector.js';
// Path B — package import (the canonical, agent-loop-equivalent path)
import {
REGISTRY as RegistryFromPackage,
registerShape,
selectShape,
type PromptShape,
} from '@waggle/agent';
function makeProbeShape(name: string): PromptShape {
// Minimal valid PromptShape stub — only structure matters for the registry test;
// method bodies are not invoked here.
return {
name,
metadata: {
description: `Probe shape ${name} for registry-injection regression test`,
modelClass: 'probe',
defaultThinking: false,
defaultMaxTokens: 100,
evidence_link: 'manifest v7 Amendment 8 §registry_invariant_test',
},
systemPrompt: () => 'probe',
soloUserPrompt: () => 'probe',
multiStepKickoffUserPrompt: () => 'probe',
retrievalInjectionUserPrompt: () => 'probe',
} as PromptShape;
}
describe('Amendment 8 §registry_invariant_test — REGISTRY cross-module-boundary', () => {
it('documents H1 failure mode: deep-relative-path REGISTRY and package REGISTRY are SEPARATE module instances', () => {
// This assertion documents the empirical finding from probe-registry-injection.ts
// run on 2026-04-28 (Node v22.19.0 + tsx). If the underlying ESM resolver ever
// deduplicates these paths, this test will fail-and-flag the change.
expect(RegistryFromScriptDeepPath).not.toBe(RegistryFromPackage);
});
it('documents H1: direct deep-path mutation does NOT propagate to package-import REGISTRY', () => {
const shapeName = 'amendment-8-h1-failure-mode-witness';
const shape = makeProbeShape(shapeName);
// Anti-pattern (the b5avslp51 bug): direct mutation via deep-path import.
(RegistryFromScriptDeepPath as Record<string, PromptShape>)[shapeName] = shape;
// Direct read on the same instance: visible.
expect(RegistryFromScriptDeepPath[shapeName]).toBe(shape);
// Read via the package-import (agent-loop's view): NOT visible.
expect(RegistryFromPackage[shapeName]).toBeUndefined();
// selectShape (from package, mirrors agent-loop call site): throws.
expect(() => selectShape('any-alias', { override: shapeName })).toThrow(
/not in REGISTRY/,
);
// Cleanup — remove the failure-mode witness so subsequent tests stay clean.
delete (RegistryFromScriptDeepPath as Record<string, PromptShape>)[shapeName];
});
it('FIX: registerShape() via @waggle/agent DOES propagate (canonical mutation API per §canonical_mutation_api)', () => {
const shapeName = 'amendment-8-canonical-fix-witness';
const shape = makeProbeShape(shapeName);
// Canonical mutation: registerShape imported from '@waggle/agent'.
registerShape(shapeName, shape);
// Read via the package-import (agent-loop's view): visible.
expect(RegistryFromPackage[shapeName]).toBe(shape);
// selectShape with override (matches agent-loop's selectShape call site).
const found = selectShape('any-alias', { override: shapeName });
expect(found).toBe(shape);
expect(found.name).toBe(shapeName);
// Cleanup — Faza 1 doesn't expose unregisterShape, so we mutate via canonical
// Path-B REGISTRY directly to keep cross-test isolation.
delete (RegistryFromPackage as Record<string, PromptShape>)[shapeName];
});
it('registerShape rejects empty name with informative error', () => {
const shape = makeProbeShape('temp');
expect(() => registerShape('', shape)).toThrow(/non-empty string/);
});
it('registerShape rejects malformed shape (missing systemPrompt method)', () => {
const malformed = {
name: 'malformed',
metadata: { description: 'nope', modelClass: 'x', defaultThinking: false, defaultMaxTokens: 1 },
// systemPrompt deliberately missing
} as unknown as PromptShape;
expect(() => registerShape('malformed-test', malformed)).toThrow(
/missing required PromptShape fields/,
);
});
it('registerShape registration survives multiple re-registrations (last-write-wins semantics)', () => {
const name = 'amendment-8-multi-register';
const shape1 = makeProbeShape(name);
const shape2 = makeProbeShape(name);
// Distinct identity but same name.
expect(shape1).not.toBe(shape2);
registerShape(name, shape1);
expect(selectShape('x', { override: name })).toBe(shape1);
registerShape(name, shape2); // re-register
expect(selectShape('x', { override: name })).toBe(shape2);
delete (RegistryFromPackage as Record<string, PromptShape>)[name];
});
it('registerShape via @waggle/agent makes the shape visible from listShapes()', async () => {
const name = 'amendment-8-listshapes-witness';
const shape = makeProbeShape(name);
const { listShapes } = await import('@waggle/agent');
const beforeNames = listShapes();
expect(beforeNames).not.toContain(name);
registerShape(name, shape);
const afterNames = listShapes();
expect(afterNames).toContain(name);
delete (RegistryFromPackage as Record<string, PromptShape>)[name];
});
});

View File

@@ -0,0 +1,263 @@
/**
* GEPA Faza 1 — acceptance validator tests.
*
* Coverage targets per manifest v7 §amendment_2_integration.scaffold_test_coverage_NEW_requirements
* mandatory_acceptance_tests:
* - §F.5 FAIL: Qwen candidate with trio_strict delta = +6pp AND mean retrieval = 1.4 → REJECTED
* - §F.5 PASS path: Qwen candidate with trio_strict delta = +6pp AND mean retrieval = 1.7 → ACCEPTED
*
* Plus comprehensive coverage of §F condition 1 (third update) for both Qwen and non-Qwen branches.
*/
import { describe, expect, it } from 'vitest';
import {
evaluateCandidate,
TRIO_STRICT_DELTA_THRESHOLD_PP,
QWEN_RETRIEVAL_ENGAGEMENT_FLOOR,
QWEN_FALSE_POSITIVE_RETRIEVAL_FLOOR,
} from '../../src/faza-1/acceptance.js';
import { type CandidateMetrics, type ShapeName } from '../../src/faza-1/types.js';
function makeCandidate(overrides: Partial<CandidateMetrics> & Pick<CandidateMetrics, 'shape'>): CandidateMetrics {
return {
candidateId: overrides.candidateId ?? `${overrides.shape}-test-candidate`,
shape: overrides.shape,
evaluations: overrides.evaluations ?? [],
trioStrictPassRateII: overrides.trioStrictPassRateII ?? 0.5,
trioStrictPassRateI: overrides.trioStrictPassRateI ?? 0.5,
meanRetrievalCallsPerTask: overrides.meanRetrievalCallsPerTask ?? 1.5,
meanCostUsd: overrides.meanCostUsd ?? 0.5,
};
}
// ───────────────────────────────────────────────────────────────────────────
// MANDATORY: §F.5 false-positive guard tests per Amendment 2 §5
// ───────────────────────────────────────────────────────────────────────────
describe('§F.5 false-positive evolution guard — mandatory Amendment 2 acceptance tests', () => {
it('Qwen candidate, trio_strict delta = +6pp, mean retrieval = 1.4 → REJECTED', () => {
// baseline = 0.20, candidate = 0.26 → delta = +6pp ≥ 5pp threshold
// retrieval = 1.4 < 1.5 false-positive floor → §F.5 triggers
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.26,
meanRetrievalCallsPerTask: 1.4,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.trioStrictDeltaPP).toBeCloseTo(6.0, 6);
expect(verdict.condition5FalsePositiveGuardTriggered).toBe(true);
expect(verdict.accepted).toBe(false);
expect(verdict.reason).toContain('REJECTED §F.5 false-positive guard');
});
it('Qwen candidate, trio_strict delta = +6pp, mean retrieval = 1.7 → ACCEPTED', () => {
// baseline = 0.20, candidate = 0.26 → delta = +6pp
// retrieval = 1.7 ≥ 1.7 floor (engagement gap closed) → §F.5 does NOT trigger
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.26,
meanRetrievalCallsPerTask: 1.7,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.trioStrictDeltaPP).toBeCloseTo(6.0, 6);
expect(verdict.condition5FalsePositiveGuardTriggered).toBe(false);
expect(verdict.condition1Pass).toBe(true);
expect(verdict.accepted).toBe(true);
expect(verdict.reason).toContain('PASS §F.1');
});
it('§F.5 boundary: retrieval = 1.49 → REJECTED (just below floor)', () => {
const candidate = makeCandidate({
shape: 'qwen-non-thinking',
trioStrictPassRateII: 0.30, // delta = +10pp
meanRetrievalCallsPerTask: 1.49,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.condition5FalsePositiveGuardTriggered).toBe(true);
expect(verdict.accepted).toBe(false);
});
it('§F.5 boundary: retrieval = 1.50 → §F.5 NOT triggered (exact floor inclusive)', () => {
// 1.50 ≥ 1.50 false-positive floor → guard does not fire
// But 1.50 < 1.70 §F.1 Qwen floor → condition 1 still fails on different criterion
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.30, // delta = +10pp
meanRetrievalCallsPerTask: 1.50,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.condition5FalsePositiveGuardTriggered).toBe(false);
expect(verdict.condition1Pass).toBe(false); // fails Qwen retrieval floor 1.7
expect(verdict.accepted).toBe(false);
expect(verdict.reason).toContain('FAIL §F.1 Qwen retrieval floor');
});
it('§F.5 does NOT trigger when delta < threshold even if retrieval low (Qwen)', () => {
// delta = +3pp < 5pp threshold → guard not even evaluated
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.23,
meanRetrievalCallsPerTask: 1.0, // low, but delta below threshold
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.condition5FalsePositiveGuardTriggered).toBe(false);
expect(verdict.condition1Pass).toBe(false);
expect(verdict.accepted).toBe(false);
expect(verdict.reason).toContain('FAIL §F.1 trio_strict delta');
});
});
// ───────────────────────────────────────────────────────────────────────────
// §F.5 does NOT apply to non-Qwen shapes
// ───────────────────────────────────────────────────────────────────────────
describe('§F.5 scoping — only applies to Qwen-targeted shapes', () => {
it('claude shape with delta = +6pp + retrieval = 0.5 → ACCEPTED (no false-positive guard)', () => {
const candidate = makeCandidate({
shape: 'claude',
trioStrictPassRateII: 0.26,
meanRetrievalCallsPerTask: 0.5, // would trigger §F.5 if Qwen, but doesn't apply here
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.condition5FalsePositiveGuardTriggered).toBe(false);
expect(verdict.condition1Pass).toBe(true);
expect(verdict.accepted).toBe(true);
});
it('gpt shape with delta = +5pp + retrieval = 0 → ACCEPTED', () => {
const candidate = makeCandidate({
shape: 'gpt',
trioStrictPassRateII: 0.25,
meanRetrievalCallsPerTask: 0,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.condition5FalsePositiveGuardTriggered).toBe(false);
expect(verdict.accepted).toBe(true);
});
it('generic-simple shape: only trio_strict delta matters, no retrieval requirement', () => {
const candidate = makeCandidate({
shape: 'generic-simple',
trioStrictPassRateII: 0.30,
meanRetrievalCallsPerTask: 1.0,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.accepted).toBe(true);
});
});
// ───────────────────────────────────────────────────────────────────────────
// §F condition 1 trio_strict delta — basic threshold tests
// ───────────────────────────────────────────────────────────────────────────
describe('§F condition 1 — trio_strict delta threshold (≥+5pp)', () => {
it('delta = +5pp exactly (boundary inclusive) → condition 1 PASS for non-Qwen', () => {
const candidate = makeCandidate({
shape: 'claude',
trioStrictPassRateII: 0.25,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.trioStrictDeltaPP).toBeCloseTo(5.0, 6);
expect(verdict.condition1Pass).toBe(true);
expect(verdict.accepted).toBe(true);
});
it('delta = +4.99pp (just below boundary) → condition 1 FAIL', () => {
const candidate = makeCandidate({
shape: 'claude',
trioStrictPassRateII: 0.2499,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.condition1Pass).toBe(false);
expect(verdict.accepted).toBe(false);
});
it('negative delta (regression) → condition 1 FAIL', () => {
const candidate = makeCandidate({
shape: 'gpt',
trioStrictPassRateII: 0.10,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.trioStrictDeltaPP).toBeCloseTo(-10.0, 6);
expect(verdict.condition1Pass).toBe(false);
});
it('exposes binding constants for external auditing', () => {
expect(TRIO_STRICT_DELTA_THRESHOLD_PP).toBe(5);
expect(QWEN_RETRIEVAL_ENGAGEMENT_FLOOR).toBe(1.7);
expect(QWEN_FALSE_POSITIVE_RETRIEVAL_FLOOR).toBe(1.5);
});
});
// ───────────────────────────────────────────────────────────────────────────
// §F condition 1 — Qwen-only retrieval engagement floor (1.7)
// ───────────────────────────────────────────────────────────────────────────
describe('§F condition 1 — Qwen retrieval engagement floor (1.7) sub-criterion', () => {
it('Qwen candidate, delta = +5pp, retrieval = 1.69 → FAIL (below 1.7 floor)', () => {
// 1.69 ≥ 1.5 false-positive floor (so §F.5 does not trigger)
// but 1.69 < 1.7 §F.1 Qwen floor (so condition 1 fails)
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.25,
meanRetrievalCallsPerTask: 1.69,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.condition5FalsePositiveGuardTriggered).toBe(false);
expect(verdict.condition1Pass).toBe(false);
expect(verdict.accepted).toBe(false);
expect(verdict.reason).toContain('FAIL §F.1 Qwen retrieval floor');
});
it('Qwen candidate, delta = +5pp, retrieval = 1.70 → PASS (exact floor inclusive)', () => {
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.25,
meanRetrievalCallsPerTask: 1.70,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.condition1Pass).toBe(true);
expect(verdict.accepted).toBe(true);
});
it('Qwen candidate, delta = +5pp, retrieval = 2.5 → PASS (well above floor)', () => {
const candidate = makeCandidate({
shape: 'qwen-non-thinking',
trioStrictPassRateII: 0.25,
meanRetrievalCallsPerTask: 2.5,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.accepted).toBe(true);
});
});
// ───────────────────────────────────────────────────────────────────────────
// Audit log invariants
// ───────────────────────────────────────────────────────────────────────────
describe('audit log invariants', () => {
it('reason string includes shape, delta, retrieval, accepted flag', () => {
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.30,
meanRetrievalCallsPerTask: 2.0,
});
const verdict = evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.20 });
expect(verdict.reason).toContain('shape=qwen-thinking');
expect(verdict.reason).toContain('trio_strict_delta=10.00pp');
expect(verdict.reason).toContain('mean_retrieval_calls=2.00');
expect(verdict.reason).toContain('accepted=true');
});
it('every shape produces a verdict (no exceptions)', () => {
const shapes: ShapeName[] = ['claude', 'qwen-thinking', 'qwen-non-thinking', 'gpt', 'generic-simple'];
for (const shape of shapes) {
const candidate = makeCandidate({ shape, trioStrictPassRateII: 0.5 });
expect(() =>
evaluateCandidate({ candidate, baselineTrioStrictPassRateII: 0.4 }),
).not.toThrow();
}
});
});

View File

@@ -0,0 +1,349 @@
/**
* GEPA Faza 1 — H3 corpus library tests.
*
* Coverage targets per manifest v7 §corpus_design + §amendment_2_integration:
* - Stratification: exactly 50 unique cells (5 × 5 × 2)
* - Deterministic enumeration order (canonical)
* - Instance validation per quality floor
* - Spot-audit sampler determinism (same seed → same sample)
* - Spot-audit halt-on-failure semantics
*/
import { describe, expect, it } from 'vitest';
import {
TASK_FAMILIES,
PERSONAS,
COMPANY_STAGES,
TOTAL_INSTANCES,
DOCS_PER_INSTANCE_MIN,
DOCS_PER_INSTANCE_MAX,
SPOT_AUDIT_SAMPLE_SIZE,
STRATIFICATION_SEED,
TASK_FAMILY_DESCRIPTORS,
type StratificationCell,
type CorpusInstance,
iterateStratificationCells,
listStratificationCells,
buildInstanceId,
validateInstance,
deterministicSample,
selectSpotAuditSample,
runSpotAudit,
corpusSha256,
} from '../../src/faza-1/corpus.js';
// ───────────────────────────────────────────────────────────────────────────
// Helpers
// ───────────────────────────────────────────────────────────────────────────
function makeValidInstance(cell: StratificationCell, ordinal: number = 1): CorpusInstance {
const docs = Array.from({ length: 7 }, (_, i) => ({
title: `DOC ${i + 1} — Sample`,
body: 'x'.repeat(800),
charCount: 800,
}));
return {
instanceId: buildInstanceId(cell, ordinal),
cell,
personaText: 'p'.repeat(200),
scenario: 's'.repeat(400),
sourceDocuments: docs,
question: 'q'.repeat(200),
materialsConcat: docs.map(d => `## ${d.title}\n\n${d.body}`).join('\n\n---\n\n'),
manifestAnchor: 'manifest-v7-gepa-faza1',
generatedBy: 'claude-opus-4-7',
generatedAtIso: '2026-04-28T00:00:00.000Z',
generationCostUsd: 0.10,
};
}
// ───────────────────────────────────────────────────────────────────────────
// Constants exposed for auditing
// ───────────────────────────────────────────────────────────────────────────
describe('manifest v7 §corpus_design constants', () => {
it('TOTAL_INSTANCES = 50', () => {
expect(TOTAL_INSTANCES).toBe(50);
});
it('TASK_FAMILIES has 5 entries', () => {
expect(TASK_FAMILIES).toHaveLength(5);
});
it('PERSONAS has 5 entries', () => {
expect(PERSONAS).toHaveLength(5);
});
it('COMPANY_STAGES has 2 entries', () => {
expect(COMPANY_STAGES).toHaveLength(2);
});
it('5 × 5 × 2 = 50 (stratification yields TOTAL_INSTANCES)', () => {
expect(TASK_FAMILIES.length * PERSONAS.length * COMPANY_STAGES.length).toBe(TOTAL_INSTANCES);
});
it('SPOT_AUDIT_SAMPLE_SIZE = 5 per Amendment 1', () => {
expect(SPOT_AUDIT_SAMPLE_SIZE).toBe(5);
});
it('STRATIFICATION_SEED = 42', () => {
expect(STRATIFICATION_SEED).toBe(42);
});
it('DOCS_PER_INSTANCE bounds', () => {
expect(DOCS_PER_INSTANCE_MIN).toBe(6);
expect(DOCS_PER_INSTANCE_MAX).toBe(8);
});
it('TASK_FAMILY_DESCRIPTORS covers all 5 families', () => {
for (const f of TASK_FAMILIES) {
expect(TASK_FAMILY_DESCRIPTORS[f]).toBeDefined();
expect(TASK_FAMILY_DESCRIPTORS[f].docsPerInstance).toBeGreaterThanOrEqual(DOCS_PER_INSTANCE_MIN);
expect(TASK_FAMILY_DESCRIPTORS[f].docsPerInstance).toBeLessThanOrEqual(DOCS_PER_INSTANCE_MAX);
}
});
it('F1-F3 mirror pilot tasks; F4-F5 are net-new', () => {
expect(TASK_FAMILY_DESCRIPTORS.F1.mirrorPilotTask).toBe('task-1');
expect(TASK_FAMILY_DESCRIPTORS.F2.mirrorPilotTask).toBe('task-2');
expect(TASK_FAMILY_DESCRIPTORS.F3.mirrorPilotTask).toBe('task-3');
expect(TASK_FAMILY_DESCRIPTORS.F4.mirrorPilotTask).toBeNull();
expect(TASK_FAMILY_DESCRIPTORS.F5.mirrorPilotTask).toBeNull();
});
});
// ───────────────────────────────────────────────────────────────────────────
// Stratification enumeration
// ───────────────────────────────────────────────────────────────────────────
describe('iterateStratificationCells / listStratificationCells', () => {
it('yields exactly 50 cells (5 × 5 × 2)', () => {
const cells = listStratificationCells();
expect(cells).toHaveLength(50);
});
it('all 50 cells are unique', () => {
const cells = listStratificationCells();
const keys = new Set(cells.map(c => `${c.family}|${c.persona}|${c.stage}`));
expect(keys.size).toBe(50);
});
it('canonical ordering: F1 first, F5 last', () => {
const cells = listStratificationCells();
expect(cells[0].family).toBe('F1');
expect(cells[cells.length - 1].family).toBe('F5');
});
it('persona enumeration is the inner loop after family', () => {
const cells = listStratificationCells();
// First 10 cells should all be family F1
for (let i = 0; i < 10; i++) {
expect(cells[i].family).toBe('F1');
}
// Cells 10-19 should all be F2
for (let i = 10; i < 20; i++) {
expect(cells[i].family).toBe('F2');
}
});
it('each (family, persona) pair appears exactly twice (once per stage)', () => {
const cells = listStratificationCells();
const pairCounts = new Map<string, number>();
for (const c of cells) {
const key = `${c.family}|${c.persona}`;
pairCounts.set(key, (pairCounts.get(key) ?? 0) + 1);
}
expect(pairCounts.size).toBe(25); // 5 × 5 family-persona pairs
for (const count of pairCounts.values()) {
expect(count).toBe(2); // once per stage
}
});
it('iterator is deterministic (same yield order across calls)', () => {
const a = listStratificationCells();
const b = listStratificationCells();
expect(a).toEqual(b);
});
});
// ───────────────────────────────────────────────────────────────────────────
// buildInstanceId
// ───────────────────────────────────────────────────────────────────────────
describe('buildInstanceId', () => {
it('produces stable IDs with h3- prefix + zero-padded ordinal', () => {
const id = buildInstanceId({ family: 'F1', persona: 'p2_cfo', stage: 'stage_a_series_b_growth_burning' }, 7);
expect(id).toBe('h3-F1-p2_cfo-stage_a_series_b_growth_burning-007');
});
it('default ordinal is 1', () => {
const id = buildInstanceId({ family: 'F3', persona: 'p4_vp_finance', stage: 'stage_b_post_profitable_consolidation' });
expect(id).toContain('-001');
});
});
// ───────────────────────────────────────────────────────────────────────────
// validateInstance
// ───────────────────────────────────────────────────────────────────────────
describe('validateInstance — quality floor', () => {
it('valid synthetic instance passes', () => {
const inst = makeValidInstance({ family: 'F1', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' });
const r = validateInstance(inst);
expect(r.valid).toBe(true);
expect(r.violations).toHaveLength(0);
});
it('FAIL: too few docs', () => {
const inst = makeValidInstance({ family: 'F1', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' });
inst.sourceDocuments = inst.sourceDocuments.slice(0, 3);
const r = validateInstance(inst);
expect(r.valid).toBe(false);
expect(r.violations[0]).toContain('docs count');
expect(r.violations[0]).toContain('< 6 min');
});
it('FAIL: too many docs', () => {
const inst = makeValidInstance({ family: 'F1', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' });
inst.sourceDocuments = [...inst.sourceDocuments, ...inst.sourceDocuments, ...inst.sourceDocuments];
const r = validateInstance(inst);
expect(r.valid).toBe(false);
expect(r.violations[0]).toContain('> 8 max');
});
it('FAIL: persona too short', () => {
const inst = makeValidInstance({ family: 'F1', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' });
inst.personaText = 'short';
const r = validateInstance(inst);
expect(r.valid).toBe(false);
expect(r.violations.some(v => v.includes('persona length'))).toBe(true);
});
it('PASS: rich persona up to 1500 chars (post-probe loosening)', () => {
const inst = makeValidInstance({ family: 'F1', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' });
inst.personaText = 'p'.repeat(1400);
const r = validateInstance(inst);
expect(r.valid).toBe(true);
});
it('FAIL: persona above 1500 cap', () => {
const inst = makeValidInstance({ family: 'F1', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' });
inst.personaText = 'p'.repeat(1600);
const r = validateInstance(inst);
expect(r.valid).toBe(false);
expect(r.violations.some(v => v.includes('persona length'))).toBe(true);
});
it('PASS: empty scenario (oracle embedded it in personaText)', () => {
const inst = makeValidInstance({ family: 'F1', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' });
inst.scenario = ''; // embedded case
const r = validateInstance(inst);
expect(r.valid).toBe(true);
});
it('FAIL: doc charCount mismatch with body length', () => {
const inst = makeValidInstance({ family: 'F1', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' });
inst.sourceDocuments[0].charCount = 999; // intentional mismatch
const r = validateInstance(inst);
expect(r.valid).toBe(false);
expect(r.violations.some(v => v.includes('charCount'))).toBe(true);
});
it('FAIL: instanceId missing h3- prefix', () => {
const inst = makeValidInstance({ family: 'F1', persona: 'p1_founder_ceo', stage: 'stage_a_series_b_growth_burning' });
inst.instanceId = 'wrong-prefix-001';
const r = validateInstance(inst);
expect(r.valid).toBe(false);
expect(r.violations.some(v => v.includes('h3- prefix'))).toBe(true);
});
});
// ───────────────────────────────────────────────────────────────────────────
// deterministicSample / selectSpotAuditSample
// ───────────────────────────────────────────────────────────────────────────
describe('deterministicSample', () => {
it('returns sample of requested size', () => {
const items = Array.from({ length: 50 }, (_, i) => `item-${i}`);
const sample = deterministicSample(items, 5);
expect(sample).toHaveLength(5);
});
it('same seed produces identical sample (reproducibility)', () => {
const items = Array.from({ length: 50 }, (_, i) => `item-${i}`);
const a = deterministicSample(items, 5, 42);
const b = deterministicSample(items, 5, 42);
expect(a).toEqual(b);
});
it('different seeds produce different samples', () => {
const items = Array.from({ length: 50 }, (_, i) => `item-${i}`);
const a = deterministicSample(items, 5, 42);
const c = deterministicSample(items, 5, 100);
expect(a).not.toEqual(c);
});
it('sampleSize >= items.length returns full list copy', () => {
const items = ['a', 'b', 'c'];
const sample = deterministicSample(items, 5);
expect(sample).toHaveLength(3);
expect(sample).toEqual(items);
expect(sample).not.toBe(items); // copy, not reference
});
});
describe('selectSpotAuditSample', () => {
it('returns 5 instances by default (manifest v7 spot_audit.sample_size)', () => {
const cells = listStratificationCells();
const instances = cells.map(c => makeValidInstance(c));
const sample = selectSpotAuditSample(instances);
expect(sample).toHaveLength(5);
});
it('reproducible with seed=42 (same instances picked across runs)', () => {
const cells = listStratificationCells();
const instances = cells.map(c => makeValidInstance(c));
const a = selectSpotAuditSample(instances);
const b = selectSpotAuditSample(instances);
expect(a.map(i => i.instanceId)).toEqual(b.map(i => i.instanceId));
});
});
// ───────────────────────────────────────────────────────────────────────────
// runSpotAudit aggregate
// ───────────────────────────────────────────────────────────────────────────
describe('runSpotAudit', () => {
it('PASS when all sampled instances valid', () => {
const cells = listStratificationCells();
const instances = cells.map(c => makeValidInstance(c));
const report = runSpotAudit(instances);
expect(report.sampleSize).toBe(5);
expect(report.haltOnFailure).toBe(false);
expect(report.haltReason).toBeUndefined();
});
it('HALT when any sampled instance invalid (manifest v7 spot_audit.halt_on)', () => {
const cells = listStratificationCells();
const instances = cells.map(c => makeValidInstance(c));
// Corrupt every instance (so sample will definitely include corrupted ones)
for (const inst of instances) {
inst.sourceDocuments = inst.sourceDocuments.slice(0, 2); // below 6 min
}
const report = runSpotAudit(instances);
expect(report.haltOnFailure).toBe(true);
expect(report.haltReason).toMatch(/spot-audit instances failed validation/);
});
});
// ───────────────────────────────────────────────────────────────────────────
// corpusSha256
// ───────────────────────────────────────────────────────────────────────────
describe('corpusSha256', () => {
it('produces deterministic SHA across runs', () => {
const cells = listStratificationCells().slice(0, 5);
const instances = cells.map(c => makeValidInstance(c));
const a = corpusSha256(instances);
const b = corpusSha256(instances);
expect(a).toBe(b);
});
it('different corpora produce different SHAs', () => {
const cells = listStratificationCells();
const a = cells.slice(0, 5).map(c => makeValidInstance(c));
const b = cells.slice(5, 10).map(c => makeValidInstance(c));
expect(corpusSha256(a)).not.toBe(corpusSha256(b));
});
});

View File

@@ -0,0 +1,180 @@
/**
* GEPA Faza 1 — cost tracker tests.
*
* Coverage targets:
* - Halt triggers: HARD_CAP_USD_BREACH ($100), INTERNAL_HALT_USD_BREACH ($80),
* SUPER_LINEAR_PROJECTION_BREACH (>30% over expected)
* - Audit cadence (every 20 evaluations)
* - Immutable state updates per coding-style.md
* - Projection multiplier 1.5× per brief §6.7
*/
import { describe, expect, it } from 'vitest';
import {
HARD_CAP_USD,
INTERNAL_HALT_USD,
SUPER_LINEAR_MULTIPLIER,
SUPER_LINEAR_OVERAGE_THRESHOLD,
AUDIT_CADENCE_EVAL_COUNT,
createCostTracker,
recordEvaluation,
checkHaltTriggers,
shouldAudit,
} from '../../src/faza-1/cost-tracker.js';
describe('constants exposed for auditing', () => {
it('HARD_CAP_USD = $100', () => {
expect(HARD_CAP_USD).toBe(100.0);
});
it('INTERNAL_HALT_USD = $80', () => {
expect(INTERNAL_HALT_USD).toBe(80.0);
});
it('SUPER_LINEAR_MULTIPLIER = 1.5 per brief §6.7', () => {
expect(SUPER_LINEAR_MULTIPLIER).toBe(1.5);
});
it('SUPER_LINEAR_OVERAGE_THRESHOLD = 0.30 (30%)', () => {
expect(SUPER_LINEAR_OVERAGE_THRESHOLD).toBe(0.30);
});
it('AUDIT_CADENCE_EVAL_COUNT = 20 per launch decision §A.7', () => {
expect(AUDIT_CADENCE_EVAL_COUNT).toBe(20);
});
});
describe('createCostTracker', () => {
it('initializes with zero spend + projection = baseline × 1.5', () => {
const t = createCostTracker(0.50);
expect(t.cumulativeUsd).toBe(0);
expect(t.evaluationCount).toBe(0);
expect(t.projectionPerEvalUsd).toBe(0.75); // 0.50 × 1.5
});
});
describe('recordEvaluation — immutable updates', () => {
it('returns new state object (does not mutate input)', () => {
const before = createCostTracker(0.50);
const after = recordEvaluation(before, 0.30);
expect(before.cumulativeUsd).toBe(0);
expect(before.evaluationCount).toBe(0);
expect(after.cumulativeUsd).toBe(0.30);
expect(after.evaluationCount).toBe(1);
expect(before).not.toBe(after);
});
it('cumulative cost accumulates across multiple recordings', () => {
let s = createCostTracker(0.50);
s = recordEvaluation(s, 0.40);
s = recordEvaluation(s, 0.60);
s = recordEvaluation(s, 0.50);
expect(s.cumulativeUsd).toBeCloseTo(1.50, 6);
expect(s.evaluationCount).toBe(3);
});
});
describe('checkHaltTriggers — HARD_CAP_USD_BREACH', () => {
it('triggers at $100.01', () => {
const s = { cumulativeUsd: 100.01, evaluationCount: 200, projectionPerEvalUsd: 0.50 };
const r = checkHaltTriggers(s);
expect(r.haltReason).toBe('HARD_CAP_USD_BREACH');
expect(r.message).toContain('HARD CAP BREACH');
});
it('does NOT trigger at $100.00 exactly (boundary inclusive of pass)', () => {
const s = { cumulativeUsd: 100.00, evaluationCount: 200, projectionPerEvalUsd: 0.50 };
const r = checkHaltTriggers(s);
expect(r.haltReason).not.toBe('HARD_CAP_USD_BREACH');
// It will trigger INTERNAL_HALT since $100 > $80, but not HARD_CAP
expect(r.haltReason).toBe('INTERNAL_HALT_USD_BREACH');
});
it('takes precedence over INTERNAL_HALT (most severe first)', () => {
const s = { cumulativeUsd: 105.0, evaluationCount: 200, projectionPerEvalUsd: 0.50 };
const r = checkHaltTriggers(s);
expect(r.haltReason).toBe('HARD_CAP_USD_BREACH');
});
});
describe('checkHaltTriggers — INTERNAL_HALT_USD_BREACH', () => {
it('triggers at $80.01', () => {
const s = { cumulativeUsd: 80.01, evaluationCount: 160, projectionPerEvalUsd: 0.50 };
const r = checkHaltTriggers(s);
expect(r.haltReason).toBe('INTERNAL_HALT_USD_BREACH');
});
it('does NOT trigger at $80.00 exactly', () => {
const s = { cumulativeUsd: 80.00, evaluationCount: 160, projectionPerEvalUsd: 0.50 };
const r = checkHaltTriggers(s);
expect(r.haltReason).not.toBe('INTERNAL_HALT_USD_BREACH');
});
});
describe('checkHaltTriggers — SUPER_LINEAR_PROJECTION_BREACH', () => {
it('triggers when actual exceeds expected by >30%', () => {
// 10 evals × $0.75/eval projection = $7.50 expected; actual $10 = 33% over
const s = { cumulativeUsd: 10.0, evaluationCount: 10, projectionPerEvalUsd: 0.75 };
const r = checkHaltTriggers(s);
expect(r.haltReason).toBe('SUPER_LINEAR_PROJECTION_BREACH');
expect(r.overageFraction).toBeCloseTo(0.333, 2);
});
it('does NOT trigger when actual is exactly at projection', () => {
const s = { cumulativeUsd: 7.50, evaluationCount: 10, projectionPerEvalUsd: 0.75 };
const r = checkHaltTriggers(s);
expect(r.haltReason).toBe('NONE');
expect(r.overageFraction).toBe(0);
});
it('does NOT trigger when overage is exactly at 30% threshold (boundary inclusive of pass)', () => {
// Expected $7.50, actual $9.75 = 30% over exactly
const s = { cumulativeUsd: 9.75, evaluationCount: 10, projectionPerEvalUsd: 0.75 };
const r = checkHaltTriggers(s);
expect(r.haltReason).toBe('NONE');
});
it('does NOT trigger before any eval recorded (no expected baseline)', () => {
const s = { cumulativeUsd: 0, evaluationCount: 0, projectionPerEvalUsd: 0.75 };
const r = checkHaltTriggers(s);
expect(r.haltReason).toBe('NONE');
});
it('takes precedence below INTERNAL_HALT (super-linear can fire while still under $80)', () => {
// Expected $1.50 at 2 evals × $0.75; actual $5 = 233% over → super-linear breach
const s = { cumulativeUsd: 5.0, evaluationCount: 2, projectionPerEvalUsd: 0.75 };
const r = checkHaltTriggers(s);
expect(r.haltReason).toBe('SUPER_LINEAR_PROJECTION_BREACH');
expect(r.cumulativeUsd).toBeLessThan(INTERNAL_HALT_USD);
});
});
describe('shouldAudit — audit cadence', () => {
it('returns false at evaluationCount = 0', () => {
expect(shouldAudit({ cumulativeUsd: 0, evaluationCount: 0, projectionPerEvalUsd: 0.5 })).toBe(false);
});
it('returns true at evaluationCount = 20 (first audit boundary)', () => {
expect(shouldAudit({ cumulativeUsd: 10, evaluationCount: 20, projectionPerEvalUsd: 0.5 })).toBe(true);
});
it('returns true at every multiple of 20', () => {
for (const n of [40, 60, 80, 100, 200]) {
expect(shouldAudit({ cumulativeUsd: n / 2, evaluationCount: n, projectionPerEvalUsd: 0.5 })).toBe(true);
}
});
it('returns false at non-boundary counts', () => {
for (const n of [1, 5, 19, 21, 39, 99]) {
expect(shouldAudit({ cumulativeUsd: n / 2, evaluationCount: n, projectionPerEvalUsd: 0.5 })).toBe(false);
}
});
});
describe('end-to-end Faza 1 cost projection', () => {
it('expected total $100.50 reaches HARD_CAP_USD_BREACH (Amendment 1 §4 tight margin)', () => {
// Simulate Faza 1 expected breakdown reaching $100.50
let s = createCostTracker(0.50); // baseline $0.50/eval
// 50 corpus + 40 NULL + 120 Gen 1 + 25 held-out = 235 evaluations × roughly $0.43/eval
// For test purposes just simulate hitting $100.50
s = { ...s, cumulativeUsd: 100.50, evaluationCount: 235 };
const r = checkHaltTriggers(s);
expect(r.haltReason).toBe('HARD_CAP_USD_BREACH');
});
});

View File

@@ -0,0 +1,595 @@
/**
* GEPA Faza 1 — fitness function tests.
*
* Coverage targets per manifest v7 §amendment_2_integration.scaffold_test_coverage_NEW_requirements:
* - 5 retrieval engagement boundary cases (1.49 / 1.50 / 1.99 / 2.00 / 2.50)
* - 5 shape-routing tests (claude/gpt/generic-simple excluded; qwen-thinking/qwen-non-thinking included)
* - Cost penalty: zero overage + positive overage scenarios
* - End-to-end computeFitness invariants on both Qwen and non-Qwen shapes
*
* §F.5 false-positive guard tests live in acceptance.test.ts (separate module).
*/
import { describe, expect, it } from 'vitest';
import {
computeRetrievalEngagementBonus,
computeCostPenalty,
computeFitness,
RETRIEVAL_ENGAGEMENT_BANDS,
computeTier2RetrievalBonus,
computeTieredFitness,
computeDeltaFloorVerdict,
TIER_2_BONUS_CAP,
TIER_2_BONUS_PER_PP,
TIER_3_BONUS_FULL_INVARIANCE,
TIER_3_ANCHOR_COUNT_FULL,
DELTA_FLOOR_THRESHOLDS,
} from '../../src/faza-1/fitness.js';
import {
type CandidateMetrics,
type ShapeName,
QWEN_TARGETED_SHAPES,
NON_QWEN_SHAPES,
NULL_BASELINE_PER_SHAPE,
NULL_BASELINE_AGGREGATE,
} from '../../src/faza-1/types.js';
// ───────────────────────────────────────────────────────────────────────────
// Helpers
// ───────────────────────────────────────────────────────────────────────────
function makeCandidate(overrides: Partial<CandidateMetrics> & Pick<CandidateMetrics, 'shape'>): CandidateMetrics {
return {
candidateId: overrides.candidateId ?? `${overrides.shape}-test-candidate`,
shape: overrides.shape,
evaluations: overrides.evaluations ?? [],
trioStrictPassRateII: overrides.trioStrictPassRateII ?? 0.5,
trioStrictPassRateI: overrides.trioStrictPassRateI ?? 0.5,
meanRetrievalCallsPerTask: overrides.meanRetrievalCallsPerTask ?? 1.5,
meanCostUsd: overrides.meanCostUsd ?? 0.5,
};
}
// ───────────────────────────────────────────────────────────────────────────
// computeRetrievalEngagementBonus — Amendment 2 §3 binding boundary tests
// ───────────────────────────────────────────────────────────────────────────
describe('computeRetrievalEngagementBonus — Amendment 2 §3 boundary cases', () => {
// Per manifest v7 §amendment_2_integration.scaffold_test_coverage_NEW_requirements
// mandatory_boundary_tests block — these 5 cases are BINDING contract tests.
it('qwen-thinking, mean retrieval_calls = 1.49 → expect bonus = -0.05', () => {
expect(computeRetrievalEngagementBonus('qwen-thinking', 1.49)).toBe(-0.05);
});
it('qwen-thinking, mean retrieval_calls = 1.50 → expect bonus = 0.00 (lower threshold inclusive)', () => {
expect(computeRetrievalEngagementBonus('qwen-thinking', 1.50)).toBe(0.0);
});
it('qwen-thinking, mean retrieval_calls = 1.99 → expect bonus = 0.00', () => {
expect(computeRetrievalEngagementBonus('qwen-thinking', 1.99)).toBe(0.0);
});
it('qwen-thinking, mean retrieval_calls = 2.00 → expect bonus = +0.05 (upper threshold inclusive)', () => {
expect(computeRetrievalEngagementBonus('qwen-thinking', 2.00)).toBe(0.05);
});
it('qwen-thinking, mean retrieval_calls = 2.50 → expect bonus = +0.05', () => {
expect(computeRetrievalEngagementBonus('qwen-thinking', 2.50)).toBe(0.05);
});
// Symmetry check on qwen-non-thinking (other Qwen-targeted shape)
it('qwen-non-thinking exhibits identical band behavior to qwen-thinking', () => {
expect(computeRetrievalEngagementBonus('qwen-non-thinking', 1.49)).toBe(-0.05);
expect(computeRetrievalEngagementBonus('qwen-non-thinking', 1.50)).toBe(0.0);
expect(computeRetrievalEngagementBonus('qwen-non-thinking', 2.00)).toBe(0.05);
});
// Edge cases beyond the 5 mandatory boundaries — defensive coverage
it('handles 0 retrieval calls (extreme low)', () => {
expect(computeRetrievalEngagementBonus('qwen-thinking', 0)).toBe(-0.05);
});
it('handles very high retrieval calls (extreme high)', () => {
expect(computeRetrievalEngagementBonus('qwen-thinking', 10.0)).toBe(0.05);
});
it('exposes binding band constants for external auditing', () => {
expect(RETRIEVAL_ENGAGEMENT_BANDS.upperThreshold).toBe(2.0);
expect(RETRIEVAL_ENGAGEMENT_BANDS.lowerThreshold).toBe(1.5);
expect(RETRIEVAL_ENGAGEMENT_BANDS.bonusPlus).toBe(0.05);
expect(RETRIEVAL_ENGAGEMENT_BANDS.bonusZero).toBe(0.0);
expect(RETRIEVAL_ENGAGEMENT_BANDS.bonusMinus).toBe(-0.05);
});
});
// ───────────────────────────────────────────────────────────────────────────
// Shape-routing tests — Amendment 2 §3 mandatory_routing_tests
// ───────────────────────────────────────────────────────────────────────────
describe('shape-routing — Amendment 2 §3 retrieval engagement excluded for non-Qwen shapes', () => {
// Per Amendment 2 §3 rationale: "Phase 4.5 finding is Qwen-specific. Opus
// shape does NOT have the gap. Applying retrieval-engagement bonus uniformly
// across all shapes would distort fitness for shapes that don't have the
// underlying behavioral problem."
it('claude shape: bonus computation NOT applied (excluded)', () => {
expect(computeRetrievalEngagementBonus('claude', 0)).toBe(0.0);
expect(computeRetrievalEngagementBonus('claude', 1.49)).toBe(0.0);
expect(computeRetrievalEngagementBonus('claude', 2.50)).toBe(0.0);
expect(computeRetrievalEngagementBonus('claude', 100)).toBe(0.0);
});
it('gpt shape: bonus computation NOT applied (excluded)', () => {
expect(computeRetrievalEngagementBonus('gpt', 0)).toBe(0.0);
expect(computeRetrievalEngagementBonus('gpt', 1.49)).toBe(0.0);
expect(computeRetrievalEngagementBonus('gpt', 2.50)).toBe(0.0);
});
it('generic-simple shape: bonus computation NOT applied (excluded)', () => {
expect(computeRetrievalEngagementBonus('generic-simple', 0)).toBe(0.0);
expect(computeRetrievalEngagementBonus('generic-simple', 1.49)).toBe(0.0);
expect(computeRetrievalEngagementBonus('generic-simple', 2.50)).toBe(0.0);
});
it('qwen-thinking shape: bonus computation IS applied', () => {
expect(computeRetrievalEngagementBonus('qwen-thinking', 1.49)).toBe(-0.05);
expect(computeRetrievalEngagementBonus('qwen-thinking', 2.50)).toBe(0.05);
});
it('qwen-non-thinking shape: bonus computation IS applied', () => {
expect(computeRetrievalEngagementBonus('qwen-non-thinking', 1.49)).toBe(-0.05);
expect(computeRetrievalEngagementBonus('qwen-non-thinking', 2.50)).toBe(0.05);
});
it('shape-class set membership matches manifest v7 declaration', () => {
// Manifest v7 §metric_operationalization.retrieval_engagement_bonus.applies_to_shapes
expect(QWEN_TARGETED_SHAPES.has('qwen-thinking')).toBe(true);
expect(QWEN_TARGETED_SHAPES.has('qwen-non-thinking')).toBe(true);
expect(QWEN_TARGETED_SHAPES.has('claude')).toBe(false);
expect(QWEN_TARGETED_SHAPES.has('gpt')).toBe(false);
expect(QWEN_TARGETED_SHAPES.has('generic-simple')).toBe(false);
// Manifest v7 §metric_operationalization.retrieval_engagement_bonus.excluded_shapes
expect(NON_QWEN_SHAPES.has('claude')).toBe(true);
expect(NON_QWEN_SHAPES.has('gpt')).toBe(true);
expect(NON_QWEN_SHAPES.has('generic-simple')).toBe(true);
expect(NON_QWEN_SHAPES.has('qwen-thinking')).toBe(false);
expect(NON_QWEN_SHAPES.has('qwen-non-thinking')).toBe(false);
});
it('partition: every ShapeName is in exactly one set (no overlap, no gap)', () => {
const all: ShapeName[] = ['claude', 'qwen-thinking', 'qwen-non-thinking', 'gpt', 'generic-simple'];
for (const s of all) {
const inQwen = QWEN_TARGETED_SHAPES.has(s);
const inNonQwen = NON_QWEN_SHAPES.has(s);
expect(inQwen !== inNonQwen).toBe(true); // exactly one
}
});
});
// ───────────────────────────────────────────────────────────────────────────
// computeCostPenalty
// ───────────────────────────────────────────────────────────────────────────
describe('computeCostPenalty — brief §3.1 0.5pp per $0.10 above baseline median', () => {
it('returns 0 when candidate cost equals baseline median', () => {
expect(computeCostPenalty(0.50, 0.50)).toBe(0.0);
});
it('returns 0 when candidate cost below baseline median', () => {
expect(computeCostPenalty(0.30, 0.50)).toBe(0.0);
});
it('returns 0.005 (0.5pp) for $0.10 overage', () => {
expect(computeCostPenalty(0.60, 0.50)).toBeCloseTo(0.005, 6);
});
it('returns 0.025 (2.5pp) for $0.50 overage', () => {
expect(computeCostPenalty(1.00, 0.50)).toBeCloseTo(0.025, 6);
});
it('returns 0.05 (5pp) for $1.00 overage', () => {
expect(computeCostPenalty(1.50, 0.50)).toBeCloseTo(0.05, 6);
});
it('handles fractional overage', () => {
expect(computeCostPenalty(0.55, 0.50)).toBeCloseTo(0.0025, 6); // $0.05 overage = 0.25pp
});
});
// ───────────────────────────────────────────────────────────────────────────
// computeFitness — end-to-end aggregate
// ───────────────────────────────────────────────────────────────────────────
describe('computeFitness — Qwen-targeted shape branch', () => {
it('applies retrieval engagement bonus + cost penalty for qwen-thinking', () => {
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.50,
meanRetrievalCallsPerTask: 2.0, // expect +0.05 bonus
meanCostUsd: 0.60, // expect +0.005 cost penalty (vs 0.50 baseline)
});
const result = computeFitness({ candidate, baselineMedianCostUsd: 0.50 });
expect(result.trioStrictPassRateII).toBe(0.50);
expect(result.retrievalEngagementBonus).toBe(0.05);
expect(result.costPenalty).toBeCloseTo(0.005, 6);
expect(result.fitness).toBeCloseTo(0.50 + 0.05 - 0.005, 6);
expect(result.retrievalEngagementApplied).toBe(true);
});
it('applies negative retrieval engagement bonus when below 1.5', () => {
const candidate = makeCandidate({
shape: 'qwen-non-thinking',
trioStrictPassRateII: 0.30,
meanRetrievalCallsPerTask: 1.0, // expect -0.05 bonus
meanCostUsd: 0.50, // no cost overage
});
const result = computeFitness({ candidate, baselineMedianCostUsd: 0.50 });
expect(result.retrievalEngagementBonus).toBe(-0.05);
expect(result.fitness).toBeCloseTo(0.30 - 0.05 - 0.0, 6);
expect(result.retrievalEngagementApplied).toBe(true);
});
it('zero band: candidate retrieves in [1.5, 2.0) gets neutral bonus', () => {
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.40,
meanRetrievalCallsPerTask: 1.7,
meanCostUsd: 0.50,
});
const result = computeFitness({ candidate, baselineMedianCostUsd: 0.50 });
expect(result.retrievalEngagementBonus).toBe(0.0);
expect(result.fitness).toBe(0.40);
});
});
describe('computeFitness — non-Qwen shape branch', () => {
it('claude shape: no retrieval engagement bonus regardless of retrieval_calls', () => {
const candidate = makeCandidate({
shape: 'claude',
trioStrictPassRateII: 0.60,
meanRetrievalCallsPerTask: 2.5, // would be +0.05 if Qwen, but excluded for claude
meanCostUsd: 0.50,
});
const result = computeFitness({ candidate, baselineMedianCostUsd: 0.50 });
expect(result.retrievalEngagementBonus).toBe(0.0);
expect(result.fitness).toBe(0.60);
expect(result.retrievalEngagementApplied).toBe(false);
});
it('gpt shape: no retrieval engagement bonus regardless of retrieval_calls', () => {
const candidate = makeCandidate({
shape: 'gpt',
trioStrictPassRateII: 0.45,
meanRetrievalCallsPerTask: 0.5, // would be -0.05 if Qwen, excluded for gpt
meanCostUsd: 0.50,
});
const result = computeFitness({ candidate, baselineMedianCostUsd: 0.50 });
expect(result.retrievalEngagementBonus).toBe(0.0);
expect(result.fitness).toBe(0.45);
expect(result.retrievalEngagementApplied).toBe(false);
});
it('generic-simple shape: cost penalty still applies, but no retrieval bonus', () => {
const candidate = makeCandidate({
shape: 'generic-simple',
trioStrictPassRateII: 0.70,
meanRetrievalCallsPerTask: 3.0,
meanCostUsd: 1.00, // $0.50 overage → 2.5pp penalty
});
const result = computeFitness({ candidate, baselineMedianCostUsd: 0.50 });
expect(result.retrievalEngagementBonus).toBe(0.0);
expect(result.costPenalty).toBeCloseTo(0.025, 6);
expect(result.fitness).toBeCloseTo(0.70 - 0.025, 6);
});
});
describe('computeFitness — invariants', () => {
it('retrievalEngagementApplied flag matches QWEN_TARGETED_SHAPES membership', () => {
const shapes: ShapeName[] = ['claude', 'qwen-thinking', 'qwen-non-thinking', 'gpt', 'generic-simple'];
for (const shape of shapes) {
const result = computeFitness({
candidate: makeCandidate({ shape, meanRetrievalCallsPerTask: 1.5, meanCostUsd: 0.5 }),
baselineMedianCostUsd: 0.5,
});
expect(result.retrievalEngagementApplied).toBe(QWEN_TARGETED_SHAPES.has(shape));
}
});
it('fitness components sum to fitness within floating point tolerance', () => {
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.55,
meanRetrievalCallsPerTask: 2.1,
meanCostUsd: 0.65,
});
const result = computeFitness({ candidate, baselineMedianCostUsd: 0.50 });
const expected = result.trioStrictPassRateII + result.retrievalEngagementBonus - result.costPenalty;
expect(result.fitness).toBeCloseTo(expected, 10);
});
});
// ───────────────────────────────────────────────────────────────────────────
// Amendment 7 — Tier 2 retrieval bonus (continuous)
// ───────────────────────────────────────────────────────────────────────────
describe('Amendment 7 — computeTier2RetrievalBonus (continuous formula)', () => {
it('returns 0 for non-Qwen shape regardless of retrieval delta', () => {
expect(computeTier2RetrievalBonus('claude', 2.0, 1.0)).toBe(0);
expect(computeTier2RetrievalBonus('gpt', 5.0, 1.0)).toBe(0);
expect(computeTier2RetrievalBonus('generic-simple', 3.0, 1.0)).toBe(0);
});
it('returns 0 for Qwen-targeted shape when delta ≤ 0 (no negative bonus)', () => {
expect(computeTier2RetrievalBonus('qwen-thinking', 1.0, 1.5)).toBe(0);
expect(computeTier2RetrievalBonus('qwen-thinking', 1.12, 1.12)).toBe(0); // exact zero delta
expect(computeTier2RetrievalBonus('qwen-non-thinking', 1.20, 1.25)).toBe(0); // small negative
});
it('formula: 0.05 bonus per pp above baseline (1pp = 0.01 absolute)', () => {
// baseline 1.12, candidate 1.13 = +0.01 = +1pp → 0.05 bonus
expect(computeTier2RetrievalBonus('qwen-thinking', 1.13, 1.12)).toBeCloseTo(0.05, 10);
// baseline 1.12, candidate 1.14 = +0.02 = +2pp → 0.10 bonus
expect(computeTier2RetrievalBonus('qwen-thinking', 1.14, 1.12)).toBeCloseTo(0.10, 10);
// baseline 1.12, candidate 1.15 = +0.03 = +3pp → 0.15 bonus
expect(computeTier2RetrievalBonus('qwen-thinking', 1.15, 1.12)).toBeCloseTo(0.15, 10);
// baseline 1.12, candidate 1.16 = +0.04 = +4pp → 0.20 bonus
expect(computeTier2RetrievalBonus('qwen-thinking', 1.16, 1.12)).toBeCloseTo(0.20, 10);
// baseline 1.12, candidate 1.17 = +0.05 = +5pp → 0.25 (cap)
expect(computeTier2RetrievalBonus('qwen-thinking', 1.17, 1.12)).toBeCloseTo(0.25, 10);
});
it('cap holds at +5pp absolute and beyond (cap = 0.25)', () => {
expect(computeTier2RetrievalBonus('qwen-thinking', 1.20, 1.12)).toBe(TIER_2_BONUS_CAP);
expect(computeTier2RetrievalBonus('qwen-thinking', 2.00, 1.12)).toBe(TIER_2_BONUS_CAP);
expect(computeTier2RetrievalBonus('qwen-thinking', 5.00, 1.12)).toBe(TIER_2_BONUS_CAP);
expect(computeTier2RetrievalBonus('qwen-non-thinking', 1.30, 1.25)).toBe(TIER_2_BONUS_CAP);
});
it('Tier 2 weight constants match Amendment 7 §fitness_function_tiered.tier_2', () => {
expect(TIER_2_BONUS_PER_PP).toBe(0.05);
expect(TIER_2_BONUS_CAP).toBe(0.25);
});
});
// ───────────────────────────────────────────────────────────────────────────
// Amendment 7 — computeTieredFitness (Tier 1/2/3 + saturated regime aggregate)
// ───────────────────────────────────────────────────────────────────────────
describe('Amendment 7 — computeTieredFitness', () => {
it('Tier 1 = NULL pass rate delta in pp (signed)', () => {
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.95,
meanRetrievalCallsPerTask: 1.12,
});
const result = computeTieredFitness({
candidate,
nullBaselinePassRateII: 0.875,
nullBaselineMeanRetrievalCallsPerTask: 1.12,
mutationValidatorPassed: true,
saturatedRegime: true,
});
expect(result.tier1DeltaPP).toBeCloseTo(7.5, 10); // 0.95 - 0.875 = 0.075 = 7.5pp
});
it('Tier 1 negative when candidate regresses below NULL baseline', () => {
const candidate = makeCandidate({
shape: 'claude',
trioStrictPassRateII: 0.75,
meanRetrievalCallsPerTask: 1.12,
});
const result = computeTieredFitness({
candidate,
nullBaselinePassRateII: 0.875,
nullBaselineMeanRetrievalCallsPerTask: 1.12,
mutationValidatorPassed: true,
saturatedRegime: true,
});
expect(result.tier1DeltaPP).toBeCloseTo(-12.5, 10); // 0.75 - 0.875 = -0.125 = -12.5pp
});
it('Tier 2 only applies to Qwen-targeted shapes', () => {
for (const shape of ['claude', 'gpt', 'generic-simple'] as const) {
const result = computeTieredFitness({
candidate: makeCandidate({ shape, meanRetrievalCallsPerTask: 5.0 }),
nullBaselinePassRateII: 0.875,
nullBaselineMeanRetrievalCallsPerTask: 1.12,
mutationValidatorPassed: true,
saturatedRegime: true,
});
expect(result.tier2RetrievalBonus).toBe(0);
}
});
it('Tier 3 = 0.10 if mutation validator passed; 0 otherwise', () => {
const base = {
candidate: makeCandidate({ shape: 'claude' }),
nullBaselinePassRateII: 0.875,
nullBaselineMeanRetrievalCallsPerTask: 1.12,
saturatedRegime: true,
};
expect(computeTieredFitness({ ...base, mutationValidatorPassed: true }).tier3CellSemanticInvarianceBonus).toBe(
TIER_3_BONUS_FULL_INVARIANCE,
);
expect(computeTieredFitness({ ...base, mutationValidatorPassed: false }).tier3CellSemanticInvarianceBonus).toBe(0);
});
it('cellSemanticAnchorInvarianceCount: 7 if validator passed; 0 otherwise', () => {
const base = {
candidate: makeCandidate({ shape: 'claude' }),
nullBaselinePassRateII: 0.875,
nullBaselineMeanRetrievalCallsPerTask: 1.12,
saturatedRegime: true,
};
expect(computeTieredFitness({ ...base, mutationValidatorPassed: true }).cellSemanticAnchorInvarianceCount).toBe(
TIER_3_ANCHOR_COUNT_FULL,
);
expect(computeTieredFitness({ ...base, mutationValidatorPassed: false }).cellSemanticAnchorInvarianceCount).toBe(0);
});
it('aggregateSaturatedRegime = tier_2 + tier_3 (Tier 1 NOT included)', () => {
const candidate = makeCandidate({
shape: 'qwen-thinking',
trioStrictPassRateII: 0.95, // would give tier1 = 7.5pp
meanRetrievalCallsPerTask: 1.15, // 1.12 baseline → +3pp → tier2 = 0.15
});
const result = computeTieredFitness({
candidate,
nullBaselinePassRateII: 0.875,
nullBaselineMeanRetrievalCallsPerTask: 1.12,
mutationValidatorPassed: true, // tier3 = 0.10
saturatedRegime: true,
});
expect(result.aggregateSaturatedRegime).toBeCloseTo(0.15 + 0.10, 10); // 0.25
expect(result.aggregateSaturatedRegime).not.toBeCloseTo(7.5 + 0.15 + 0.10, 1); // tier1 not in aggregate
});
it('saturatedRegimeApplied flag mirrors input', () => {
const base = {
candidate: makeCandidate({ shape: 'claude' }),
nullBaselinePassRateII: 0.875,
nullBaselineMeanRetrievalCallsPerTask: 1.12,
mutationValidatorPassed: true,
};
expect(computeTieredFitness({ ...base, saturatedRegime: true }).saturatedRegimeApplied).toBe(true);
expect(computeTieredFitness({ ...base, saturatedRegime: false }).saturatedRegimeApplied).toBe(false);
});
it('NULL_BASELINE_PER_SHAPE constants match Checkpoint A v2 §B.2 pinned values', () => {
expect(NULL_BASELINE_PER_SHAPE.claude.trioStrictPassRateII).toBe(0.875);
expect(NULL_BASELINE_PER_SHAPE['qwen-thinking'].trioStrictPassRateII).toBe(0.875);
expect(NULL_BASELINE_PER_SHAPE['qwen-non-thinking'].trioStrictPassRateII).toBe(1.0);
expect(NULL_BASELINE_PER_SHAPE.gpt.trioStrictPassRateII).toBe(0.75);
expect(NULL_BASELINE_PER_SHAPE['generic-simple'].trioStrictPassRateII).toBe(0.875);
expect(NULL_BASELINE_AGGREGATE.trioStrictPassRateII).toBe(0.875);
expect(NULL_BASELINE_AGGREGATE.meanRetrievalCallsPerTask).toBe(1.12);
});
});
// ───────────────────────────────────────────────────────────────────────────
// Amendment 7 — computeDeltaFloorVerdict (3 OR-gated thresholds)
// ───────────────────────────────────────────────────────────────────────────
describe('Amendment 7 — computeDeltaFloorVerdict (§gen_1_pre_registered_delta_floor)', () => {
it('PROCEED if threshold 1 (aggregate Tier 1 ≥+3pp) passes alone', () => {
const verdict = computeDeltaFloorVerdict({
aggregateTrioStrictPassRateII: 0.910, // +3.5pp vs 0.875 NULL
aggregateNullBaselinePassRateII: 0.875,
qwenShapeRetrievalMeans: { 'qwen-thinking': 1.12 }, // no Qwen retrieval signal
qwenShapeNullBaselineRetrievalMeans: { 'qwen-thinking': 1.12 },
qwenAggregateTier2Bonus: 0,
});
expect(verdict.threshold1AggregateTier1).toBe('PASS');
expect(verdict.threshold1ValuePP).toBeCloseTo(3.5, 10);
expect(verdict.threshold2QwenRetrievalAbsolute).toBe('FAIL');
expect(verdict.threshold3CompoundTier1PlusTier2).toBe('FAIL');
expect(verdict.overallVerdict).toBe('PROCEED');
});
it('PROCEED if threshold 2 (Qwen retrieval ≥+0.10) passes alone', () => {
const verdict = computeDeltaFloorVerdict({
aggregateTrioStrictPassRateII: 0.875, // exactly NULL → 0pp (fails threshold 1 ≥+3pp)
aggregateNullBaselinePassRateII: 0.875,
qwenShapeRetrievalMeans: { 'qwen-thinking': 1.30 }, // 1.30 - 1.12 = +0.18 ≥ 0.10
qwenShapeNullBaselineRetrievalMeans: { 'qwen-thinking': 1.12 },
qwenAggregateTier2Bonus: 0, // not enough for threshold 3
});
expect(verdict.threshold1AggregateTier1).toBe('FAIL');
expect(verdict.threshold2QwenRetrievalAbsolute).toBe('PASS');
expect(verdict.threshold2MaxDeltaAbsolute).toBeCloseTo(0.18, 10);
expect(verdict.overallVerdict).toBe('PROCEED');
});
it('PROCEED if threshold 3 (Tier 1 ≥0pp AND Tier 2 ≥0.05) passes alone', () => {
const verdict = computeDeltaFloorVerdict({
aggregateTrioStrictPassRateII: 0.880, // +0.5pp ≥ 0pp; fails threshold 1 ≥+3pp
aggregateNullBaselinePassRateII: 0.875,
qwenShapeRetrievalMeans: { 'qwen-thinking': 1.13 }, // +0.01 < 0.10 — fails threshold 2
qwenShapeNullBaselineRetrievalMeans: { 'qwen-thinking': 1.12 },
qwenAggregateTier2Bonus: 0.05, // ≥ 0.05
});
expect(verdict.threshold1AggregateTier1).toBe('FAIL');
expect(verdict.threshold2QwenRetrievalAbsolute).toBe('FAIL');
expect(verdict.threshold3CompoundTier1PlusTier2).toBe('PASS');
expect(verdict.overallVerdict).toBe('PROCEED');
});
it('HALT_INVESTIGATE if all three thresholds fail', () => {
const verdict = computeDeltaFloorVerdict({
aggregateTrioStrictPassRateII: 0.870, // -0.5pp — fails threshold 1 + threshold 3 (Tier 1 < 0pp)
aggregateNullBaselinePassRateII: 0.875,
qwenShapeRetrievalMeans: { 'qwen-thinking': 1.13 }, // +0.01 < 0.10
qwenShapeNullBaselineRetrievalMeans: { 'qwen-thinking': 1.12 },
qwenAggregateTier2Bonus: 0.04, // < 0.05
});
expect(verdict.threshold1AggregateTier1).toBe('FAIL');
expect(verdict.threshold2QwenRetrievalAbsolute).toBe('FAIL');
expect(verdict.threshold3CompoundTier1PlusTier2).toBe('FAIL');
expect(verdict.overallVerdict).toBe('HALT_INVESTIGATE');
});
it('threshold 1 exact-boundary: 3.0pp passes (≥+3pp inclusive with EPSILON)', () => {
const verdict = computeDeltaFloorVerdict({
aggregateTrioStrictPassRateII: 0.905, // +3.0pp exactly
aggregateNullBaselinePassRateII: 0.875,
qwenShapeRetrievalMeans: {},
qwenShapeNullBaselineRetrievalMeans: {},
qwenAggregateTier2Bonus: 0,
});
expect(verdict.threshold1AggregateTier1).toBe('PASS');
expect(verdict.threshold1ValuePP).toBeCloseTo(3.0, 10);
});
it('threshold 2 exact-boundary: +0.10 absolute passes (≥+0.10 inclusive with EPSILON)', () => {
const verdict = computeDeltaFloorVerdict({
aggregateTrioStrictPassRateII: 0.875,
aggregateNullBaselinePassRateII: 0.875,
qwenShapeRetrievalMeans: { 'qwen-thinking': 1.22 }, // 1.22 - 1.12 = +0.10 exact
qwenShapeNullBaselineRetrievalMeans: { 'qwen-thinking': 1.12 },
qwenAggregateTier2Bonus: 0,
});
expect(verdict.threshold2QwenRetrievalAbsolute).toBe('PASS');
expect(verdict.threshold2MaxDeltaAbsolute).toBeCloseTo(0.10, 10);
});
it('threshold 2 takes max delta across multiple Qwen shapes', () => {
const verdict = computeDeltaFloorVerdict({
aggregateTrioStrictPassRateII: 0.875,
aggregateNullBaselinePassRateII: 0.875,
qwenShapeRetrievalMeans: {
'qwen-thinking': 1.13, // +0.01
'qwen-non-thinking': 1.40, // +0.15
},
qwenShapeNullBaselineRetrievalMeans: {
'qwen-thinking': 1.12,
'qwen-non-thinking': 1.25,
},
qwenAggregateTier2Bonus: 0,
});
expect(verdict.threshold2MaxDeltaAbsolute).toBeCloseTo(0.15, 10);
expect(verdict.threshold2QwenRetrievalAbsolute).toBe('PASS');
});
it('handles empty Qwen data: threshold 2 max delta = 0 (FAIL since 0 < 0.10)', () => {
const verdict = computeDeltaFloorVerdict({
aggregateTrioStrictPassRateII: 0.875,
aggregateNullBaselinePassRateII: 0.875,
qwenShapeRetrievalMeans: {},
qwenShapeNullBaselineRetrievalMeans: {},
qwenAggregateTier2Bonus: 0,
});
expect(verdict.threshold2MaxDeltaAbsolute).toBe(0);
expect(verdict.threshold2QwenRetrievalAbsolute).toBe('FAIL');
});
it('Δ-floor threshold constants match Amendment 7 §gen_1_pre_registered_delta_floor', () => {
expect(DELTA_FLOOR_THRESHOLDS.threshold1AggregateTier1PP).toBe(3);
expect(DELTA_FLOOR_THRESHOLDS.threshold2QwenRetrievalAbsolute).toBe(0.10);
expect(DELTA_FLOOR_THRESHOLDS.threshold3Tier1MinPP).toBe(0);
expect(DELTA_FLOOR_THRESHOLDS.threshold3Tier2MinBonus).toBe(0.05);
});
});

View File

@@ -0,0 +1,204 @@
/**
* GEPA Faza 1 — κ audit utility tests.
*
* Coverage targets:
* - Drift band semantics: PASS / DRIFT_LOW / DRIFT_HIGH per Faza 1 §F.3
* - Conservative trio = min of three pairwise (matches manifest v6 §5.4 + v7 anchor)
* - v6 policy floor cross-validation reporting
* - Cohen's κ computation against known-correct values from kappa-recal artifact
*/
import { describe, expect, it } from 'vitest';
import {
CANONICAL_KAPPA,
KAPPA_DRIFT_THRESHOLD,
KAPPA_DRIFT_BAND_LOW,
KAPPA_DRIFT_BAND_HIGH,
V6_KAPPA_POLICY_FLOOR_PASS,
auditKappa,
computeCohensKappa,
} from '../../src/faza-1/kappa-audit.js';
// ───────────────────────────────────────────────────────────────────────────
// Constants exposed for external auditing
// ───────────────────────────────────────────────────────────────────────────
describe('canonical κ + drift band constants', () => {
it('CANONICAL_KAPPA matches kappa-recal artifact value (0.7877758913412564)', () => {
expect(CANONICAL_KAPPA).toBe(0.7877758913412564);
});
it('drift threshold is 0.05 per brief §4 condition 3', () => {
expect(KAPPA_DRIFT_THRESHOLD).toBe(0.05);
});
it('drift band low = canonical - 0.05', () => {
// Canonical is 0.7877758913412564; minus 0.05 = 0.7377758913412564 ≈ 0.7378.
// toBeCloseTo precision 4 = absolute diff < 5e-5 (covers the ~2.4e-5 rounding gap).
expect(KAPPA_DRIFT_BAND_LOW).toBeCloseTo(0.7378, 4);
// Stronger invariant: equals canonical minus drift threshold exactly (within IEEE 754).
expect(KAPPA_DRIFT_BAND_LOW).toBe(CANONICAL_KAPPA - KAPPA_DRIFT_THRESHOLD);
});
it('drift band high = canonical + 0.05', () => {
expect(KAPPA_DRIFT_BAND_HIGH).toBeCloseTo(0.8378, 4);
expect(KAPPA_DRIFT_BAND_HIGH).toBe(CANONICAL_KAPPA + KAPPA_DRIFT_THRESHOLD);
});
it('v6 policy floor pass threshold is 0.70', () => {
expect(V6_KAPPA_POLICY_FLOOR_PASS).toBe(0.70);
});
});
// ───────────────────────────────────────────────────────────────────────────
// Drift band verdicts
// ───────────────────────────────────────────────────────────────────────────
describe('auditKappa — drift band verdicts', () => {
it('canonical value triggers PASS_WITHIN_DRIFT_BAND', () => {
const r = auditKappa({ kOpusGpt: CANONICAL_KAPPA, kOpusMinimax: CANONICAL_KAPPA, kGptMinimax: CANONICAL_KAPPA });
expect(r.kConservativeTrio).toBe(CANONICAL_KAPPA);
expect(r.verdict).toBe('PASS_WITHIN_DRIFT_BAND');
expect(r.driftFromCanonical).toBe(0);
});
it('value below band low triggers DRIFT_LOW_BELOW_BAND', () => {
const r = auditKappa({ kOpusGpt: 0.85, kOpusMinimax: 0.85, kGptMinimax: 0.70 });
// min = 0.70 < 0.7378 band low
expect(r.verdict).toBe('DRIFT_LOW_BELOW_BAND');
});
it('value above band high triggers DRIFT_HIGH_ABOVE_BAND', () => {
const r = auditKappa({ kOpusGpt: 0.90, kOpusMinimax: 0.90, kGptMinimax: 0.85 });
// min = 0.85 > 0.8378 band high
expect(r.verdict).toBe('DRIFT_HIGH_ABOVE_BAND');
});
it('exact band low boundary inclusive (PASS)', () => {
const r = auditKappa({ kOpusGpt: 1.0, kOpusMinimax: 1.0, kGptMinimax: KAPPA_DRIFT_BAND_LOW });
expect(r.verdict).toBe('PASS_WITHIN_DRIFT_BAND');
});
it('exact band high boundary inclusive (PASS)', () => {
const r = auditKappa({ kOpusGpt: KAPPA_DRIFT_BAND_HIGH, kOpusMinimax: KAPPA_DRIFT_BAND_HIGH, kGptMinimax: KAPPA_DRIFT_BAND_HIGH });
expect(r.verdict).toBe('PASS_WITHIN_DRIFT_BAND');
});
});
// ───────────────────────────────────────────────────────────────────────────
// Conservative trio = min — anchor against actual kappa-recal data
// ───────────────────────────────────────────────────────────────────────────
describe('auditKappa — conservative trio = min of pairwise', () => {
it('reproduces v6-kappa-recal conservative trio from pairwise', () => {
// From benchmarks/calibration/v6-kappa-recal/_summary-v6-kappa.json
// (SHA 657d4490... pinned in launch decision §B)
const r = auditKappa({
kOpusGpt: 0.847958297132928,
kOpusMinimax: 0.8548922056384745,
kGptMinimax: 0.7877758913412564,
});
expect(r.kConservativeTrio).toBe(0.7877758913412564);
expect(r.verdict).toBe('PASS_WITHIN_DRIFT_BAND');
expect(r.v6PolicyFloorPass).toBe(true);
});
it('v6 policy floor PASS when conservative >= 0.70', () => {
const r = auditKappa({ kOpusGpt: 0.75, kOpusMinimax: 0.72, kGptMinimax: 0.70 });
expect(r.v6PolicyFloorPass).toBe(true);
});
it('v6 policy floor FAIL when conservative < 0.70', () => {
const r = auditKappa({ kOpusGpt: 0.75, kOpusMinimax: 0.72, kGptMinimax: 0.65 });
expect(r.v6PolicyFloorPass).toBe(false);
});
});
// ───────────────────────────────────────────────────────────────────────────
// Audit log line format
// ───────────────────────────────────────────────────────────────────────────
describe('auditKappa — audit log line format', () => {
it('audit log contains all required fields', () => {
const r = auditKappa({ kOpusGpt: 0.85, kOpusMinimax: 0.84, kGptMinimax: 0.78 });
expect(r.auditLogLine).toContain('κ_conservative_trio=');
expect(r.auditLogLine).toContain('canonical=');
expect(r.auditLogLine).toContain('drift=');
expect(r.auditLogLine).toContain('verdict=');
expect(r.auditLogLine).toContain('v6_policy_floor=');
});
it('positive drift includes + sign', () => {
const r = auditKappa({ kOpusGpt: 0.83, kOpusMinimax: 0.83, kGptMinimax: 0.80 });
expect(r.driftFromCanonical).toBeGreaterThan(0);
expect(r.auditLogLine).toContain('drift=+');
});
it('negative drift uses - sign', () => {
const r = auditKappa({ kOpusGpt: 0.85, kOpusMinimax: 0.85, kGptMinimax: 0.70 });
expect(r.driftFromCanonical).toBeLessThan(0);
expect(r.auditLogLine).toContain('drift=-');
});
});
// ───────────────────────────────────────────────────────────────────────────
// Cohen's κ primitive
// ───────────────────────────────────────────────────────────────────────────
describe('computeCohensKappa primitive', () => {
it('returns 1.0 for perfect agreement', () => {
const k = computeCohensKappa({
bothCorrect: 50,
bothIncorrect: 50,
firstCorrectSecondIncorrect: 0,
firstIncorrectSecondCorrect: 0,
});
expect(k).toBe(1.0);
});
it('returns 0 for chance-level agreement (50/50 base rate, random co-occurrence)', () => {
// 100 trials, both raters each correct 50% with independent assignment
const k = computeCohensKappa({
bothCorrect: 25,
bothIncorrect: 25,
firstCorrectSecondIncorrect: 25,
firstIncorrectSecondCorrect: 25,
});
expect(k).toBeCloseTo(0, 2);
});
it('reproduces v6 Opus-vs-GPT pairwise κ from kappa-recal data', () => {
// From _summary-v6-kappa.json line 58-63 confusion_opus_gpt:
// correct_correct: 32, incorrect_incorrect: 61
// correct_incorrect: 7, incorrect_correct: 0
// expected κ = 0.847958297132928 (per same file line 3 k_opus_gpt)
const k = computeCohensKappa({
bothCorrect: 32,
bothIncorrect: 61,
firstCorrectSecondIncorrect: 7,
firstIncorrectSecondCorrect: 0,
});
expect(k).toBeCloseTo(0.847958297132928, 6);
});
it('returns NaN for empty observation set', () => {
const k = computeCohensKappa({
bothCorrect: 0,
bothIncorrect: 0,
firstCorrectSecondIncorrect: 0,
firstIncorrectSecondCorrect: 0,
});
expect(Number.isNaN(k)).toBe(true);
});
it('handles unanimous-correct edge case (100% base rate, no variance)', () => {
// Both raters agree everything is correct — observed = 1, expected = 1, κ undefined → return 1
const k = computeCohensKappa({
bothCorrect: 100,
bothIncorrect: 0,
firstCorrectSecondIncorrect: 0,
firstIncorrectSecondCorrect: 0,
});
expect(k).toBe(1.0);
});
});

View File

@@ -0,0 +1,180 @@
/**
* GEPA Faza 1 — mutation oracle fork tests.
*
* Coverage targets per manifest v7 §amendment_2_integration.scaffold_test_coverage_NEW_requirements
* mandatory_routing_tests:
* - Qwen branch: qwen-thinking + qwen-non-thinking → "qwen" template
* - Non-Qwen branch: claude + gpt + generic-simple → "non-qwen" template
* - Template content actually exists at expected paths
* - Placeholder substitution works for both branches
*/
import { describe, expect, it } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import {
classifyShape,
templatePathForShape,
loadTemplate,
buildOraclePrompt,
} from '../../src/faza-1/mutation-oracle-fork.js';
import { type ShapeName } from '../../src/faza-1/types.js';
const ORACLE_DIR = path.resolve(__dirname, '../../oracle/faza-1');
// ───────────────────────────────────────────────────────────────────────────
// Shape classification — Amendment 2 §4 mandatory routing tests
// ───────────────────────────────────────────────────────────────────────────
describe('classifyShape — Amendment 2 §4 fork routing', () => {
it('qwen-thinking → qwen branch', () => {
expect(classifyShape('qwen-thinking')).toBe('qwen');
});
it('qwen-non-thinking → qwen branch', () => {
expect(classifyShape('qwen-non-thinking')).toBe('qwen');
});
it('claude → non-qwen branch', () => {
expect(classifyShape('claude')).toBe('non-qwen');
});
it('gpt → non-qwen branch', () => {
expect(classifyShape('gpt')).toBe('non-qwen');
});
it('generic-simple → non-qwen branch', () => {
expect(classifyShape('generic-simple')).toBe('non-qwen');
});
it('partition: every ShapeName routes to exactly one branch', () => {
const all: ShapeName[] = ['claude', 'qwen-thinking', 'qwen-non-thinking', 'gpt', 'generic-simple'];
const qwenCount = all.filter(s => classifyShape(s) === 'qwen').length;
const nonQwenCount = all.filter(s => classifyShape(s) === 'non-qwen').length;
expect(qwenCount).toBe(2);
expect(nonQwenCount).toBe(3);
expect(qwenCount + nonQwenCount).toBe(all.length);
});
});
// ───────────────────────────────────────────────────────────────────────────
// Template paths
// ───────────────────────────────────────────────────────────────────────────
describe('templatePathForShape', () => {
it('Qwen-targeted shapes resolve to mutation-prompt-template-qwen.md', () => {
expect(path.basename(templatePathForShape('qwen-thinking'))).toBe('mutation-prompt-template-qwen.md');
expect(path.basename(templatePathForShape('qwen-non-thinking'))).toBe('mutation-prompt-template-qwen.md');
});
it('Non-Qwen shapes resolve to mutation-prompt-template-non-qwen.md', () => {
expect(path.basename(templatePathForShape('claude'))).toBe('mutation-prompt-template-non-qwen.md');
expect(path.basename(templatePathForShape('gpt'))).toBe('mutation-prompt-template-non-qwen.md');
expect(path.basename(templatePathForShape('generic-simple'))).toBe('mutation-prompt-template-non-qwen.md');
});
it('Both template files exist on disk in the oracle directory', () => {
expect(fs.existsSync(path.join(ORACLE_DIR, 'mutation-prompt-template-qwen.md'))).toBe(true);
expect(fs.existsSync(path.join(ORACLE_DIR, 'mutation-prompt-template-non-qwen.md'))).toBe(true);
});
});
// ───────────────────────────────────────────────────────────────────────────
// loadTemplate
// ───────────────────────────────────────────────────────────────────────────
describe('loadTemplate', () => {
it('Qwen template contains anti-premature-finalization scaffolding', () => {
const t = loadTemplate('qwen-thinking');
// Case-insensitive: template prose uses lowercase, headings may capitalize.
expect(t.toLowerCase()).toContain('anti-premature-finalization');
// These instruction phrases are unique to the Qwen branch:
expect(t).toContain('Continue retrieving until');
expect(t).toContain('multi-turn retrieval');
});
it('Non-Qwen template lacks Qwen-specific scaffolding instructions', () => {
const t = loadTemplate('claude');
expect(t).toContain('Standard mutation directions');
expect(t).toContain('reasoning scaffold');
// The non-qwen template legitimately MENTIONS the absence of qwen
// scaffolding ("no Qwen-specific anti-premature-finalization scaffolding
// required"). The proper test is for absence of the actual prescriptive
// INSTRUCTION phrases that appear only in the Qwen template.
expect(t).not.toContain('Continue retrieving until');
expect(t).not.toContain('multi-turn retrieval');
});
it('Both templates lock cell semantic boundaries', () => {
const qwenT = loadTemplate('qwen-thinking');
const nonQwenT = loadTemplate('claude');
for (const t of [qwenT, nonQwenT]) {
expect(t).toContain('DO NOT modify');
expect(t).toContain('MULTI_STEP_ACTION_CONTRACT');
}
});
it('Both templates expose all 4 placeholder tokens', () => {
const placeholders = [
'###BASELINE_SHAPE_CONTENT###',
'###FAILURE_MODE_SUMMARY###',
'###SHAPE_NAME###',
'###TEMPLATE_CLASS###',
];
for (const shape of ['qwen-thinking', 'claude'] as ShapeName[]) {
const t = loadTemplate(shape);
for (const ph of placeholders) {
expect(t).toContain(ph);
}
}
});
});
// ───────────────────────────────────────────────────────────────────────────
// buildOraclePrompt — placeholder substitution
// ───────────────────────────────────────────────────────────────────────────
describe('buildOraclePrompt', () => {
it('substitutes all 4 placeholders in Qwen template', () => {
const prompt = buildOraclePrompt({
shape: 'qwen-thinking',
baselineShapeContent: 'export const baselineShape = {...};',
failureModeSummary: '1. unsupported-specifics\n2. missed\n3. shallow',
});
expect(prompt).toContain('export const baselineShape');
expect(prompt).toContain('unsupported-specifics');
expect(prompt).toContain('qwen-thinking');
expect(prompt).toContain('Template class:** qwen');
expect(prompt).not.toContain('###BASELINE_SHAPE_CONTENT###');
expect(prompt).not.toContain('###FAILURE_MODE_SUMMARY###');
expect(prompt).not.toContain('###SHAPE_NAME###');
expect(prompt).not.toContain('###TEMPLATE_CLASS###');
});
it('substitutes all 4 placeholders in non-Qwen template', () => {
const prompt = buildOraclePrompt({
shape: 'claude',
baselineShapeContent: 'export const claudeShape = {...};',
failureModeSummary: '1. conflation\n2. weak-synthesis\n3. fabrication',
});
expect(prompt).toContain('export const claudeShape');
expect(prompt).toContain('conflation');
expect(prompt).toContain('claude');
expect(prompt).toContain('Template class:** non-qwen');
});
it('Qwen-targeted shapes get the same template (qwen-thinking + qwen-non-thinking interchangeable)', () => {
const a = buildOraclePrompt({
shape: 'qwen-thinking',
baselineShapeContent: 'X',
failureModeSummary: 'Y',
});
const b = buildOraclePrompt({
shape: 'qwen-non-thinking',
baselineShapeContent: 'X',
failureModeSummary: 'Y',
});
// Same template body, but ###SHAPE_NAME### is substituted differently
expect(a.replace(/qwen-thinking/g, 'X')).toBe(b.replace(/qwen-non-thinking/g, 'X'));
});
});

View File

@@ -0,0 +1,48 @@
/**
* GEPA Faza 1 — regression test for null-baseline promptShapeOverride bug.
*
* Per Amendment 6: the original NULL-baseline runner passed `shape: PromptShape`
* to runOneEval but never forwarded it to runRetrievalAgentLoop, meaning all
* 40 evals used the model-alias-default shape (qwen-thinking for Qwen subject)
* regardless of the per-shape evaluation label.
*
* Fix: pass `promptShapeOverride: shape.name` to runRetrievalAgentLoop.
*
* This test verifies the fix is present in the script source — a structural
* source-text invariant. A behavioral test would require refactoring the runner
* to expose a testable function; for the Faza 1 timeline, source-text check is
* sufficient regression protection.
*/
import { describe, expect, it } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
const RUNNER_PATH = path.resolve(__dirname, '../../scripts/faza-1/run-null-baseline.ts');
describe('Amendment 6 regression — null-baseline promptShapeOverride wiring', () => {
const source = fs.readFileSync(RUNNER_PATH, 'utf-8');
it('script source contains promptShapeOverride passed to runRetrievalAgentLoop', () => {
// The fix introduces the literal `promptShapeOverride: shape.name` in the
// runRetrievalAgentLoop call within runOneEval.
expect(source).toContain('promptShapeOverride: shape.name');
});
it('script source contains Amendment 6 bug-fix annotation comment', () => {
expect(source).toContain('Amendment 6');
expect(source).toContain('bug fix per Amendment 6');
});
it('script source still passes modelAlias = SUBJECT_ALIAS', () => {
// Make sure the fix didn't inadvertently change the subject (which is
// shape-independent: subject is always Qwen, override controls shape).
expect(source).toContain('modelAlias: SUBJECT_ALIAS');
});
it('runOneEval receives shape parameter typed as PromptShape', () => {
// The shape parameter must remain in scope so promptShapeOverride: shape.name
// resolves correctly.
expect(source).toMatch(/runOneEval\s*\(\s*shape\s*:\s*PromptShape/);
});
});

View File

@@ -0,0 +1,206 @@
/**
* GEPA Faza 1 — selection tests.
*
* Coverage targets:
* - top-1-per-shape selection by fitness
* - acceptance verdict per best-per-shape
* - run-aggregate §F.2 condition (≥3/5 shapes positive delta)
* - error handling: missing baseline entry
*/
import { describe, expect, it } from 'vitest';
import { runSelection } from '../../src/faza-1/selection.js';
import { type CandidateMetrics, type ShapeName } from '../../src/faza-1/types.js';
function makeCandidate(
shape: ShapeName,
candidateId: string,
trioII: number,
retrieval: number = 1.5,
cost: number = 0.5,
): CandidateMetrics {
return {
candidateId,
shape,
evaluations: [],
trioStrictPassRateII: trioII,
trioStrictPassRateI: trioII, // simplified for test
meanRetrievalCallsPerTask: retrieval,
meanCostUsd: cost,
};
}
describe('runSelection — top-1 per shape', () => {
it('selects highest-fitness candidate per shape', () => {
const candidatesPerShape = new Map<ShapeName, CandidateMetrics[]>([
['claude', [
makeCandidate('claude', 'c-low', 0.30),
makeCandidate('claude', 'c-high', 0.50),
makeCandidate('claude', 'c-mid', 0.40),
]],
]);
const baselineRate = new Map<ShapeName, number>([['claude', 0.20]]);
const baselineCost = new Map<ShapeName, number>([['claude', 0.50]]);
const report = runSelection({
candidatesPerShape,
baselineTrioStrictPassRateII: baselineRate,
baselineMedianCostUsd: baselineCost,
});
expect(report.perShape).toHaveLength(1);
expect(report.perShape[0].shape).toBe('claude');
expect(report.perShape[0].bestCandidate.candidateId).toBe('c-high');
expect(report.perShape[0].allCandidatesRanked).toHaveLength(3);
// Sorted descending by fitness
expect(report.perShape[0].allCandidatesRanked[0].candidate.candidateId).toBe('c-high');
expect(report.perShape[0].allCandidatesRanked[2].candidate.candidateId).toBe('c-low');
});
it('skips shapes with empty candidate lists', () => {
const candidatesPerShape = new Map<ShapeName, CandidateMetrics[]>([
['claude', []],
['gpt', [makeCandidate('gpt', 'g1', 0.40)]],
]);
const baselineRate = new Map<ShapeName, number>([
['claude', 0.20],
['gpt', 0.20],
]);
const baselineCost = new Map<ShapeName, number>([
['claude', 0.50],
['gpt', 0.50],
]);
const report = runSelection({
candidatesPerShape,
baselineTrioStrictPassRateII: baselineRate,
baselineMedianCostUsd: baselineCost,
});
expect(report.perShape).toHaveLength(1);
expect(report.perShape[0].shape).toBe('gpt');
});
it('throws on missing baseline trio_strict rate for a shape', () => {
const candidatesPerShape = new Map<ShapeName, CandidateMetrics[]>([
['claude', [makeCandidate('claude', 'c1', 0.30)]],
]);
expect(() =>
runSelection({
candidatesPerShape,
baselineTrioStrictPassRateII: new Map(),
baselineMedianCostUsd: new Map([['claude', 0.50]]),
}),
).toThrow(/missing baseline trio_strict/);
});
it('throws on missing baseline median cost for a shape', () => {
const candidatesPerShape = new Map<ShapeName, CandidateMetrics[]>([
['claude', [makeCandidate('claude', 'c1', 0.30)]],
]);
expect(() =>
runSelection({
candidatesPerShape,
baselineTrioStrictPassRateII: new Map([['claude', 0.20]]),
baselineMedianCostUsd: new Map(),
}),
).toThrow(/missing baseline median cost/);
});
});
describe('runSelection — Qwen retrieval engagement bonus affects ranking', () => {
it('Qwen candidate with higher retrieval engagement outranks higher trio_strict but low retrieval', () => {
// Candidate A: trio=0.40, retrieval=1.0 (-0.05 bonus → fitness ~0.35)
// Candidate B: trio=0.36, retrieval=2.0 (+0.05 bonus → fitness ~0.41)
// B wins despite lower trio_strict, because the bonus tips it
const candidatesPerShape = new Map<ShapeName, CandidateMetrics[]>([
['qwen-thinking', [
makeCandidate('qwen-thinking', 'A-high-trio-low-retrieval', 0.40, 1.0),
makeCandidate('qwen-thinking', 'B-mid-trio-high-retrieval', 0.36, 2.0),
]],
]);
const report = runSelection({
candidatesPerShape,
baselineTrioStrictPassRateII: new Map([['qwen-thinking', 0.20]]),
baselineMedianCostUsd: new Map([['qwen-thinking', 0.50]]),
});
expect(report.perShape[0].bestCandidate.candidateId).toBe('B-mid-trio-high-retrieval');
});
it('Non-Qwen ranking depends on trio_strict alone (no retrieval bonus tip)', () => {
// Same trio_strict + retrieval setup as above but for claude shape
// Now A wins (higher trio_strict) because no retrieval bonus applies
const candidatesPerShape = new Map<ShapeName, CandidateMetrics[]>([
['claude', [
makeCandidate('claude', 'A-high-trio', 0.40, 1.0),
makeCandidate('claude', 'B-mid-trio', 0.36, 2.0),
]],
]);
const report = runSelection({
candidatesPerShape,
baselineTrioStrictPassRateII: new Map([['claude', 0.20]]),
baselineMedianCostUsd: new Map([['claude', 0.50]]),
});
expect(report.perShape[0].bestCandidate.candidateId).toBe('A-high-trio');
});
});
describe('runSelection — run-aggregate §F.2 condition (≥3/5 shapes positive delta)', () => {
function setupAllShapes(deltas: Record<ShapeName, number>) {
const candidates = new Map<ShapeName, CandidateMetrics[]>();
const baselineRates = new Map<ShapeName, number>();
const baselineCosts = new Map<ShapeName, number>();
const BASELINE = 0.20;
for (const [shape, delta] of Object.entries(deltas) as Array<[ShapeName, number]>) {
candidates.set(shape, [
makeCandidate(shape, `${shape}-best`, BASELINE + delta / 100, 2.0),
]);
baselineRates.set(shape, BASELINE);
baselineCosts.set(shape, 0.5);
}
return { candidates, baselineRates, baselineCosts };
}
it('PASS §F.2: 5/5 shapes positive', () => {
const { candidates, baselineRates, baselineCosts } = setupAllShapes({
'claude': 6, 'qwen-thinking': 6, 'qwen-non-thinking': 6, 'gpt': 6, 'generic-simple': 6,
});
const report = runSelection({
candidatesPerShape: candidates,
baselineTrioStrictPassRateII: baselineRates,
baselineMedianCostUsd: baselineCosts,
});
expect(report.runAggregate.shapesWithPositiveDelta).toBe(5);
expect(report.runAggregate.condition2Pass).toBe(true);
});
it('PASS §F.2: 3/5 shapes positive (boundary)', () => {
const { candidates, baselineRates, baselineCosts } = setupAllShapes({
'claude': 6, 'qwen-thinking': 6, 'qwen-non-thinking': 6, 'gpt': -2, 'generic-simple': -2,
});
const report = runSelection({
candidatesPerShape: candidates,
baselineTrioStrictPassRateII: baselineRates,
baselineMedianCostUsd: baselineCosts,
});
expect(report.runAggregate.shapesWithPositiveDelta).toBe(3);
expect(report.runAggregate.condition2Pass).toBe(true);
});
it('FAIL §F.2: 2/5 shapes positive', () => {
const { candidates, baselineRates, baselineCosts } = setupAllShapes({
'claude': 6, 'qwen-thinking': 6, 'qwen-non-thinking': -2, 'gpt': -2, 'generic-simple': -2,
});
const report = runSelection({
candidatesPerShape: candidates,
baselineTrioStrictPassRateII: baselineRates,
baselineMedianCostUsd: baselineCosts,
});
expect(report.runAggregate.shapesWithPositiveDelta).toBe(2);
expect(report.runAggregate.condition2Pass).toBe(false);
});
});