The agent loop is the thing that turns "LLM answers a question" into "LLM gets something done." It's also the thing that turns $0.002 in tokens into $40 in tokens when you look away. Most of agent engineering is designing the loop so it fails gracefully instead of expensively.
This page is a failure-mode catalogue. For the shape of the loop itself and which pattern (ReAct vs graph vs supervisor) to pick, start at AgenticWorkflowDesign.
A working agent loop is six lines of pseudocode:
state = init(goal)
while not terminal(state):
action = model(state)
result = dispatch(action)
state = append(state, action, result)
return extract_answer(state)
Every production problem comes from one of those six lines doing something slightly different from what you expected.
The loop spans three layers — model, tools, state. Every failure lives in one of them.
Schema drift on tool calls. The model emits {"user_id": "42"} instead of {"user_id": 42}. Frequency: extremely high, especially under context pressure or after summarisation.
{"error": "user_id must be integer, got string '42'"}. The model self-corrects on the next turn 9 times out of 10.Hallucinated tool names. Model calls search_emails when your tool is email_search. Common when the context has been summarised and the tool spec evicted.
{"error": "unknown tool 'search_emails'. Available: [email_search, calendar_get, ...]"}. The model recovers near-perfectly when given the option list.Response truncation at max_tokens. The model is mid-way through a tool call when it hits max_tokens and returns incomplete JSON. You get {"action": "update_record", "args": {"id": 42, "na and then nothing.
max_tokens (e.g. 4096 even for short-reply loops — the tokens are free if unused). Parse failures on truncated JSON should surface with a distinct error code: retry once with higher limit, then escalate.Rate-limit / 429 responses. The model API throttles you.
Retry-After literally. Don't jitter, don't exponential-backoff faster than the header says. Naive exponential retries are how you compound your way into a 30-minute outage when the provider is degraded.Non-determinism at temperature=0. Even at temp 0, outputs can differ between calls — batch size, load, and version drift produce small variances. Your eval comparing "prod output" to "expected output" will fail occasionally for reasons unrelated to your code.
Retry on non-idempotent tools. Agent retries send_email because the first call timed out. Recipient gets two emails.
idempotency_key that the agent generates once per intended operation. The tool's backend dedupes on that key. If the agent can't be trusted to generate stable keys, the orchestrator generates them and substitutes at dispatch time.Slow tool blocking the loop. A fetch_document tool that normally takes 2s takes 40s, and your whole agent stalls. You're also likely blocking other concurrent agents.
{"error": "timed_out after 30s"} to the model. The agent will often pivot — "ok, I'll try a different approach."Tool returns an implicit error. HTTP 200 with {"status": "failed"} in the body. The agent parses the outer success and proceeds on bad data.
{"ok": false, "error": "...", "retryable": bool}. Agents see a consistent shape and you stop seeing "the agent confidently reported success" bugs.Tool spec drift. Backend team adds a required field to create_ticket; your tool spec still says it's optional. Loop hangs on "missing required field."
Silent context overflow. The oldest messages — including the original user goal — get truncated. The agent optimises for whatever it thinks the goal is now, which is "whatever the most recent message said."
goal: slot in the system prompt that your summariser is instructed never to drop. Store the goal with the conversation and reassert it explicitly every N turns.Infinite replan loop. Plan → attempt → fail → replan → attempt same thing → fail → replan. Cost accrues linearly in steps.
Memory saturation with irrelevant history. A 30-step loop ends up with 80% of its context spent on tool responses from step 3.
Stale working memory. The agent noted "user is an admin" in turn 2; in turn 20 it's still acting on that assumption even though the user role field has actually changed.
These don't cause crashes, they cause bills.
Tool-response bloat. A search_docs tool returns 50k tokens of raw HTML on every call.
fetch_full with id=X to see the rest."Verbose model reasoning. Chain-of-thought at every step with no control. You pay for every "Let me think step by step about whether to use tool A or B..." paragraph.
reasoning_effort=low or equivalent and reserve the deeper reasoning mode for "I'm stuck, think harder" moments.Prompt cache miss from rotating system prompt. Your system prompt includes current_time=2026-04-24T15:22:18Z. Cache hit rate: 0%.
You cannot debug what you cannot see. Minimum telemetry per loop:
Without this you'll spend half your debugging time re-running the failing task trying to reproduce what happened.