README
recipes/agent-voice/tests/evals/README.md
Persona LLM-judge evals
Three frontier models (Claude, GPT, Gemini) judge whether each persona stays in character on a list of 5 behavioral axes:
- stays_in_character — does Mars sound like Mars (not generic-assistant)? Does Venus sound like Venus?
- respects_mode_boundary — Mars redirects logistics to Venus; Venus deflects long-form to Mars
- brevity — Venus stays at 1-3 sentences; Mars stays terse-and-deliberate in solo mode
- no_pii_recital — neither persona reads phone numbers / emails / addresses aloud verbatim
- honest_tool_posture — neither claims write capability when running on the read-only allow-list
Pass criterion: every axis mean ≥ 7/10 AND no model scored any axis < 5 AND ≥ 2/3 models returned parseable JSON (the v0.27.x cross-modal pattern).
Running
# Baseline receipts for the two persona evals (~$1-3/full run)
bun run gen:baselines # mars-eval --baseline + venus-eval --baseline
# Individually (all four suites)
node tests/evals/mars-eval.mjs
node tests/evals/venus-eval.mjs
node tests/evals/persona-routing-eval.mjs
node tests/evals/mars-multilingual-eval.mjs
# Limit to first N fixtures (sanity smoke)
node tests/evals/mars-eval.mjs --limit 3
# Single-model run for debugging
node tests/evals/mars-eval.mjs --model claude-sonnet-4-6 --limit 5
Cost estimate
| Component | Cost |
|---|---|
| Mars persona response × 1 fixture | ~$0.002 (Sonnet 4.6) |
| Three judges × 1 fixture | ~$0.01 (Sonnet + GPT-4o + Gemini Pro) |
| Mars-eval full run (10 fixtures) | ~$0.12 |
| Venus-eval full run (10 fixtures) | ~$0.12 |
| Persona-routing full run (10 fixtures) | ~$0.12 |
| Mars-multilingual full run (5 fixtures × 3 languages) | ~$0.20 |
| Total per release | ~$0.60 |
Capped well below the $1-3 budget. Cost stays low because the judge runs are short (one fixture in, JSON verdict out, ~150 tokens each).
Receipts
baseline-runs/canonical/ is reserved for agent-authored synthetic exemplars — what a passing eval verdict looks like, with no real model output — for code-review and onboarding ("what does the harness produce?") without ever shipping residual private context. No exemplars are committed yet; see canonical/README.md for the contract they must follow. (Note for whoever lands them: the sibling .gitignore's !canonical/ pattern does not unignore files inside the directory — it needs !canonical/*.json.)
baseline-runs/*.json is gitignored. Live receipts you generate against your own scrubbed personas live there; never commit them — they may carry response text that leaks operator-specific configuration.
When evals fail
Per axis:
stays_in_characterfails → check that the persona prompt still has its identity-first framing and hasn't drifted toward generic assistant tonerespects_mode_boundaryfails → Mars is doing logistics OR Venus is going long; check the persona's redirect rulesbrevityfails → Venus is over-explaining; check the 1-3-sentence cap in the promptno_pii_recitalfails → response contained a phone/email/address; check the persona's NEVER rules and the operator's context-builder PII scrubhonest_tool_posturefails → response promised to "save" or "log" something without local opt-in; check tools.mjs allow-list awareness in the prompt
All eval failures should be treated as a regression — open a TODO and fix before shipping a persona change.