When I first came across the Ralph Loop, my immediate reaction was:
Isn't this just a
whileloop around an LLM?
Technically, a Ralph implementation can be almost that simple. What took longer to click for me was where its memory lives: what survives when one worker exits, and how the next worker picks up the work.
I built a small implementation using the Gemini SDK to see this happen. It produced two solutions to the same problem, improved the algorithm between iterations, and stopped without creating a third file. Before getting to that run, it helps to distinguish the worker's own loop from the loop around it.
First: Observe → Think → Act
A typical agent observes its environment, reasons about what to do, performs an action, and observes the result. The output of one action becomes input to the next step.
┌───────────┐
│ Observe │
└─────┬─────┘
↓
┌───────────┐
│ Think │
└─────┬─────┘
↓
┌───────────┐
│ Act │
└─────┬─────┘
│
└──────────→ Observe again
This is the worker's inner cycle. It describes how an agent makes progress during an invocation, but it doesn't tell us how work continues after that invocation ends.
So What Is the Ralph Loop?
Ralph repeatedly starts a fresh worker against the current state of the environment. Each worker makes some progress, persists it, and exits. The next worker inspects what was left behind.
A Ralph worker can itself perform Observe → Think → Act:
RALPH LOOP
┌─────────────────┐
│ Fresh Worker #1 │
│ │
│ Observe │
│ Think │
│ Act │
└────────┬────────┘
↓
Persistent State
↓
┌─────────────────┐
│ Fresh Worker #2 │
│ │
│ Observe │
│ Think │
│ Act │
└────────┬────────┘
↓
Persistent State
↓
┌─────────────────┐
│ Fresh Worker #3 │
└─────────────────┘
↓
DONE
At the orchestration level, the code can look familiar. Conceptually:
while not complete:
state = observe()
decision = think(state)
act(decision)
verify()
The useful properties are the fresh invocation, persistent external state, verification, and a completion condition. Calling a model repeatedly without carrying changes forward would just restart the original task.
The Filesystem Becomes Memory
Suppose worker #1 creates solution_v1.py and exits. Worker #2 can read that file without receiving the conversation that produced it. The result of the previous iteration is now part of the environment.
Worker #1
│
│ writes
▼
┌──────────────────────┐
│ Persistent State │
│ │
│ source code │
│ tests │
│ progress files │
│ Git history │
└──────────┬───────────┘
│
│ reads
▼
Worker #2
For software work, this memory can be source files, tests, specifications, progress files, and Git history. That is what made the pattern click for me: the next worker can inspect the work itself instead of relying on a transcript of what the previous worker said it did.
Let's Build Ralph Ourselves
I used the Gemini SDK directly rather than an agent framework so I could see the mechanics. The worker receives the classic Longest Increasing Subsequence problem:
Input:
[10, 9, 2, 5, 3, 7, 101, 18]
Output:
4
LIS is a small enough problem to inspect. The experiment is about whether a fresh worker can improve an existing solution using files as its record of progress.
The workspace looks roughly like this:
ralph-loop/
│
├── ralph.py
├── prompt.md
│
├── workspace/
│ ├── problem.md
│ ├── progress.md
│ └── solutions/
│
└── tests/
└── test_solutions.py
Each iteration reads this workspace, asks Gemini for an improvement, writes a new attempt, and verifies it.
Observe
The program rereads the problem, progress, and existing solutions at the beginning of each iteration. Conceptually:
def observe():
read("problem.md")
read("progress.md")
read_all("solutions/*.py")
Iteration two therefore sees whatever iteration one persisted. Nothing needs to be copied from an earlier Gemini conversation.
Think — With a Fresh Gemini Request
The filesystem snapshot goes into a new model request. Conceptually:
response = client.models.generate_content(
contents=current_filesystem_state
)
There is no accumulated conversation like this:
history = [
previous_prompt,
previous_response,
previous_prompt,
previous_response,
]
The current state is reconstructed from disk. Reusing the SDK client doesn't change that: each generate_content request remains stateless from the perspective of this application.
Act — Keep the Previous Attempt
An improvement goes into a new file: solution_v1.py, then solution_v2.py, and so on. Earlier attempts stay intact, making the evolution visible and giving later workers something concrete to inspect.
Verify Independently
After each generated solution, the orchestrator runs deterministic tests. The suite includes normal LIS examples, duplicates, negative values, increasing and decreasing inputs, and exhaustive small inputs checked against an independent oracle.
If a solution fails, the failure is persisted so the next worker can inspect it and attempt a repair. The model's claim that its code is correct isn't the verification step.
What Actually Happened?
I didn't prescribe a progression from brute force to dynamic programming to binary search. The prompt tells Gemini:
If no solution exists, create a simple correct solution. If one exists, look for a meaningful asymptotic improvement.
Gemini chooses the algorithm. Here is the actual run.
Iteration 1: Straight to Dynamic Programming
==================================================
RALPH ITERATION 1
==================================================
Observing workspace...
Existing solutions: None
Asking Gemini for the next improvement...
Gemini proposed: Dynamic Programming
Created:
workspace/solutions/solution_v1.py
Verification: PASS
PASS: 131 deterministic cases
Continuing Ralph loop...
Gemini skipped an exponential brute-force implementation and went directly to dynamic programming. The first persisted solution, solution_v1.py, passed all 131 deterministic cases.
That was already a useful observation: “simple correct solution” didn't produce the sequence I might have scripted myself.
Iteration 2: An Asymptotic Improvement
==================================================
RALPH ITERATION 2
==================================================
Observing workspace...
Existing solutions: solution_v1.py
Asking Gemini for the next improvement...
Gemini proposed:
Patience Sorting with Binary Search
Created:
workspace/solutions/solution_v2.py
Verification: PASS
PASS: 131 deterministic cases
Continuing Ralph loop...
The fresh worker read the dynamic-programming solution and proposed patience sorting with binary search, moving from O(n²) to O(n log n). It wrote solution_v2.py, which also passed the 131 cases. Both implementations remained on disk.
Iteration 3: No New File
==================================================
RALPH ITERATION 3
==================================================
Observing workspace...
Existing solutions:
solution_v1.py,
solution_v2.py
Asking Gemini for the next improvement...
Gemini proposed:
Patience Sorting with Binary Search
STATUS: COMPLETE
There was no solution_v3.py. After inspecting both implementations, the worker decided there was no meaningful further worst-case time-complexity improvement to make for the general problem. Ralph reverified the persisted solutions and stopped.
The important observation for this experiment was that the second request could improve the first request's work, and the third could inspect both, without carrying their conversational history.
The Architectural Trade-off
A continuous conversation works for many tasks. Modern coding agents already maintain context through long sequences of tool calls. Ralph becomes interesting when the state of the work can be stored separately from the conversation and reconstructed by a fresh invocation.
That shifts attention toward the environment: what the goal says, which files describe progress, what feedback survives, and how completion is determined. If an iteration discards its changes, or leaves no useful record of failure, starting another worker won't fix that.
It also offers a simpler starting point than immediately splitting work among a planner, coder, reviewer, and debugging agent. Multiple autonomous components introduce questions about state ownership, context transfer, retries, disagreement, and completion. Sometimes that coordination is appropriate. For other problems, one capable worker operating repeatedly against a well-designed environment may be enough.
When I Would Use It—and When I Wouldn't
I'd consider this pattern when progress can be externalized and objectively inspected:
- Software tasks such as refactors, migrations, test generation, dependency upgrades, and incremental feature implementation.
- Research artifacts, documentation, or structured data cleanup when there is persistent state and a useful evaluator.
- Evaluation-driven prompt improvement or iterative reports when feedback can guide the next attempt.
For a paragraph summary, a function explanation, or a single JSON response, a normal model request is usually enough. Without reliable feedback or a completion signal, an outer loop can produce more tokens without producing more progress.
The question I'd ask is whether each iteration can leave an inspectable result that helps the next one.
What the Tests Don't Prove
The LIS verifier ran 131 deterministic cases. Passing them is evidence about those cases; it does not mathematically prove correctness for every possible input.
Likewise, Gemini's claim that an implementation is O(n log n) needs inspection. Its decision that no further improvement is meaningful is also a model judgment. Re-running correctness tests doesn't establish optimality.
Deterministic verification is useful precisely because it checks something independently of the model. It still matters to be clear about what was checked.
What I Took Away
I started with “isn't this just a while loop?” Building it made the filesystem's role much clearer. In this run, one request left a working solution, another improved it, and a third used both files to decide to stop.
The part I'd carry into other systems is the design of that handoff: leave useful state, preserve feedback, and make the stopping condition explicit. The outer loop is small; making its next iteration useful is the engineering work.
Source Code
The complete runnable Gemini SDK implementation includes the prompt, deterministic verifier, and setup instructions.
GITHUB / SOURCE CODEExplore the Ralph Loop implementationsriraman-10 / applied-ai-concepts / ralph-loopThe implementation intentionally avoids LangChain, LangGraph, and agent frameworks so the Observe → Think → Act → Persist → Repeat mechanics remain visible.