AltimateAI / AltimateAI/altimate-code
todowrite runaway: 9,139 calls/session escapes per-message doom-loop detector
- Dominant language
- TypeScript
- Stars
- 811
- Forks
- 134
- Avg merge
- 3d 2h
- Merged PRs (30d)
- 50
Description
## Problem
Telemetry-2026-05-21 (2,503 machines, 14 days) shows the todowrite loop pattern from March is **still active and worse**:
- 29,562 total todowrite calls in the window
- Top 3 machines = 63% of all calls
- Single machine peak: **9,139 todowrite calls across 28 sessions** (~325/session)
Sessions at this scale burn tokens, induce context bloat, and produce visibly broken UX. The existing doom-loop detector in `session/processor.ts` (`TOOL_REPEAT_THRESHOLD = 30`) is per-assistant-message scope: its counter resets every time a new assistant message starts. The pathological pattern is *slow-burn* — ~10-15 calls per LLM turn, accumulating across many turns within a single session.
## Root cause
`SessionProcessor.create()` instantiates a fresh `toolCallCounts: Record` per message. A new processor is created per assistant message, so the counter never accumulates across the full session. The detector only catches a single turn that spams a tool 30+ times.
Real loops look like this: agent calls todowrite 10x in turn 1, 10x in turn 2, 10x in turn 3... totaling 100 within 10 turns. Each turn is under threshold; the cross-turn pattern is invisible to the per-message counter.
## Fix
Add a tool-level per-session counter inside `TodoWriteTool` itself. New constants:
- `TODOWRITE_WARN_THRESHOLD = 25` — emit warning in tool output + telemetry event; the agent sees the warning text and can self-correct
- `TODOWRITE_REFUSE_THRESHOLD = 50` — refuse the call entirely; return a structured message telling the agent to stop calling todowrite and continue with the work
Both thresholds emit `doom_loop_detected` telemetry events so dashboards see the pattern even when the agent ignores the warning.
Extracted into a testable helper `recordTodoWriteCall(sessionID)` returning `{ action: "ok" | "warn" | "refuse", count }`. Counter is module-scoped in-memory Map; stale entries from old sessions are harmless because lookup is per-sessionID.
## Impact
Telemetry should show:
- `todowrite` call volume per session capped at ~50 (down from 9,000+ peaks)
- `doom_loop_detected` events for `todowrite` at much higher rates (every session that crosses the threshold now reports)
- Reduced token/context burn from runaway todo updates
Contributor guide
Assessment
This issue has not been assessed yet.