---
title: "Chapter 08: A Bounded Ralph Loop"
description: "Inspect a finite controller that restarts context, checks real outcomes, and stops on failure conditions."
type: "lesson"
order: 8
chapter: "03-controlled-agents"
duration: "45–90 minutes"
---

# Chapter 08: A Bounded Ralph Loop

## Objective

Inspect a finite controller that restarts context, checks real outcomes, and stops on failure conditions.

Your tutor should stop at each gate and wait for your evidence before continuing.

## Concept

A Ralph-style outer loop starts a fresh agent process, lets it read persistent project state, and evaluates its work. The next process does not inherit the previous conversation unless you explicitly resume it. Source files, a short plan, and recent validation output provide continuity.

An infinite shell loop is a useful sketch of the idea, but a poor first experiment. Our controller allows three attempts, twelve agent turns per attempt, five minutes for the agent and three minutes for validation. It stops on timeout, no source progress, or changed protected files. Successful completion requires both a successful agent exit and passing independent validation.

Read the controller here; run it only after the isolation gate in Chapter 09. Its checks catch cooperative mistakes. They do not defend against a hostile process running as the same user: that process could alter Git metadata, spawn detached children, or interfere with the controller. Stronger acceptance belongs outside the worker VM.

## Concept check

**Q1.** What survives a fresh process and what does not?

**Q2.** Can the controller’s success message replace independent review?

<details>
<summary>Concept answer key — attempt first</summary>

## Answer key (concept)

**Q1.** Files and committed state survive; unrecorded conversational context does not. **Pass criteria:** explicit persistent memory.

**Q2.** No. The agent and controller share a machine. **Pass criteria:** explain the authority problem.

</details>

## Gate: Inspect the controller

Read [scripts/loop.py](/downloads/nix-agent-lab/scripts/loop.py) in the archive. Locate:

| Control | Mechanism |
|---|---|
| Fresh context | A new `claude --bare -p` process for each attempt; no resume flag |
| Finite attempts | `range(1, 4)` |
| Wall time | Subprocess timeout, terminate process group, then kill after grace period |
| Token/work budget | CLI turn and estimated dollar caps |
| Scope check | Hash original tracked files outside the two allowed paths |
| Independent command | Controller invokes `devenv test` after the agent |
| No progress | Compare source hashes between attempts |
| Evidence | Per-attempt agent logs and the latest validation log |

The controller intentionally does not commit or push. It does not use the plan’s checkboxes as a completion detector.

## Gate: Trace three outcomes without an API call

Walk through the code with your tutor:

1. The agent edits source and tests pass: stop for owner review.
2. The agent edits source but acceptance fails: the next fresh process gets the current files and latest failure log.
3. The agent changes tests, makes no source progress, or exceeds wall time: stop and investigate.

Explain why authentication failures must not create an endless paid retry cycle. The same applies to rate limits and a broken environment. Repair the cause before restarting the controller.

## Gate: Prepare a fresh baseline

Use a separate clone of the Chapter 06 exercise state. Reapply and commit the reviewed agent pin from Chapter 07. Confirm `tests/test_completion.py` is present and the working tree is clean. Build the pinned agent once to warm its dependencies; keep the `.agent-cli` link ignored.

The command below is for the disposable VM after Chapter 09, not your ordinary laptop:

```bash
export LAB_DISPOSABLE_VM=yes
export LAB_MODEL='replace-with-your-available-exact-model-id'
devenv shell python scripts/loop.py
```

The environment marker acknowledges a completed setup; setting it does not create isolation. Inject the scoped API key out of band. The controller grants Read/Edit/Write/Bash for this toy repository only within the VM you prepared. Do not use a global “skip permissions” flag.

## Gate: Judge the result

Inspect all logs, not just the last line. Record attempts, wall time, estimated and provider-reported cost, touched files, and the externally rerun test result. A cheap failed run with a precise blocker can be more useful than an expensive “success” that weakens tests.

For a longer plan, choose one small task per run, review and commit it, and give the next run a new clean baseline. Do not turn three attempts into overnight autonomy until you have measured failure modes.

## Known failure modes

- “All done” text is not a completion protocol.
- A budget flag is an estimate-based stop, not a guarantee of a maximum invoice.
- Killing a process group may not stop descendants that detached themselves. Tear down the VM at the external deadline.
- Repeated failures often require a better spec or verifier, not more iterations.

## Check your understanding

**Q3.** What happens if the agent exits zero but tests fail?

**Q4.** Why keep cloud lifetime enforcement outside the controller?

<details>
<summary>Answer key — attempt every question first</summary>

## Answer key

**Q3.** It is not accepted; retry only if source progress and budget remain. **Pass criteria:** tests outrank self-report.

**Q4.** A worker can hang or alter local controls. **Pass criteria:** independent deadline and teardown.

</details>

## References

- [Original Ralph description](https://ghuntley.com/ralph/)
- [Python subprocess timeouts](https://docs.python.org/3/library/subprocess.html)
- [Claude CLI limits](https://code.claude.com/docs/en/cli-reference)

---

[← Chapter 07](/nix-agent-lab/03-controlled-agents/07-one-shot-agent/) · [Next: Chapter 09 →](/nix-agent-lab/03-controlled-agents/09-isolation-before-autonomy/)
