Weigh before/after eval harness
exchange-triage · run 2026-10-19-prompts · 2026-10-20

v3 holds quality and uses 6.2% more prompt tokens.

Exchange Triage extraction prompts v1, v2 and v3 over the 27-case golden set, through the production pipeline (intake, extraction, grounding, appetite; writer off) on claude sonnet via the CLI. Baseline v2 (what shipped 2026-07-27), candidate v3 (production), v2-repeat as the noise floor, v1 as the frozen plain prompt. Reference: the golden labels. Judges: two source-attribution classifiers, validated against human labels in suites/exchange-triage.labels.jsonl. 27 cases, two variants, 7 binary checks. Tokens and cost are metered from the provider's usage report.

Gate passed. Pass-all did not drop and decision_correct, fields_correct did not fall.
Noise floor: the candidate's change is within it. The baseline run twice (v2 against v2-repeat) moved pass-all +0.0% against the candidate's +22.2%; decision_correct +0.0% against the candidate's +0.0%, fields_correct +0.0% against the candidate's +3.7%. A drop no larger than the baseline's own drift is not evidence of a regression.
26%v2
pass-all rate, baseline (7 of 27)
48%v3
pass-all rate, candidate (13 of 27)
+6.2%
prompt tokens per case, 4,778 to 5,076 (+6.0% to +6.5% on the mean)
-17.3%
cost per case, $0.0232 to $0.0192 (API-equivalent)
+6.4%
latency per case, 89.7 s to 95.4 s

Checks

Every check is pass or fail per case. Rates carry Wilson 95% intervals. A check both variants pass everywhere is saturated: it guards against regressions but no longer discriminates.

checkkindv2v3delta95% interval (candidate)
extraction_schema
Extraction JSON matched the schema within the pipeline's two attempts.
code27/27 100.0%27/27 100.0%+0.0%88% to 100%saturated
fields_correct
Every labelled field matches the golden value under the app's own field scorer (0.5% on dollar amounts, canonical construction class, null means null).
code26/27 96.3%27/27 100.0%+3.7%88% to 100%
construction_canonical
constructionType is the canonical ISO class the appetite rules consume, not the broker's wording.
code18/18 100.0%18/18 100.0%+0.0%82% to 100%saturated
nulls_stay_null
Fields the source never states come back null (no guessed TIV, year built, state or subclass).
code5/5 100.0%5/5 100.0%+0.0%57% to 100%saturated
decision_correct
The routing decision matches the golden label.
code23/27 85.2%23/27 85.2%+0.0%68% to 94%
attribution_jev
Jev (TypeSafe yes/no classifier) says every `source` attribution is truthful: the value is stated in the document it names. Pass at probability 0.5 or above.
judge19/27 70.4%27/27 100.0%+29.6%88% to 100%
attribution_claude
Claude Haiku, given only the source text and the extracted fields, says every `source` attribution is truthful.
judge11/27 40.7%16/27 59.3%+18.5%41% to 75%

1 per-case regression (baseline passed, candidate failed): GOLD-09 attribution_claude. Read these first; a single run of a nondeterministic model also produces them on noise, so the gate is on rates.

Prompt tokens, paired

One row per case. Grey is v2; the colored dot is v3. Mean change +6.2%, median change 314 tokens, bootstrap 95% interval on the mean difference 287 to 310 tokens.

01,3732,7474,1205,493prompt tokens
v2v3 used fewerv3 used more

Per-case ledger

Dots are checks in the order of the table above; hollow means the check did not apply. Tokens are prompt tokens the model read (fresh input plus cache read plus cache creation).

casev2v3tokens v2tokens v3changedecisionfitScoreretriesfieldsWrongmodelErrors
GOLD-014,9755,289+6.3%IN APPETITE9000
GOLD-025,0285,342+6.2%IN APPETITE8800
GOLD-035,2855,493+3.9%IN APPETITE8400
GOLD-044,8115,125+6.5%IN APPETITE8400
GOLD-055,0495,291+4.8%IN APPETITE8800
GOLD-065,0545,332+5.5%DECLINED1200
GOLD-074,8815,165+5.8%DECLINED1200
GOLD-084,8165,100+5.9%INCOMPLETE7200
GOLD-094,6575,043+8.3%INCOMPLETE7400
GOLD-104,9395,187+5.0%REFERRED3200
V-014,7185,032+6.7%IN APPETITE9000
V-024,6014,915+6.8%INCOMPLETE8000
V-034,5224,840+7.0%INCOMPLETE7400
V-044,6714,955+6.1%IN APPETITE8800
V-054,6704,984+6.7%IN APPETITE8800
V-064,6854,999+6.7%IN APPETITE8600
V-074,7375,031+6.2%IN APPETITE8800
V-084,5954,909+6.8%INCOMPLETE8800
V-094,6774,951+5.9%IN APPETITE8200
V-104,7425,026+6.0%DECLINED2800
V-114,7615,075+6.6%IN APPETITE8800
V-124,7205,034+6.7%IN APPETITE8600
V-134,6824,996+6.7%INCOMPLETE8400
V-144,5794,893+6.9%INCOMPLETE7200
V-154,8095,093+5.9%IN APPETITE8800
V-164,5994,913+6.8%INCOMPLETE8600
GOLD-274,7395,051+6.6%IN APPETITE8800

Judge validation

Each judge check is a classifier. Its verdicts count only as far as they agree with a person's labels on a split it never saw. Positive means a failure is present. TPR is the share of human-labelled failures the judge caught; TNR the share of human-labelled passes it left alone. Two judges on the same property can be compared row for row.

attribution_claude

splitnhuman failshuman passesTPRTNR
all541440100.0% 78% to 100%65.0% 50% to 78%
train12111100.0% 21% to 100%54.5% 28% to 79%
dev22319100.0% 44% to 100%73.7% 51% to 88%
test201010100.0% 72% to 100%60.0% 31% to 83%

54 labels matched a judge verdict; 0 did not.

attribution_jev

splitnhuman failshuman passesTPRTNR
all54144050.0% 27% to 73%100.0% 91% to 100%
train12111100.0% 21% to 100%100.0% 74% to 100%
dev22319100.0% 44% to 100%100.0% 83% to 100%
test20101030.0% 11% to 60%100.0% 72% to 100%

54 labels matched a judge verdict; 0 did not.

Where runs fail

Rows are the last stage that succeeded, columns the first that failed. The matrix says where to look before any judge is built.

v2

last success \ first failuregroundingextractionappetite
extraction5
intake1
grounding3

9 of 27 traces failed a stage.

v3

last success \ first failuregroundingappetite
extraction5
grounding3

8 of 27 traces failed a stage.

Method

Variants. v2: Extraction prompt v2: canonical construction classes and the broker-working-value rule. Shipped 2026-07-27. v2-repeat: v2 run a second time on identical inputs: the noise floor. v3: Extraction prompt v3, production: null-first subclass rule and source tied to a citable span, after a live over-extraction Arize caught. v1: Extraction prompt v1, frozen: plain instructions, no construction mapping, no few-shot examples.

Checks. extraction_schema (code): Extraction JSON matched the schema within the pipeline's two attempts. fields_correct (code): Every labelled field matches the golden value under the app's own field scorer (0.5% on dollar amounts, canonical construction class, null means null). construction_canonical (code): constructionType is the canonical ISO class the appetite rules consume, not the broker's wording. nulls_stay_null (code): Fields the source never states come back null (no guessed TIV, year built, state or subclass). decision_correct (code): The routing decision matches the golden label. attribution_jev (judge): Jev (TypeSafe yes/no classifier) says every `source` attribution is truthful: the value is stated in the document it names. Pass at probability 0.5 or above. attribution_claude (judge): Claude Haiku, given only the source text and the extracted fields, says every `source` attribution is truthful.

Gate. Candidate pass-all may not drop by more than 0%, and the rate of decision_correct and fields_correct may not be lower on the candidate. Regressions per case are listed, not gated.

Numbers. Token counts and cost come from the provider's usage report on every call (metered). Cost is the API-equivalent figure; these calls ran on a subscription. Latency is wall time per case including every call the variant made. Intervals: Wilson for rates, seeded percentile bootstrap (2,000 resamples) for paired means. No p-values.

Pairing. Only cases both variants completed are compared, in suite order, so every paired number has the same n (27). Errors: 0 on v2, 0 on v3.

Reproduce.

bun src/cli.ts run exchange-triage.ts --run-id 2026-10-19-prompts
bun src/cli.ts report exchange-triage 2026-10-19-prompts