Spaces:
Sleeping
Sleeping
docs: adding detailed docs for agent_prompt , grade and scenarios
Browse files- docs/agent_prompt.md +260 -0
- docs/grader_and_judge.md +327 -0
- docs/scenarios.md +340 -0
docs/agent_prompt.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Agent Prompt β Design Reference
|
| 2 |
+
|
| 3 |
+
The agent prompt is the single most important lever in the inference pipeline. It determines whether the agent inspects the right sources, picks the right label, includes a valid fix, and submits at the right time. This document explains every section of the system prompt, what it does, and why it was written that way.
|
| 4 |
+
|
| 5 |
+
The prompt lives in `inference.py` as `SYSTEM_PROMPT`. It is sent as the `system` role message on every API call.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Overview
|
| 10 |
+
|
| 11 |
+
The prompt has six sections, each targeting a different failure mode in agent behaviour:
|
| 12 |
+
|
| 13 |
+
| Section | What it controls |
|
| 14 |
+
|---|---|
|
| 15 |
+
| Role + Action Space | Frames the task; lists valid actions |
|
| 16 |
+
| Output Format | Forces JSON-only output |
|
| 17 |
+
| Diagnosis Process | Step-by-step investigation procedure |
|
| 18 |
+
| Label Decision Rules | Exact rules for picking the correct label |
|
| 19 |
+
| Null Data Rule | Handles missing gradient data gracefully |
|
| 20 |
+
| Stop Rules | When to stop inspecting and submit |
|
| 21 |
+
| Rules | Final constraints and exact label vocabulary |
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
## Section 1 β Role and Action Space
|
| 26 |
+
|
| 27 |
+
```
|
| 28 |
+
You are a machine learning engineer diagnosing a failed training run.
|
| 29 |
+
Each turn you receive data and must decide what to investigate next.
|
| 30 |
+
|
| 31 |
+
Available actions:
|
| 32 |
+
inspect_logs β examine training loss/accuracy curves
|
| 33 |
+
inspect_config β examine hyperparameter config (lr, optimizer, etc.)
|
| 34 |
+
inspect_gradients β examine gradient norm statistics
|
| 35 |
+
submit_diagnosis β submit your final diagnosis (ends the episode)
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
**What it does:**
|
| 39 |
+
Sets the persona and enumerates the action space.
|
| 40 |
+
|
| 41 |
+
**Why it's written this way:**
|
| 42 |
+
LLMs perform better when given a concrete professional role rather than an abstract task description. "You are an ML engineer" anchors the model in a domain where it has relevant training data. Listing the four actions explicitly prevents the model from hallucinating action names or attempting actions that don't exist in the environment.
|
| 43 |
+
|
| 44 |
+
The one-line description after each action (`examine training loss/accuracy curves`) is there so the model knows what data each action returns before it takes the action. Without this, the model may inspect sources in the wrong order or inspect the same source twice looking for something it already saw.
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
|
| 48 |
+
## Section 2 β Output Format
|
| 49 |
+
|
| 50 |
+
```
|
| 51 |
+
OUTPUT FORMAT β STRICT:
|
| 52 |
+
Output ONLY a raw JSON object. No markdown, no code fences, no backticks, no explanation.
|
| 53 |
+
Start with { and end with }. One line only.
|
| 54 |
+
|
| 55 |
+
Examples:
|
| 56 |
+
{"action_type": "inspect_logs"}
|
| 57 |
+
{"action_type": "submit_diagnosis", "diagnosis": "overfitting", "suggested_fix": "add dropout=0.3 and weight_decay=0.01", "reasoning": "train_loss fell to 0.03 by epoch 20 while val_loss rose to 2.34; train_acc=0.99 vs val_acc=0.54 β clear generalization gap. Config shows dropout=0.0 and weight_decay=0.0."}
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
**What it does:**
|
| 61 |
+
Constrains the model to produce parseable JSON with no surrounding text.
|
| 62 |
+
|
| 63 |
+
**Why it's written this way:**
|
| 64 |
+
The inference loop calls `json.loads()` on the model's output directly. Any markdown, preamble, or explanation causes a parse error and falls back to a default action. The "STRICT" label and the explicit instructions about `{` / `}` and "one line only" are there because frontier models default to markdown code blocks when asked to output JSON in a conversational context.
|
| 65 |
+
|
| 66 |
+
The two examples serve a second purpose: they show the model the difference between an inspection action (just `action_type`) and a diagnosis action (all four fields). Without an example, models sometimes include all fields on every action or omit fields on diagnosis. The `response_format={"type": "json_object"}` in the API call enforces JSON at the tokeniser level, but the examples set expectations about structure.
|
| 67 |
+
|
| 68 |
+
The `reasoning` example is intentionally long and specific. It models the desired output: quote exact numbers, name the metric, state the conclusion. This shapes the judge score β the LLM judge rewards reasoning that cites specific values.
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
## Section 3 β Diagnosis Process
|
| 73 |
+
|
| 74 |
+
```
|
| 75 |
+
DIAGNOSIS PROCESS β follow this every episode:
|
| 76 |
+
1. Call inspect_logs first β always.
|
| 77 |
+
2. Read the Data field carefully. Note the exact numeric values (loss, acc, lr, gradient norms, model).
|
| 78 |
+
3. If Feedback says "Next required action: inspect_X" β call that action next, no exceptions.
|
| 79 |
+
4. When no required actions remain, form your diagnosis based ONLY on values you actually saw in Data.
|
| 80 |
+
5. Your reasoning MUST quote specific numbers from the Data you received (e.g. "val_loss=2.34 at epoch 20, train_acc=0.99"). If you cannot quote a specific number from the Data, you have not read it β do not submit yet.
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
**What it does:**
|
| 84 |
+
Provides a deterministic five-step procedure the agent should follow every episode.
|
| 85 |
+
|
| 86 |
+
**Why each step is there:**
|
| 87 |
+
|
| 88 |
+
**Step 1 β `inspect_logs first β always`:**
|
| 89 |
+
Logs are the primary diagnostic signal in every scenario across all difficulty tiers. Starting elsewhere wastes a step and costs efficiency points. Making this an unconditional rule removes any ambiguity about where to begin.
|
| 90 |
+
|
| 91 |
+
**Step 2 β `Note the exact numeric values`:**
|
| 92 |
+
Models tend to read observations superficially and reason from priors ("loss was high, must be underfitting") rather than from the actual numbers in the data. Explicitly instructing the model to note specific values primes it to treat the data as ground truth rather than confirmation of a hypothesis it already had.
|
| 93 |
+
|
| 94 |
+
**Step 3 β `If Feedback says "Next required action: inspect_X" β call that action next, no exceptions`:**
|
| 95 |
+
The environment's feedback field gives explicit guidance on what to inspect next when required sources remain. This rule ensures the agent follows that guidance rather than submitting early. The phrase "no exceptions" is intentional β without a strong constraint here, models sometimes skip to `submit_diagnosis` after seeing one confident pattern in the logs, missing required config or gradient evidence.
|
| 96 |
+
|
| 97 |
+
**Step 4 β `based ONLY on values you actually saw`:**
|
| 98 |
+
This is an anti-hallucination constraint. Without it, models sometimes cite values from their training data (e.g. "the learning rate of 1e-3 is too low for Adam") rather than values they observed in the episode. The diagnosis must be grounded in what was returned by the environment.
|
| 99 |
+
|
| 100 |
+
**Step 5 β the `quote specific numbers` requirement:**
|
| 101 |
+
This does two things. First, it forces the model to verify it actually read the data before submitting. Second, it directly improves the LLM judge score β the judge's `evidence_grounding` criterion rewards reasoning that cites specific observed values. Without this instruction, models often submit correct diagnoses with vague reasoning like "the loss was high" rather than "val_loss=2.74 at epoch 20".
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
+
## Section 4 β Label Decision Rules
|
| 106 |
+
|
| 107 |
+
```
|
| 108 |
+
LABEL DECISION RULES β use these to pick the exact diagnosis label:
|
| 109 |
+
- train_loss is NaN from epoch 1 AND config shows extreme weight_init (e.g. std=100) AND gradient norms are massive (>10000) β "bad weight initialization". Check config FIRST before applying the NaN rule below.
|
| 110 |
+
- train_loss is NaN or inf AFTER at least one finite epoch β "exploding gradients". ABSOLUTE RULE. No other label applies.
|
| 111 |
+
- loss oscillates wildly epoch-to-epoch but stays finite (no NaN) AND config shows batch_size β€ 4 β "batch size too small" (NOT "learning rate too high"). PRIORITY RULE: check batch_size in config before applying the oscillation β lr rule.
|
| 112 |
+
- loss oscillates wildly epoch-to-epoch but stays finite (no NaN) AND config shows batch_size > 4 β "learning rate too high"
|
| 113 |
+
- both train_loss AND val_loss stay high with no gap (train_acc β val_acc, both near random baseline ~10%) AND config shows SGD optimizer with momentum=0.0 β "optimizer misconfiguration" (NOT "underfitting"). Check config for SGD momentum before applying the underfitting rule.
|
| 114 |
+
- both train_loss AND val_loss stay high with no gap (train_acc β val_acc, both near random baseline ~10%) AND config does NOT show SGD with momentum=0.0 β "underfitting". ABSOLUTE RULE. Do NOT wait for gradients. Submit immediately after seeing the logs.
|
| 115 |
+
- train_loss low, val_loss rising AND config shows weight_decay=0.0 exactly AND dropout=0.0 exactly β "missing regularization" (NOT "overfitting")
|
| 116 |
+
- train_loss low, val_loss rising AND config shows ANY non-zero weight_decay OR ANY non-zero dropout β "overfitting" (NOT "missing regularization")
|
| 117 |
+
- gradient norm = 0.0 exactly in hidden layers AND config shows ReLU activation β "dying relu"
|
| 118 |
+
- gradient norm tiny but nonzero (e.g. 1e-5, 1e-8) AND config EXPLICITLY shows activation=sigmoid or activation=tanh β "vanishing gradients". Do NOT assume activation β it must be stated in the config data you actually received.
|
| 119 |
+
- config shows lr_scheduler with gamma > 1.0 β "lr scheduler misconfiguration"
|
| 120 |
+
- config shows weight_init with extreme std AND gradient norms >10000 β "bad weight initialization"
|
| 121 |
+
- config shows SGD optimizer with momentum=0.0 β "optimizer misconfiguration"
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
**What it does:**
|
| 125 |
+
Provides explicit conditional logic for every ambiguous pair of scenarios. This is the most critical section of the prompt.
|
| 126 |
+
|
| 127 |
+
**Why it exists:**
|
| 128 |
+
LLMs have strong priors from their training data. "Loss oscillates wildly" β the model's prior is "learning rate too high". "Both losses high" β prior is "underfitting". These priors are reasonable but wrong for some scenarios. The rules override the prior with conditional logic based on config values.
|
| 129 |
+
|
| 130 |
+
**Why each rule is structured the way it is:**
|
| 131 |
+
|
| 132 |
+
**`bad_weight_initialization` rule comes first:**
|
| 133 |
+
Both `bad_weight_initialization` and `exploding_gradients` produce NaN. The distinguishing condition is timing (NaN from epoch 1 vs after epoch 1) and config (extreme weight_init). By checking `bad_weight_initialization` first and making `exploding_gradients` an "AFTER at least one finite epoch" rule, the two cases are unambiguously separated. If the order were reversed, a model might apply the NaNβexploding rule and never check the config.
|
| 134 |
+
|
| 135 |
+
**`batch_size_too_small` priority rule:**
|
| 136 |
+
Oscillating loss is caused by both high LR and tiny batch sizes. The PRIORITY label means: before concluding "learning rate too high" from oscillation, check `batch_size` in the config. If `batch_size β€ 4`, that overrides. This rule was added after observing the model consistently labelling `batch_size_too_small` as "learning rate too high" β the oscillation pattern was overriding the config evidence.
|
| 137 |
+
|
| 138 |
+
**`optimizer_misconfiguration` before `underfitting`:**
|
| 139 |
+
Flat losses near baseline look exactly like underfitting. But `optimizer_misconfiguration` (SGD, momentum=0.0) produces the same log pattern for a different reason. The rule requires checking config for SGD + momentum=0.0 before committing to "underfitting". This was the other medium-tier failure: the model saw flat losses and submitted "underfitting" without checking the optimizer config.
|
| 140 |
+
|
| 141 |
+
**"ABSOLUTE RULE" labels:**
|
| 142 |
+
`exploding_gradients` and `underfitting` are marked as absolute rules because they were the two cases where the model still incorrectly applied a different label after seeing unambiguous evidence. The "ABSOLUTE RULE" phrasing raises the salience of the constraint β it signals that this rule has no exceptions and no competing rule can override it.
|
| 143 |
+
|
| 144 |
+
**`overfitting` vs `missing_regularization`:**
|
| 145 |
+
Both show train-val divergence. The split is on exact config values: `weight_decay=0.0 exactly AND dropout=0.0 exactly` β missing regularization. Any non-zero value β overfitting. The word "exactly" and "ANY non-zero" are deliberate β the model previously classified "weight_decay=0.001" as "weight_decayβ0" and got the label wrong.
|
| 146 |
+
|
| 147 |
+
**`vanishing_gradients` β "Do NOT assume activation":**
|
| 148 |
+
Vanishing gradients are caused by saturating activations (sigmoid, tanh). Without this constraint, the model sometimes infers the activation from the gradient decay pattern rather than from the config data it actually received. The rule requires explicit evidence from the config.
|
| 149 |
+
|
| 150 |
+
---
|
| 151 |
+
|
| 152 |
+
## Section 5 β Null Data Rule
|
| 153 |
+
|
| 154 |
+
```
|
| 155 |
+
NULL DATA RULE:
|
| 156 |
+
- If Data shows {"gradient_norms": null}, gradient data was NOT collected for this run. This is normal for some scenarios β it is NOT a data pipeline error.
|
| 157 |
+
- "missing data", "missing gradients", "insufficient data" are NEVER valid diagnoses. NEVER submit these. Always diagnose the ML failure mode from what you have seen.
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
**What it does:**
|
| 161 |
+
Handles the case where gradient data is null (which happens in easy and medium scenarios, where gradients are present in the scenario dict but not required).
|
| 162 |
+
|
| 163 |
+
**Why it exists:**
|
| 164 |
+
Early testing showed the model sometimes submitting "missing gradients" or "insufficient data" as the diagnosis when it saw `{"gradient_norms": null}`. This is a valid observation (the data is null) but a catastrophically wrong response (it's not a diagnosis). The null data rule explicitly blocks this failure mode and redirects the model toward diagnosing the ML failure from whatever evidence it does have.
|
| 165 |
+
|
| 166 |
+
The phrase "NEVER valid diagnoses. NEVER submit these" uses double emphasis because this failure mode produced a score of 0.0 β no partial credit, no keywords matched.
|
| 167 |
+
|
| 168 |
+
---
|
| 169 |
+
|
| 170 |
+
## Section 6 β Stop Rules
|
| 171 |
+
|
| 172 |
+
```
|
| 173 |
+
STOP RULES β mandatory:
|
| 174 |
+
- "This source is not required for this failure mode." means STOP IMMEDIATELY. Submit your diagnosis on the very next action. Do NOT call any more inspect actions β not even one.
|
| 175 |
+
- "Relevant clue found" with no "Next required action" β all sources covered. Submit on the next action.
|
| 176 |
+
- CRITICAL: If Feedback contains "Next required action: inspect_X", you MUST call that action before submitting.
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
**What it does:**
|
| 180 |
+
Controls exactly when the agent submits its diagnosis β not too early and not too late.
|
| 181 |
+
|
| 182 |
+
**Why each rule is there:**
|
| 183 |
+
|
| 184 |
+
**"STOP IMMEDIATELY" rule:**
|
| 185 |
+
When the environment returns "This source is not required for this failure mode", it means the agent has wandered into an irrelevant source. Every additional inspection costs β0.02 (evidence score) and adds to the step count (efficiency score). The "not even one" phrasing prevents the model from making "just one more" check after being told to stop.
|
| 186 |
+
|
| 187 |
+
**"No Next required action" rule:**
|
| 188 |
+
The feedback field explicitly tells the agent when all required sources have been inspected. At that point, the agent has all the evidence it needs β inspecting further only wastes steps. This rule makes the trigger explicit: no "Next required action" in the feedback = submit now.
|
| 189 |
+
|
| 190 |
+
**"CRITICAL: If Feedback contains Next required action" rule:**
|
| 191 |
+
This is the counterbalancing rule. The previous two rules push the agent toward early submission. This one prevents premature submission when required sources are still outstanding. The CRITICAL label flags it as the most important stop rule β submitting too early on a hard scenario loses both efficiency points and evidence score.
|
| 192 |
+
|
| 193 |
+
---
|
| 194 |
+
|
| 195 |
+
## Section 7 β Rules (Label Vocabulary and Final Constraints)
|
| 196 |
+
|
| 197 |
+
```
|
| 198 |
+
RULES:
|
| 199 |
+
- submit_diagnosis MUST include all three fields: diagnosis, suggested_fix, reasoning.
|
| 200 |
+
- diagnosis is the short failure mode label β it is REQUIRED, never omit it.
|
| 201 |
+
- Use exact failure mode phrasing for diagnosis: "exploding gradients", "overfitting", "underfitting",
|
| 202 |
+
"learning rate too high", "learning rate too low", "vanishing gradients",
|
| 203 |
+
"dying relu", "missing regularization", "batch size too small",
|
| 204 |
+
"optimizer misconfiguration", "bad weight initialization", "lr scheduler misconfiguration".
|
| 205 |
+
- Never inspect the same source twice.
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
**What it does:**
|
| 209 |
+
Enforces structural requirements on the submit action and provides the exact vocabulary for labels.
|
| 210 |
+
|
| 211 |
+
**Why each rule is there:**
|
| 212 |
+
|
| 213 |
+
**All three fields required:**
|
| 214 |
+
The fix scorer deducts β0.05 if `suggested_fix` is absent. The LLM judge returns `None` (skipping its contribution) if `reasoning` is absent. The `diagnosis` field is the grader's primary input. Omitting any of them costs points in measurable ways.
|
| 215 |
+
|
| 216 |
+
**Exact failure mode phrasing:**
|
| 217 |
+
The grader's exact keyword map contains specific phrases like `"exploding gradients"`, `"dying relu"`, `"lr scheduler misconfiguration"`. A diagnosis that says "exploding gradient" (singular) still gets partial credit from the category keywords, but misses the +0.40 exact match. Providing the canonical list gives the model the exact strings to use β no paraphrasing, no guessing.
|
| 218 |
+
|
| 219 |
+
**Never inspect the same source twice:**
|
| 220 |
+
Re-inspecting a source returns the same data and costs β0.05 (step reward). It also wastes a step toward the efficiency score. This rule is explicit because models occasionally re-inspect logs "to double-check" before submitting.
|
| 221 |
+
|
| 222 |
+
---
|
| 223 |
+
|
| 224 |
+
## User Prompt
|
| 225 |
+
|
| 226 |
+
Each step, a user message accompanies the system prompt. It has three parts:
|
| 227 |
+
|
| 228 |
+
```
|
| 229 |
+
Step {step}
|
| 230 |
+
|
| 231 |
+
Observation:
|
| 232 |
+
{obs_summary}
|
| 233 |
+
|
| 234 |
+
Recent history:
|
| 235 |
+
{history_block}
|
| 236 |
+
|
| 237 |
+
Before responding: read the Data above carefully. What specific numeric values do you see?
|
| 238 |
+
Quote at least one value from the Data in your reasoning before submitting a diagnosis.
|
| 239 |
+
Respond with a JSON action.
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
**`obs_summary`** β formatted string with `Task`, `Feedback`, and `Data` (the visible_data JSON). This is the agent's primary input.
|
| 243 |
+
|
| 244 |
+
**`history_block`** β the last 4 step summaries: `Step N: {action} β reward={R} | {feedback}\n Data: {data}`. Four steps is enough to cover the full hard-tier trajectory (3 inspections + 1 submit) without exceeding the context budget.
|
| 245 |
+
|
| 246 |
+
**"Quote at least one value"** β repeated from the system prompt in the user turn. The repetition is intentional. Models attend more strongly to instructions that appear close to the end of the prompt. Placing the evidence-citation reminder in the user message, adjacent to the actual data, increases compliance.
|
| 247 |
+
|
| 248 |
+
---
|
| 249 |
+
|
| 250 |
+
## Summary β What Each Section Guards Against
|
| 251 |
+
|
| 252 |
+
| Section | Failure it prevents |
|
| 253 |
+
|---|---|
|
| 254 |
+
| Role + Action Space | Hallucinated actions, wrong action names |
|
| 255 |
+
| Output Format | Markdown wrapping, parse errors, missing fields |
|
| 256 |
+
| Diagnosis Process | Inspecting in wrong order, not reading numbers, submitting without evidence |
|
| 257 |
+
| Label Decision Rules | Wrong label on ambiguous patterns (oscillationβlr vs batch, flatβunderfitting vs optimizer) |
|
| 258 |
+
| Null Data Rule | Diagnosing "missing gradients" instead of the actual ML failure |
|
| 259 |
+
| Stop Rules | Inspecting too many sources (efficiency loss) or too few (evidence loss) |
|
| 260 |
+
| Rules | Wrong label phrasing, missing fix/reasoning, redundant inspections |
|
docs/grader_and_judge.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Grader & LLM Judge β Internal Design
|
| 2 |
+
|
| 3 |
+
This document explains how WhyDidItFail scores an agent's performance after each episode.
|
| 4 |
+
Scoring has two layers: a **programmatic grader** that runs keyword matching, and an **LLM judge** that evaluates reasoning quality. The two scores are combined into a single final number.
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
## Overview
|
| 9 |
+
|
| 10 |
+
```
|
| 11 |
+
Agent submits diagnosis
|
| 12 |
+
β
|
| 13 |
+
βββΊ Programmatic Grader (server/graders.py)
|
| 14 |
+
β five sub-scores β keyword_score [0.0β1.0]
|
| 15 |
+
β
|
| 16 |
+
βββΊ LLM Judge (server/llm_judge.py)
|
| 17 |
+
three criteria β judge_score [0.0β1.0]
|
| 18 |
+
|
| 19 |
+
Final Score = 0.85 Γ keyword_score + 0.15 Γ judge_score
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
The grader is fast and deterministic β it runs synchronously inside the environment's `step()` call. The judge is a separate LLM call that runs **after** the episode ends (the WebSocket is already closed by then), so it never adds latency to the agent's action loop.
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## Part 1 β Programmatic Grader
|
| 27 |
+
|
| 28 |
+
**File:** `server/graders.py`
|
| 29 |
+
**Entry point:** `grade(diagnosis, suggested_fix, scenario, steps_taken, inspection_order)`
|
| 30 |
+
|
| 31 |
+
The grader produces five sub-scores and sums them. The result is clamped to `[0.0, 1.0]`.
|
| 32 |
+
|
| 33 |
+
```
|
| 34 |
+
Total = diagnosis_score + evidence_diagnosis_penalty
|
| 35 |
+
+ evidence_score + efficiency_score + fix_score + ordering_bonus
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
### 1.1 Diagnosis Score β up to +0.70
|
| 39 |
+
|
| 40 |
+
This is the biggest sub-score. It checks whether the agent named the correct failure mode.
|
| 41 |
+
|
| 42 |
+
Every scenario has a `correct_diagnosis` field (e.g. `"exploding_gradients"`). There are two keyword maps in the grader:
|
| 43 |
+
|
| 44 |
+
**Exact keywords** β strong signal, worth +0.40 each:
|
| 45 |
+
```
|
| 46 |
+
"exploding_gradients" β ["exploding gradients", "exploding"]
|
| 47 |
+
"overfitting" β ["overfitting", "overfit"]
|
| 48 |
+
"dying_relu" β ["dying relu", "dead relu"]
|
| 49 |
+
...
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
**Category keywords** β weaker signal, worth +0.10 each:
|
| 53 |
+
```
|
| 54 |
+
"exploding_gradients" β ["nan", "gradient", "overflow", "diverge"]
|
| 55 |
+
"overfitting" β ["generalization", "val loss", "memoriz"]
|
| 56 |
+
...
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
The agent's diagnosis string is lowercased and scanned for both sets. Matches accumulate. The result is capped at 0.70.
|
| 60 |
+
|
| 61 |
+
**Vague answer penalty:** If no exact keyword matched and the diagnosis is fewer than 3 words, β0.10 is applied. This discourages one-word guesses.
|
| 62 |
+
|
| 63 |
+
**Practical meaning:** A correct exact label ("exploding gradients" or "exploding") earns +0.40 right away. Hitting a few category keywords on top of that can bring it up toward the 0.70 cap. A wrong diagnosis with some related vocabulary still earns partial credit, but cannot exceed 0.70.
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
### 1.2 Evidence-Diagnosis Penalty β 0.00 to β0.10
|
| 68 |
+
|
| 69 |
+
This penalty fires when the agent had access to the right data but still got the diagnosis wrong. It distinguishes between an agent that guessed blind versus one that inspected evidence and reasoned poorly.
|
| 70 |
+
|
| 71 |
+
| Situation | Penalty |
|
| 72 |
+
|---|---|
|
| 73 |
+
| All required sources inspected, diagnosis wrong | β0.10 |
|
| 74 |
+
| Some required sources inspected, diagnosis wrong | β0.05 |
|
| 75 |
+
| No required sources inspected, diagnosis wrong | 0.00 |
|
| 76 |
+
| Diagnosis correct (any case) | 0.00 |
|
| 77 |
+
|
| 78 |
+
The evidence score (next section) already penalises skipping required sources. This penalty stacks on top when the agent collected the evidence but drew the wrong conclusion β a clear reasoning failure.
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
### 1.3 Evidence Score β up to +0.25 (floored at β0.15)
|
| 83 |
+
|
| 84 |
+
This sub-score measures whether the agent inspected the right data sources before submitting.
|
| 85 |
+
|
| 86 |
+
Each scenario defines which sources are required. Easy scenarios require only `logs`. Medium requires `logs + config`. Hard requires `logs + config + gradients`.
|
| 87 |
+
|
| 88 |
+
```
|
| 89 |
+
+0.08 per required source inspected
|
| 90 |
+
β0.10 per required source NOT inspected
|
| 91 |
+
β0.02 per irrelevant source inspected
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
The raw sum is clamped to `[β0.15, +0.25]`.
|
| 95 |
+
|
| 96 |
+
**Example β hard scenario, agent inspected logs + config but skipped gradients:**
|
| 97 |
+
- logs required and inspected: +0.08
|
| 98 |
+
- config required and inspected: +0.08
|
| 99 |
+
- gradients required but missing: β0.10
|
| 100 |
+
- Total: +0.06
|
| 101 |
+
|
| 102 |
+
**Example β easy scenario, agent inspected logs + gradients (gradients not required):**
|
| 103 |
+
- logs required and inspected: +0.08
|
| 104 |
+
- gradients not required: β0.02
|
| 105 |
+
- Total: +0.06
|
| 106 |
+
|
| 107 |
+
The β0.02 for irrelevant sources is intentionally mild β some exploration is acceptable. Missing a required source is much more costly.
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
### 1.4 Efficiency Score β up to +0.15
|
| 112 |
+
|
| 113 |
+
This sub-score rewards acting without waste. The minimum number of steps for any episode is: `len(required_sources) + 1` (inspect all required sources, then submit).
|
| 114 |
+
|
| 115 |
+
```
|
| 116 |
+
If steps_taken == min_steps: score = 0.15 (full reward)
|
| 117 |
+
If steps_taken > min_steps: score = max(0.0, 0.15 β 0.02 Γ extra_steps^1.2)
|
| 118 |
+
If steps_taken < min_steps: score = max(0.0, 0.15 β 0.05 Γ missing_steps)
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
The exponent `1.2` makes the penalty accelerate as the agent wastes more steps β each additional wasted step costs slightly more than the previous one.
|
| 122 |
+
|
| 123 |
+
Early submission (fewer steps than the minimum) is also penalised because the agent almost certainly skipped a required source β but that case is already caught harder by the evidence score.
|
| 124 |
+
|
| 125 |
+
**If `steps_taken > max_steps`** (which is `len(required_sources) Γ 3 + 2`), the grader immediately returns `0.0` and skips all sub-scores. This is the hard ceiling.
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
### 1.5 Fix Score β up to +0.15 (or β0.05)
|
| 130 |
+
|
| 131 |
+
This sub-score checks whether the agent's suggested fix is actionable and correct.
|
| 132 |
+
|
| 133 |
+
Each scenario has a `correct_fix` string (e.g. `"enable gradient clipping (clip_grad_norm=1.0)"`). The grader tokenises that string, strips stop words (`to`, `a`, `the`, `and`, `or`, `use`, `set`, `by`), and keeps content words longer than 2 characters.
|
| 134 |
+
|
| 135 |
+
It then counts how many of those content words appear anywhere in the agent's `suggested_fix` string.
|
| 136 |
+
|
| 137 |
+
| Match ratio | Score |
|
| 138 |
+
|---|---|
|
| 139 |
+
| 100% of keywords matched | +0.15 |
|
| 140 |
+
| β₯ 60% matched | +0.10 |
|
| 141 |
+
| β₯ 30% matched | +0.05 |
|
| 142 |
+
| < 30% matched | 0.00 |
|
| 143 |
+
| No fix provided | β0.05 |
|
| 144 |
+
|
| 145 |
+
Omitting the fix always costs points. Even a partially correct fix earns more than silence.
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
### 1.6 Ordering Bonus β +0.05
|
| 150 |
+
|
| 151 |
+
A small bonus for inspecting required sources in the canonical order: `logs β config β gradients`.
|
| 152 |
+
|
| 153 |
+
The grader extracts the subsequence of inspected sources that are required (ignoring any irrelevant ones inspected in between), and checks whether that subsequence matches the canonical order for the scenario.
|
| 154 |
+
|
| 155 |
+
For an easy scenario (only `logs` required) the bonus is trivially earned. For hard scenarios (all three required) the agent must visit them in order.
|
| 156 |
+
|
| 157 |
+
This rewards structured investigation β the same order a human engineer would follow.
|
| 158 |
+
|
| 159 |
+
---
|
| 160 |
+
|
| 161 |
+
### 1.7 Maximum Achievable Scores
|
| 162 |
+
|
| 163 |
+
| Sub-score | Max | Min |
|
| 164 |
+
|---|---|---|
|
| 165 |
+
| Diagnosis | +0.70 | 0.00 |
|
| 166 |
+
| Evidence-Diagnosis Penalty | 0.00 | β0.10 |
|
| 167 |
+
| Evidence | +0.25 | β0.15 |
|
| 168 |
+
| Efficiency | +0.15 | 0.00 |
|
| 169 |
+
| Fix | +0.15 | β0.05 |
|
| 170 |
+
| Ordering Bonus | +0.05 | 0.00 |
|
| 171 |
+
| **Total (clamped)** | **1.00** | **0.00** |
|
| 172 |
+
|
| 173 |
+
The theoretical maximum without fix is `0.70 + 0.25 + 0.15 + 0.05 = 1.15`, which is clamped to `1.00`. The fix and ordering bonus are therefore "free" once the other scores are high β they can push a near-perfect run to a clean `1.00`.
|
| 174 |
+
|
| 175 |
+
---
|
| 176 |
+
|
| 177 |
+
## Part 2 β Step-Level Rewards
|
| 178 |
+
|
| 179 |
+
The grader described above runs only at `submit_diagnosis`. But the environment also emits a small reward on every inspection step, giving the agent an in-episode signal.
|
| 180 |
+
|
| 181 |
+
These step rewards come from `_inspect_reward()` in the environment, not from `graders.py`.
|
| 182 |
+
|
| 183 |
+
| Situation | Step Reward |
|
| 184 |
+
|---|---|
|
| 185 |
+
| First required source discovered | +0.10 |
|
| 186 |
+
| Second required source discovered | +0.07 |
|
| 187 |
+
| Third required source discovered | +0.05 |
|
| 188 |
+
| Irrelevant source inspected | β0.03 |
|
| 189 |
+
| Re-inspecting a source already seen | β0.05 |
|
| 190 |
+
|
| 191 |
+
The decaying reward schedule (+0.10 β +0.07 β +0.05) reflects that each additional required source is slightly less surprising than the first. It also gives a larger signal for discovering the first clue, which is usually the most diagnostic piece of evidence.
|
| 192 |
+
|
| 193 |
+
These step rewards are reported in `[STEP]` lines during inference but they are **not** included in the final episode score β `grade()` computes the terminal score independently.
|
| 194 |
+
|
| 195 |
+
---
|
| 196 |
+
|
| 197 |
+
## Part 3 β LLM Judge
|
| 198 |
+
|
| 199 |
+
**File:** `server/llm_judge.py`
|
| 200 |
+
**Entry point:** `judge(client, model, diagnosis, reasoning, suggested_fix, scenario, inspection_order)`
|
| 201 |
+
|
| 202 |
+
### 3.1 What It Evaluates
|
| 203 |
+
|
| 204 |
+
The programmatic grader is blind to *how* the agent arrived at its answer. An agent could get lucky with the right keyword and still have incoherent reasoning. The LLM judge evaluates the *quality of the agent's reasoning* across three criteria:
|
| 205 |
+
|
| 206 |
+
| Criterion | Question it asks | Max |
|
| 207 |
+
|---|---|---|
|
| 208 |
+
| `evidence_grounding` | Does the reasoning cite specific values from the data the agent actually saw? | 5 |
|
| 209 |
+
| `causal_chain` | Does it logically connect that evidence to the diagnosed failure mode? | 5 |
|
| 210 |
+
| `fix_rationale` | Is the fix directly justified by the evidence and diagnosis? | 5 |
|
| 211 |
+
|
| 212 |
+
Raw score range: 0β15. Normalised to 0.0β1.0 by dividing by 15.
|
| 213 |
+
|
| 214 |
+
---
|
| 215 |
+
|
| 216 |
+
### 3.2 When It Runs
|
| 217 |
+
|
| 218 |
+
The judge runs **after** the episode ends β specifically after `submit_diagnosis` returns and the WebSocket connection is closed. This is intentional: the WebSocket session is single-use, so the judge call can't interfere with the agent's action loop.
|
| 219 |
+
|
| 220 |
+
```
|
| 221 |
+
Agent calls submit_diagnosis
|
| 222 |
+
β
|
| 223 |
+
βββΊ environment returns final_reward, done=True
|
| 224 |
+
β
|
| 225 |
+
βββΊ WebSocket closes
|
| 226 |
+
β
|
| 227 |
+
βββΊ inference.py calls judge()
|
| 228 |
+
β
|
| 229 |
+
βββΊ one LLM call β score
|
| 230 |
+
β
|
| 231 |
+
βββΊ Final Score = 0.85 Γ keyword + 0.15 Γ judge
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
---
|
| 235 |
+
|
| 236 |
+
### 3.3 What the Judge Sees
|
| 237 |
+
|
| 238 |
+
The judge prompt is built from:
|
| 239 |
+
1. The agent's `diagnosis`, `suggested_fix`, and `reasoning` strings
|
| 240 |
+
2. The **data the agent actually inspected** β reconstructed from `inspection_order` and the scenario's source data
|
| 241 |
+
|
| 242 |
+
The judge is given only what the agent had access to, not the full scenario. This prevents the judge from penalising an agent for not citing data it never saw.
|
| 243 |
+
|
| 244 |
+
```python
|
| 245 |
+
seen = {}
|
| 246 |
+
if "logs" in inspection_order:
|
| 247 |
+
seen["training_logs"] = scenario["logs"]
|
| 248 |
+
if "config" in inspection_order:
|
| 249 |
+
seen["config"] = scenario["config"]
|
| 250 |
+
if "gradients" in inspection_order:
|
| 251 |
+
seen["gradient_norms"] = scenario["gradient_norms"]
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
---
|
| 255 |
+
|
| 256 |
+
### 3.4 The Judge Prompt
|
| 257 |
+
|
| 258 |
+
The judge receives a single-turn user message (no system prompt). It is asked to return a JSON object with integer scores:
|
| 259 |
+
|
| 260 |
+
```
|
| 261 |
+
{"evidence_grounding": <int>, "causal_chain": <int>, "fix_rationale": <int>}
|
| 262 |
+
```
|
| 263 |
+
|
| 264 |
+
Temperature is set to `0.0` for determinism. `max_tokens=64` is enough for the JSON response and prevents runaway output.
|
| 265 |
+
|
| 266 |
+
---
|
| 267 |
+
|
| 268 |
+
### 3.5 Fallback Behaviour
|
| 269 |
+
|
| 270 |
+
If the agent omits the `reasoning` field, the judge returns `None` immediately (no API call made). The caller treats `None` as "judge unavailable" and uses keyword score at full weight:
|
| 271 |
+
|
| 272 |
+
```
|
| 273 |
+
judge_score is None β Final Score = 1.0 Γ keyword_score
|
| 274 |
+
judge_score is float β Final Score = 0.85 Γ keyword_score + 0.15 Γ judge_score
|
| 275 |
+
```
|
| 276 |
+
|
| 277 |
+
If the LLM call itself fails (network error, parse error, etc.), the judge also returns `None` and logs the exception to stderr. The episode score is never blocked waiting on the judge.
|
| 278 |
+
|
| 279 |
+
---
|
| 280 |
+
|
| 281 |
+
### 3.6 Weight Rationale
|
| 282 |
+
|
| 283 |
+
The 85/15 split keeps the judge in a supporting role. The programmatic grader is:
|
| 284 |
+
- **Fast** β no extra API call during the episode
|
| 285 |
+
- **Deterministic** β same input always produces same score
|
| 286 |
+
- **Directly tied to the correct answer** β keyword matching is unambiguous
|
| 287 |
+
|
| 288 |
+
The judge adds 15% for reasoning quality. This is enough to meaningfully separate agents that cite evidence from those that guess correctly without explaining why, but not enough to override a solid keyword score with a harsh reasoning judgment.
|
| 289 |
+
|
| 290 |
+
---
|
| 291 |
+
|
| 292 |
+
## Part 4 β Combined Final Score
|
| 293 |
+
|
| 294 |
+
```
|
| 295 |
+
Final Score = clamp(0.85 Γ keyword_score + 0.15 Γ judge_score, 0.0, 1.0)
|
| 296 |
+
```
|
| 297 |
+
|
| 298 |
+
Both the keyword score and the judge score are already in `[0.0, 1.0]`, so the final score is also in that range without needing clamping in practice. The clamp is a safeguard.
|
| 299 |
+
|
| 300 |
+
**Example β perfect run:**
|
| 301 |
+
- keyword_score = 1.00 (correct label, all sources, minimum steps, full fix, correct order)
|
| 302 |
+
- judge_score = 1.00 (cites specific numbers, clean causal chain, fix matches evidence)
|
| 303 |
+
- Final = 0.85 Γ 1.00 + 0.15 Γ 1.00 = **1.00**
|
| 304 |
+
|
| 305 |
+
**Example β correct label, poor reasoning:**
|
| 306 |
+
- keyword_score = 0.90 (correct label, good evidence, slightly wasteful)
|
| 307 |
+
- judge_score = 0.40 (vague reasoning, no specific numbers cited)
|
| 308 |
+
- Final = 0.85 Γ 0.90 + 0.15 Γ 0.40 = 0.765 + 0.060 = **0.825**
|
| 309 |
+
|
| 310 |
+
**Example β reasoning provided but LLM call fails:**
|
| 311 |
+
- keyword_score = 0.90
|
| 312 |
+
- judge_score = None
|
| 313 |
+
- Final = 1.00 Γ 0.90 = **0.90**
|
| 314 |
+
|
| 315 |
+
---
|
| 316 |
+
|
| 317 |
+
## Summary
|
| 318 |
+
|
| 319 |
+
| Component | File | Weight | What it measures |
|
| 320 |
+
|---|---|---|---|
|
| 321 |
+
| Diagnosis Score | graders.py | Up to 0.70 of keyword_score | Correct failure mode label |
|
| 322 |
+
| Evidence-Diagnosis Penalty | graders.py | Up to β0.10 of keyword_score | Reasoning failure despite having evidence |
|
| 323 |
+
| Evidence Score | graders.py | Up to 0.25 of keyword_score | Right data sources inspected |
|
| 324 |
+
| Efficiency Score | graders.py | Up to 0.15 of keyword_score | Steps taken vs minimum |
|
| 325 |
+
| Fix Score | graders.py | Up to 0.15 of keyword_score | Actionable and correct fix |
|
| 326 |
+
| Ordering Bonus | graders.py | +0.05 of keyword_score | Canonical inspection order |
|
| 327 |
+
| LLM Judge | llm_judge.py | 15% of final score | Reasoning quality (evidence citation, causal logic, fix rationale) |
|
docs/scenarios.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Scenarios β Full Reference
|
| 2 |
+
|
| 3 |
+
WhyDidItFail has 12 scenarios across three difficulty tiers. Each one plants a specific, realistic ML failure mode into a synthetic training run. The agent must read the available evidence and name the failure.
|
| 4 |
+
|
| 5 |
+
This document explains every scenario in detail: what the data looks like, why the failure happens, what the agent needs to see, and what a correct diagnosis and fix look like.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## How Scenarios Work
|
| 10 |
+
|
| 11 |
+
Every scenario is a Python dictionary with the same structure:
|
| 12 |
+
|
| 13 |
+
| Field | What it is |
|
| 14 |
+
|---|---|
|
| 15 |
+
| `difficulty` | `easy`, `medium`, or `hard` |
|
| 16 |
+
| `required_sources` | Which data sources the agent must inspect before submitting |
|
| 17 |
+
| `logs` | Per-epoch training and validation metrics |
|
| 18 |
+
| `config` | Hyperparameter configuration |
|
| 19 |
+
| `gradient_norms` | Per-layer or per-epoch gradient magnitudes |
|
| 20 |
+
| `correct_diagnosis` | The exact internal label the grader checks against |
|
| 21 |
+
| `correct_fix` | The expected remediation (used by the fix scorer) |
|
| 22 |
+
|
| 23 |
+
The `required_sources` field is the contract between the scenario and the grader. Inspecting a required source earns the agent reward. Submitting without inspecting one costs points. Inspecting a source not in the list is penalised mildly.
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## Easy Tier β Logs Only
|
| 28 |
+
|
| 29 |
+
Easy scenarios are diagnosable from the training logs alone. The config and gradients exist in the data but are **not required** β inspecting them costs the agent β0.02 each.
|
| 30 |
+
|
| 31 |
+
The four easy scenarios exercise four distinct log patterns: catastrophic divergence (NaN), chaotic oscillation, train-val split, and flatness.
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
### Exploding Gradients
|
| 36 |
+
|
| 37 |
+
**Correct diagnosis:** `exploding_gradients`
|
| 38 |
+
**Required sources:** logs
|
| 39 |
+
**Correct fix:** `enable gradient clipping (clip_grad_norm=1.0)`
|
| 40 |
+
|
| 41 |
+
**What happens:**
|
| 42 |
+
The model starts normally at epoch 1 (`train_loss=2.31`), then catastrophically diverges. By epoch 2, loss has jumped to `847.2`. By epoch 3, both train and validation loss are `NaN`. The gradient norms tell the same story: `0.43` at epoch 1, `6821.4` at epoch 2, then `NaN`.
|
| 43 |
+
|
| 44 |
+
The config shows a reasonable learning rate (`lr=0.001`, Adam optimizer) with `clip_grad=False`. The learning rate is not the culprit β the problem is that gradients are not being clipped and grow uncontrollably during backpropagation.
|
| 45 |
+
|
| 46 |
+
**What the agent needs to see:**
|
| 47 |
+
The NaN in the logs is the definitive signal. Loss going from 2.31 β 847.2 β NaN in three epochs is an unambiguous divergence pattern. No config or gradient inspection is required to make this call.
|
| 48 |
+
|
| 49 |
+
**Why it's easy:**
|
| 50 |
+
NaN loss is one of the most recognisable failure patterns in ML. There's no ambiguity, no competing hypothesis β any agent that reads the logs and sees NaN should label this correctly.
|
| 51 |
+
|
| 52 |
+
**Disambiguation note:**
|
| 53 |
+
The config shows `lr=0.001` (not high). This rules out "learning rate too high". Once training has already produced finite loss at epoch 1, the failure at epochs 2-3 is exploding gradients, not bad weight initialization (which would be NaN from epoch 1).
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
### Learning Rate Too High
|
| 58 |
+
|
| 59 |
+
**Correct diagnosis:** `learning_rate_too_high`
|
| 60 |
+
**Required sources:** logs
|
| 61 |
+
**Correct fix:** `reduce learning_rate to 0.01`
|
| 62 |
+
|
| 63 |
+
**What happens:**
|
| 64 |
+
The loss oscillates wildly with no convergence trend. It goes `2.30 β 15.21 β 0.82 β 9.73 β 3.15` across five epochs. There's no NaN β the model is technically alive β but it can't settle. Accuracy fluctuates similarly: `0.11 β 0.47 β 0.31 β 0.44 β 0.29`. The gradient norms swing between 0.19 and 11.2.
|
| 65 |
+
|
| 66 |
+
The config shows `lr=1.0` with SGD. A learning rate of 1.0 is far too large for SGD on CIFAR-10 with a VGG16. Each gradient step overshoots the loss minimum, sending the model into a different part of the landscape every epoch.
|
| 67 |
+
|
| 68 |
+
**What the agent needs to see:**
|
| 69 |
+
The oscillation pattern is visible from the logs alone. The lr in the logs (`"lr": 1.0`) also appears inline with each epoch β a sharp agent can confirm the culprit without even inspecting the config separately.
|
| 70 |
+
|
| 71 |
+
**Why it's easy:**
|
| 72 |
+
Wildly oscillating loss with no NaN is a classic high-LR symptom. The `lr=1.0` value in the log lines is a strong additional signal.
|
| 73 |
+
|
| 74 |
+
**Disambiguation note:**
|
| 75 |
+
This scenario uses `batch_size=64`, which rules out batch_size_too_small (which has `batch_size=2`). The oscillation here is caused by the step size, not by gradient variance from small batches.
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
### Overfitting
|
| 80 |
+
|
| 81 |
+
**Correct diagnosis:** `overfitting`
|
| 82 |
+
**Required sources:** logs
|
| 83 |
+
**Correct fix:** `increase dropout to 0.3 and weight_decay to 0.01`
|
| 84 |
+
|
| 85 |
+
**What happens:**
|
| 86 |
+
Train loss falls steadily while validation loss climbs: `train_loss` goes from `2.10 β 0.06` by epoch 20, while `val_loss` rises from `2.16 β 2.74`. Train accuracy reaches `0.99`; val accuracy falls from `0.55` to `0.49`. The model has memorised the training data and fails to generalise.
|
| 87 |
+
|
| 88 |
+
The config shows `weight_decay=0.001` and `dropout=0.1` β regularisation is present but insufficient for a ResNet50 on CIFAR-10. The model is powerful enough to overfit even with light regularisation.
|
| 89 |
+
|
| 90 |
+
**What the agent needs to see:**
|
| 91 |
+
The train-val divergence pattern in the logs is the definitive signal. `train_acc=0.99` and `val_acc=0.49` by epoch 20 is a textbook generalisation gap.
|
| 92 |
+
|
| 93 |
+
**Why it's easy:**
|
| 94 |
+
Train-val divergence is one of the most studied phenomena in ML. The logs make it immediately visible.
|
| 95 |
+
|
| 96 |
+
**Disambiguation note:**
|
| 97 |
+
The config shows **non-zero** `weight_decay=0.001` and `dropout=0.1`. This distinguishes it from `missing_regularization`, which has `weight_decay=0.0` and `dropout=0.0`. An agent that reads the log pattern correctly but then inspects the config will see regularisation present β the correct label remains "overfitting" because the regularisation is insufficient, not absent.
|
| 98 |
+
|
| 99 |
+
---
|
| 100 |
+
|
| 101 |
+
### Underfitting
|
| 102 |
+
|
| 103 |
+
**Correct diagnosis:** `underfitting`
|
| 104 |
+
**Required sources:** logs
|
| 105 |
+
**Correct fix:** `increase model capacity or use a deeper architecture`
|
| 106 |
+
|
| 107 |
+
**What happens:**
|
| 108 |
+
Both train and validation loss stay high with almost no improvement over 20 epochs. `train_loss` goes from `2.29 β 2.21`. `train_acc` barely moves: `0.11 β 0.14`. Val metrics track train metrics almost exactly β there is no train-val gap. The model is stuck near random baseline (~10% on 10-class CIFAR-10).
|
| 109 |
+
|
| 110 |
+
The config reveals the cause: `architecture=LinearClassifier`. A linear model simply doesn't have the representational capacity to learn CIFAR-10. It's the wrong tool for the job.
|
| 111 |
+
|
| 112 |
+
**What the agent needs to see:**
|
| 113 |
+
Both losses are high and similar β there's no generalisation gap. This is the defining characteristic of underfitting. The architecture field in the config confirms it, but the log pattern alone is sufficient.
|
| 114 |
+
|
| 115 |
+
**Why it's easy:**
|
| 116 |
+
Flat losses near random baseline are unambiguous. There's no NaN, no oscillation, no divergence β just failure to learn.
|
| 117 |
+
|
| 118 |
+
**Disambiguation note:**
|
| 119 |
+
The config shows `optimizer=adam`, `momentum` not applicable. This rules out `optimizer_misconfiguration` (which requires SGD with momentum=0.0). If the agent is careful, it notes that `weight_decay=0.0` β but that's irrelevant here because both losses are high and similar, which points to capacity problems, not regularisation.
|
| 120 |
+
|
| 121 |
+
---
|
| 122 |
+
|
| 123 |
+
## Medium Tier β Logs + Config
|
| 124 |
+
|
| 125 |
+
Medium scenarios cannot be diagnosed from logs alone. The log pattern is ambiguous β it looks like something, but the config is needed to confirm or redirect the diagnosis. Both sources are required.
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
### Learning Rate Too Low
|
| 130 |
+
|
| 131 |
+
**Correct diagnosis:** `learning_rate_too_low`
|
| 132 |
+
**Required sources:** logs, config
|
| 133 |
+
**Correct fix:** `increase learning_rate to 0.001`
|
| 134 |
+
|
| 135 |
+
**What happens:**
|
| 136 |
+
Loss is decreasing, but imperceptibly. Over 20 epochs: `2.302 β 2.298 β 2.290 β 2.275`. The model is technically learning β loss goes down β but at a rate that would take thousands of epochs to reach convergence. Gradient norms are tiny (`0.0031 β 0.0021`) but non-zero.
|
| 137 |
+
|
| 138 |
+
The config reveals the cause: `lr=0.000001` (1e-6). Adam is being used, but with a learning rate 1000Γ smaller than typical. Each update moves the weights by almost nothing.
|
| 139 |
+
|
| 140 |
+
**What the agent needs to see:**
|
| 141 |
+
The logs alone show slow convergence, but "slow convergence" could also be underfitting (wrong architecture) or a hard dataset. The config's `lr=0.000001` is the confirmation β this tiny value is the explicit reason for the glacial pace.
|
| 142 |
+
|
| 143 |
+
**Why it requires both sources:**
|
| 144 |
+
From the logs alone, the agent sees: loss is decreasing but slowly. That's ambiguous. Only when the config shows `lr=0.000001` does the diagnosis become unambiguous β the optimizer is working correctly, just with too small a step size.
|
| 145 |
+
|
| 146 |
+
**Disambiguation note:**
|
| 147 |
+
Gradient norms are small but non-zero (0.003 range). This rules out vanishing gradients, where norms would be exponentially decaying toward zero across layers. The norms are uniformly small here because the LR makes every update tiny, not because gradients are being crushed during backprop.
|
| 148 |
+
|
| 149 |
+
---
|
| 150 |
+
|
| 151 |
+
### Missing Regularization
|
| 152 |
+
|
| 153 |
+
**Correct diagnosis:** `missing_regularization`
|
| 154 |
+
**Required sources:** logs, config
|
| 155 |
+
**Correct fix:** `add weight_decay=0.01 and dropout=0.3`
|
| 156 |
+
|
| 157 |
+
**What happens:**
|
| 158 |
+
Train loss drops fast and val loss rises: `train_loss=0.01`, `val_loss=2.10` by epoch 30. `train_acc=1.00`, `val_acc=0.56`. The log pattern looks exactly like overfitting.
|
| 159 |
+
|
| 160 |
+
But the config shows `weight_decay=0.0` and `dropout=0.0`. There is no regularisation at all. A ResNet101 on CIFAR-10 with zero regularisation will memorise the training set completely β not because the regularisation is insufficient, but because it was never added.
|
| 161 |
+
|
| 162 |
+
**What the agent needs to see:**
|
| 163 |
+
The log pattern alone is indistinguishable from `overfitting`. Only the config confirms the distinction: `overfitting` has `weight_decay=0.001, dropout=0.1` (regularisation present but insufficient), while `missing_regularization` has both at exactly `0.0`.
|
| 164 |
+
|
| 165 |
+
**Why it requires both sources:**
|
| 166 |
+
Logs β "train-val divergence, probable overfitting". Config β "weight_decay=0.0 AND dropout=0.0" β "missing regularization, not overfitting". The config is the discriminating evidence.
|
| 167 |
+
|
| 168 |
+
**Disambiguation note:**
|
| 169 |
+
The label distinction matters for the fix. "Overfitting" β increase existing regularisation. "Missing regularization" β add regularisation from scratch. An agent that labels this "overfitting" has diagnosed the symptom correctly but missed the root cause.
|
| 170 |
+
|
| 171 |
+
---
|
| 172 |
+
|
| 173 |
+
### Batch Size Too Small
|
| 174 |
+
|
| 175 |
+
**Correct diagnosis:** `batch_size_too_small`
|
| 176 |
+
**Required sources:** logs, config
|
| 177 |
+
**Correct fix:** `increase batch_size to at least 32`
|
| 178 |
+
|
| 179 |
+
**What happens:**
|
| 180 |
+
Loss oscillates wildly across 8 epochs: `4.12 β 3.87 β 4.45 β 3.21 β 4.78 β 3.44 β 4.93 β 3.67`. There's a downward trend on average, but every epoch spikes or drops dramatically. Gradient norms alternate: `1.21 β 0.38 β 2.04 β 0.29 β 1.87 β 0.41 β 2.31 β 0.55`.
|
| 181 |
+
|
| 182 |
+
The config reveals the cause: `batch_size=2`. With only 2 samples per gradient update, each update is a noisy estimate of the true gradient. The model steps in a random direction every epoch β sometimes helpful, sometimes not.
|
| 183 |
+
|
| 184 |
+
**What the agent needs to see:**
|
| 185 |
+
The oscillation pattern in the logs looks like "learning rate too high" β and the agent might guess that first. The config's `batch_size=2` is the discriminating signal. The learning rate is reasonable (`lr=0.001` with SGD + momentum=0.9).
|
| 186 |
+
|
| 187 |
+
**Why it requires both sources:**
|
| 188 |
+
Oscillating loss is caused by both high LR and tiny batch sizes. The logs alone cannot disambiguate. Only after seeing `batch_size=2` in the config can the agent correctly attribute the noise to gradient variance rather than step size.
|
| 189 |
+
|
| 190 |
+
**Disambiguation note:**
|
| 191 |
+
The learning rate (`lr=0.001`) and optimizer (`sgd`, `momentum=0.9`) are standard β this is not a high LR situation. The batch size is the anomaly. An agent that stops at the logs and labels this "learning rate too high" has confused the symptom with the wrong cause.
|
| 192 |
+
|
| 193 |
+
---
|
| 194 |
+
|
| 195 |
+
### Optimizer Misconfiguration
|
| 196 |
+
|
| 197 |
+
**Correct diagnosis:** `optimizer_misconfiguration`
|
| 198 |
+
**Required sources:** logs, config
|
| 199 |
+
**Correct fix:** `set momentum=0.9 for SGD optimizer`
|
| 200 |
+
|
| 201 |
+
**What happens:**
|
| 202 |
+
Both train and validation loss decline extremely slowly and plateau: `2.30 β 2.25 β 2.25 β 2.23 β 2.22` over 20 epochs. The losses are similar (no train-val gap), and accuracy never improves meaningfully. Gradient norms are normal (`0.42 β 0.36`), ruling out gradient-level pathologies.
|
| 203 |
+
|
| 204 |
+
The config shows the cause: `optimizer=sgd, momentum=0.0`. SGD with no momentum has no gradient averaging. On a complex loss landscape with saddle points and flat regions, momentum is what allows SGD to keep moving. Without it, the optimizer stalls on flat plateaus and saddle points.
|
| 205 |
+
|
| 206 |
+
**What the agent needs to see:**
|
| 207 |
+
The logs show flat losses similar to underfitting. The config reveals `momentum=0.0` as the specific misconfiguration β this is not an architecture capacity problem, it's a missing optimizer hyperparameter.
|
| 208 |
+
|
| 209 |
+
**Why it requires both sources:**
|
| 210 |
+
From logs alone, flat losses near baseline look identical to underfitting (wrong architecture). Only the config β specifically `optimizer=sgd, momentum=0.0` β reveals the true cause.
|
| 211 |
+
|
| 212 |
+
**Disambiguation note:**
|
| 213 |
+
The architecture is `ResNet18`, which is not a capacity-limited model like `LinearClassifier` in the underfitting scenario. The learning rate (`lr=0.01`) is also reasonable. The only anomaly is `momentum=0.0`. An agent that sees flat losses and labels this "underfitting" has missed the config evidence.
|
| 214 |
+
|
| 215 |
+
---
|
| 216 |
+
|
| 217 |
+
## Hard Tier β Logs + Config + Gradients
|
| 218 |
+
|
| 219 |
+
Hard scenarios require all three data sources. The logs alone are ambiguous, the config narrows it down, and the gradient norms provide the final confirming evidence. All three are required.
|
| 220 |
+
|
| 221 |
+
---
|
| 222 |
+
|
| 223 |
+
### Vanishing Gradients
|
| 224 |
+
|
| 225 |
+
**Correct diagnosis:** `vanishing_gradients`
|
| 226 |
+
**Required sources:** logs, config, gradients
|
| 227 |
+
**Correct fix:** `switch activation to relu and add batch normalization`
|
| 228 |
+
|
| 229 |
+
**What happens:**
|
| 230 |
+
Training on MNIST with a 20-layer MLP using sigmoid activations. Loss barely moves over 20 epochs: `2.30 β 2.27`, accuracy stuck at `0.11-0.12`. The logs suggest the model is barely learning.
|
| 231 |
+
|
| 232 |
+
The config reveals the activation: `activation=sigmoid`. The gradients tell the full story: the output layer has norm `0.21`, but by layer 1 (the input end) the norm has decayed to `0.00000001` β eight orders of magnitude smaller. The gradient is effectively zero at the input layers, so those weights never update.
|
| 233 |
+
|
| 234 |
+
**What the agent needs to see:**
|
| 235 |
+
Logs β slow/stuck learning (ambiguous). Config β sigmoid activation in a deep network (suspicious). Gradients β exponential decay from output to input (`0.21 β 0.0031 β 0.000042 β 0.0000003 β 0.00000001`) confirms vanishing gradients. All three required.
|
| 236 |
+
|
| 237 |
+
**The mechanism:**
|
| 238 |
+
Sigmoid squashes its output to (0, 1) and its gradient is at most 0.25. In a 20-layer network, the gradient is multiplied by this factor at every layer during backpropagation. `0.25^20 β 10^-12` β essentially zero by the time it reaches the input.
|
| 239 |
+
|
| 240 |
+
**Disambiguation note:**
|
| 241 |
+
The gradient decay is exponential across layers, and each layer's norm is non-zero (unlike dying ReLU, where norms are exactly 0.0). The config explicitly states `activation=sigmoid`, which is the known culprit for this failure mode.
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
### Dying ReLU
|
| 246 |
+
|
| 247 |
+
**Correct diagnosis:** `dying_relu`
|
| 248 |
+
**Required sources:** logs, config, gradients
|
| 249 |
+
**Correct fix:** `reduce learning_rate to 0.001 or switch to leaky_relu activation`
|
| 250 |
+
|
| 251 |
+
**What happens:**
|
| 252 |
+
Training starts: `train_loss=2.31` at epoch 1, then partially improves to `1.95` by epoch 2. But then it freezes β epochs 2, 3, and 5 all show identical numbers: `train_loss=1.95, val_loss=2.01, train_acc=0.28`. The model has stopped learning completely.
|
| 253 |
+
|
| 254 |
+
The config shows `lr=0.1` (high for SGD) and `activation=relu`. The gradients explain the freeze: the output layer has norm `0.15`, but every hidden layer (`layer_8`, `layer_6`, `layer_4`, `layer_2`) shows a gradient norm of exactly `0.0`. Not small β exactly zero.
|
| 255 |
+
|
| 256 |
+
**What the agent needs to see:**
|
| 257 |
+
Logs β partial improvement then sudden freeze (unusual pattern). Config β high LR with ReLU (combination that causes dying ReLU). Gradients β exact zeros in all hidden layers confirms dead neurons.
|
| 258 |
+
|
| 259 |
+
**The mechanism:**
|
| 260 |
+
ReLU neurons output 0 when their input is negative. With a high learning rate, a large gradient update can push a neuron's weights so negative that its input is always negative β making it always output 0. Once dead, the gradient through a dead ReLU is also 0, so the neuron can never recover. At `lr=0.1`, many neurons die in the first few updates, causing the training loss to freeze.
|
| 261 |
+
|
| 262 |
+
**Disambiguation note:**
|
| 263 |
+
The key signal is gradient norms of exactly `0.0` in hidden layers. Vanishing gradients produce tiny but nonzero norms (e.g. `1e-8`). Dying ReLU produces norms that are precisely zero because the ReLU derivative is a hard 0 for dead neurons β not an approximation.
|
| 264 |
+
|
| 265 |
+
---
|
| 266 |
+
|
| 267 |
+
### Bad Weight Initialization
|
| 268 |
+
|
| 269 |
+
**Correct diagnosis:** `bad_weight_initialization`
|
| 270 |
+
**Required sources:** logs, config, gradients
|
| 271 |
+
**Correct fix:** `use kaiming or xavier weight initialization`
|
| 272 |
+
|
| 273 |
+
**What happens:**
|
| 274 |
+
Loss is `NaN` from epoch 1. There's no "first good epoch" β the model fails immediately. The gradient norms are astronomically large: `98432.1`, `74219.8`, `55103.4` across the first three layers. These values are orders of magnitude higher than any normal run.
|
| 275 |
+
|
| 276 |
+
The config shows `weight_init=normal_std_100`. Initialising weights from a normal distribution with standard deviation 100 means the initial weights are enormous. The first forward pass immediately overflows: large weights β enormous pre-activations β NaN in the loss.
|
| 277 |
+
|
| 278 |
+
**What the agent needs to see:**
|
| 279 |
+
Logs β NaN from epoch 1 (but NaN from epoch 1 could also be exploding gradients in a different sense). Config β `weight_init=normal_std_100` (the explicit anomaly). Gradients β norms in the tens of thousands confirming the extreme magnitude.
|
| 280 |
+
|
| 281 |
+
**Why it's different from exploding gradients:**
|
| 282 |
+
`exploding_gradients` shows a finite loss at epoch 1 that then diverges. `bad_weight_initialization` is NaN from the very first epoch, before training has meaningfully begun. The config's `weight_init=normal_std_100` is the discriminating evidence. The correct rule: if NaN starts at epoch 1 AND config shows extreme weight_init β bad weight initialization. If NaN starts after epoch 1 β exploding gradients.
|
| 283 |
+
|
| 284 |
+
**Disambiguation note:**
|
| 285 |
+
An agent that labels this "exploding gradients" based on the NaN alone has not used the config or gradient evidence. The gradient norms (`98432`) and weight_init (`std=100`) are specific, confirming evidence that changes the label.
|
| 286 |
+
|
| 287 |
+
---
|
| 288 |
+
|
| 289 |
+
### LR Scheduler Misconfiguration
|
| 290 |
+
|
| 291 |
+
**Correct diagnosis:** `lr_scheduler_misconfiguration`
|
| 292 |
+
**Required sources:** logs, config, gradients
|
| 293 |
+
**Correct fix:** `set gamma to 0.1 so the scheduler decreases lr instead of increasing it`
|
| 294 |
+
|
| 295 |
+
**What happens:**
|
| 296 |
+
Training progresses normally for the first few epochs, then suddenly explodes at epoch 6: `train_loss` jumps from `1.20 β 9.87`. The model recovers and trains again, then explodes again at epoch 11: `train_loss=87.3`. The log shows the lr inline: `lr=0.001` at epoch 5, `lr=0.01` at epoch 6, `lr=0.1` at epoch 11 β the learning rate is growing 10Γ every 5 epochs. Gradient norms follow: `0.42` at epoch 5, `18.73` at epoch 6, `156.2` at epoch 11.
|
| 297 |
+
|
| 298 |
+
The config reveals the cause: `lr_scheduler=StepLR, step_size=5, gamma=10.0`. StepLR multiplies the learning rate by `gamma` every `step_size` epochs. With `gamma=10.0` (a value greater than 1.0), the scheduler is **increasing** the learning rate at each step instead of decreasing it. This is a configuration error β the intended behaviour is `gamma < 1.0` (e.g. 0.1) to decay the learning rate over time.
|
| 299 |
+
|
| 300 |
+
**What the agent needs to see:**
|
| 301 |
+
Logs β periodic spikes at epochs 6 and 11, correlated with lr jumps. Config β `StepLR, gamma=10.0` (the `> 1.0` gamma is the bug). Gradients β norm spikes at the same intervals confirming the lr jump as the trigger.
|
| 302 |
+
|
| 303 |
+
**The mechanism:**
|
| 304 |
+
StepLR is a common scheduler used to reduce the learning rate as training progresses (e.g. every 5 epochs, multiply by 0.1). Setting `gamma=10.0` inverts this β every 5 epochs the lr grows by 10Γ. By epoch 11, the lr has gone from 0.001 to 0.1, which at that point is high enough to cause divergence.
|
| 305 |
+
|
| 306 |
+
**Disambiguation note:**
|
| 307 |
+
The loss spikes are **periodic and predictable** β exactly every 5 epochs. This is the distinguishing characteristic of a scheduler bug. Random exploding gradients don't follow a fixed interval. An agent that sees the spike at epoch 6 and doesn't look for another one at epoch 11 may miss the periodic pattern.
|
| 308 |
+
|
| 309 |
+
---
|
| 310 |
+
|
| 311 |
+
## Scenario Matrix
|
| 312 |
+
|
| 313 |
+
| Scenario | Tier | Required Sources | Key Signal | Discriminator |
|
| 314 |
+
|---|---|---|---|---|
|
| 315 |
+
| exploding_gradients | Easy | logs | loss β NaN after epoch 1 | NaN after finite start |
|
| 316 |
+
| learning_rate_too_high | Easy | logs | loss oscillates wildly, stays finite | lr=1.0 visible in logs |
|
| 317 |
+
| overfitting | Easy | logs | train-val gap widens | val_loss rising while train_loss falls |
|
| 318 |
+
| underfitting | Easy | logs | both losses flat near baseline | no train-val gap |
|
| 319 |
+
| learning_rate_too_low | Medium | logs + config | imperceptibly slow loss decrease | config: lr=1e-6 |
|
| 320 |
+
| missing_regularization | Medium | logs + config | train-val divergence (same as overfitting) | config: weight_decay=0.0 AND dropout=0.0 |
|
| 321 |
+
| batch_size_too_small | Medium | logs + config | oscillating loss | config: batch_size=2 |
|
| 322 |
+
| optimizer_misconfiguration | Medium | logs + config | flat losses (same as underfitting) | config: sgd, momentum=0.0 |
|
| 323 |
+
| vanishing_gradients | Hard | logs + config + gradients | slow learning | gradients decay exponentially by layer |
|
| 324 |
+
| dying_relu | Hard | logs + config + gradients | partial improvement then frozen | gradients exactly 0.0 in hidden layers |
|
| 325 |
+
| bad_weight_initialization | Hard | logs + config + gradients | NaN from epoch 1 | config: weight_init=normal_std_100 |
|
| 326 |
+
| lr_scheduler_misconfiguration | Hard | logs + config + gradients | periodic loss spikes | config: gamma=10.0 (> 1.0) |
|
| 327 |
+
|
| 328 |
+
---
|
| 329 |
+
|
| 330 |
+
## Design Principles
|
| 331 |
+
|
| 332 |
+
**Paired ambiguity:** Several scenarios are deliberately paired to test fine-grained discrimination:
|
| 333 |
+
- `overfitting` vs `missing_regularization` β same log pattern, different config
|
| 334 |
+
- `underfitting` vs `optimizer_misconfiguration` β same log pattern, different config
|
| 335 |
+
- `exploding_gradients` vs `bad_weight_initialization` β both NaN, different timing
|
| 336 |
+
- `vanishing_gradients` vs `dying_relu` β both near-zero gradients, different kind (decay vs exact zero)
|
| 337 |
+
|
| 338 |
+
**Progressive evidence:** Each tier adds one more required source. Easy = logs only. Medium = logs must be correlated with config. Hard = all three sources must align. An agent that inspects only logs on a hard scenario will have ambiguous evidence and is likely to mis-diagnose.
|
| 339 |
+
|
| 340 |
+
**Realistic data:** The numeric values (loss curves, gradient magnitudes, learning rates) are calibrated to match what a practitioner would actually see in a real training run, not toy examples.
|