Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Reference-grade guide for AI engineers building autonomous and tool-using agents — the plan→act→observe loop and its failure physics, the five budgets every agent must set (iteration, tool-call, token, wall-clock, cost), termination and no-progress detection, runaway prevention (circuit breakers, kill switch), the three guardrail layers (input/output/action), verify-before-acting, bounded sub-agent orchestration, reflection cost, determinism-where-possible, checkpoint/resume, and the canonical f
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 137% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 148% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 153% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 124% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 145% | 0% |
An agent is a loop that lets an LLM choose its own next action. That single property — the model, not your code, decides what happens next — is the source of every reliability problem here. A chatbot that hallucinates wastes one reply; an agent that hallucinates executes the hallucination, then feeds the result back into its own context and decides again. Errors don't stay put. They compound, they loop, and without budgets they burn real money until something external stops them.
This skill is the control system you wrap around that loop. None of it is optional polish: an agent with no iteration cap, no kill switch, and no action gate is a liability the first time a prompt goes sideways. Set every budget. Add every guardrail. Default to stop and report, never silently continue.
The canonical agent loop:
state = init(goal)
while not done(state):
plan = llm.decide(state) # choose next action (tool + args)
result = execute(plan.action) # run the tool
state = observe(state, result) # fold result back into context
# repeat — the model sees its own last result and decides againThree structural failure classes fall straight out of this shape:
0.9^10 ≈ 35% end-to-end. Reliability decays geometrically with loop length. The fix is not a smarter model — it's shorter loops, verification gates, and checkpoints that stop the decay from propagating.Everything below exists to bound, detect, or interrupt one of these three.
A budget is a hard ceiling checked before each action. Exceeding any one halts the loop. Never ship an agent with fewer than all five. They catch different runaway shapes: a tight loop blows the iteration budget, an expensive-tool loop blows the cost budget, a slow-tool hang blows wall-clock.
| Budget | Bounds | Starting value (tune per task) | Trips on | |---|---|---|---| | Iteration / steps | loop count | 15–30 typical task; ≤8 simple; ≤50 hard | tight loops, oscillation | | Tool calls | total + per-tool | ~2× iterations total; per-tool cap (e.g. ≤5 web_search) | one tool spammed | | Token | cumulative in+out | per task (e.g. 200k); separate context-window guard | context bloat, long transcripts | | Wall-clock / timeout | real time, whole run + per tool | run 2–10 min; per-tool 30–60s | hung tool, slow external API | | Cost | cumulative USD | hard cap per run (e.g. $0.50) + daily/account cap | expensive-model or expensive-tool loops |
pythonclass Budget: def __init__(self, max_steps, max_tool_calls, max_tokens, deadline_s, max_usd): self.max_steps, self.steps = max_steps, 0 self.max_tool_calls, self.tool_calls = max_tool_calls, 0 self.max_tokens, self.tokens = max_tokens, 0 self.deadline = time.monotonic() + deadline_s self.max_usd, self.usd = max_usd, 0.0 def check(self): # call BEFORE every action if self.steps >= self.max_steps: return Halt("step budget") if self.tool_calls >= self.max_tool_calls: return Halt("tool budget") if self.tokens >= self.max_tokens: return Halt("token budget") if self.usd >= self.max_usd: return Halt("cost budget") if time.monotonic() >= self.deadline: return Halt("wall-clock") return Ok()
On exhaustion: stop and report. Do not silently continue, do not auto-raise the limit. Return the partial result, the reason, what was tried, and the next concrete step. A budget that auto-extends is not a budget. Surface exhaustion to the caller (and to a human for long-running agents) — it is a signal something is wrong, not a routine event to swallow.
Budget hygiene
web_search or read_file loop blows token budget long before step budget.The loop must be able to stop on purpose, not only on budget exhaustion. Budget exhaustion is the failure exit; these are the success and giving-up exits.
Always emit a terminal reason (success | no_progress | repetition | budget_* | uncertain | error | human_abort). Downstream you need to distinguish "finished" from "gave up" from "ran out of money" — they trigger different handling.
Detection + a way to pull the plug. Three layers: detect, break, kill.
Detect — the "are we making progress?" check, every step
pythondef progress_guard(history, k=3): last = history[-1] # 1. exact repetition: same action+args back-to-back if count_consecutive(history, key=lambda h: (h.tool, h.args)) >= k: return Halt("repetition: identical action ×%d" % k) # 2. error loop: same error class repeated if count_consecutive(history, key=lambda h: h.error_signature) >= k: return Halt("stuck: same error ×%d" % k) # 3. oscillation: A,B,A,B cycle in recent window if has_cycle(history[-6:]): return Halt("oscillation A↔B") # 4. no forward progress: success metric flat for k steps if metric_flat(history, k): return Halt("no progress for %d steps" % k) return Ok()
(tool, args) consecutively. Cheapest, catches the dumbest loop.Circuit breakers. Per-tool failure breaker: after M consecutive failures of a given tool (M≈3), trip it — stop calling that tool, tell the model it's unavailable, let it route around or terminate. Prevents hammering a dead API. Add backoff before any retry; never retry-storm an external service.
Kill switch. An out-of-band stop the loop checks every iteration — env flag, file sentinel, control-channel message, dashboard button. Must be reachable without the agent's cooperation (the agent may be the thing misbehaving). For long-running/background agents this is mandatory: a human needs to stop a runaway in seconds, not wait for a budget to drain.
pythonwhile not done(state): if kill_switch.tripped(): return abort("operator kill switch") if (h := budget.check()).halt: return stop(h.reason, state) if (p := progress_guard(history)).halt: return stop(p.reason, state) ...
Three layers. Each independent; an action can pass input and output checks and still be blocked at the action gate. Layer them — do not collapse into one "is this ok?" LLM call.
Screen user input and tool results before they reach the model — tool output is untrusted input too (a fetched web page can carry injected instructions).
Validate every model output before it's used or shown.
execute() unparsed model JSON.The most important layer — this is where an agent stops being a chatbot. Gate the side effects.
rm, DROP, force-push, overwrite), irreversible (sent email, payment, deploy), and high-cost actions require explicit human approval before execution. Default-deny these.| Risk tier | Examples | Gate | |---|---|---| | Low | read file, search, list, dry-run | auto | | Medium | write in-scope file, in-scope shell, create branch | confirm / log | | High | delete, overwrite, deploy, migrate, send msg, pay, write out-of-scope | human approval |
> Argo models this directly: the Rust kernel's assess() returns Allow / Ask / Block, the TS agent layer cannot bypass it, and Rule Zero — no deletes, overwrites, out-of-scope writes, or secret handling without explicit approval — is enforced on every tool call. That separation (enforced boundary in one layer, orchestration in another) is the pattern: put action guardrails somewhere the agent can't reach to disable them.
For bounded or single-use actions, approval is not a reusable sentence in chat. Model its lifecycle in code:
Prompt text can describe this contract, but enforcement belongs in the action layer and durable state.
The agent must not trust its own tool output, and must not declare success on its own narration.
Evidence before assertion, always. "It passes" with no command output is a guess.
Sub-agents multiply both capability and blast radius. Bound them hard.
Reflection (the agent critiques its own output and retries) raises quality on hard tasks but is not free:
Every decision handed to the LLM is a decision that can go wrong, loop, or cost tokens. Move known logic into deterministic code.
Rule of thumb: the LLM should make the fuzzy decisions; everything downstream of a decision that has a correct answer should be deterministic. Less LLM surface = fewer failure modes, lower cost, reproducible behavior.
Long agents will be interrupted (timeout, crash, kill switch, budget). Make them resumable so an interruption costs minutes, not the whole run.
events.jsonl) or a state store. The transcript in memory is volatile; the durable log is truth.You cannot debug or trust what you cannot see. Instrument the loop.
| Failure mode | Cause | Guard that catches it | |---|---|---| | Runaway loop burning tokens | no iteration/token budget; tight loop | iteration + token budget (§2), repetition detect (§4) | | Hallucinated tool call executed | model invents a tool/args; no validation | schema output guardrail (§5b), allowlist (§5c) | | False-done (declares success, didn't do it) | model narration trusted as terminal | verify-before-done predicate (§6) | | Infinite oscillation (A↔B↔A) | retry without progress check | oscillation/no-progress detect (§4) | | Cost blowup | no cost budget; expensive tool/model loop | cost budget + daily cap (§2), per-tool cap | | Destructive action without approval | no action gate; prompt-only "be careful" | action guardrail / HITL / kernel assess (§5c) | | Prompt injection via tool output | retrieved content treated as instructions | input guardrail on tool results (§5a) | | Hung run | tool blocks forever | wall-clock + per-tool timeout (§2) | | Sub-agent fork bomb | recursive/unbounded spawning | depth cap + budget inheritance (§6) | | Resumed runaway | resume resets budgets to full | persist & restore remaining budget (§10) | | Reflection thrash | self-correction oscillates | cap rounds + progress guard (§8) | | Stuck on dead tool | hammering a failing API | per-tool circuit breaker (§4) | | Authorization replay | crash/retry reuses a consumed approval | atomic consumption marker + durable state (§5d) | | Scope drift after approval | target or mechanism changes before execution | exact binding + immediate revalidation (§5d) |
Do
Don't
The throughline: an agent is only as safe as the budgets and gates around it. Bound it, verify it, and put the action guardrails somewhere the agent can't reach.
Other measured skills in the registry, with their headline benchmark lift.