| name | e2e-driven-iteration |
| description | Use at the start of every inner-loop iteration where the goal is to fix a bug / close a failing test / reach a green E2E. Forbids editing code without first running the E2E to capture a fresh loss signal, and forbids declaring "done" without the E2E passing. Makes the cycle "E2E → loss → 5-why-by-layer → fix → E2E" the only admissible rhythm. |
E2E-Driven-Iteration — inner loop (∂L/∂code), the forward-pass cadence
The Metaphor
The lab scientist with a hypothesis. Before any intervention: measure the baseline. After any intervention: measure again. Between the two measurements: exactly one change. "I know what this reagent does" without running the control experiment is not science — it is storytelling. The E2E run is the measurement device. Skipping it turns every edit into a story and leaves the loss unexamined.
Overview
In Gradient Descent for Agents, the E2E run is the forward pass. Every iteration of the inner loop has one: it produces the loss number this iteration descends against. This skill enforces the rhythm measure → diagnose → fix → measure inside loop-driven-engineering. The existing dach-skill says "run E2E when warranted." That's too soft. Under time pressure, "warranted" silently becomes "never" and the iteration degrades into blind-patching based on stale assumptions.
Core principle: when you are in a fix-loop (trying to close a failing signal), every iteration begins and ends with an E2E run of the failing case. The E2E is the forward pass; its output is the loss; the delta between iterations is the gradient signal that tells you whether the last edit helped or regressed.
No E2E at the start → no loss measurement → your edit is speculative. No E2E at the end → you don't know if you converged or just stopped.
See ../../docs/ldd/convergence.md §5 for how this skill implements the loss trace across the test pyramid.
When to Use
Invoke at the start of every inner-loop iteration whose purpose is:
- Fixing a failing test
- Closing a rejected gate
- Resolving a production incident
- Debugging an unexpected behavior
- Converging on any goal where "is it fixed yet?" is the question
Do not use for: new-feature work where there's no failing E2E yet (write the E2E first, then enter this loop), pure refactors (no behavioral E2E to run), documentation-only changes.
The Six-Step Iteration
k-th iteration:
1. Run E2E → loss_k = measured rubric score / failing items
2. Compare → Δloss_k = loss_{k-1} − loss_k (was the last edit a step or a random walk?)
3. Diagnose → invoke root-cause-by-layer, name layer 4/5
4. Fix → one focused edit, smallest coherent unit, at the named layer
5. Run E2E again → loss_{k+1} becomes loss_k of iteration k+1
6. Emit trace → python -m ldd_trace append ... (see using-ldd §"Persisted trace")
Do not skip step 1 — "I know what broke, I'll just fix it" is the anti-pattern. Until you measured the current state, you don't know if the last change helped, hurt, or was a no-op.
Do not skip step 2 — if Δloss_k ≤ 0, the previous edit did not help. You are in oscillation or a local minimum. Invoke loss-backprop-lens before proposing another edit.
Do not skip step 5 — without the post-edit run, "fixed" is a claim, not a measurement.
Do not skip step 6 — the user needs to see the loss descend live, per-iteration. v0.5.1 makes this a hard step, not an optional summary at task end. Running python -m ldd_trace append ... appends to .ldd/trace.log AND prints the full trace block (sparkline + chart + per-iteration line) — one command satisfies both mandates. If the tool is unavailable, render the trace block manually per using-ldd/SKILL.md §"Loss visualization".
Red Flags — STOP, the loop is degrading
- "I'll fix it then run the E2E at the end" → no loss measurement per step; you won't notice regressions
- "The E2E is slow, I'll skip it this iteration" → you lost the gradient
- "The last edit probably worked, don't need to re-run" → you don't know
- "E2E keeps failing the same way, let me try something else" →
Δloss is not going down; step size wrong (invoke loss-backprop-lens)
- "I'll run the unit test instead, same signal" → only if the unit test covers the same failure surface; otherwise you're measuring a different loss
- "Let me batch 3 fixes and run once" → conflates 3 gradient steps; if one regressed, you can't attribute
- "I'll emit the trace block at the end of the whole task, the user doesn't need it per iteration" → NO. The user CANNOT see convergence without per-iteration emission; a final-block-only cadence gives them a single data point instead of a trajectory. Per-iteration trace is step 6, not optional.
E2E selection — cheap before expensive
If the failure reproduces at a cheaper pyramid tier (unit, integration), iterate there first. Only escalate to full E2E when the cheap tier can't reproduce.
| Tier | Use as iteration gate when |
|---|
| Unit | The failure is localized to one function / class / module with no external deps |
| Integration | The failure crosses module boundaries but is deterministic with test doubles |
| E2E (fake externals) | The failure involves orchestration but external APIs can be mocked |
| E2E (real externals) | The failure involves real LLM / DB / network non-determinism |
Running full E2E for a typo catchable at unit tier is a pyramid violation; it wastes budget and slows the gradient signal. Running only unit tests for a cross-boundary contract bug misses the real loss surface.
Budget interaction
This skill runs inside loop-driven-engineering's K_MAX = 5. The E2E-per-iteration cost sets a floor on how fast iterations can happen; if one E2E takes 30 minutes, your total budget is 2.5 hours. Plan accordingly:
- Before entering the loop, estimate E2E cost and multiply by 5 — that's your budget ceiling
- If estimate > available time, either cheapen the E2E (subset of scenarios, fake externals) OR accept you'll hit K_MAX and escalate
- At K_MAX: escalation per
loop-driven-engineering's §Escalation — with the added requirement of attaching the loss_k trace across all iterations
How to Apply — checklist
- Define the failing E2E. Exact command, expected state, measured state. Save the output of iteration 0 as
loss_0. If .ldd/trace.log exists in the project, first run python -m ldd_trace status --project . to see prior iterations.
- Enter the loop. At the start of each iteration: run E2E, capture
loss_k, compare to loss_{k-1}.
- Interpret the delta. Negative → progress, continue. Zero → no-op, rethink. Positive → regression, revert immediately and re-diagnose.
- Diagnose via
root-cause-by-layer. Calibrate via loss-backprop-lens.
- Edit smallest coherent unit.
- Rerun E2E. That's
loss_{k+1}.
- Emit trace block. Run
python -m ldd_trace append --project . --loop inner --auto-k --skill <skill> --action "<what changed>" --loss-norm <v> --raw <n>/<max>. The tool appends to .ldd/trace.log AND prints the full trace block — show the output to the user.
- Close only when E2E is fully green AND the regularizers (contracts, docs) are honored — hand off to
docs-as-definition-of-done. Run python -m ldd_trace close ... to record terminal status.
Common Rationalizations
| Excuse | Reality |
|---|
| "The E2E is slow, every iteration is expensive" | Yes. That's why K_MAX = 5 and you must think, not guess. Slow E2E is itself a gradient — it tells you to invest in a faster test surface. |
| "I'll skip the start-of-iteration run to save time" | You saved 5 minutes and lost the ability to tell if your last edit helped. Net loss. |
| "The loss barely moved, close enough" | "Close enough" is a claim, not a measurement. Either the rubric is too lax (outer-loop issue → method-evolution) or you're stopping before convergence. |
| "E2E passed once on try 2, ship" | You owe a reproducibility-first check. One pass is one sample. |
| "I'll fix all three issues in one iteration, E2E at end" | You cannot attribute which edit helped and which regressed. Run per edit. |
Relation to other skills
reproducibility-first runs before this skill enters the loop — you must have a reproducible signal to begin.
root-cause-by-layer runs at step 3 of each iteration — diagnosis.
loss-backprop-lens runs when Δloss ≤ 0 to recalibrate step size.
loop-driven-engineering is the parent — this skill is what it dispatches during a fix-loop.
docs-as-definition-of-done runs at close — when the E2E is green, the loop isn't closed until docs are synced.
Real-World Cue
If your iteration log for a bug fix doesn't show a loss_k trace — one number per iteration, visibly decreasing toward zero — you were not doing E2E-driven iteration. You were hoping.