Skip to content

Hypotheses by category

Which failures deserve an experiment, a claim template for each category of failure seen in traces, how to set each one up with one factor, and which hypothesis to test first.

A hypothesis is a claim with a number that a series can refute. It comes from a failure you saw in traces, not from a generic list. First filter out what an experiment can’t help with: a prompt that never asks for the behavior, a broken binding, an infrastructure error. Then pick the category the failure belongs to, write the claim from its template, and test the riskiest claim first: the one that could make the product useless.

What you seeKind of gapWhat to do
the prompt, a fragment or the type never asks for the behavior you expectspecificationfix the prompt or the type, then look again; no experiment
the prompt preview shows an empty or literal placeholderwiringfix the in: binding; it isn’t the model
the attempt is an infrastructure error: a provider key, a timeout, broken codeinfrastructurefix the environment or the code; such attempts make a series invalid, they don’t make a model look bad
the behavior is clearly asked for, and the model gets it wrong on part of the inputsgeneralizationa hypothesis and an experiment

Only generalization gaps become experiments. An experiment on behavior the prompt never asks for spends budget and proves nothing.

Every template is a claim with a number. The “setup” column names the question kind and the factor. Each experiment changes one factor: agent, prompt, use or flow.

CategoryWhat you see in tracesClaim to testSetupCases and checksIf confirmed
Output contract and limitsMODEL_RETRIES_EXHAUSTED, MODEL_SCHEMA_MISMATCH, repair retries in the eventsagent A on step S passes its output contract in fewer than X of attemptsthreshold on success_rate, below, variant: A; range from: S, to: S; a second variant with an agent factor gives a reference number; read schema_valid_first_tryinputs that push fields to their limits, rare enum values, deep nesting; probe with aqven models shapes --live firstis the limit in the prompt? If not, it’s a specification gap. Otherwise another agent, more output.retries, or trimming in a code step
Instruction followingthe output is valid but breaks a rule of the prompt: language, length, a ban, a formatinstruction I is broken more often than Xthreshold on one binary check per instructionlanguage, max_words, regex, no_pii, or your own run: check; cases where the instruction fights the inputreorder or simplify the instruction, add a runtime check with on_fail: retry, split the step
Long or noisy inputerrors grow with length, a distracting opening, noise, the position of the key facton length: very_long the right answer drops below X; condensing first beats one step by mthreshold with cases.tags on one level; compare with a flow factor: the reading step behind a call node, one local flow per way of readingordered levels of one tag with the same truth; expected on the decision fielda switch on length, a condensing step, less context in the prompt
Class boundariesneighboring classes get confused; the decision follows the first topic of a messageon cases whose opening topic differs from the intent, the right class drops below Xthreshold on expected with a tag filter; compare with a prompt factor for a new rubrictruth by construction: label first, then text; boundary pairs, with clear cases as negative controlssharpen the rubric in a fragment, add an escalation class
Error propagationthe first failing node is above the node where the failure shows; a critic notices but doesn’t fixa wrong output of S1 reaches the flow’s output more often than X; the critic notices the defect but the fixed output is right less than Ythreshold on the range below S1, with a planted wrong S1 output in the cases’ node_outputs; two checks: “noticed” and “final output right”pairs of cases: a clean and a broken S1 output, tagged plantedfix S1; a code check between the steps; don’t count on the critic
Toolsthe wrong tool or parameter; an answer that ignores the tool’s resultwith a missing parameter the step invents a value more often than Xthreshold on a check of the resultchecks see the step’s output and the outputs of other nodes, not the tool calls inside an llm step: read those in the run events on working casesa tool node instead of the agent’s tool, parameters from a code step, a check before the call
Judge reliabilitythe judge misses a clear defect or blocks a good answerjudge J catches more than X of planted defects and passes more than Y of clean answersa local flow that runs only the judge as the subject; threshold on expected; one experiment per tag valueclean answers and copies with one planted defect each, tagged planted and defect; defects written by a model of another family than the judgevalidated_by: <experiment_id> on every check that uses the judge
Cost and latencyexpensive cases, long answers, a slow p95the variant stays under $X per case; p95 stays under Y msthreshold on cost_usd or latency_p95_ms, belowthe longest cases; p95 needs 20 attemptsanother agent, a smaller max_tokens, one step less
Stabilitya case passes on some repeats and fails on othersthe share of cases that pass only sometimes is above X; two variants differ by less than the A/A noiseany question with repeats: 3 or more; read stability and pass^k; an A/A pair: two variants as written, compare, margin: 0fixed working casesalways failing: fix the step; sometimes failing: a retry with a check, or a vote. Don’t lower the temperature to look stable
Agent per stepan expensive agent on a simple stepagent B on step S is not worse than A by more than m, and a pass costs lessnoninferior with an agent factor on S, range from: S, to: S; guardrails cost_of_pass, schema_valid_first_try, latency_p95_mscases carry the outputs of the nodes above Spoint the node’s agent: at B
Split a stepone step fails on complex inputsa chain S1 → S2 beats one call by more than m, and not only because it calls morea flow factor on a call slot: the chain, a local flow of equal budget, and the single step as variants; compare the chain against the equal-budget flow; guardrail cost_of_pass with relative: truethe same cases for every variant; every flow in the slot has the slot’s input and output typesthe winning flow goes into the project flow. If the chain only beats the single step, the gain came from more calls: keep one step with a vote or a retry

The Lumen example project has a worked experiment for several of these rows. Each changes one factor, and the A/A pair keeps every variant as written:

CategoryLumen experimentSetup
Long or noisy input, Split a stepintent_split_long_messagescompare with a flow factor on the classify call slot: one step against condense-then-decide
Judge reliabilitycritique_planted_defects, critique_recall_by_agentthreshold over replies with planted defects: the first on expected, the second on its own blocked check with an agent factor
Agent per stepreply_noninferior_mistralnoninferior with an agent factor on the revise step
A new rubricpanel_judge_promptcompare with a prompt factor on the panel judges
An algorithm in a code steppanel_merge_rulenoninferior with a use factor on the aggregate node
Cost and latencyreply_stage_budget, panel_single_judgethreshold on cost_usd; compare of the panel against one judge with a flow factor
Stabilitypanel_aa_noisean A/A pair: run_a and run_b, compare with margin: 0

The full files are on Lumen patterns.

Invariance (“a typo or a reordering must not change the answer”) has no question kind of its own. Put a code step in front of the step you test, one that passes its input through unchanged. Add an alternative of it that corrupts the input on purpose, and compare the two with a use factor, as a noninferior question on the decision field. Read the effect against the A/A noise. Unit-test the corrupting function before the series: a corruption that changes the right answer isn’t a test of invariance.

  1. The one that could kill the product. If the first step can’t read the input at all, nothing downstream matters: tuning a judge over invoice totals that were never extracted is wasted. Test that before tuning thresholds.
  2. Then by count. Failure modes with the largest count from error analysis. In practice, three modes often cover more than half of the problems.
  3. Then by harm. A wrong amount, a promise the decision doesn’t give, unsafe advice rank above wording and style.
  4. Cheap structural guards need no experiment. A code check, a switch or a trim that can’t break goes in without one. An experiment answers “does it help”, not “does the code run”.
  5. Hypotheses from the literature without a failure you saw go last, and only with a tag dimension that provokes them.

Before you pay for new runs, test a premise on the outputs you already have. Every attempt of an earlier series is a run whose output you can read back, and a unit test can call a new check function on those outputs. Some questions get answered without a single new model call.

Answers on an ordinal scale deserve a quick look first. A model that squeezes every answer toward the middle of a scale makes every comparison look like “no effect”.

A hypothesis often bundles two claims, such as “the critic blocks a reply with a planted defect” and “the critic lets a clean reply through”. Each claim gets its own check that could refute it, on the cases it is about. The defect a claim is about comes from the case’s tag, not from what the model happened to say. “Found a problem” doesn’t measure “found this defect”. See Correctness needs ground truth and a control.

WhereWhatWhat breaks without it
description in experiment.yamlthe claim with its number: who, on what, which metric, which bound or marginevery outcome “confirms” the hypothesis
failure_modethe id of the failure mode from error analysisthe finding misses its section of FINDINGS.md
questionkind, metric, bound or margin, guardrailsthe metric gets picked after the data
planthe default cases and repeatsthe size gets fitted to the result you want
experiment.mdPurpose, Cases, Reading the result, Falsifier (which outcome refutes it), If confirmed (what changes in the flow), Caveatknowledge never turns into a change, or gets applied to inputs it doesn’t cover

The failure mode triage_contract_broken: the triage step broke the 200-character limit of an observation on its first answer and both retries. The claim is that the current agent passes the output contract in fewer than 95% of attempts. A second agent on the same inputs gives a reference number:

YAML
apiVersion: "aqven/v1"
kind: "Experiment"
description: "gemini on triage passes the output contract in fewer than 95% of attempts, with gpt on the same inputs as a reference"
failure_mode: "triage_contract_broken"
subject:
flow: "support_case"
from: "triage"
to: "triage"
varies:
what: "agent"
nodes:
- "triage"
cases:
dataset: "support_case_cases"
variants:
- id: "gemini"
- id: "gpt"
nodes:
triage: "gpt"
question:
kind: "threshold"
metric: "success_rate"
variant: "gemini"
below: 0.95
margin: 0.02
plan:
cases: 12
repeats: 3

confirmed means the high end of the interval for gemini is below 0.93: the risk is real. refuted means the success rate is not below the bound, give or take the margin. It never means “no risk”. Twelve cases give about six per half, so on held-out cases this only confirms a large risk. The launch plan says so with short_of_cases or wide.

  • Read traces and agree on failure modes before you write a single hypothesis.
  • Fix specification, wiring and infrastructure gaps directly. Experiment only on generalization gaps.
  • Write the claim with a number, pick its category, and set it up with one factor.
  • Test the riskiest claim first, and test a premise on outputs you already paid for.
  • Write the Falsifier and If confirmed before the first number.