Agents
An LLM in a
whileloop with tools. Everything you've heard about "agentic AI" is built on this three-line idea.
What an agent actually does
An agent is a model that doesn't just answer once — it decides what to do, does it (via tools), looks at what happened, and decides what to do next. That cycle continues until the task is finished or you stop it. The model is steering. Your code provides the hands.
That single rhythm — think, act, observe, repeat — is what people mean when they say "agentic AI." It's also why agents can do open-ended work (debug a build, file an expense, navigate a website) that a single prompt-and-response can't.
That's the whole concept. Below: the loop in code, when not to use an agent, and the failure modes that hit production hard.
When you'd reach for it
When the steps can't be planned in advance and the model genuinely has to react to what it finds:
- Coding assistants that edit files, run tests, see failures, iterate (Cursor, Claude Code).
- Research assistants that browse, read, follow links, summarise.
- Customer-support agents that look up account state, try a fix, check the result.
- Anything resembling exploration where a fixed pipeline would short-circuit too early.
You wouldn't reach for an agent when the workflow is known. A scripted three-step pipeline is simpler, cheaper, and easier to debug than a wandering loop. Anthropic's Building Effective Agents makes this explicit: most "agent" use cases shouldn't be agents at all.
How it's actually built
The core loop, in pseudo-code:
while not done:
response = model.generate(messages, tools)
if response.tool_calls:
results = run_tools(response.tool_calls)
messages.append(model_request(response))
messages.append(tool_results(results))
else:
return response.text
done = True
The model decides which tool to call. You run it. You hand back the result. The model decides what's next. The loop ends when the model produces a text answer (no more tool calls), hits a max-step limit, or the user interrupts.
That's the entire architectural innovation. Every "agent framework" — LangGraph, AutoGen, CrewAI, OpenAI Assistants, Claude Code, Cursor's agent mode — is some refinement of that loop.
Where it bites in real life
Patterns that work
- Plan-then-execute — model writes a plan in step 1, executes it in subsequent steps.
- Orchestrator + workers — a "lead" agent dispatches sub-tasks to specialists, synthesises their structured outputs. (Advanced track.)
- Reflection loops — after a candidate answer, run a "critic" pass, then a revise pass. Cheap quality boost.
- Bounded recursion — caps on steps, output, and per-tool budget.
Common failure modes
| Failure | Cause | Mitigation |
|---|---|---|
| Infinite loops | No progress check | Max steps; detect repeated identical tool calls |
| Cost explosion | Long contexts × many turns | Summarise history; use prompt caching; cap tool budget |
| Wrong tool chosen | Vague descriptions | Improve descriptions; add few-shot in system prompt |
| Brittle to errors | Tool throws → agent gives up | Return structured errors with hints; allow N retries |
| Drifts from goal on long tasks | Lost in conversation noise | Periodic re-grounding: "Your goal is still X" |
Under the hood (optional)
A real-but-minimal agent harness in Python: a for loop bounded by MAX_STEPS, asking the model, running its tool calls, feeding results back. About 25 lines. Skip if you don't code — that one sentence covers the entire agent framework concept.
›Show example code (Python, ~25 lines)click to expand
MAX_STEPS = 10
def agent(user_goal: str, tools, model="claude-sonnet-4-6"):
messages = [{"role": "user", "content": user_goal}]
for step in range(MAX_STEPS):
resp = client.messages.create(
model=model, tools=tools, messages=messages, max_tokens=2048
)
messages.append({"role": "assistant", "content": resp.content})
tool_uses = [b for b in resp.content if b.type == "tool_use"]
if not tool_uses:
return final_text(resp)
results = []
for use in tool_uses:
try:
output = registry[use.name](**use.input)
except Exception as e:
output = {"error": str(e)}
results.append({
"type": "tool_result",
"tool_use_id": use.id,
"content": str(output),
})
messages.append({"role": "user", "content": results})
raise RuntimeError(f"Agent didn't finish in {MAX_STEPS} steps")That harness, plus a thoughtful set of tools, is most of what an "agent framework" gives you. Frameworks add traces, retries, parallel tool execution, persistence, and ergonomics — useful, but optional.
Check your understanding
- 1. Mechanically, what is an agent?
- 2. Anthropic's 'Building Effective Agents' main point about when NOT to use agents:
- 3. Which guardrail should every agent harness have?
Found this useful? Share it with someone learning AI.
Further reading
- Building Effective Agents (Anthropic) — the seminal piece on when and how.
- ReAct: Synergizing Reasoning and Acting (Yao et al. 2022) — the original paper.
- Lilian Weng — LLM-powered Autonomous Agents — the canonical survey.
- LangGraph docs — if you decide you need a framework.