Appearance
When a task fails, don't retry in code — emit a new task on the board that inherits the description and adjusts tags for the failure mode, so retry becomes a first-class artifact the operator can see, audit, and budget.
Pattern
Correction Task
The shape
A failure spawns a new row. The board shows the whole retry history.
task-board/plan-042.md
before failure
[✅ adw001]Implement OAuth2 provider
[⧗ adw002]Add user profiles
↓ task_failed(adw002, context_overflow)
task-board/plan-042.md
after correction
[✅ adw001]Implement OAuth2 provider
[❌ adw002]Add user profiles// Failed: context overflow
[⧗ adw003]Add user profiles[retry 1]{opus}
Original stays for audit. Correction is inserted above and inherits the description — but carries a new tag, a retry counter, and its own adw_id. One glance explains the whole history.
If retry is hidden inside the executor, the operator sees one row go green with no idea three attempts happened.
The policy
Failure mode determines the tag delta — nothing else changes.
context_overflow
→
+{opus}larger context, more headroom
undefined_symbol
→
+{with_codebase_grep}grant a discovery skill
rate_limit
→
+delay(N min)no tag change; back off
test_flake
→
+{retry}identical attempt, record flag
One small function — failure_reason → tag_additions. Kept in one place. Versioned. Reviewed. Identical retry on identical failure is an infinite loop.
The budget
Cap the retry count. Three is usually right.
retry_count ladder
per task · capped at MAX_RETRIES
0
Original
1
Correction
2
Correction
3
Last chance
HUMAN
Escalate
first trystochasticstochasticborderlinesystemic
✓ Under cap Likely stochastic — model variance, flaky infra, transient rate limits. A tag-adjusted retry usually lands.
✗ Over cap Four failures means it's systemic, not stochastic. Stop spending. Tag [❌ human-needed] and notify.
No cap → flaky tests burn budget indefinitely. The cap exists because stochastic failures look the same as systemic ones until you count them.
Why first-class
Retry in code is invisible. Retry on the board is inspectable.
Hard-coded retryinvisible
for attempt inrange(3):
result = run(task)
if result.ok: break
else:
raiseFailed()
# operator sees one row. ever.
- Rationale lost — why were we retrying?
- Per-attempt tag adjustments impossible
- No audit trail on the board
- Policy buried in the executor; can't tune without code
Correction taskinspectable
ontask_failed(task, error):
correction = Task(
description=task.description,
tags=task.tags + retry_tags(error),
original_adw_id=task.adw_id,
)
board.insert_above(correction, task)
- Each attempt has its own row — full history visible
- Tag delta encodes why the retry differs
- Original stays at
[❌]for audit - Policy is data — operator can tune without redeploy
The pattern pairs with Orphan Reaper (accidental failure recovery) as the deliberate failure counterpart. Both turn implicit code behavior into first-class artifacts.
The discipline
Retry is a first-class artifact — a task row, not a loop counter.
A retry hidden in code is a retry the operator can't see, can't tune, and can't audit. Put it on the board, adjust its tags, cap the count — then failure becomes data about the system, not a hidden cost paid silently by the executor.