# src/hooks/todo-continuation-enforcer/ — Boulder Continuation Mechanism

**Generated:** 2026-02-21

## OVERVIEW

14 files (~2061 LOC). The "boulder" — Continuation Tier hook that forces Sisyphus to keep rolling when incomplete todos remain. Fires on `session.idle`, injects continuation prompt after 2s countdown toast.

## HOW IT WORKS

```
session.idle
  → Is main session (not prometheus/compaction)? (DEFAULT_SKIP_AGENTS)
  → No abort detected recently? (ABORT_WINDOW_MS = 3s)
  → Todos still incomplete? (todo.ts)
  → No background tasks running?
  → Cooldown passed? (CONTINUATION_COOLDOWN_MS = 30s)
  → Failure count < max? (MAX_CONSECUTIVE_FAILURES = 5)
  → Start 2s countdown toast → inject CONTINUATION_PROMPT
```

## KEY FILES

| File | Purpose |
|------|---------|
| `handler.ts` | `createTodoContinuationHandler()` — event router, delegates to idle/non-idle handlers |
| `idle-event.ts` | `handleSessionIdle()` — main decision gate for session.idle |
| `non-idle-events.ts` | `handleNonIdleEvent()` — handles session.error (abort detection) |
| `session-state.ts` | `SessionStateStore` — per-session failure/abort/cooldown state |
| `todo.ts` | Check todo completion status via session store |
| `countdown.ts` | 2s countdown toast before injection |
| `abort-detection.ts` | Detect MessageAbortedError / AbortError |
| `continuation-injection.ts` | Build + inject CONTINUATION_PROMPT into session |
| `message-directory.ts` | Temp dir for message injection exchange |
| `constants.ts` | Timing constants, CONTINUATION_PROMPT, skip agents |
| `types.ts` | `SessionState`, handler argument types |

## CONSTANTS

```typescript
DEFAULT_SKIP_AGENTS = ["prometheus", "compaction"]
CONTINUATION_COOLDOWN_MS = 30_000     // 30s between injections
MAX_CONSECUTIVE_FAILURES = 5          // Then 5min pause (exponential backoff)
FAILURE_RESET_WINDOW_MS = 5 * 60_000  // 5min window for failure reset
COUNTDOWN_SECONDS = 2
ABORT_WINDOW_MS = 3000                // Grace after abort signal
```

## STATE PER SESSION

```typescript
interface SessionState {
  failureCount: number       // Consecutive failures
  lastFailureAt?: number     // Timestamp
  abortDetectedAt?: number   // Reset after ABORT_WINDOW_MS
  cooldownUntil?: number     // Next injection allowed after
  countdownTimer?: Timer     // Active countdown reference
}
```

## RELATIONSHIP TO ATLAS

`todoContinuationEnforcer` handles **main Sisyphus sessions** only.
`atlasHook` handles **boulder/ralph/subagent sessions** with a different decision gate.
Both fire on `session.idle` but check session type first.
