#+TITLE: Comparative Agent Loop & Recovery Study
#+FILETAGS: :notes:comparative-study:agent-loop:recovery:architecture:

* Purpose

Compare agent loop architectures and error recovery mechanisms across Claude Code, OpenCode, OpenClaw, and Hermes Agent. Inform Passepartout's signal pipeline (v0.9.0) and recovery strategies.

* Findings Summary

| Dimension | Claude Code | OpenCode | OpenClaw | Hermes | Passepartout |
|-----------+-------------+----------+----------+--------+--------------|
| Loop style | Async generator while(true) | Effect while(true) | Dual nested while(true) | Plain while + budget | while(process-signal) |
| Streaming tools | StreamingToolExecutor during API stream | AI SDK streamText + tool dispatch | Dual-loop attempt dispatch | Always streaming, chunk iteration | Not yet (v0.7.1) |
| Watchdog | 90s stream idle, 30s stall | Via Effect cancellation | TUI watchdog + 5-count idle breaker | 90s stale-stream, 60-120s read | Not implemented |
| Auto-retry | 10 max, 3 for 529, stream→non-stream fallback | Effect retry/fallback | Model fallback + profile rotation | 30+ error flags with specific recovery | Not implemented |
| Compaction layers | 5 (snip, micro, collapse, auto, reactive) | Auto-compaction + pruning | Post-compaction loop guard | ContextCompressor + 1-line tool pruning | Foveal-peripheral (single layer) |
| Interrupt | AbortController + signal.reason | Runner.cancel() + BusyError | AbortSignal propagation | Thread-scoped set + 3-level cascade | Single SIGINT handler |
| Cost control | Token budget + task_budget API | Per-step tracking | Idle-timeout breaker (cost runaway) | IterationBudget + subagent caps | Planned (v0.5.0 token economics) |
| Busy-mode | N/A (single REPL) | Interrupt running + queue | Interrupt + queue | interrupt/queue/steer (3 modes) | Not implemented |

* Claude Code — Loop Architecture

**Core loop**: Async generator queryLoop() with mutable State struct tracking messages, tool context, compaction state, recovery counts, transitions. Each iteration: snip → microcompact → collapse → auto-compact → blocking limit → call model → execute tools → loop.

**Stop conditions** (return Terminal): completed, blocking_limit, aborted_streaming, aborted_tools, max_turns, stop_hook_prevented, hook_stopped, model_error, prompt_too_long, image_error.

**StreamingToolExecutor**: Tools execute DURING the API stream. As tool_use blocks arrive, immediately dispatched. Concurrency model: concurrent-safe tools run in parallel; non-concurrent serialized. Bash tool errors trigger siblingAbortController.

**5-Layer Recovery:**
1. Auto-retry: 10 max, 3 for 529, persistent retry mode (CLAUDE_CODE_UNATTENDED_RETRY), fallback model switch
2. Stale LLM detection: 90s stream idle timeout, half-time warning at 45s, fallback to non-streaming
3. Tool error recovery: user_interrupted → REJECT_MESSAGE, streaming_fallback → executor recreated, missing tool results → synthetic errors
4. Compaction retry: reactive compact on 413 prompt-too-long. Two-stage: context-collapse drain → reactive compact
5. Watchdog/context overflow: blocking limit pre-check before model call

**Unique**: Task budgets carried across compaction boundaries. Thinking block validation at query level. Memory prefetch concurrent with LLM stream.

* OpenCode — Effect-TS Functional Pipeline

**Core loop**: Effect.fn(SessionPrompt.run) → while(true). Exit on: last assistant has finish reason not tool-calls, structured output produced, processor returns stop/compact, doom loop detected, max steps reached.

**Doom loop detection**: 3 consecutive identical tool calls → permission prompt.

**Interrupt**: SessionRunState with Runner.onInterrupt. Cancel() sets status to idle. BusyError when starting while running. New request → cancel current → interrupt work returns last assistant message.

**Undo/Redo**: SessionRevert with file system snapshots. Revert restores file patches in reverse. Unrevert restores pre-revert snapshot. Per-step diff tracking.

**Streaming**: AI SDK streamText with event handling (reasoning-start/delta/end, tool-input-start, tool-call, tool-result, tool-error, text-delta, finish-step). Snapshot diff on each step.

* OpenClaw — Dual-Loop with Failover

**Outer loop** (runWithModelFallback): Model/provider fallback chain with result classification.
**Inner loop** (runEmbeddedAttemptWithBackend): Attempt dispatch with auth/profile rotation.

**Idle-timeout cost-runaway breaker**: 5 consecutive idle timeouts with no progress → halt paid model calls. Prevents $20-30 runaway from a single code bug.

**Auth profile rotation**: Multiple profiles per provider, rotated on failures. MAX_SAME_MODEL_IDLE_TIMEOUT_RETRIES=1 before model switch.

**Post-Compaction Loop Guard**: Detects loops where compaction happens but produces no progress.

**Streaming watchdog**: Armed on every delta. TUI detects idle stream, updates status. 30s timeout.

* Hermes Agent — Monolithic 14,672-line Agent

**Core loop**: while(api_call_count < max_iterations AND iteration_budget.remaining > 0). Checks: interrupt flag, iteration budget, steer drain, API retry loop (with rate limiting), compaction loop (max 3), process tool calls.

**3-Level Ctrl+C Cascade:**
1. Graceful interrupt: sets _interrupt_requested, per-thread signal, propagates to children
2. Clear & Queue: clear_interrupt(), busy mode queue (next turn)
3. /steer: Non-disruptive injection into next tool result (thread-safe)

**IterationBudget**: Parent gets 90 iterations, subagents get 50. execute_code iterations refunded. _budget_grace_call for one extra.

**Streaming**: Always preferred path. 90s stale detection, 60-120s read timeout. Chunk iteration with last_chunk_time tracking. Ollama-specific tool call reuse fix.

**Recovery flags**: 30+ provider-specific retry flags. JSON repair (5 passes: strict, commas, braces, control chars, unicode). Surrogate sanitization. Dead connection cleanup. Message sequence repair. Credential pool rotation.

* Passepartout Blindspot Assessment

1. **No streaming tool execution** — Passepartout's pipeline is strictly sequential. Claude Code executes tools during the API stream (hiding ~1s latency). Passepartout's streaming (v0.7.1) should consider whether tool calls can execute during stream. [Action: v0.7.1 protocol design]

2. **No recovery layers** — Passepartout has handler-case around think() but no auto-retry, no stale detection, no model fallback. All 3 competitors have multi-layer recovery. [Action: add to v0.9.0 signal pipeline]

3. **No busy-mode** — When the agent is running and user types, Passepartout has no defined behavior. Hermes has interrupt/queue/steer. Passepartout should add queue mode at minimum. [Action: v0.9.0 priority queue]

4. **Single SIGINT handler** — The 3-level Ctrl+C cascade is universal. Passepartout should match this. [Action: v0.7.0 Ctrl keys]

5. **No compaction layers** — Passepartout's foveal-peripheral pruning is a single strategy. Claude Code has 5. Should Passepartout add reactive compaction (compact on PTL) and tool output summarization? [Action: context/compaction study needed]

6. **No doom loop detection** — OpenCode detects 3 identical tool calls. Hermes detects budget exhaustion. Passepartout could loop forever on a stuck tool. [Action: add to v0.9.0]
