Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
15 KiB
Background Task Retry Timeline Implementation Plan
For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add structured retry-attempt history to background tasks and surface a compact attempt timeline in parent chat while preserving separate retry child sessions.
Architecture: Extend BackgroundTask with explicit attempts[] state and currentAttemptID, add small helper functions to keep task-level fields as a projection of the current attempt, and wire those helpers into background retry, session creation, and completion/error paths. Parent notifications remain the UI surface, but they are generated from structured attempt state instead of ad hoc retry text.
Tech Stack: TypeScript, Bun test, OpenCode background task engine, parent chat notification flow
File Structure
Files to modify
-
src/features/background-agent/types.ts- Extend
BackgroundTaskwithattempts[]andcurrentAttemptID - Add attempt type definition and any retry-observability support fields needed
- Extend
-
src/features/background-agent/manager.ts- Add/consume helper functions for attempt lifecycle
- Bind retry child session ids to exact attempts in
startTask() - Resolve lifecycle events through
sessionID -> attemptID - Generate final parent summary from
attempts[]
-
src/features/background-agent/fallback-retry-handler.ts- Create next attempt entry during retry scheduling
- Finalize failed attempt before queueing retry
- Preserve retry notification metadata without mutating historical attempts
-
src/features/background-agent/background-task-notification-template.ts- Add compact attempt timeline rendering for parent-facing notifications
-
src/tools/background-task/task-result-format.ts- Optional first-pass alignment if task results need to reference attempt-derived terminal state consistently
Files to test
src/features/background-agent/manager.test.tssrc/features/background-agent/fallback-retry-handler.test.tssrc/tools/background-task/task-result-format.test.ts
Files to inspect for patterns/reference only
src/features/background-agent/session-idle-event-handler.tssrc/features/background-agent/task-history.tssrc/features/background-agent/session-status-classifier.tsdocs/superpowers/specs/2026-04-27-background-task-retry-timeline-design.md
Task 1: Define structured attempt state
Files:
-
Modify:
src/features/background-agent/types.ts -
Test:
src/features/background-agent/manager.test.ts -
Step 1: Add a focused failing test that expects attempt state on a new background task
Add a test in src/features/background-agent/manager.test.ts that launches a background task and expects:
-
attemptsto exist -
first attempt to have
attemptNumber: 1 -
currentAttemptIDto point at that first attempt -
top-level task fields to still exist for compatibility
-
Step 2: Run the new test to verify it fails for the expected reason
Run:
bun test src/features/background-agent/manager.test.ts
Expected: the new assertion fails because attempts[] and currentAttemptID do not exist yet.
- Step 3: Add the attempt state types to
BackgroundTask
Update src/features/background-agent/types.ts to add:
-
BackgroundTaskAttempttype/interface with:attemptIDattemptNumbersessionID?providerID?modelID?variant?statuserror?startedAt?completedAt?
-
attempts?: BackgroundTaskAttempt[] -
currentAttemptID?: string -
Step 4: Initialize first attempt state when tasks are created
In src/features/background-agent/manager.ts, when launch() creates the initial BackgroundTask, initialize:
-
one attempt entry in
pending -
currentAttemptIDreferencing that entry -
top-level
modelcopied into attempt model fields -
Step 5: Re-run the test to verify the new task has attempt state
Run:
bun test src/features/background-agent/manager.test.ts
Expected: the new launch/creation test passes.
Task 2: Add attempt lifecycle helper functions
Files:
-
Modify:
src/features/background-agent/manager.ts -
Test:
src/features/background-agent/manager.test.ts -
Step 1: Add a failing test for exact attempt binding in
startTask()
Add a test that simulates:
- a task with a pending retry attempt
startTask()creating a child session- the session being bound to the exact scheduled attempt, not merely "the latest pending attempt"
The test should assert:
-
sessionIDlands on the correct attempt -
currentAttemptIDremains correct -
top-level task
sessionIDmirrors that active attempt -
Step 2: Run the test to verify it fails before helpers exist
Run:
bun test src/features/background-agent/manager.test.ts
Expected: binding assertions fail or require manual task mutation not yet implemented.
- Step 3: Implement helper functions inside
manager.ts
Add small focused helpers, either in manager.ts or a dedicated sibling helper file if needed:
startAttempt(task, initialModel)bindAttemptSession(task, attemptID, sessionID, model)scheduleRetryAttempt(task, failedAttemptID, nextModel, error)finalizeAttempt(task, attemptID, terminalStatus, error?)
These helpers must enforce:
-
only
currentAttemptIDis mutable -
finalized attempts are immutable
-
binding by explicit
attemptID -
Step 4: Add a
sessionID -> attemptIDmapping strategy
Implement one of:
- a map stored on the task
- or a lookup derived from attempts by session id
The first implementation can be simple, but every lifecycle event must resolve the attempt through this mapping before mutating state.
- Step 5: Define an explicit queued work contract that carries
attemptIDintostartTask()
Update the implementation plan so queued background work carries the scheduled attemptID explicitly.
Concretely:
- extend the queue item / queued work shape to include
attemptID - ensure retry scheduling writes that
attemptIDat queue time - ensure
startTask()receives the exactattemptIDand never infers “latest pending attempt”
This is required to satisfy the approved spec’s exact-binding rule.
- Step 6: Re-run the manager tests
Run:
bun test src/features/background-agent/manager.test.ts
Expected: new binding and helper tests pass.
Task 3: Record retries as new attempts instead of overwriting task state
Files:
-
Modify:
src/features/background-agent/fallback-retry-handler.ts -
Test:
src/features/background-agent/fallback-retry-handler.test.ts -
Step 1: Add a failing test for retry scheduling creating Attempt 2
Add a test that starts with a task already representing Attempt 1 and then runs tryFallbackRetry().
Expected behavior:
-
Attempt 1 becomes terminal
error -
Attempt 2 is created as
pending -
currentAttemptIDmoves to Attempt 2 -
top-level
task.modelmirrors Attempt 2 model -
Step 2: Run the retry-handler test to verify it fails
Run:
bun test src/features/background-agent/fallback-retry-handler.test.ts
Expected: no structured attempt chain exists yet, so assertions fail.
- Step 3: Update retry scheduling to use attempt helpers
In src/features/background-agent/fallback-retry-handler.ts:
-
finalize the current attempt before retry queueing
-
create the next pending attempt
-
preserve retry notification metadata on the task
-
keep top-level compatibility fields aligned with the new active attempt
-
Step 4: Re-run the retry-handler tests
Run:
bun test src/features/background-agent/fallback-retry-handler.test.ts
Expected: retry now produces a correct attempt chain.
Task 4: Route all session lifecycle mutations through attempt identity
Files:
-
Modify:
src/features/background-agent/manager.ts -
Reference:
src/features/background-agent/session-idle-event-handler.ts -
Test:
src/features/background-agent/manager.test.ts -
Step 1: Add a failing stale-event regression test
Create a test that simulates:
- Attempt 1 fails and Attempt 2 becomes current
- a late event from Attempt 1’s old
sessionIDarrives
Expected:
-
Attempt 2 and top-level task projection do not change
-
stale event is ignored for state mutation
-
Step 2: Run the test to verify the stale-event case fails first
Run:
bun test src/features/background-agent/manager.test.ts
Expected: stale-event mutation is not yet blocked.
- Step 3: Update lifecycle handlers to resolve
sessionID -> attemptIDfirst
Apply this rule in relevant background manager paths:
message.updatedsession.errorsession.status- completion/idle handling if they mutate attempt/task state
Before mutating state:
- resolve the
attemptIDfrom the incomingsessionID - verify it still matches
currentAttemptID - otherwise ignore/log as stale
- Step 4: Re-run the manager tests
Run:
bun test src/features/background-agent/manager.test.ts
Expected: stale-event regression passes.
Task 5: Render the attempt timeline in parent chat summaries
Files:
-
Modify:
src/features/background-agent/background-task-notification-template.ts -
Test:
src/features/background-agent/manager.test.ts -
Step 1: Add a failing notification-format test for multi-attempt tasks
Create a test that builds a completed/failed task with three attempts and expects parent-facing summary text containing:
-
attempt number
-
status
-
model
-
session id
-
Step 2: Run the test to verify it fails first
Run:
bun test src/features/background-agent/manager.test.ts
Expected: current notifications do not include a structured attempt timeline.
- Step 3: Update notification template to render compact attempt timeline
In background-task-notification-template.ts:
-
keep the summary compact
-
render one line per attempt
-
include error text only for failed attempts where useful
-
do not replace separate retry reminders; final summary is additive
-
Step 4: Update manager-side aggregation so final summaries carry attempt history
notifyParentSession() currently batches through completedTaskSummaries in manager.ts, which only stores task-level summary data.
Modify that aggregation path so the final per-task notification has access to the task’s structured attempts[] data at summary time.
Allowed implementation directions:
- extend
BackgroundTaskNotificationTaskto include attempt timeline data - or bypass the reduced aggregation shape for final parent summaries and pass the original task objects (or a richer projection)
The key requirement is that the final parent summary must render the authoritative attempt timeline from structured state, not from task-level status alone.
- Step 5: Re-run notification tests
Run:
bun test src/features/background-agent/manager.test.ts
Expected: parent-summary timeline is now shown from attempts[] state.
Task 6: Preserve retry observability messages from state
Files:
-
Modify:
src/features/background-agent/manager.ts -
Test:
src/features/background-agent/manager.test.ts -
Step 1: Add a failing test that retry-scheduled and retry-session-ready notifications are derived from attempt state
The test should verify:
-
retry scheduled reminder still includes failed session id, failed model, error, next model
-
retry session ready reminder includes new retry session id and attempt number
-
Step 2: Run the test to verify current behavior is incomplete
Run:
bun test src/features/background-agent/manager.test.ts
Expected: notifications are not yet driven by structured attempt state.
- Step 3: Refactor retry notifications to read from attempts
Make the existing retry observability path use attempts[] + currentAttemptID instead of ad hoc fields where practical.
- Step 4: Re-run manager tests
Run:
bun test src/features/background-agent/manager.test.ts
Expected: retry observability remains correct after the attempt-state refactor.
Task 7: End-to-end regression sweep for background retry history
Files:
-
Test:
src/features/background-agent/manager.test.ts -
Test:
src/features/background-agent/fallback-retry-handler.test.ts -
Test:
src/tools/background-task/task-result-format.test.ts -
Step 1: Add an end-to-end regression covering multiple retries followed by success
Test expectations:
-
3 attempts recorded
-
first two failed with distinct models/session ids
-
third completed successfully
-
parent summary contains all three attempts in order
-
Step 2: Run the focused regression suite
Run:
bun test src/features/background-agent/manager.test.ts src/features/background-agent/fallback-retry-handler.test.ts src/tools/background-task/task-result-format.test.ts
Expected: all focused tests pass.
- Step 3: Run the broader fallback regression suite
Run:
bun test src/features/background-agent/manager.test.ts src/features/background-agent/fallback-retry-handler.test.ts src/features/background-agent/error-classifier.test.ts src/tools/background-task/task-result-format.test.ts src/tools/delegate-task/sync-session-poller.test.ts src/tools/delegate-task/sync-task.test.ts src/plugin/event.test.ts src/shared/model-error-classifier.test.ts
Expected: all tests pass.
- Step 4: Run typecheck and build
Run:
bun run typecheck
bun run build
Expected: both commands succeed with no errors.
Task 8: Final verification and handoff
Files:
-
Review: all modified files above
-
Step 1: Manually verify task-level projection consistency
Check in code review that:
-
active attempt and top-level fields always agree
-
finalized attempts are not mutated later
-
stale events are ignored
-
Step 2: Confirm parent chat UX remains compact
Check that final attempt timeline is readable and not overly verbose.
- Step 3: Prepare implementation summary
Document:
-
files changed
-
new attempt-state invariants
-
tests added/updated
-
Step 4: Commit
git add src/features/background-agent/types.ts src/features/background-agent/manager.ts src/features/background-agent/fallback-retry-handler.ts src/features/background-agent/background-task-notification-template.ts src/features/background-agent/manager.test.ts src/features/background-agent/fallback-retry-handler.test.ts src/tools/background-task/task-result-format.test.ts docs/superpowers/specs/2026-04-27-background-task-retry-timeline-design.md docs/superpowers/plans/2026-04-27-background-task-retry-timeline.md
git commit -m "feat(background-task): add retry attempt timeline"
Plan complete and saved to docs/superpowers/plans/2026-04-27-background-task-retry-timeline.md. Ready to execute?