Repository navigation
v2.3.1 — Per-step retry budgets
A step that loops back via onFailGoto can now declare its own "maxRetries" — that step's fix loop uses the step's budget instead of the pipeline-wide default. One long loop no longer inflates every other edge's retry budget.
Why
Loop-controller pipelines (Planning → Development → Review → back to Planning) need many rounds on one edge — but the pipeline-wide maxRetries: 2 capped every loop alike, and raising it globally inflated every other fix loop (review→coding, security→coding, test→coding).
Usage
{ "maxRetries": 2, // pipeline-wide default, unchanged
"pipeline": [
{ "id": "planning", "onFailGoto": "development", "maxRetries": 20, ... },
{ "id": "review", "onFailGoto": "coding", ... } // keeps the global 2
] }
- Resolution: step.maxRetries ?? global maxRetries ?? 2. The budget belongs to the step declaring onFailGoto (the failing step), matching the edge-keyed retry counter.
- 0 is legal — fail fast, never loop (first failure stops the run).
- Validated at load: a negative or fractional value (step-level or top-level) dies before any agent starts, with the offending value in the message.
- Visible in --dry-run: a declared budget renders as onFail-> xN (e.g. onFail->development x20).
Compatibility
Configs that don't set a per-step maxRetries behave exactly as before — byte-identical logs, same default budget. All shipped pipelines are untouched.
Tests
New E2E pins: E32 (step budget 3 over global 1 — attempts n/3, exhaustion at 3, bounded 8 worker-starts), F13/F13b (load-time validation), F14 (dry-run xN rendering + no global-budget leak). Existing retry pins E7/E8/E31 unchanged. Full suite: 347/347.
Full changelog: README What's new + ROADMAP.md (Shipped, v2.3.1 row).