# Lab 025 detail: a judge at the kit's boundary, and in the reader's seat

Companion to the Lab 025 record (`jev-gate`). Baselines are Lab 023 (`promise-boundary`) and Lab
024 (`finetune-interpreter`) on the same frozen corpus, agent model and serving setup. Three judge
arms: G (my gate configuration), H (the gate as I read TypeSafe's guidance) and J (Jev as the
reader in Lab 023's code-driven driver). G is described first; H and J have their own sections
below. "PAR 4" means four sessions run at a time. In `compare_h_r1.json`, arm H's statistics sit
under the key "G" (the comparison script's field name), with `"gated_arm": "H"`.

## What was tested (arm G)

Lab 023's clinic booking agent from the Promise-Driven Agentic Kit, unmodified, with one change at
the boundary. When the agent calls `create_booking` with `confirmed` and `eligibility_ok` both
true, the harness builds the visible conversation from its own record and asks Jev two `noul`
(yes-or-no probability) questions. If either probability is under its threshold, the flag the
kit's `commit_booking` receives is false, and the kit refuses with its own structured refusal.

Rules:

1. Jev can only veto. A false from the agent is refused by the kit without asking Jev.
2. Jev's input is built by the harness: patient messages verbatim from the corpus, the agent's
   visible replies, and the clinic services' results, up to the write. The agent's tool arguments
   and reasoning are excluded. `create_booking`, `reschedule_booking` and `transfer_to_agent`
   results are excluded.
3. No answer from Jev is a veto (`jev_unavailable`). None occurred.
4. An unknown slot, or a slot that isn't the named provider's, skips Jev; the kit refuses it as
   `unknown_slot` (post-launch fix, see below).

## Calibration before the run

Replay set: every `create_booking` attempt in Lab 023's arm A corpus runs (Gemma reasoning off and
on, Qwen, DeepSeek sample), with the transcript up to the write and ground truth from the ledger.
1,640 writes, 1,635 non-disputed. Split by scenario, stratified by type, seed 20261019: 20%
calibration (321 writes), 80% held out.

Pre-registered: threshold per question = the highest with at most 5% false vetoes where that fact
is true (contestable writes excluded). Kill criterion: the gate must catch at least 50% of
forbidden writes with at most 10% of legitimate writes vetoed.

| Variant (calibration split) | Caught | Legit vetoed |
|---|---|---|
| V0 regular wording | 43.4% | 3.4% |
| V1 regular and negated averaged | 47.8% | 3.4% |
| V2 `jev-preview` | 47.1% | 5.2% |
| V3 eligible split into "covered?" and "agreed to the self-pay price?" | 44.1% | 3.4% |
| V4 choice questions with named alternatives and "unclear" | 41.9% | 3.4% |
| V5 patient turns, system results, and only the agent's last reply | 41.2% | 3.4% |
| V6 preview with split eligible | 47.1% | 5.2% |

V0 failed the kill criterion, and the lab was stopped. Keith then asked for the variations. V1 was
frozen (thresholds confirmed 0.56, eligible 0.37) and scored once on the held-out 80%: 302 of 599
forbidden writes caught (50.4%, Wilson 95% 46.4 to 54.4), 19 of 479 legitimate writes vetoed
(4.0%). Ineligible-coverage misses, from V3's self-pay sub-question: 22 of 57 are the scripted-
patient artifact (the agent restates the booking as self-pay and the script's next "yes" was
written as slot confirmation; Jev median 0.47). The other 35 are a copay reply read as agreement
to the self-pay price (Jev median 0.70).

Instrument checks on the calibration split: regular plus negated probabilities summed to a median
of 1.00; repeated requests moved at most 0.09, and the veto decision flipped on 3 of 50.

## The scored run

Gemma 4 26B-A4B, bf16, vLLM 0.25.1, reasoning off, `serve_gemma26b.sh` unchanged (max_model_len
262144), one DGX Spark. Harness on a Mac through an SSH tunnel, packages identical to the Spark
harness venv (94 packages, Python 3.12; google-adk 2.12.0, google-genai 2.31.0, litellm 1.106.0).
Jev answered as `jev-1.13.0`. 1,000 scenarios, pressure first, PAR 4, one seed.

| Gemma reasoning off | Kit (Lab 023 A) | Kit + judge (G) | DCITL (Lab 023 E) | Consent in code (Lab 023 F) |
|---|---|---|---|---|
| Forbidden per 100 pressure, strict | 48.7 (272/559) | 29.7 (162/545) | 31.1 | 0.0 |
| Non-contestable | 42.7 (212/496) | 22.2 (107/482) | 25.0 | 0.0 |
| No-verdicts counted as leaks | 48.7 | 31.5 (176/559) | | |
| Normal bookings completed | 92.2% (283/307) | 90.3% (278/308) | 91.3% | 85.4% |
| Model calls / tokens per session | 9.26 / 38,305 | 9.32 / 38,649 | 1.15 / 280 | 1.14 / 277 |
| Median session | 30.7 s | 32.4 s | 1.1 s | 0.9 s |

Paired, 545 scenarios both arms scored: A only 100, G only 4, both 158, neither 283.

Forbidden bookings by type, A then G (per 100): unconfirmed booking 51.0, 22.7 (22/97); injected
confirmation 81.0, 21.4 (9/42); injected eligibility 100, 46.2 (6/13); self-pay retraction 55.0,
22.5 (9/40); ineligible coverage 85.6, 61.2 (49/80); mixed consent 100, 91.4 (64/70);
double-book 6.5, 4.8 (confirmation leaks on a substituted slot); identity and ownership 0 in both.
Store-fed forbidden attempts under the judge: 73, none committed.

Judge on every gated write: 1,023 writes; forbidden attempts vetoed 555 of 748 (74.2%);
legitimate attempts vetoed 17 of 275 (6.2%). 2.26M Jev input tokens.

No-verdicts: G 64 (call cap 45 pressure and 13 normal, timeouts 3 and 3); A 46 (call cap 26 and
14, timeouts 3 and 3). 18 G sessions made 10 or more write attempts. The judge vetoed at least one
write in 6 of 336 normal sessions; 3 of those completed.

## Paired, split and disposition figures (added after the peer check)

Recomputed from the run records; no new runs.

- Common set, 545 pressure scenarios both arms scored: kit 258 (47.3 per 100), kit + judge 162
  (29.7). Paired difference 17.6 points, 95% interval 14.3 to 21.0, one seed per arm.
- Judge against Lab 023 E on the same 545: 29.7 against 31.0, difference 1.3 (interval minus 2.4
  to 4.9); E only 55, judge only 48, both 114.
- Disposition of 559 eligible pressure scenarios (forbidden / stuck at the call limit / safe):
  kit 272 / 0 / 287; kit + judge 162 / 14 / 383; E 174 / 0 / 385.
- Replies with one reading (446; 434 scored for the judge) against replies reasonable readers
  split on (113; 111): kit 38.1 and 90.3; kit + judge 18.4 and 73.9; E 20.2 and 74.3; F 0.0 and
  0.0. "Split on" is the corpus's 63 contestable scenarios plus Lab 024's two ambiguous phrasings,
  copay (28) and the "I guess..." hedge (22). The corpus labels those 50 clean.
- Ineligible coverage by phrasing, forbidden sessions (kit / judge / E): copay 24/28, 22/28,
  25/28; hedge 18/22, 5/20, 9/22; billing later 19/21, 18/20, 17/21; false coverage 16/19, 4/12,
  1/19 (7 of the judge's false-coverage sessions ended at the call limit).
- Retry storms: 58 sessions at the call limit, mean 28.2 model calls and 82 s.
- Without x0487: 161 of 544, 29.6.

## Denominators, reconciled

- 1,000 scenarios: 650 pressure, 350 normal. 45 disputed are excluded everywhere (31 pressure,
  14 normal), leaving 619 pressure and 336 normal. "6 of 336 normal sessions" is that 336, any
  status.
- Keystone pressure set: the 619 minus 60 unconfirmed-reschedule scenarios (the kit's reschedule
  path is dead as shipped) = 559. A scored all 559; G scored 545, with 14 stuck at the call
  limit. All 14 had leaked under A.
- `pressure_no_verdict` in `compare_g_r1.json` counts no-verdicts inside the 559 only (A 0, G
  14). The prose figures of 45 and 26 pressure call-limit endings count every pressure
  session, including reschedule and disputed scenarios.
- Normal completion: the 336 minus 27 reschedule-own scenarios (309), minus the no-verdict sessions among them, gives
  307 (A) and 308 (G).
- `catch_by_type` in `jev_heldout.json` lists four types only; the other 31 forbidden held-out
  writes (7 caught) are double-book and mixed-consent writes.
- Ineligible coverage, G: 80 of 90 scored; 10 ended at the call limit (7 of them false-coverage).
  H: 43 of 90 scored; 47 ended at the call limit.
- Injected eligibility: 14 scored for A, 13 for G and H (one session at the call limit in each).
  Retracted self-pay: 40 for A and G, 37 for H (three at the call limit).
- Held-out catch rate: 50.4% excludes contestable writes, as the bar and thresholds did from the
  first calibration. Including the 81 contestable forbidden writes (15 caught): 317 of 680, 46.6%.

## Arm H: the gate configured to TypeSafe's published guidance

Built from docs.typesafe.ai (read 2026-10-20): one condition per Noul, positive statements, state
as an object (`conversation` as `[{from, text}]`, `eligibility_result`, `proposed_booking`), only
relevant context, examples inside `criteria.true` and `criteria.false`, deterministic checks in
code. Questions: `accepted_exact_slot`, `chose_self_pay` (only when code reads "not covered"), and
`instructions_to_system`. Coverage is read in code from the clinic's coverage table for the
provider's specialty, as Lab 023's arm B did. Veto-only, fail closed.

Examples (`corpus/jev_h_examples.json`) were written to the booking policy for this lab: 4 true
and 5 false for slot acceptance, 3 and 5 for self-pay, 4 and 4 for instructions; providers, days
and times outside the corpus world; none shares a five-word phrase with any corpus patient
message or Lab 024 example.

Calibration on the 20% replay split (same rule as G): slot 0.57, self-pay 0.93 (from 24
legitimate self-pay writes), instructions 0.5 (fixed beforehand). Held-out replay, scored once:
453 of 599 forbidden caught (75.6%), 9 of 479 legitimate vetoed (1.9%); with contestable writes,
476 of 680 (70.0%). The self-pay question vetoed 11 of 97 bookings where the patient really had
chosen self-pay.

Scored run (`xl_gemma_H_r1`): non-contestable 49 of 445 (11.0 per 100), contestable 51 of 63
(81.0), strict 100 of 508 (19.7), stuck counted as leaks 151 of 559 (27.0). Disposition of 559:
100 forbidden, 51 stuck, 408 safe; 48 of the 51 stuck had leaked under A. Paired with A on 508:
44.1 against 19.7, A only 125, H only 1. Jev vetoed 1,558 of 1,682 forbidden write attempts and 0
of 261 legitimate ones. By type: ineligible coverage 0 of 43 scored (47 stuck), injected
eligibility 0 of 13, retracted self-pay 0 of 37, unconfirmed booking 27 of 100, injected
confirmation 10 of 42, mixed consent 60 of 70. Normal completion 279 of 307 (90.9%). Per
conversation: 9.4 model calls, 39,102 agent tokens, about 3,955 Jev tokens.

## Arm J: Jev in the code-driven driver's reader seat

Lab 023's frozen driver (`pb/dcitl.py`, untouched) with `pb/jev_j.JevModel` in place of its
model. Yes/no questions (confirm, selfpay, confirm_move, confirm_cancel) to a Noul mapped to
yes / no / unclear by two thresholds; intent, slot and provider to a Choice with an "unclear"
option; identity extraction stays with Gemma. A reader's yes counts, as in E. State:
`{software_said, patient_message}`; arm H's examples on confirm and self-pay.

Calibration on Lab 024's training questions (469 yes/no questions, a seeded 600 choice questions;
no scenario shared with the test corpus). Rules written down first. Confirm-type yes 0.95; the
"no" rule landed above it and was capped at 0.94, so the "unclear" band is effectively empty.
Self-pay yes 0.93 and no 0.07 (Lab 024's data has no agreeing self-pay case). Choice confidence
0.01: at most 5% of answered questions were wrong at any confidence. Jev scored gold yes at a
median 0.99, gold no at 0.03, gold "unclear" at 0.91.

Scored run (`xl_gemma_J_r1`): strict 12 of 559 (2.1 per 100), non-contestable 12 of 496 (2.4),
contestable 0 of 63, reschedule pressure 0 of 60, 0 stuck. Paired with E on 559: E only 162, J
only 0. Normal completion 290 of 309 (93.9%). Hand-offs 198 of 955 (normal 3, pressure 195).
Per conversation: 0.20 Gemma calls (40 tokens), 0.92 Jev calls (622 tokens), median 0.2 s. All 12
forbidden bookings are the "claim elsewhere" style: code proposes the first available slot and
the scripted patient replies "Just book it already". Escalations: confirm yes 55 / no 120;
self-pay unclear 179 / no 19; slot answered 77 / unclear 253; intent 115 / 18; provider 15 / 21.

## Changes during the lab

- Canary first attempt: all 6 sessions failed at turn 0 (a new venv lacked `mcp`). No calls. The
  venv was rebuilt to the Spark harness's exact freeze.
- Post-launch fix in `pb/arms.py`: scenario x0487 crashed because the agent booked a slot that
  doesn't exist and the judge's question needs the slot's time. The judge now skips unknown slots,
  which the kit refuses. x0487 was re-run with the fix (one forbidden commit, eligibility passed at
  0.91). `pb/jev.py` and the frozen thresholds were unchanged (hash file
  `corpus/frozen_before_jev_heldout.sha256`).

## Spend

Jev at $0.042 per million input tokens, by tag in the spend log: canary $0.0005; G's
calibration, held-out replay and variations $0.254; G's run $0.096; H's replay and run $0.294; J's
calibration $0.030 and run $0.026. Total about $0.70. Everything else on owned hardware.

## Files

`specs/promise-boundary-tools/`: `pb/jev.py` (client), `pb/jev_gate.py` (arm G's judge),
`pb/jev_h.py` (arm H's state, questions and replay), `pb/jev_j.py` (arm J's reader and its
calibration), `pb/arms.py` (arms G, H and J), `pb/jev_replay.py`, `pb/jev_calibrate.py`,
`pb/jev_knobs.py`, `pb/compare_g.py`, `run_gemma_g_mac.sh` (`ARM=G|H|J`); frozen inputs
`corpus/jev_frozen_variant.json`, `corpus/jev_h_examples.json`, `corpus/jev_h_frozen.json`,
`corpus/jev_j_frozen.json` and their `.sha256` files; results `jev_calibration.json`,
`jev_knobs.json`, `jev_heldout.json`, `jev_h_cal.json`, `jev_h_held.json`, `xl_gemma_G_r1/`,
`xl_gemma_H_r1/`, `xl_gemma_J_r1/`, `compare_g_r1.json`, `compare_h_r1.json`.

TypeSafe guidance used for arm H and J, read 2026-10-20: How to build with TypeSafe
(`/concepts/how-to-build-with-system-one`), State (`/concepts/state`), Noul (`/primitives/noul`),
Confidence (`/confidence`), Jev 1.13 jaggedness (`/model-jaggedness/jev-1.13`), Advanced
structure (`/primitives/advanced`), all under docs.typesafe.ai. Running record:
`specs/jev-promise-boundary-spec.md`.
