Context
An LLM onboarding conversation has an eval harness: five scenarios, an LLM-driven simulated user, eleven to thirteen assertions per run. It reported 58/58 checks passed. Driving the same flow through a real browser in the same session surfaced defects in every category the suite claims to cover, and re-reading the green transcripts surfaced four more that the suite had walked straight past.
The suite was not badly written. Each blind spot is a reasonable check implemented one notch weaker than its name implies.
The five blind spots, each found inside a passing run
user_messages_persistedcounts instead of validating alternation. One transcript had two consecutiveUsermessages with no assistant turn between them, the second answering a question the assistant never asked. 13/13 passed, because four user messages were indeed present. This was the exact structural shape of a previously-fixed, separately-documented bug (the model authoring a turn in the user's voice at a resume boundary) — the fix landed, the regression check did not. Assert role alternation, not message count.no_duplicate_contentcompares across messages, not within one. Passing run, verbatim:"Your account is all set, Mahmoud! You're all set, Mahmoud! Your default space is ready to go."Two near-identical clauses in one sentence, invisible to a pairwise cross-message similarity check.no_implementation_leaksonly inspected assistant messages. The date-picker widget posted1990-06-15 (America/New_York)as the user's own chat message — raw ISO date plus raw IANA identifier, underscore included, attributed to a human. Every one of the five runs contained it. Leak checks must cover every role, because widgets author user-role content.calculator_linksasserted the link exists, not that any prose refers to it. A passing run ended:"…this is exactly where to start: Run through that and come back."Dangling colon; the recommended item is never named in prose at all, existing only as navigation metadata rendered into a button."Run through that"has no antecedent. The link was present, so the check was satisfied.One scenario's check set omitted the terminal assertion. The "user restarts the flow" scenario passed 12/12 while never collecting the user's name, never collecting their date of birth, and never emitting
registration_complete. The scenario that best models a hesitant real user was the one that never proved they could finish. Every scenario whose name implies completion needs a completion assertion, or it is testing that the conversation doesn't crash.
The simulator is systematically easier than a human
Comparing five simulated runs against one real browser run:
- The simulator never produced a one-sentence answer. Every turn was 2-4 sentences of polished prose. The real user's first answer was nine words.
- The simulator never misunderstood a question. Asked "who are you looking out for — just yourself, or is there anyone else?", the real user replied "Not really, I just put whatever is left into savings" — a non-sequitur that forced a clarification turn. No simulated run ever triggered one, so the recovery path had zero coverage despite being exercised within four turns of real use.
- The simulator always answered completely in one turn. Real users drip-feed.
- All five runs shared one demographic (same age, family structure, income, occupation) because nobody passed the persona flag the harness already supported. Five runs, one user.
- The simulator only ever touches the chat. It never navigates away mid-conversation, never types while a response is streaming — the two actions that produced the session's most serious real defects (a modal trap, and messages being silently discarded).
Also: the report artifact truncated the thing the process required reading
The harness's own process doc says green checks are the floor and mandates a close read of at least one transcript per session. The transcript it writes truncates every message at ~200 characters with an ellipsis — and the truncated tail is precisely where the registration prompt, the recommendation transition, and tool results live. Two of the findings above were only visible after re-dumping the conversation from the database with a separate command. If your process says "read the artifact," the artifact must not elide.
Transferable rules
- Name your checks after what they assert, or make them assert what they're named. Every blind spot here is a gap between the check's name and its implementation, and the name is what people trust when they read a green run.
- A green suite plus a passing regression check for bug X is not evidence X can't recur; it's evidence X can't recur in the exact form the check inspects. Re-derive the bug's structural shape and assert on that.
- Budget one adversarial human pass per eval session and treat the simulator as a smoke test. Ours found, within twenty minutes: an input that silently eats text, a factual error in the first parroted fact, and two parallel UIs collecting the same three fields from the same user.
- If your harness supports persona variation, using the default persona for every run makes the run count misleading. Five identical users is n=1.
- Structured-input turns (button taps, widget submissions) are a distinct class from text turns and tend to escape prompt-level budgets and content rules. Ours drew a five-bubble, ~95-word reply to a submitted date.