DEV Community

Jordan Huang
Jordan Huang

Posted on

Your Agent Loop Didn't Fail at Step 41. Your Budget Did.

Your Agent Loop Didn't Fail at Step 41. Your Budget Did.

Picture an agent loop that stops at step 41. The transcript ends mid-sentence. No exit code, no stack trace.

Was it the model? The network? The box? Most postmortems blame the last thing they saw. That blame is usually wrong.

Here is a myth-busting FAQ about long agent loops on cheap compute. Four claims, the evidence against each, and a corrected model. Then a runnable step gate and a host decision table.

Myth 1: "If the run is free, the failure is free"

The claim: cheap compute means a cheap crash. Restart it and move on.

The evidence: the bill is not the only cost. Wall clock, context growth, and your attention all burn while a loop retries. A crash at step 41 costs whatever steps 1 through 40 produced. If those steps wrote files, you keep the value. If they only wrote prose, you keep nothing.

Corrected model: budget four dimensions at once.

  1. Steps — tool calls allowed before the loop must stop.
  2. Wall clock — minutes before a human must look.
  3. Context bytes — how large the journal may grow.
  4. Disk — what the loop leaves behind on the box.

One number cannot cover four failure modes. That is the entire point.

Myth 2: "A green tool result means the command ran"

The claim: the transcript says exit 0, so the work happened on the machine.

The evidence: a tool result is text. Text is produced by the agent runtime, not by the kernel. Nothing in exit 0 binds to a PID, a container, or an inode.

Ask the hard question. Which host wrote that line? Was it the remote box, your laptop, or the model's own prediction?

Corrected model: assert on side effects, not on prose.

# Weak: the runtime says it worked.
grep -qE 'exit.*0' transcript.log

# Strong: the artifact exists and has a known hash.
test -f out/report.json
sha256sum out/report.json
Enter fullscreen mode Exit fullscreen mode

If a step claims to write a file, check the file. If it claims to deploy, hit the endpoint. Text is a rumor. A hash is evidence.

Myth 3: "Same prompt, same loop"

The claim: rerun the prompt and you reproduce the run.

The evidence: sampling, context truncation, and tool ordering all vary. Even at low temperature, a long loop drifts. Step 30 sees a different context than it did last time.

Corrected model: treat the loop as an event log, not as a function.

  • Give every step an id and a timestamp.
  • Record the exit status and the bytes produced.
  • Record which artifacts changed, by hash.

Now a rerun is comparable. You diff journals instead of arguing about vibes.

Myth 4: "Retries are free, so retry harder"

The claim: a failed step needs another attempt. Wrap it in a retry loop.

The evidence: a naive retry loop multiplies cost and hides the real fault. A rate limit answered with instant retries is a self-inflicted outage.

Corrected model: back off, then stop.

  1. Retry twice with exponential backoff.
  2. On the third failure, write the error into the journal.
  3. Resume from the last good step, never from step 1.

Where MonkeyCode fits

MonkeyCode offers free model access and a free server option. Both are availability claims from the operator, not benchmarks I measured. Check the current terms before you depend on either.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

Why mention it at all? Because this workflow has two separate needs. The loop needs a host that stays awake. The tuning phase needs cheap steps while you adjust budget thresholds.

The free server option addresses the first need. Free model access addresses the second. Neither one removes the budgets. Cheap compute still dies at step 41.

The artifact: a step gate with a journal

Label this as a template. Tune the thresholds before you trust the numbers.

#!/usr/bin/env bash
# loop_gate.sh — stop an agent loop when a budget trips.
# Template only. Replace run_step with your own agent invocation.
set -euo pipefail

JOURNAL="${JOURNAL:-steps.jsonl}"
MAX_STEPS="${MAX_STEPS:-40}"
MAX_MINUTES="${MAX_MINUTES:-20}"
MAX_CONTEXT_BYTES="${MAX_CONTEXT_BYTES:-200000}"

step=0
start=$(date +%s)

log_step() { # step, exit code, bytes
  printf '{"step":%d,"ts":%s,"exit":%d,"bytes":%d}\n' \
    "$1" "$(date +%s)" "$2" "$3" >> "$JOURNAL"
}

run_step() { # replace this with your agent call
  agent_step --step "$1" 2>&1
}

while :; do
  step=$((step + 1))
  elapsed=$(( ($(date +%s) - start) / 60 ))

  if [ "$step" -gt "$MAX_STEPS" ]; then
    echo "STOP: step budget ${MAX_STEPS} tripped" >&2
    exit 2
  fi
  if [ "$elapsed" -gt "$MAX_MINUTES" ]; then
    echo "STOP: wall-clock budget ${MAX_MINUTES}m tripped" >&2
    exit 3
  fi

  out=$(run_step "$step" || true)
  bytes=$(printf '%s' "$out" | wc -c)
  log_step "$step" 0 "$bytes"

  total=$(wc -c < "$JOURNAL")
  if [ "$total" -gt "$MAX_CONTEXT_BYTES" ]; then
    echo "STOP: context budget ${MAX_CONTEXT_BYTES}B tripped" >&2
    exit 4
  fi
done
Enter fullscreen mode Exit fullscreen mode

Two details matter more than the code itself.

First, every stop gets a distinct exit code. Values 2, 3, and 4 mean different budgets. Your CI can tell them apart without parsing logs.

Second, the journal survives the crash. Resume by reading the last line:

tail -n 1 steps.jsonl | jq -r '.step'
Enter fullscreen mode Exit fullscreen mode

Now a dead loop is a data point instead of a mystery.

Decision table: where the loop should run

Constraint Local laptop Free server option Paid box
Loop under 15 minutes, you watch it Good Overkill Waste
Laptop sleeps or closes Fails Good Good
Needs pinned CPU or RAM guarantees No Verify current terms Yes
Handles long-lived secrets Yes Treat as shared, rotate keys Yes
Needs a specific accelerator Maybe Verify current terms Likely

The middle column is a trade, not a free lunch. You trade control for uptime.

Who should not use this approach

  • Teams that need hardware guarantees, SLAs, or audit trails.
  • Anyone storing production credentials on a shared box.
  • Loops whose failure mode is silent data corruption.

If your loop writes to a real database, a budget gate is not enough. Add idempotency keys and transactions first.

The one-line takeaway

The loop did not fail at step 41. The budget was missing from step 1.

Add the gate, write the journal, and the next crash becomes a diff instead of a debate. If you try one thing from this post, try the distinct exit codes. They turn "it broke" into a number you can actually act on.

Top comments (1)

Collapse
 
jo-do profile image
Jo Do

Budgeting four dimensions at once is the correct model, and "a crash at step 41 costs whatever steps 1 through 40 produced" is the line to quote. The disk row is underrated: if the loop journals its state to disk as it goes, a budget kill becomes a pause instead of a loss - you resume with a fresh context and replay only the summary. Loops that only write prose are the ones that die expensively.