Skip to content
RALPH LOOP

How to Inspect and Debug Inside an AI Agent Sandbox

A developer opening an interactive shell into a running Docker Sandbox microVM to inspect logs and reproduce a failing command from an AI coding agent.

When an autonomous agent gets stuck, do not guess from the outside. Open a shell in the running sandbox and look. A Ralph sandbox is a normal container once you are inside it, so you find it with sbx ls, shell in with sbx exec -it <name> bash, and debug the agent the same way you debug any other process. This is the field guide to inspecting and debugging an AI agent sandbox: locating the microVM, getting an interactive shell, reproducing the failure by hand, and reading the network log.

A loop driven by Ralph does not fail silently. The agent emits an explicit signal when it cannot proceed: <promise>BLOCKED:reason</promise> when it needs human help (exit code 2) or <promise>DECIDE:question</promise> when it needs a decision (exit code 3). A run that hits the iteration cap exits with 1, and a clean finish exits with 0. The promise text tells you what the agent thinks went wrong. It does not always tell you why.

That gap is what a shell closes. You get inside when a task keeps failing the same way, when an install hangs, when tests pass for you but not for the agent, or when you just want to see the state the agent left behind. Because the agent runs in bypass-permissions mode (see running agents in YOLO mode safely for why that is fine inside a microVM), the inside of the sandbox holds the real evidence: the working tree, the logs, the installed packages, and the exact environment the agent saw.

The sandbox is the same boundary described in the pillar guide, how to run AI coding agents in Docker Sandboxes safely. Debugging it does not weaken that boundary. You are stepping into the contained space on purpose, doing your work, and stepping back out.

You cannot shell into a sandbox you cannot name. Ralph names every sandbox deterministically, so the name is predictable, but you still want to confirm what is actually running.

Start with the list:

Terminal window
sbx ls

This prints every sandbox on the machine. The one you want follows the Ralph format:

ralph-<agent>-<current-dir>-<hash8>

A project at /Users/me/Work/My App running Claude shows up as ralph-claude-my-app-a1b2c3d4. If you ran more than one agent against the same project, you will see one sandbox per agent, because the agent slug is part of the name. That is deliberate, so a Claude run and a Codex run never stomp on each other’s state.

You do not have to read the name off a list. Ralph prints it on the Starting Ralph line at launch, and you can ask for it any time:

Terminal window
./ralph.sh --print-name
./ralph.sh --print-name --agent cursor

The --agent flag targets a specific agent’s sandbox for the same project. Capture the value once and reuse it for every sbx command below, so you are not retyping a hash.

The name carries information, which helps when you have several sandboxes open. The pieces are documented in the repo’s sandbox naming notes:

  • <agent> is the agent slug, lowercased: claude, codex, copilot, cursor, gemini, or opencode.
  • <current-dir> is the basename of the project directory, sanitized to [a-z0-9-].
  • <hash8> is the first eight hex characters of sha256 of the absolute project path.

The path hash is the part that prevents collisions. Two directories both named app, one at ~/Work/app and one at /tmp/app, get different hashes and therefore different sandboxes. So when sbx ls shows two ralph-claude-app-... entries, the trailing hash is how you tell them apart.

This is the command you came for. Open an interactive shell:

Terminal window
sbx exec -it ralph-claude-my-app-a1b2c3d4 bash

The -it flags allocate an interactive terminal. Now you are inside the microVM with full control, the same as any container. You can install packages, run the test suite, read files, and poke at the environment the agent ran in.

The project is mounted at the same absolute path it has on your host. If your project lives at /Users/you/Work/my-app on the host, it lives at /Users/you/Work/my-app inside the sandbox too. That is on purpose, so tooling, config files, and lockfiles resolve identically and the agent never trips over a path difference.

So the first move after shelling in is usually:

Terminal window
cd /Users/you/Work/my-app

Now your working directory matches the agent’s. Any relative path the agent used resolves the same way for you.

The most useful trick is driving the agent yourself, interactively, from inside the sandbox. You see exactly what it sees, including the permission mode and the network reality:

Terminal window
sbx exec -it ralph-claude-my-app-a1b2c3d4 bash
cd /Users/you/Work/my-app
claude

Swap claude for codex, copilot, cursor, gemini, or opencode depending on which agent that sandbox was built for. Running the CLI by hand is how you tell apart a prompt problem from an environment problem. If the agent stalls the same way interactively, the issue is the task, the prompt, or the repo state. If it works by hand but fails in the loop, the issue is in how the loop set things up, and you can compare the two.

sbx exec works while the sandbox is running. To reattach the exact sandbox Ralph uses, for a manual login or a longer debugging session, use the attach form of sbx run:

Terminal window
sbx run ralph-claude-my-app-a1b2c3d4

There is one sharp edge here, and it trips people up, so state it plainly. The create form, sbx run --name <name> <agent> ., is create-only. Passing --name for a sandbox that already exists fails with an error that says --name can only be used when creating a new sandbox. So you use the --name create form exactly once, before the sandbox exists, and the bare sbx run <name> attach form every time after.

Ralph handles this branching for you. It probes sbx ls before every iteration and emits the create form when the sandbox is missing and the attach form when it exists. That probe is also why a loop self-heals: if you sbx rm a sandbox mid-run while debugging, the next iteration simply creates it again. Re-running ./ralph.sh, ./ralph.sh --login, or ./ralph.sh --ports is therefore safe at any point.

Debug inside: logs, reproduction, and missing tools

Section titled “Debug inside: logs, reproduction, and missing tools”

Once you have a shell, the sandbox is just an environment. Three moves cover most debugging sessions.

Ralph keeps its state on disk, not in chat history, because the filesystem and git history are the memory layer. That means everything the agent knows is sitting in the project, ready to read:

Terminal window
cat .agent/logs/LOG.md
ls .agent/history/
cat .agent/tasks.json
git log --oneline -20

.agent/logs/LOG.md is the running log. .agent/history/ holds per-iteration logs, so you can see what happened on the iteration that broke. .agent/tasks.json is the task lookup table, and the git log shows what the agent actually committed. Reading these in order usually tells you whether the agent thrashed on one task, committed something broken, or stalled waiting on a decision. For the wider picture of making a run auditable from the outside, including live output and screenshots, see observability for autonomous coding agents.

The fastest way to understand a failure is to run the thing that failed. Inside the sandbox, at the project path, run the verification stack the loop assumes:

Terminal window
npm test
npx tsc --noEmit
npx eslint .
npx playwright test

If the agent reported a failing test, run that test directly and read the full output without the loop’s framing. The repo mantra is “if you didn’t test it, it doesn’t work,” and the same applies to your debugging: reproduce the exact failure before you theorize about it. Because you are inside the same microVM the agent used, a failure you reproduce here is the real failure, not a near miss caused by a different machine.

Sometimes the agent is blocked because a binary it needs is not installed. You have full root-style control inside the sandbox, so install it and confirm the theory:

Terminal window
apt-get update && apt-get install -y <package>

If an install itself fails, that is almost never a package manager bug. It is the network gate, which is the next section.

Docker Sandboxes are deny-by-default on outbound HTTP and HTTPS. So the single most common “the agent is stuck” cause is a blocked connection: npm install fails, an API call is refused, or a package download hangs. The gate is doing its job, but you need to see what it blocked.

Read the connection history:

Terminal window
sbx policy log

This shows which outbound connections the sandbox attempted and which the policy allowed or denied. List the active rules to see what is currently permitted:

Terminal window
sbx policy ls

When the log shows a denied host the task legitimately needs, allow that specific domain rather than opening everything:

Terminal window
sbx policy allow network ralph-claude-my-app-a1b2c3d4 registry.npmjs.org

Allow several at once with a comma-separated list, which is the usual case when an install is blocked:

Terminal window
sbx policy allow network ralph-claude-my-app-a1b2c3d4 "*.npmjs.org,*.pypi.org,files.pythonhosted.org,github.com"

Changes take effect immediately and persist across sandbox restarts. Resist the urge to debug with the full-open "**" rule and leave it there, because that throws away half the boundary. The disciplined way to build an allowlist that lets installs through while keeping exfiltration out is covered in network policies for AI agent sandboxes. The primary reference for the policy engine is the Docker Sandboxes documentation.

There are only a handful of ways into and out of a sandbox, and they compose cleanly. This is the whole surface you work with.

flowchart LR
  Host["Your machine (host)"]
  subgraph Entry["Debug entry points"]
    List["sbx ls: find the name"]
    Shell["sbx exec -it name bash: interactive shell"]
    Attach["sbx run name: reattach the loop sandbox"]
    NetLog["sbx policy log: connection history"]
    Stop["sbx stop name: end the session"]
  end
  subgraph Sandbox["Sandbox: ralph-claude-my-app-a1b2c3d4"]
    Proj["Project at the same path as the host"]
    Logs[".agent/logs, .agent/history, git log"]
    CLI["Agent CLI you can run by hand"]
    Net["Network gate: deny-by-default"]
  end
  Host --> List
  Host --> Shell
  Host --> Attach
  Host --> NetLog
  Host --> Stop
  Shell --> Proj
  Shell --> Logs
  Shell --> CLI
  Attach --> CLI
  NetLog --> Net

sbx ls gives you the name. sbx exec and sbx run put you inside. sbx policy log explains network failures from the outside. sbx stop ends the session. You rarely need anything else.

When you finish a manual debugging session, stop the sandbox you were poking at:

Terminal window
sbx stop ralph-claude-my-app-a1b2c3d4

Stopping targets only that one name. Sandboxes you started for other agents in the same project are left alone. Ralph runs this same command for you through its exit trap when a loop ends, by normal exit, by a double Ctrl+C, or by any path that fires the trap, and the cleanup is guarded so it runs at most once.

Stopping is not deleting. A stopped sandbox can be reattached later with sbx run <name>, which is handy when you want to come back to the same state. When you are truly done and want the microVM gone, remove it explicitly:

Terminal window
sbx rm ralph-claude-my-app-a1b2c3d4

If sbx is not installed at all, Ralph fails fast before any of this with a clear pointer to the Docker Sandboxes getting-started guide rather than a confusing downstream error.

Putting the moves in order, here is what a real session looks like when a loop reports BLOCKED.

First, get the name and confirm the sandbox is there:

Terminal window
./ralph.sh --print-name
sbx ls

Shell in and go to the project path:

Terminal window
sbx exec -it ralph-claude-my-app-a1b2c3d4 bash
cd /Users/you/Work/my-app

Read what the agent recorded, then reproduce the failure it hit:

Terminal window
cat .agent/logs/LOG.md
git log --oneline -10
npm test

If the failure is a blocked install, check the network log from your host in another terminal, allow the domain, and retry the install inside the sandbox:

Terminal window
sbx policy log
sbx policy allow network ralph-claude-my-app-a1b2c3d4 registry.npmjs.org

If the failure is a task or prompt problem, run the agent CLI by hand and watch it work the task interactively. When you understand the cause, exit the shell, fix the task spec or the prompt on the host, and let the loop pick up where it left off. Ralph re-probes the sandbox on the next iteration, so your fix runs in the same contained environment.

This is the Ralph technique applied to its own failures. The loop, in the tradition of Geoffrey Huntley’s original Ralph writeup, keeps its state on disk so you can always step in, read the evidence, reproduce the problem, and step back out. The sandbox is contained, so debugging it costs you nothing on the host. The worst case stays cheap, and the agent gets back to work.

Frequently asked questions

How do I open a shell inside a running AI agent sandbox?

Find the sandbox name with sbx ls or with ./ralph.sh --print-name, then run sbx exec with the interactive flags and bash, for example sbx exec -it ralph-claude-my-app-a1b2c3d4 bash. You now have full control inside the microVM, so you can navigate to the project path, run tests, install packages, and inspect files like any container.

What is the difference between sbx exec and sbx run for debugging?

Use sbx exec with -it and bash to open an interactive shell in a sandbox that is already running. Use sbx run with the name to reattach the sandbox Ralph uses for a manual login or a longer session. The create form sbx run --name name agent dot is create-only and only works the first time, before the sandbox exists.

Why does an install or API call fail inside the sandbox?

Docker Sandboxes block outbound network by default. Run sbx policy log to see which connections were denied, then allow the specific domain with sbx policy allow network using the sandbox name. Allow only the hosts the task needs, such as registry.npmjs.org or the comma-separated npm, PyPI, and GitHub domains, rather than opening all traffic.

Where does the agent store the logs I should read when it gets stuck?

Ralph keeps its state on disk inside the project, not in chat history. Read .agent/logs/LOG.md for the running log, list .agent/history for per-iteration logs, check .agent/tasks.json for the task table, and run git log to see what was committed. Together these usually show whether the agent thrashed on a task or stalled waiting on a decision.

How do I clean up a sandbox after debugging?

Run sbx stop with the sandbox name to stop only that sandbox; sandboxes for other agents are untouched. Stopping is not deleting, so you can reattach later with sbx run and the name. When you want the microVM gone for good, remove it with sbx rm. Ralph also stops its own sandbox automatically through its exit trap when a loop ends.