-
Notifications
You must be signed in to change notification settings - Fork 3
State-aware idle detection with backoff and max idle duration #28
Description
Problem
When a ralph enters an idle state (no work to do), it keeps iterating at full speed, burning tokens every cycle just to report "nothing to do."
Real example from a project manager ralph:
Iteration 4 (46.2s) — completed task, created PR
Iteration 5 (13.9s) — "Status: IDLE. Nothing to do."
Iteration 6 (11.4s) — "Status: IDLE. Nothing to do."
...
Iteration 11 (12.0s) — "Status: IDLE. Nothing to do."
Iterations 5-11 are pure waste. Each one runs all commands, assembles the full prompt, and pipes it to the agent just to get "idle" back.
Proposal
1. Structured state communication via markers
Define a structured protocol for agents to communicate state back to the orchestration loop. The agent includes a marker in its output:
<!-- ralph:state idle -->
This is an HTML comment, invisible in rendered markdown and unambiguous to parse. The engine checks result_text (already captured on AgentResult) for the marker after each iteration.
2. Frontmatter configuration
---
agent: claude -p
idle:
delay: 30s # initial delay before next iteration
backoff: 2.0 # multiplier applied each consecutive idle iteration
max_delay: 5m # cap on backoff
max: 6h # stop the loop entirely after this cumulative idle time
---3. Behavior
- After an iteration, check agent output for the
<!-- ralph:state idle -->marker - When idle is detected: wait
delayseconds, thendelay * backoff, thendelay * backoff^2, ..., capped atmax_delay - Reset on activity: as soon as an iteration does NOT signal idle, reset the delay to the initial value and clear the cumulative idle timer
- Max idle (
idle.max): if total consecutive idle time exceeds this duration, stop the loop with a clear message. Supports human-readable durations (30m,6h,1d) - Terminal UX during wait: show a countdown so the user knows it's paused, not stuck. Single
Ctrl+Cskips the delay and runs immediately; doubleCtrl+Cstops the loop - Events: emit a new
ITERATION_IDLEevent type so UIs can render idle state distinctly from completed/failed - Commands always run, even during idle iterations. Commands feed new information into the loop, so skipping them could prevent the agent from noticing new work
The marker format (<!-- ralph:state <name> -->) should be designed to be expandable for future per-state config, but only idle is handled initially. If the idle block is absent from frontmatter, the loop runs exactly as before with no behavior change.
4. Implementation notesi
The engine already has the right hooks:
result_textonAgentResult/IterationEndedDatacarries agent output. Parse markers from there_delay_if_needed()inengine.pyalready handles interruptible sleep between iterations. Extend it with backoff logic_frontmatter.pyparses the YAML config. Addidleas a new frontmatter fieldRunConfiggets new idle-related fields;RunStatetracks consecutive idle count and cumulative idle duration