Releases: edxeth/pi-ralph-loop
Release list
v1.3.0
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.3.0Or latest:
pi install git:github.com/edxeth/pi-ralph-loop✨ Features
Live Ralph loop status above the editor
Refs: 84aeafc
Ralph now shows a live two-line status widget above the editor while a loop is running. The widget shows the current phase, elapsed time, iteration count, current work, and error count without changing the /ralph-loop command interface.
Bundle-mode loops show item progress as ✓ P/T items with the iteration budget as secondary context. Plain/custom loops continue to show iteration N/M as the main progress signal. Running and transitioning states get a truecolor-only burnt-orange shimmer when the terminal supports it; lower color modes stay static.
Completed and manually stopped loops clear their info banners
Refs: 84aeafc
The informational banners for a completed Ralph loop and a manual stop now auto-clear like the other transient lifecycle notices. Warning and error notices still persist so recovery and failure states remain visible.
🔧 Other Changes
Live coverage now matches plain-loop fresh-session behavior
Refs: 474a088
The plain/custom live RPC test now documents the actual Ralph contract: bundle_mode stays false, and accepted NEXT promises still use Ralph's normal fresh-session handoff. This removes a racy intermediate-state assertion and keeps the live suite stable under the fast scripted test provider.
v1.2.1
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.2.1Or latest:
pi install git:github.com/edxeth/pi-ralph-loop🐛 Bug Fixes
Loop prompts no longer get corrupted by --- delimiters in bundle item text
Refs: 8c09222
Ralph persists its loop state in .ralph/loop.md frontmatter, including a bundle_items_snapshot string that holds a JSON snapshot of the bundle items. When an item's description contained a markdown --- horizontal rule, the state parser mistook that delimiter text inside the serialized value for the closing frontmatter delimiter. On the next read it split at the wrong place, so the serialized snapshot fragment (bundle_items_snapshot, git_head, item text, etc.) leaked into later iteration prompts as if it were the task body.
Ralph now writes state strings as JSON-escaped scalars, so a --- inside a value can never be confused with a real delimiter line. Frontmatter parsing and task-body extraction now go through one shared parser that only treats a complete --- line as the boundary. Legacy state files written by older versions still load correctly, and raw Windows session paths such as C:\new\table\session.jsonl are preserved during upgrade rather than being rewritten by JSON escape handling.
This keeps each iteration receiving only the clean bundle prompt body, even when the bundle content itself contains frontmatter-style delimiter lines.
v1.2.0
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.2.0Or latest:
pi install git:github.com/edxeth/pi-ralph-loop✨ Features
New <promise>WAIT</promise> for iterations parked on async work
Refs: 36a56fd
Ralph gains a fourth control promise: WAIT. Emit <promise>WAIT</promise> when an iteration is intentionally waiting on an async result — a helper/subagent return, a background command, a review, a process alert, or a future tool result. Unlike NEXT (which starts a fresh session), WAIT keeps the current iteration in the same session and parks it.
WAIT arms a bounded 30-minute timeout. If no async result arrives before it fires, Ralph sends a structured re-check prompt asking the agent to re-check the iteration and choose WAIT, NEXT, or COMPLETE. The timeout nudge is token- and state-aware, so a stale prompt can't leak into a later turn or iteration. Resume adapts to WAIT too: from the same owning session, resuming after a WAIT keeps the iteration parked instead of advancing.
The control-tag table in the README now documents WAIT alongside NEXT, COMPLETE, and STOP. Missing-promise turns now receive an explicit control-tag prompt instead of a bare continue. STOP still works if the agent emits it, but Ralph no longer offers STOP in its default correction prompts or the ralph-plan-writer skill. The context-window limit reminders now reference WAIT, NEXT, and COMPLETE.
🐛 Bug Fixes
Stale provider-error banner no longer lingers over a recovering run
Refs: 54541e6
The "Provider error at Ralph iteration N; waiting for Pi's retry handling" banner was an aboveEditor widget with no auto-clear, and finalizeLoop only cleared the status line, not the widget — so it sat over a healthy recovering run until the next loop action or a Pi restart.
Ralph now clears problem notices (warnings and errors) at the moments a stale warning becomes inaccurate: after multi-turn provider recovery (supersedeProviderWait on turn_end), after single-turn recovery (agent_end), and when the next fresh iteration starts. Transient info confirmations ("Ralph loop started", "Starting iteration N") are left alone since they already auto-clear, and terminal notices stay because the loop is done.
Block configured human-input tools during unattended Ralph runs
Refs: 8812d3c
Some third-party tools open custom human-input UIs and then wait forever for a person to answer. Ralph can't reliably recover after such a tool has already started, because Pi exposes no generic "this tool is waiting for a human" signal and some custom UIs ignore abort.
Ralph now blocks a user-owned list of tool names while a loop is running:
export RALPH_BLOCKED_TOOLS=tool_name_1,tool_name_2
piThe guard only fires while .ralph/loop.md says a loop is running in the current workspace; normal Pi sessions outside a running loop are unaffected. Ralph never hard-codes any tool name — you decide exactly which tools to block, matching by exact name only.
Plan-writer skill now designs Ralph loops for fully unattended runs
Refs: 8812d3c
The ralph-plan-writer skill now treats every Ralph session as unattended. Generated .ralph/prompt.md instructions tell the runtime agent never to ask the user, request approval, wait for human input, or use tools whose purpose is to ask the user. Ordinary ambiguity is now implementation work — make a conservative, reversible assumption, record it in .ralph/progress.md, and keep moving — while hard blockers (missing credentials, external accounts, paid services, hardware, admin permissions, destructive approval, or an unavailable verification dependency) are the only reason to leave an item's passes at false.
When an item depends on an unavailable external service, the skill directs the agent to look for a safe local substitute (a mock, fixture, fake service, local no-network adapter, or narrower item-specific check) before giving up, without ever weakening runtime_contract.verification_gates. Substitutes must be documented, and passes is set to true only if the substitute still satisfies the item's steps honestly. If no item can proceed without human input, the loop records each hard blocker once, leaves all passes values unchanged, and ends without emitting NEXT or COMPLETE rather than fabricating a pass. At planning time, if every acceptance path depends on unavailable external dependencies with no safe substitute, the skill refuses to write the bundle.
A helper process exiting no longer cancels a running loop
Refs: fafaf84
A running Ralph loop was falsely finalized as user_cancelled whenever any other Pi process that loaded the extension in the same workspace exited normally. The real-world trigger was a background helper (pi -p) that loads all extensions, including pi-ralph-loop: its print-mode dispose emitted session_shutdown with reason quit from a pid that wasn't the loop's owner, and the shutdown handler treated that as a cancel.
The shutdown handler is now guarded by the owner_pid identity recorded in .ralph/loop.md: the event is ignored unless the shutting-down process is actually the owner. The genuine cancel path is preserved — the owner's own quit still marks cancel_requested, and a quit during a committed handoff still finalizes as interrupted. A non-owner quitting mid-handoff is now a no-op rather than killing the owner's loop. The legacy owner_pid: null path keeps its prior behavior.
Ralph now actively recovers from abnormal agent endings instead of stopping
Refs: 57115e4
Previously, when a provider error or a malformed terminal stop survived Pi's retry window, Ralph finalized the loop as a fatal error immediately. Ralph now runs a multi-stage recovery before giving up.
After Pi's retry handling, Ralph shows a guarded countdown, then sends up to five same-iteration recovery nudges (one minute apart). If the model still cannot produce a normal turn, Ralph opens one fresh fallback session for that same iteration — without re-snapshotting the bundle, so item progress is preserved — and only stops as a resumable error if that fallback also exhausts recovery. The one-fallback-per-iteration cap is persisted in loop state, so recovery cannot loop forever opening fresh sessions.
User input cancels any pending recovery countdown, so a person stepping in always wins over automated recovery. This commit also fixes off-by-one budgets in the missing-promise nudge and bundle-rejection correction paths (the bundle-rejection counter now compares correctly and shows the right N/MAX denominator).
Stale cancellation notice is cleared on resume
Refs: 501e5ae
When an agent turn was interrupted, Ralph recorded a user-cancelled stop and showed an info notice. Resuming the same session correctly reset the durable loop state, but the "Ralph loop cancelled by user" banner could stay above the editor because the notice clearer only dismissed warning and error widgets. Cancellation notices now auto-clear, and an explicit same-session resume clears stale info notices too, so the UI can't keep showing a false cancellation banner while the loop runs again.
📝 Documentation
Add Acknowledgements section to the README
Refs: 7895881
The README now has a dedicated Acknowledgements section near the top, thanking @FasalZein and @isthatyousaf for their contributions, ideas, and for providing access to frontier models (GPT and Claude) that made experimenting with and building the extension possible. It's kept separate from the bottom Credits section, which covers the foundational Ralph technique and research.
v1.1.3
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.1.3Or latest:
pi install git:github.com/edxeth/pi-ralph-loop🐛 Bug Fixes
Loops no longer stall when an agent launches a background subagent
Refs: b816ed05
When the parent agent kicked off a background or async subagent, its turn ended on a pending tool call. Ralph's loop engine misread that as a dead provider: it armed the provider-error timeout and finalized the loop with stop_reason "error" before the child could finish. Each affected iteration then required a manual /ralph-resume to continue.
Ralph now recognizes a turn that ends on a tool call as an intentional async suspension and stays parked until the tool result arrives, instead of arming the wait or counting it as an error. The check is tool-agnostic, so it applies to any async tool, not just subagents, and Ralph stays decoupled from pi-subagents. Genuine stopReason "error" retries and terminal/bundle paths are unchanged.
v1.1.2
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.1.2Or latest:
pi install git:github.com/edxeth/pi-ralph-loop🐛 Bug Fixes
Fresh Ralph iterations keep the loop’s selected model and thinking level
Refs: 6153ed8
Ralph loops that hand off to a fresh session now preserve the model provider, model id, and thinking level owned by the active loop. If another Pi session changes models while Ralph is running, the next Ralph iteration no longer inherits that unrelated model selection.
This also applies when resuming or restarting a loop. Ralph records the model fields in .ralph/loop.md as model_provider, model_id, and thinking_level, then restores them before sending the next fresh-iteration prompt. If the saved model is unavailable or cannot be selected in the current Pi instance, Ralph stops the loop with a clear error instead of silently continuing on the wrong model.
The fresh-session handoff now uses Pi’s replacement-session API correctly after ctx.newSession(), avoiding stale extension API objects during iteration transitions.
v1.1.1
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.1.1Or latest:
pi install git:github.com/edxeth/pi-ralph-loop🐛 Bug Fixes
Observer sessions no longer stop a live Ralph loop
Refs: 442f763
Starting a second Pi process in the same workspace no longer causes Ralph to treat an active loop as crashed. Previously, an observer session could mark the loop as errored while the original loop owner was still running, so the owner’s later <promise>NEXT</promise> was ignored and the loop stopped advancing.
Ralph now records an ownership heartbeat in .ralph/loop.md while a loop is running. Startup cleanup and /ralph-resume or /ralph-restart only reclaim the loop when that owner heartbeat is stale; otherwise they leave the live loop alone.
This also lets /ralph-resume and /ralph-restart recover stale loop owners without requiring a Pi restart.
v1.1.0
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.1.0Or latest:
pi install git:github.com/edxeth/pi-ralph-loop🐛 Bug Fixes
An interrupted NEXT handoff is now resumable instead of fatal
Refs: 47b5e10
When the agent emits NEXT, the iteration advances and the loop briefly transitions while it starts a fresh session. Previously, if Pi quit, reloaded, or crashed during that window, the loop was finalized with stop reason error — making a valid, already-advanced iteration look dead and discouraging resume.
A new interrupted stop reason now covers a committed handoff that gets cut off (quit/reload mid-transition, or a crash detected at next startup that was mid-transition). /ralph-resume continues the saved iteration as normal. A mid-iteration crash with no committed handoff still finalizes as error. This does not add unattended auto-resume after the host process exits — that still requires /ralph-resume from a long-lived session.
Verification gates no longer re-run at promise emission (advisory only)
Refs: b6b6cd9
verification_gates were re-executed by the harness on every NEXT/COMPLETE. For a real gate like a full test suite, this ran synchronously and froze Pi's event loop for the gate's entire duration after the agent had already gone idle, and duplicated the test run the agent already did in-iteration with its output hidden from chat.
Gates are now advisory: they stay in items.json and are surfaced to the runtime agent (which is instructed to run every required gate before emitting a promise), but the harness no longer executes them at promise emission. The cheap, useful checks are unchanged and still enforced: item-state, progress-append, source-doc, and commit checks. If you relied on the harness blocking promises on gate failure, that enforcement now lives with the agent.
Long multi-turn recovery no longer killed mid-work by the provider-error timer
Refs: 089ad6e
A provider-error reply arms a 180s wait to give Pi's auto-retry time to land before declaring the loop dead. That wait was only superseded by the next agent_end — but during a long multi-turn recovery, no agent_end arrives until the whole unit finishes. The timer could fire mid-work and finalize the loop as error while the agent was healthy and productive, with a clean NEXT later landing in an already-dead loop.
The wait is now superseded on every turn_end. A landed turn is itself proof Pi recovered, so an active recovery keeps the loop alive no matter how long it runs.
Promise nudges reset after a provider error
Refs: 5d97f15
A broken provider connection could poison the current promise-nudge budget: missing-promise nudges accumulated before the error counted against the recovered replies, sometimes failing the loop immediately after recovery. Ralph now resets the missing-promise nudge chain whenever it enters provider-error wait handling, so recovered replies get a fresh chain. Provider failures still increment error_count and use the existing retry window, and the five-total-reply boundary for consecutive no-promise replies is unchanged.
v1.0.7
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.0.7Or latest:
pi install git:github.com/edxeth/pi-ralph-loop🐛 Bug Fixes
Noisy verification gates no longer block the loop
Refs: da41d1b, 4b087d8
Verification gates (the verification_gates commands Ralph runs before accepting <promise>NEXT</promise> or <promise>COMPLETE</promise>) could be misclassified as failures when a command passed but printed a lot of output. The old runner buffered gate output in memory with a fixed maxBuffer, so a gate that exited 0 while emitting ~20 KB+ of logs hit ENOBUFS and was treated as failing. That rejected NEXT, so no fresh iteration session ever started and the loop appeared to hang until it timed out.
Gate execution now runs in a dedicated child runner that streams stdout/stderr and caps what it keeps incrementally, instead of relying on a single in-memory buffer. A passing-but-chatty gate is now correctly accepted, NEXT advances, and the next iteration session opens as expected. Genuine failures (non-zero exit, timeout, signal) still report a capped, truncated diagnostic so output stays bounded.
🔧 Other Changes
Add direct dev dependency on @earendil-works/pi-tui
Refs: da41d1b
@earendil-works/pi-tui is now declared as a direct dev dependency. It was previously only available transitively through runtime modules that the test suite imports, so this pins it explicitly for a reliable test environment.
Regression coverage for the noisy-gate handoff
Refs: 34d15b5, 172ce39
Added a PI_RALPH_LIVE regression test that reproduces the original symptom: a 2-item bundle driven by a deterministic fake provider (no network) with a gate that passes while emitting ~20 KB of output. It asserts NEXT is accepted (bundle_rejection_count 0), a fresh session opens for iteration 2, and the loop reaches stop_reason: complete. The test also fails fast (~4s) with a diagnostic naming the observed stop_reason and bundle_rejection_count when the handoff regresses, instead of hanging until the 150s timeout.
v1.0.6
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.0.6Or latest:
pi install git:github.com/edxeth/pi-ralph-loop🐛 Bug Fixes
Ralph loop session handoffs are more reliable
Refs: 3cd1444, d45d84f
Ralph now seeds replacement sessions from Pi’s ctx.newSession({ withSession }) handoff instead of splitting startup across delayed timers, session_start, and global transition state. This keeps iteration startup bound to the fresh replacement-session context and avoids stale command-context failures after session replacement.
If Pi is quit or reloaded during a Ralph-managed session transition, Ralph now finalizes the loop as an error instead of leaving .ralph/loop.md stuck in a running transition state. Normal Ralph-created /new transitions are preserved.
/ralph-loop starts faster in empty sessions
Refs: d45d84f
When /ralph-loop is started from a session with no user or assistant turns, Ralph now starts the first iteration directly in the current session. This avoids the unnecessary first-session replacement that made the submitted prompt feel delayed.
Sessions with existing history still start the loop in a fresh session, and /ralph-resume and /ralph-restart continue to force fresh-session startup for safer recovery behavior.
Fresh-session prompts no longer flash through the old chat
Refs: 3cd1444, d45d84f
For replacement-session iterations, Ralph still starts each automated iteration in a clean session, but now schedules the prompt after Pi has rendered the replacement session. This avoids the old-chat flash while preserving one fresh session per automated iteration.
Ralph lifecycle notices are less noisy
Refs: d45d84f
Ralph lifecycle feedback now appears as a muted above-editor notice widget when supported by Pi, with transient notices auto-clearing after a short delay. Terminal and error messages remain visible, duplicate notification/widget output is avoided, and redundant iteration-start notices have been removed.
RPC and subprocess launch behavior is documented
Refs: 3cd1444
The README now documents that RPC clients, API wrappers, and subprocess launchers must keep the Pi process and stdin open for the duration of a Ralph run. A one-shot wrapper that sends /ralph-loop and then closes stdin can cause Pi to quit before Ralph’s fresh-session handoff runs.
If that happens, start a long-lived Pi session and run /ralph-resume.
v1.0.5
Install:
pi install git:github.com/edxeth/pi-ralph-loop@v1.0.5Or latest:
pi install git:github.com/edxeth/pi-ralph-loop🐛 Bug Fixes
/ralph-resume no longer restarts the current unit of work
Refs: 0647d5b
Resuming from the same Pi session that owns the saved iteration used to re-send the full task prompt. Because that prompt tells the agent to pick an unfinished item and start working, the agent would begin a brand new unit instead of continuing the one in progress.
Resume now adapts to where it runs. From the session that owns the saved iteration, it reads the last assistant turn instead of re-seeding: an already-emitted COMPLETE or STOP ends the loop, NEXT advances to a fresh iteration, and if no promise was emitted yet it nudges continue so the agent finishes the current unit. From any other session, it still restarts the saved iteration in a fresh session. The seed prompt is now delivered exactly once per session.
Provider retries are no longer cut off as a dead loop
Refs: 75238f2
When a retryable provider error hit (such as a dropped WebSocket), Pi auto-retries with exponential backoff and stays idle between attempts. The loop treated that idleness as a stalled run and finalized with stop_reason: "error" inside Pi's first backoff window, before the first retry even started. A retry that later succeeded was ignored because the loop had already stopped.
The loop now waits out a fixed window (180s, well above Pi's max per-attempt backoff) after a provider-error turn before declaring the loop dead. Any later turn, whether a recovered success or a fresh failure, supersedes the pending wait, so a genuine retry is honored even when it does not advance the iteration.
♻️ Refactoring
Internal restructuring of the loop core and state modules
Refs: 1ca5ab2
Consolidated repeated structures with no change to observable behavior: state read/write now run off a single ordered schema descriptor, terminal-state writes route through one finalizeLoop path, and loop-engine was split into focused helpers (provider-wait, limit-reminders, idle, snapshot-store), dropping it from 592 to about 440 lines. Verified with a clean tsc, 74 passing unit tests, and 5 live Pi RPC tests.