AI Agents: Beyond Chat
How the reason-act loop works, why tool descriptions matter more than tool names, and where the approval boundary has to sit.
What you'll be able to do
- Trace a single iteration of an agent loop and name each part
- Write a tool description the model can actually use correctly
- Decide which actions must sit behind human approval
Assumes: Lesson 2 β Prompt Engineering Techniques
The difference in one line
A chatbot produces text. An agent produces text and actions β it can call tools, read the results, and decide what to do next.
That loop is the whole idea. Everything else is engineering around it.
The loop
history = [user_request]
while True:
response = model(history, tools)
if response.is_final_answer:
return response.text
result = execute(response.tool_call) # your code, not the model's
history.append(response, result)
Each turn: the model reasons about the state, acts by requesting a tool, observes the returned result, and repeats. This is the ReAct pattern β reason and act β and it underpins essentially every agent framework.
The critical detail, easy to miss and important for everything below:
The model never executes anything. It emits a structured request β a tool name and arguments. Your code decides whether to run it.
That gap is where every safety control lives.
A worked example
βFind my most recent order and tell me if it can still be returned.β
| Step | Model reasons | Tool call | Result |
|---|---|---|---|
| 1 | Need the userβs orders | get_orders(user_id, limit=1) | Order #4521, shipped 3 March |
| 2 | Need the returns policy | get_policy("returns") | 30 days from shipping |
| 3 | Need todayβs date | current_date() | 2026-08-29 |
| 4 | 179 days elapsed β outside window | β | Final answer |
Note what did not happen. The agent did not process a return. It reported a finding and stopped. That boundary was a design decision, and it is the right one.
Tools: descriptions do the work
A tool definition is a name, a description, and a parameter schema. Newcomers polish the name. The model reads the description.
Weak:
{ "name": "search", "description": "Searches" }
Usable:
{
"name": "search_products",
"description": "Search the product catalogue by keyword. Returns up to 5 products with name, price in GBP, and stock status. Use for questions about what is available or what something costs. Does NOT return order history β use get_orders for that.",
"parameters": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "Keywords: product name, category, or description terms" },
"max_price": { "type": "number", "description": "Optional ceiling in GBP" }
},
"required": ["query"]
}
}
The second version states what it returns, when to reach for it, and what it is not for. That last clause prevents a whole class of wrong-tool errors.
Three practical rules:
- Validate arguments before executing. Models hallucinate parameter values. Never pass them straight into a query.
- Return errors as usable text.
"No product found matching 'blue widget'"gives the model something to act on. A stack trace does not. - Keep the toolset small. Selection accuracy falls as the list grows. Ten focused tools beat forty overlapping ones.
Connecting tools has begun to standardise β the Model Context Protocol is the emerging common interface, so a tool written once can be exposed to different assistants. Worth knowing the name exists.
Memory
Agents need three distinct kinds, and conflating them causes bugs:
- Working memory β the current taskβs history. Lives in the context window; disappears at the end.
- Conversation memory β this sessionβs dialogue, usually summarised as it grows.
- Long-term memory β preferences and facts persisted in a database across sessions, retrieved when relevant.
Only the first is automatic. The other two are systems you build.
The approval boundary
This is the part that matters most, and where the earlier version of this course got it wrong.
The test is reversibility, not risk of error:
| Reversible β let it run | Irreversible β require approval |
|---|---|
| Search, read, look up | Send an email or message |
| Calculate, compare, summarise | Take a payment or issue a refund |
| Draft something for review | Delete or overwrite records |
| Query a read-only replica | Book, cancel, or commit an order |
A retrieval that returns the wrong row costs one retry. A message sent to the wrong customer cannot be recalled. That asymmetry, not how clever the model is, decides where the gate goes.
So: an agent that finds you the cheapest flight and presents it is a good design. An agent that books it unprompted is a bad one β not because it will often be wrong, but because when it is, nothing can be done.
Three more structural guards, none optional:
- Iteration cap. Loops happen. Bound them.
- Least privilege. Give each agent only the tools its job needs. A support agent has no business holding a database-admin tool.
- Log every call. When something goes wrong you need the sequence of tool calls and results, or you are guessing.
Common failure modes
- Repeating a failing call β expecting a different result. Cap iterations; return descriptive errors.
- Invented arguments β a plausible-looking ID that does not exist. Validate everything.
- Context exhaustion β long runs fill the window and early instructions fall out.
- Error cascade β one bad result poisons every subsequent step.
- Scope creep β the agent finds a way to do something it was never meant to. Least privilege is the answer.
Try this: Take a task you would want an agent to do and list every tool it would need. Mark each one reversible or irreversible. The irreversible ones are your approval gates β and if that list is long, the task may not be a good candidate for automation yet.
Go deeper
Quick Quiz
Test what you just learned. Pick the best answer for each question.
Q1 What does the model actually produce when it 'uses a tool'?
Q2 Which change most improves an agent's tool selection?
Q3 Which action belongs behind human approval?
Q4 An agent calls the same failing tool six times in a row. What is the fix?