Skip to content

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.