Warming up the neural circuits...
By the end of this chapter you will:
An LLM that only chats is a librarian who only reads aloud. An LLM with agent capabilities is a librarian who walks to the shelves, pulls the book, cross-references three other volumes, updates the catalog, and emails you a summary — all without you giving a second instruction. Tool use, function calling, and planning are what transform a text generator into a system that actually does things.
A Michelin-starred kitchen runs on a brigade system. The head chef (the "agent") doesn't cook every dish. She plans the menu (planning), calls out orders to the sauce chef, grill chef, and pastry chef (tool calling), tastes every dish before it leaves the pass (evaluation), and adjusts the next round based on what's running low or what a VIP guest requested (memory + replanning).
A single LLM chat completion is asking the head chef "what's a good sauce for salmon?" — you get an answer, then the conversation ends. An agent is the entire brigade: the LLM decides which station to call, calls the right chef with the right parameters, checks the result, decides whether to plate it or send it back, and remembers that table 7 is allergic to dairy for the of the evening.
The difference between "ChatGPT told me how to query my database" and "an agent queried my database, analyzed the results, and posted a Slack summary at 9 AM every Monday" is the difference between a cookbook and a kitchen.
Every AI agent, from a 50-line script to Devin, follows the same fundamental loop. Understanding this loop is the difference between building something that works once and something that works at 3 AM without human intervention.
┌─────────────────────────────────────────────────────┐
│ THE AGENT LOOP │
│ │
│ 1. USER INPUT → "What's the top-selling product │
│ in our European warehouses this quarter?" │
│ │ │
│ ▼ │
│ 2. LLM DECIDES → "I need to call get_sales_data() │
│ with region='EU' and period='Q2_2026'" │
│ │ │
│ ▼ │
│ 3. TOOL EXECUTION → Your function runs, returns │
│ [{product: "Widget X", revenue: 450000}, ...] │
│ │ │
│ ▼ │
│ 4. LLM EVALUATES → "Got 12 rows. Widget X is #1. │
│ Should I also check inventory levels?" │
│ │ │
│ ▼ │
│ 5. MORE TOOLS? → Yes → call check_inventory() │
│ → No → format final answer │
│ │ │
│ ▼ │
│ 6. FINAL RESPONSE → Natural language answer + │
│ optional structured data │
└─────────────────────────────────────────────────────┘Here's the minimal agent loop — this is the engine that powers everything from customer support bots to autonomous coding agents:
// === agent-loop.ts ===
// The core loop: 60 lines that turn an LLM into an agent
interface Tool {
name: string;
description: string;
parameters: Record<string, unknown>;
execute: (args: Record
Function calling is the mechanism that lets an LLM say "I need to run this specific function with these specific arguments" instead of just "you should probably check the database." The LLM doesn't execute code — it outputs structured JSON that your application executes.
// === tool-definitions.ts ===
// Define tools with JSON Schema — the LLM uses these to decide what to call
const tools: Tool[] = [
{
name: "search_knowledge_base",
description:
"Search the internal knowledge base for articles matching a query. " +
"Use this when users ask about company policies, product docs, or historical decisions.",
parameters: {
type
The LLM decides which tool to call based entirely on the tool's description field. A vague description like "searches stuff" will cause the LLM to either never call the tool or call it for everything. Write descriptions as if you're explaining the tool to a colleague who's never seen your codebase: when to use it, what it returns, and what the parameters mean. Bad tool descriptions are the #1 cause of agent failures in production.
The ReAct (Reasoning + Acting) pattern interleaves thought with action. Instead of the LLM planning everything upfront, it thinks about what it knows, decides on one action, observes the result, then thinks again. This is the pattern behind most production agents.
// === react-pattern.ts ===
// The ReAct system prompt — teaches the LLM to think-then-act
const REACT_SYSTEM_PROMPT = `You are an AI assistant that solves problems step by step.
For every user request, follow this pattern:
1. THOUGHT: Analyze what you know and what you need to find out.
Write your reasoning as if thinking aloud.
2. ACTION: If you need information, call the appropriate tool.
If you have enough information, provide the final answer.
3. OBSERVATION: After a tool returns results, evaluate them.
Are they sufficient? Is something missing? Should you try a different approach?
IMPORTANT RULES:
- Never assume information you haven't retrieved via a tool.
- If a tool returns an error, try an alternative approach before giving up.
- If you've tried 3 different approaches and all failed, explain what you tried and ask the user for guidance.
- Cite sources when using tool results.`;Chain of Thought (CoT) is the simplest of planning — the LLM writes out its reasoning steps before answering. You enable it with a system prompt that says "think step by step" or by showing examples of step-by-step reasoning. For agents, CoT is essential: the LLM needs to explain WHY it's calling a tool, not just call it.
Tree of Thoughts (ToT) extends CoT by exploring multiple reasoning paths simultaneously. The LLM generates several possible next steps, evaluates each, and pursues the most promising one. This is computationally expensive (you're making multiple LLM calls per step) but dramatically improves accuracy on tasks that require exploration — debugging complex code, solving math problems, or planning multi-step workflows.
// === tree-of-thoughts.ts ===
// Simplified ToT: generate N candidate next steps, score them, pick the best
async function treeOfThoughtsStep(
problem: string,
candidates: number = 3
): Promise<string> {
// Step 1: Generate candidates
const brainstormPrompt
Single agents hit a ceiling. A coding agent that also manages your calendar and answers support tickets becomes confused — the system prompt grows to 5000 tokens, tool definitions balloon, and the agent starts mixing contexts. Multi-agent architectures solve this by giving each agent a focused role.
// === multi-agent-orchestrator.ts ===
// A coordinator agent delegates to specialist agents
interface SpecialistAgent {
name: string;
description: string;
capabilities: string[];
run: (input: string) => Promise
Multi-agent architectures shine when: (1) each role needs different tools — a code reviewer needs linter access, a doc writer needs wiki access; (2) prompts would conflict — "be thorough and critical" vs "be helpful and encouraging"; (3) you need parallel execution — code review AND test generation can run simultaneously. For simple workflows, a single agent with well-defined tools is faster and cheaper.
Cognition's Devin is the most prominent AI coding agent. It doesn't just suggest code — it plans features, writes code across multiple files, runs terminal commands, debugs test failures, and iterates until tests pass. The architecture reveals patterns every backend engineer should understand.
The execution environment is a sandbox. Devin doesn't run commands on your machine. It spins up a container with a shell, code editor, and browser — every tool (write_file, run_test, search_web, git_commit) operates inside this sandbox. This is the only safe way to give an LLM shell access. The sandbox has network restrictions, filesystem quotas, and a time limit per session. If Devin runs rm -rf /, it nukes its own container — not your laptop.
Planning before coding. Devin writes a plan before writing code. This isn't a gimmick — it's the ReAct pattern at scale. The planner agent breaks the feature into subtasks, the coder agent implements each subtask, and the reviewer agent checks the output. Failed tests loop back to the coder. This three-agent loop can run for 20-30 iterations on a complex feature.
The same pattern works at smaller scale. CrewAI and AutoGen are open-source frameworks that implement multi-agent orchestration. CrewAI defines agents with roles, goals, and tools — then runs them sequentially or in parallel. AutoGen (from Microsoft) adds conversation patterns: two agents can debate a solution, or a manager agent can delegate to worker agents. These frameworks aren't magic — they're the agent loop with better abstractions, retry logic, and conversation management.
| Mistake | Why it's wrong | What to do instead |
|---|---|---|
No maxIterations limit on the agent loop | An LLM that keeps calling tools forever burns through your budget in minutes. A single agent loop with a buggy tool can generate $50+ in API costs before anyone notices | Always set maxIterations (start with 10). Add a budget tracker that throws if total cost exceeds $X per request |
| Giving the agent tools it doesn't need | The LLM may call the wrong tool, confuse parameters, or get distracted by irrelevant options. More tools = more decisions = more chances to be wrong | Give each agent exactly the tools it needs for its role. A code reviewer doesn't need Slack access. A notification agent doesn't need database write access |
| Blindly executing tool results without | The LLM might hallucinate user IDs, queries, or file paths. Executing DELETE FROM users WHERE id=99999 because the LLM guessed an ID will corrupt your database | Validate all tool outputs before acting on them. Check that IDs exist, file paths are within allowed directories, and SQL queries pass a safety check (no DROP, no DELETE without WHERE) |
| Using the same system prompt for agents and chatbots | "You are a helpful assistant" doesn't teach the LLM how to use tools, when to stop, or how to handle failures. The agent will either never call tools or call them endlessly | Write agent-specific system prompts that teach the ReAct pattern, define stopping criteria, and include error-handling instructions |
| Not logging tool calls and their results | When an agent produces a wrong answer, you can't debug it without knowing which tools it called, with what arguments, and what they returned | Log every tool call with: timestamp, tool name, arguments, result (or error), and duration. Store in structured format (JSON) for analysis |
| Running agents synchronously in request handlers | An agent loop with 5 tool calls can take 15-30 seconds. Your HTTP request will time out, and the user gets a 504 error | Use a (BullMQ, SQS) for long-running agent tasks. Return a task ID immediately, let the user poll or connect via for results |
| Skipping tool result truncation |
search_kb_v2) and keep old versions alive for a deprecation window while in-flight conversations drain.tool.execute() call in a timeout . If the tool doesn't respond in N seconds, return an error to the LLM so it can try an alternative approach.Promise.all(). This cuts iteration time from 3× tool_latency to max(tool_latency).get_user(userId=42) three times in one conversation, the second and third calls are wasted. Cache tool results in a Map keyed by toolName + JSON.stringify(args). Invalidate the cache when a write operation changes the underlying data.Build a single-tool agent: Write an agent that has exactly one tool — get_current_weather(city: string). The tool should return hardcoded weather data for 5 cities. Test that the agent: (a) calls the tool when asked about weather, (b) returns a natural language response incorporating the tool's data, and (c) doesn't call the tool when asked a non-weather question like "who won the world cup?"
Add iteration limits: Take the single-tool agent from exercise 1 and deliberately create a scenario where the tool returns confusing data (e.g., { error: "ambiguous city name" }). Verify that your agent: (a) retries at most N times (where N is your maxIterations), (b) eventually returns a graceful failure message to the user, and (c) logs each failed attempt with the tool name and error.
Build a ReAct agent with 3 tools: Create an agent with these tools: search_docs(query), list_available_apis(), and get_api_schema(apiName). The agent should help developers find the right API for their use case. Test with: "I need to send emails programmatically — what API should I use and what parameters does it need?" The agent should: search docs for "email", list available APIs, find the email API, retrieve its schema, and present the answer.
Implement multi-agent delegation: Build a coordinator agent that delegates to two specialist agents: a "translator" agent (detects language, translates to English) and a "summarizer" agent (takes English text and returns a 3-bullet summary). Test with a French paragraph. The coordinator should route French text → translator → summarizer → final output. Log the handoff between agents so you can trace the full pipeline.
Build an agent with a budget tracker: Extend any agent from the exercises above with a cost-tracking . Track: tokens consumed per LLM call, total tokens per session, estimated cost (using current OpenAI/Anthropic pricing), and tool execution time. Add a configurable budget limit — when the session exceeds the limit, the agent should gracefully stop and return what it has so far. Test with a budget of $0.05 and verify the agent stops at the limit.
Implement a human-in-the-loop approval flow: Build an agent that can propose database schema changes. When the agent wants to execute ALTER TABLE, it should not run the command. Instead, it should: (a) output the proposed SQL, (b) explain what the change does and why it's needed, (c) wait for a "approved" or "rejected" response (simulated via a callback function or a mock HTTP endpoint), and (d) only execute on approval. The approval mechanism should have a 5-minute timeout — if no response, the agent should abandon the change and explain why.
Q: What's the difference between an LLM and an AI agent? A: An LLM (Large Language Model) takes text input and produces text output — it's a completion engine. An AI agent wraps an LLM in a loop that can: call external tools (APIs, databases, file systems), maintain memory across multiple turns, plan multi-step workflows, and evaluate its own outputs. An LLM tells you HOW to query a database. An agent queries the database, analyzes the result, and tells you what it found. The agent loop (decide → act → observe → decide) is what separates them.
Q: What is function calling in the context of LLMs? A: Function calling (also called tool use) is a capability where an LLM outputs structured JSON specifying which function to call and with what arguments, instead of (or in addition to) natural language text. The LLM doesn't execute the function — your application reads the JSON, executes the function, and feeds the result back to the LLM. The LLM uses the function's name, description, and parameter schema (provided in the API request) to decide when and how to call it. It's the bridge between "the AI thinks something" and "the AI does something."
Q: Why do you need a maxIterations limit in an agent loop?
A: Without a limit, an agent can enter an infinite loop if: a tool keeps returning unexpected results, the LLM gets confused and retries the same failing approach, or two tools create a dependency cycle (tool A's output makes the LLM call tool B, whose output makes it call tool A again). Each iteration costs money (LLM API call) and time. A maxIterations limit (typically 5-15) acts as a circuit breaker — when hit, the agent summarizes what it found and stops. In production, you should also track cost and abort if the session exceeds a dollar threshold.
Q: You're building a customer support agent that can refund orders, cancel subscriptions, and modify account details. How do you prevent the agent from taking incorrect destructive actions?
A: There are four layers of defense. First, tool design: destructive tools should require explicit confirmation parameters (e.g., confirmed: boolean that must be true) so the LLM can't accidentally trigger them. Second, human-in-the-loop: any tool that modifies production data, moves money, or sends customer-facing communications should queue the action for human approval rather than executing immediately. Third, validation at the tool level: before executing, validate that the order ID exists, the refund amount matches the original payment, and the user has permission to perform the action. Fourth, audit logging: every destructive action should log who requested it (end user + agent session ID), what was proposed, who approved it, and the final result. These logs are critical for both debugging and compliance. The principle: the LLM proposes, the application validates, the human approves.
Q: How would you design a multi-agent system where agents need to share intermediate results without blowing up the context window?
A: Don't pass raw outputs between agents — pass summaries and structured data. Implement a shared "blackboard" (a key-value store like Redis) where agents write their findings in a structured format. Each agent reads only the keys relevant to its task. For example, the "code reviewer" agent writes { file: "auth.ts", issues: [...], score: 7 } to the blackboard. The "test generator" agent reads only the issues key to generate targeted tests. The coordinator maintains a lightweight context — just the task description and references to blackboard keys. This keeps each agent's context window focused on its specific job. Additionally, implement context window monitoring: if an agent's accumulated messages approach the limit, summarize older messages before adding new ones.
Q: An agent in production is behaving inconsistently — sometimes it's brilliant, sometimes it calls the wrong tools or produces garbage output. How do you systematically debug this? A: Start with observability. You need structured logs for every agent session containing: the full message history (system prompt + user messages + assistant responses + tool calls + tool results), which model was used with what parameters, and the final output. Reproduce the failing session by replaying the exact message history — this tells you whether the issue is deterministic (same input → same bad output) or stochastic (temperature-related variance). If deterministic, inspect the tool descriptions and system prompt: is there ambiguity that could cause the LLM to pick the wrong tool? If stochastic, lower the temperature and add more explicit instructions in the tool descriptions. Next, implement evaluation: for each agent task type, maintain a set of 20-50 test cases with expected tool calls and outputs. Run these evals on every prompt or tool description change. Finally, consider a "grader" agent — a separate, cheaper LLM that reviews the main agent's tool calls and flags suspicious ones (e.g., "agent called delete_user for a request about changing email").
AI agents transform LLMs from text generators into systems that act. The agent loop — decide, call tools, observe results, decide again — is the universal pattern. Function calling bridges language and action by letting the LLM output structured JSON that your application executes. The ReAct pattern interleaves reasoning with action, producing more reliable results than planning everything upfront. Multi-agent architectures split complex tasks across specialist agents with focused tools and prompts. The two hard problems in production agents are reliability (making the right tool calls consistently) and safety (preventing destructive actions). Solve reliability with great tool descriptions, structured logging, and eval suites. Solve safety with tool-level validation, human-in-the-loop for destructive actions, and per-agent tool restrictions. Agents are powerful but expensive — every iteration costs money, so budget tracking and iteration limits are not optional.
maxIterations is mandatory. Without it, buggy tools or confusing results cause infinite loops that burn money.What is the core loop that every AI agent follows? A) Ask user → respond → ask user → respond B) Decide → call tools → observe results → decide again C) Train → validate → test → deploy D) Receive request → query database → format response → send
In function calling, who executes the function — the LLM or your application code? A) The LLM executes it in a sandbox B) Your application code executes it — the LLM only outputs which function to call and with what arguments C) A cloud function provided by OpenAI D) The user's browser
Why is maxIterations essential in an agent loop?
A) It's required by the OpenAI API B) To prevent infinite loops that burn API budget — an agent with buggy tools can call them forever C) To comply with GDPR D) To make the agent faster
What does the ReAct pattern stand for? A) React + Act — a UI framework for agents B) Reasoning + Acting — interleave thought with tool calls C) Reactive + Active — two agent modes D) Read + Act — ingest then execute
When should you use multi-agent architecture instead of a single agent? A) Always — more agents are always better B) Only when the task fits in one prompt C) When different roles need different tools, prompts would conflict, or tasks can run in parallel D) Only for frontend applications
What's the most important defense for agents that can perform destructive actions? A) A longer system prompt B) Human-in-the-loop approval — the agent proposes, a human approves before execution C) Faster API calls D) Using a larger model
How do you reduce per-request cost in an agent system? A) Use the most expensive model for everything for best quality B) Use cheaper/faster models for planning/decision steps, expensive models only for final synthesis C) Skip tool validation D) Remove all logging
| A database query returning 50,000 rows will blow past the LLM's context window. The LLM sees truncated JSON, gets confused, and hallucinates the rest |
| Truncate tool results to a reasonable size (e.g., first 50 rows + count of remaining rows). Include metadata: "Returned 50 of 50,000 matching records" |