🔁Native Looping in Synthex: Why I Stopped Using the Ralph Plugin
I just shipped native looping in Synthex↗, and it's already a noticeable improvement to my loop-based workflows.
For the past few months I've been using Geoffrey Huntley's Ralph Wiggum Loop↗ — the pattern of do $PROMPT until ($DONE_SIGNAL=true) — to drive long autonomous runs. The pattern is brilliant: when your project, prompting, and guardrails are mature enough, you can start a loop, walk away, and come back to a finished plan. It's the baker model of software delivery — set the right ingredients, set a timer, taste before serving — rather than the farmer model where you're hand-watering every row. There's an official Claude Code plugin↗ for it, and Synthex has supported it since v0.3.6.
But over the past few weeks — especially as I leaned harder on Opus 4.7 at higher effort levels — Ralph and Synthex weren't playing as nicely together as I'd have liked. So this past weekend, I built native looping directly into Synthex.
Where Ralph Started Getting in My Way
Two failure modes kept showing up.
Ralph's stop-hook re-entry collided with Synthex's internal review loops. Ralph re-injects the prompt into a fresh agent every iteration — elegant for simple prompts, but Synthex commands like next-priority already run their own internal review loops and milestone gates. The interaction caused stalls, occasional double-execution, and missed completion signals.
Two sessions on the same project couldn't both loop. Ralph keeps state in a single .claude/ralph-loop.local.md file. No way to run, say, one autonomous build and one targeted refinement at the same time on the same repo.
I'd come back from coffee to find a loop that had broken (or hadn't broken when it should have), and the cause was almost always somewhere in that re-entry handoff.
The Design
The whole point of Synthex's orchestration layer is to know what each command is doing — a loop framework that doesn't know that is forever playing catch-up. So native looping runs inside the command: pass --loop, and the command's own instructions wrap its workflow in a tight outer loop. Single agent thread. No external coordinator. State lives at .synthex/loops/<loop-id>.json — one file per loop.
Each iteration does the same four things: check the boundary, increment-and-persist, run the real workflow, then scan its own output for a <promise>X</promise> signal to decide whether to exit or go around again.
Rendering diagram...
Because there's no second process re-injecting prompts, there's nothing to collide with the command's own review loops — the thing that was tripping up the Ralph integration simply can't happen anymore.
The state file is deliberately boring. Roughly:
json{"id": "release-1","command": "next-priority","iteration": 7,"maxIterations": 30,"completionPromise": "ALLDONE","status": "running","updatedAt": "2026-05-21T14:32:08Z"}
That single JSON file per loop is what makes the rest of the features fall out for free.
Ralph vs. native looping
| Ralph plugin | Synthex native looping | |
|---|---|---|
| Iteration mechanism | Stop hook re-injects the prompt into a fresh agent | Command wraps its own workflow in an outer loop |
| Process model | External coordinator | Single in-process agent thread |
| State | One shared .claude/ralph-loop.local.md | One .synthex/loops/<id>.json per loop |
| Concurrent loops per repo | No | Yes |
| Aware of command internals | No | Yes |
What Fell Out of It
A few capabilities I'd been wanting independently were essentially free once the foundation was right:
- Concurrent loops on the same project. Two sessions, two loop IDs, two state files. One can run
next-priority --loopfor an autonomous build while another runsteam-review --loopagainst a specific change./synthex:list-loopsand/synthex:cancel-loopround out the management surface. - Crash-safe resume. Close your laptop or kill Claude Code mid-iteration — state persists.
--resume <name>picks up where you left off;--resume-lastgrabs the most-recently-running loop if you don't remember the name. - Auto-compaction safety. State on disk, iteration work in your artifact, short iteration markers in chat. When Claude Code's auto-compaction fires, the loop survives.
Because each loop is just a named JSON file, concurrency is the natural consequence rather than a feature I had to bolt on:
Rendering diagram...
In practice:
bash/synthex:next-priority --loop \--completion-promise "ALLDONE" \--max-iterations 30 \--name release-1
next-priority does its thing — pick unblocked tasks, spin up worktrees, delegate, merge, mark done, repeat — until every task is done and the command emits <promise>ALLDONE</promise>.
What's Still Rough
Opus 4.7 still tries to be too clever. Even with native looping owning the iteration semantics, it occasionally breaks itself out of a loop to fish for a pat on the back, or because it decides I probably wouldn't have wanted it to keep going. Annoying, but I haven't had any truly broken loops since the switch. A --force or --keep-going flag is almost certainly in its future.
The full spec is in the docs↗ — state schema, iteration loop pseudocode, the lot. If you're already using Synthex, --loop is available on the loopable commands today. If you're not, the getting started guide↗ is the right place to start.
You'll feel the baker/farmer distinction in your bones after the first overnight run.
Synthex
Part of the LumenAI plugin marketplace
15 specialized AI agents. 11 commands. Full delivery lifecycle coverage — from brainstorming your first idea to shipping validated, reviewed, production-ready code.
/plugin marketplace add bluminal/lumenai/synthex:init/synthex:next-priority