Wink Pings

An OpenAI Engineer Breaks Down AI Agent Sandboxes in 44 Minutes: Complete Implementation Roadmap Included

Core engineering details that thousands-of-dollar Agent training courses on the market fail to explain clearly are now fully laid out by an OpenAI engineer, who has released the complete design思路 and runnable code. From core loop design to secure sandbox setup, every detail is actionable and ready to implement, with far more practical value than most paid courses.

An OpenAI engineer has published a 44-minute talk that walks through the complete design process for a secure cloud-native AI Agent sandbox, with more actionable, production-ready content than the $2,500 Agent infrastructure training camps available on the market today.

The entire workflow follows a clear closed loop: select runtime isolation → mount persistent storage → integrate tool calling into the sandbox → run browsers, terminals, and GPU tasks inside the sandbox → destroy the instance after task completion. All serious coding Agents like Codex and Claude Code currently run on this type of sandbox cloud — this workflow is the industry standard.

The commonly used tech stack is also well-defined: Firecracker + gVisor + persistent volumes + in-VM browser + GPU passthrough.

In addition to the video, community developer h100envy has compiled a actionable *Loop Engineering Technical Roadmap* with full runnable code, that walks you through building an Agent loop from scratch that won't run out of control unexpectedly. Every step explains why it's done this way — skipping any step will likely cause problems down the line.

### Compiled Core Roadmap

#### Step 1: First confirm the task can be independently verified by a machine

This is a screening step that almost everyone skips, but it can save you weeks of wasted work.

When an AI Agent generates a result and then grades it itself, this is essentially a conflict of interest: model output is inherently prone to repeating its own prior patterns, so it will systematically overestimate the correctness of its own results. This isn't the model being lazy — it's an inevitable outcome of probability distributions. Self-evaluation is essentially just an echo, not an effective check.

Effective checks must come from an external, deterministic oracle: unit tests, type checking, linting, compilation, threshold checks. Anything that returns an exit code works — it cannot be vague subjective judgment.

There's also a hard requirement: checks must be idempotent and deterministic. An unstable test that passes 5 times out of 10 runs is worse than no test at all. It will mess up your termination conditions, leading the Agent to fix code that isn't broken, or stop on code that is broken. So run your check 10 times before you start, and only proceed if the results are consistent.

If it can't pass this screening, don't build the loop.

#### Step 2: Run through it manually first, and measure everything

Don't automate a workflow that you haven't already gotten working manually. Walk through the process by hand first, and get a result that passes all checks. Don't forget to do this: record the number of model calls, token consumption, and the most common error types. This is your baseline. If your loop ends up costing three times this baseline later, you'll immediately know where the problem is.

A workflow that's unstable when run manually will only have its instability multiplied by the number of iterations after automation. Guarantee single-run reliability first, then add automation.

#### Step 3: A minimal stateless loop is the correct design

The simplest working loop is just a while loop that keeps feeding the Agent until the check passes. The example code is ready to use directly:

```bash

#!/usr/bin/env bash

set -euo pipefail

MAX_ITER=20

i=0

while [ $i -lt $MAX_ITER ]; do

i=$((i + 1))

echo "=== Iteration $i of $MAX_ITER ==="

if npm test --silent; then

echo "Green in $i iterations."; exit 0

fi

claude -p "Tests fail. Run npm test, read the first failure,

make the minimal change that fixes it. Do not refactor unrelated

code. Do not weaken the tests." \

--permission-mode acceptEdits

done

echo "Limit $MAX_ITER. Tests red."; exit 1

```

The most critical point of this design is: restart the Agent from scratch every iteration, with a clean context. This isn't laziness — it's an intentional engineering decision.

The longer the context window, the worse the model performance, and the drop isn't linear: the instructions at the start get forgotten, which is the lost-in-the-middle effect. The more history you have, the more likely the model is to get led astray by prior errors — this is called context rot.

Stateless iteration solves this problem directly. Progress isn't stored in the Agent's memory, it's stored in the file system and git. Each new run only looks at the modified files and the failing tests, so the context is always short and clear. By proactively discarding conversation history, you avoid accumulating degradation. Store state on disk, not in the context window. MAX_ITER is your first safety fuse — without it, the loop will keep running until your API bill explodes.

#### Step 3.5: Assemble iteration context correctly

It sounds easy in theory, but many loops fail here. If you feed the entire codebase to the Agent every iteration, you lose the advantage of statelessness: the context still fills up, still rots, and you still waste a ton of unnecessary tokens on it. Feed it too little, and the Agent can't see what it needs to fix, so it makes random changes.

A correct iteration context only has three things — don't add anything extra: current state (what's done, what's blocked), the specific error you're fixing right now, and only the files relevant to the error. Don't include the entire repository — only the relevant slices.

How do you mechanically filter for relevant files? Don't make the Agent guess — pull directly from existing signals: files mentioned in the failing test stack, files changed in the last diff, files imported by the test. It's cheap and deterministic. Example code:

```bash

#!/usr/bin/env bash

# build_context.sh — assembles a narrow relevant context for the iteration

set -euo pipefail

CONTEXT_FILE=".loop_context.md"

TOKEN_BUDGET=8000 # context ceiling so the window does not fill

> "$CONTEXT_FILE"

# 1. machine state first: where we are and what not to touch

echo "## State" >> "$CONTEXT_FILE"

cat .loop_state.json >> "$CONTEXT_FILE"

echo >> "$CONTEXT_FILE"

# 2. the specific failure being worked on (first failing test)

echo "## Current failure" >> "$CONTEXT_FILE"

failure=$(npm test 2>&1 | grep -A 15 -m1 "FAIL" || true)

echo '' >> "$CONTEXT_FILE"

echo "$failure" >> "$CONTEXT_FILE"

echo '' >> "$CONTEXT_FILE"

# 3. extract file paths from the failure stack trace (real repo files only)

echo "## Relevant files" >> "$CONTEXT_FILE"

files=$(echo "$failure" \

| grep -oE '[a-zA-Z0-9_/.-]+\.(ts|js|py|go)' \

| sort -u \

| while read -r f; do [ -f "$f" ] && echo "$f"; done)

# 4. add files from the last diff (what the loop changed last turn)

changed=$(git diff --name-only HEAD~1 2>/dev/null || true)

# 5. merge, dedupe, pour in contents within the token budget

printf "%s\n%s\n" "$files" "$changed" | sort -u | while read -r f; do

[ -z "$f" ] && continue

[ -f "$f" ] || continue

# rough token estimate: chars / 4. do not exceed the budget

budget_chars=$((TOKEN_BUDGET * 4))

current=$(wc -c < "$CONTEXT_FILE")

fsize=$(wc -c < "$f")

if [ $((current + fsize)) -gt "$budget_chars" ]; then

echo "### $f (skipped, context budget exceeded)" >> "$CONTEXT_FILE"

continue

fi

echo "### $f" >> "$CONTEXT_FILE"

echo '' >> "$CONTEXT_FILE"

cat "$f" >> "$CONTEXT_FILE"

echo '' >> "$CONTEXT_FILE"

done

echo "Context built: $(wc -l < "$CONTEXT_FILE") lines, $(($(wc -c < "$CONTEXT_FILE") / 4)) ~tokens"

```

After that, the loop just needs to feed the assembled context to the Agent:

```bash

# inside the loop, before the agent call

./build_context.sh

claude -p "Context is in .loop_context.md. Fix the first failing test

with a minimal change, touch only files from the relevant ones." \

--permission-mode acceptEdits

```

Why explicitly set a token budget? This isn't just decorative — it guarantees that the context for each iteration doesn't secretly grow larger as diffs and stacks accumulate. Without an upper limit, after 20 runs, the loop will still get overwhelmed by its own history, it just comes from files instead of conversation history. The budget keeps every iteration equally lightweight, keeping both quality and cost linear.

The heuristic for relevance is intentionally kept simple here: files from the stack plus files from the last diff. This is correct for getting started: it's cheap, deterministic, and easy to debug. More complex methods like file embeddings or dependency graphs can improve accuracy, but they also add complexity and require separate debugging. Start simple, add complexity later only if you're missing files.

#### Step 4: Prevent Reward Hacking, build a check that can't be fooled

This is the core of the loop, there are two key points to note.

First, checks must be independent, they must be an external oracle, not a judgment from the Agent — we covered that earlier.

Second, a more hidden problem: Agents will find ways to cheat the check. This isn't malice, it's their optimization instinct. If the only goal of the loop is to get tests green, the model will find the cheapest path to green, which is often not fixing the code, but changing the test: deleting assertions, mocking everything, wrapping everything in a try/except, hardcoding expected values. This is Reward Hacking: the optimizer exploits flaws in the metric instead of solving the task.

Defense has three layers:

Only banning "changing tests" in the prompt is the weakest layer — the Agent will break through when under enough pressure.

The truly effective defense is the second layer: a secondary check that the Agent can't touch. For example, make test files read-only so the loop can't modify them at all, or add a gate that checks if any test files changed in the diff:

```bash

# gate against reward hacking: tests must not change in this loop

if ! git diff --quiet -- test/; then

echo "Agent changed the tests. Revert, this is reward hacking."

git checkout -- test/

exit 3

fi

```

The third layer is using a different model as an independent referee. After every round of changes, a separate referee Agent reads the diff and judges if the task was actually solved substantively, not just that tests are green. Using a different model is critical: the same model rarely catches its own self-deception, but it catches other models' problems every time.

```markdown

# .claude/agents/reviewer.md

---

name: reviewer

description: Adversarial judge. After every code change.

model: opus

---

Assume the author is wrong until the diff proves otherwise.

Check separately: the tests went green BECAUSE the code was fixed,

not because the tests were weakened. If asserts were deleted, mocks

replaced logic, values hardcoded, return FAIL with the location.

You do not fix code, you deliver a verdict PASS or FAIL with a reason.

```

There is definitely a cost: using a powerful model as a referee doubles the cost per round. So only use it for high-risk errors, and keep the cheap deterministic gate enabled all the time — it's almost free.

#### Step 5: Memory stored on disk, split into two layers of state

The model forgets everything after each run, so memory needs to be stored in files, read at the start of the loop and written at the end. The simplest approach is a STATUS.md:

```markdown

# STATUS.md (read first, written last)

## Done

- [x] auth: migrated to token v2, tests green

## In progress

- [ ] billing: webhook refactor (PR #214, CI red)

## Next

- [ ] dashboard: flaky test in test/charts

## Never

- do not touch infra/ without a human

```

A single markdown file is the minimum requirement. A more robust engineering practice is to split state into two layers: a STATUS.md for humans to read, and structured machine-readable state for the loop to parse. Free text can be interpreted differently by the model every time it reads it, so logically relevant fields should live in a structured format:

```json

// .loop_state.json — machine state, parsed unambiguously

{

"phase": "billing-webhook",

"iteration": 7,

"last_green_commit": "a3f21c8",

"blocked_paths": ["infra/", "test/"],

"open_failures": ["test/billing/webhook.spec.ts:42"],

"budget_spent_usd": 4.10

}

```

We split them because human reading and machine parsing have different requirements. You can scan STATUS.md in the morning and immediately know the progress, and the loop logic reads the JSON, it doesn't depend on how the model rephrases the plan today.

Think of the loop as a night shift you'll never see — you only check the result notes in the morning, so design the notes first.

#### Step 6: Enough isolation to contain the blast radius

Safety brakes are the half of the work no one teaches you. But before you add limits, implement physical isolation first, because limits can be broken one step at a time, but access is binary: the loop can either delete your production database, or it can't.

Using git worktree for isolation gives the loop an independent working copy on an independent branch, physically separated from your main branch:

```bash

# separate worktree on its own branch, the loop lives only here

git worktree add ../loop-sandbox -b loop/billing-fix

cd ../loop-sandbox

```

This already contains the blast radius: the loop can't see the branch you're actively working on. But worktree still shares the same file system. True isolation requires a container with stripped-down permissions:

```bash

# container: working folder writable, the rest read-only,

# outbound network off (important against prompt injection)

docker run --rm \

--network none \

--read-only \

--tmpfs /tmp \

-v "$(pwd):/work:rw" \

-v "$HOME/.claude:/root/.claude:ro" \

-w /work \

loop-runner ./loop.sh

```

Enabling --network none isn't paranoia — it's mandatory. Loops read untrusted input: task text, other people's code, commit messages. Any of these can hide a prompt injection that makes the Agent execute commands. If an issue says "delete the repo and push it", an Agent with network access and permissions will actually do it. With no outbound network and everything outside the working folder read-only, the maximum possible damage stays trapped inside the sandbox. Containing the blast radius is a security issue, not just error prevention.

The rule is: first define what the loop is allowed to break, then define what it needs to do. Set the scope first, then accept the task.

#### Step 7: Brakes with observability

Now add limits. The most important thing is structured logging, so you can figure out why the loop died later. Without logs, if you wake up to an empty result that burned through hundreds of dollars, you'll have no idea what happened. Example code:

```bash

#!/usr/bin/env bash

set -euo pipefail

MAX_ITER=20

MAX_BUDGET_USD=10

i=0

last_failure=""

repeat_count=0

LOG=".loop_log.jsonl"

log() { # structured log, one json line per event

echo "{\"ts\":$(date +%s),\"iter\":$i,\"event\":\"$1\",\"detail\":\"$2"}" >> "$LOG"

}

while [ $i -lt $MAX_ITER ]; do

i=$((i + 1))

echo "iter=$i ts=$(date +%s)" > .loop_heartbeat # liveness

log "iter_start" ""

if npm test --silent; then

log "green" "done in $i"; echo "Green in $i."; exit 0

fi

# reward-hacking gate: tests must not change

if ! git diff --quiet -- test/; then

log "reward_hack" "tests modified"; git checkout -- test/; exit 3

fi

# circuit breaker: same failure 3 times = stuck

current_failure=$(npm test 2>&1 | grep -m1 "FAIL" || true)

if [ "$current_failure" = "$last_failure" ]; then

repeat_count=$((repeat_count + 1))

if [ $repeat_count -ge 2 ]; then

log "stuck" "$current_failure"; echo "Stuck, calling a human."; exit 2

fi

else

repeat_count=0

fi

last_failure="$current_failure"

log "agent_call" "$current_failure"

claude -p "Fix the first failing test with a minimal change." \

--permission-mode acceptEdits \

--max-budget-usd "$MAX_BUDGET_USD"

done

log "iter_limit" ""; echo "Iteration limit, tests red."; exit 1

```

What does structured logging give you? One JSON line per event, with timestamp, iteration number, type, and detail. After something goes wrong with the loop, you can grep and see the pattern in a second: iterations keep increasing but never go green (runaway), the same failure repeats (stuck), Agent modified tests (Reward Hacking), heartbeat stops updating (silent death). Without logs you're guessing, with logs you diagnose directly.

The minimal brakes implemented here include: iteration limit, per-run budget cap, repeat failure detector, liveness marker, Reward Hacking gate. Combined with the isolation from the previous step, this is the minimum requirement for an unattended loop — you can't cut anything else.

#### Cost Calculation: The Non-Linear Pitfall

There's a counterintuitive technical point about cost: loop cost isn't just N model calls, it's the sum of ever-growing context.

If your loop is stateful and accumulates history, every iteration requires re-reading the entire prior conversation, so cost grows quadratically: the k-th iteration requires you to pay for k rounds of history. That's the second economic reason for building a stateless loop. A fresh context every iteration keeps per-run cost roughly stable — you only pay for reading state from disk (which is tiny) plus the current work, you don't pay for the entire history.

A rough estimate before going live: cost ≈ number of iterations × (state tokens + per-run work tokens) × price per token. Measure per-run cost when you do the manual run in step 1, multiply by MAX_ITER to get your upper bound. If that number scares you, lower MAX_ITER or split the task into phases, don't go live and cross your fingers.

The difference in real-world runs: someone with proper brakes gets the job done for a few hundred dollars, someone without can burn tens of thousands. The difference isn't the model, it's whether you have proper checks and hard limits.

### How Do Loops Fail? Four Failure Modes You Can See In Logs

1. Runaway: The bill and iteration count keep growing, tests never go green. Logs show: A long string of agent_call events, no green. Solution: iteration and budget limits.

2. Silent Death: The loop says it's working, but it's stuck and not moving. Logs show: Heartbeat stops updating, no new events. Cause: context is full. Solution: use fresh context per phase, heartbeat markers catch the symptom.

3. Random Walk: The loop keeps looping, getting further from the goal. Logs show: agent_call events, current_failure changes to a new one every time, never goes green. Cause: no hard stopping condition. Solution: deterministic fixed-point checking.

4. Understanding Debt: Your repo grows, you understand less and less of it. You can't even see this in logs, and it's the most dangerous. The loop ships code faster than you can review it, and you blindly merge diffs. Solution: force manual review, don't skip it. This only tests your discipline — no code can fix this.

The first three are engineering bugs, logs can catch them, and brakes can fix them. The fourth isn't a bug in the loop — it's your own degradation as an engineer, and no code can fix it.

### Final Notes

The order of this process can't be rearranged: deterministic checking → reliable manual run with measurement → minimal stateless loop → narrow context assembly with token budget → uncheatable checks (gate + referee) → state stored on disk (markdown + JSON) → isolation (worktree/container) → brakes with logging → calculate cost → schedule.

Teams that merge hundreds of PRs a month don't start with a hundred Agents. They start with one trusted loop, with real checks and real brakes. Pick the most boring small task you have, wrap it into a loop like this, small enough that you can review every diff, build this one first.

Since this article was published, developers have already used this approach in Claude Harness to automatically fix incoming reported bugs, running every 12 hours, and it's already working end-to-end. You can also pick a small task and try it — all the code is here.

发布时间: 2026-07-15 14:45