The agent loop in code
An agent is a loop: the model looks, decides, acts, and looks again. Write that loop yourself in about 30 lines of Python, with stop conditions and a step limit.
In 60 seconds
The agent loop in code
An agent is a loop: the model looks, decides, acts, and looks again. Write that loop yourself in about 30 lines of Python, with stop conditions and a step limit.
The three steps, again and again
- 1
Observe
Collect what the agent knows now: the task, earlier messages and the latest tool result. - 2
Think
Send all of that to the model. It answers with either a tool call or a final answer. - 3
Act
If it asked for a tool, your code runs the tool and adds the result to the messages. - 4
Check the stop conditions
Final answer? Step limit reached? Error that cannot be fixed? If not, go back to Observe.
A complete loop you can run offline
call_model() with a call to your provider's chat API with tools enabled, and turn its reply into the same small dictionary.def get_weather(city: str) -> str:
fake = {"Chennai": "34 C and humid", "Shimla": "12 C and clear"}
return fake.get(city, "error: unknown city " + city)
TOOLS = {"get_weather": get_weather}
def call_model(messages: list) -> dict:
# Placeholder model. Plug in your provider here and convert its reply to
# {"tool": name, "args": {...}} or {"answer": text}.
last = messages[-1]
if last["role"] == "user":
return {"tool": "get_weather", "args": {"city": "Chennai"}}
return {"answer": "It is " + last["content"] + " in Chennai. Carry water."}
def run_agent(task: str, max_steps: int = 5) -> str:
messages = [{"role": "user", "content": task}]
for step in range(max_steps):
reply = call_model(messages) # think
if "answer" in reply: # stop: model is done
return reply["answer"]
name, args = reply.get("tool"), reply.get("args", {})
if name not in TOOLS:
result = "error: there is no tool called " + str(name)
else:
try:
result = TOOLS[name](**args) # act
except Exception as err:
result = "error: " + str(err)
messages.append({"role": "tool", "content": result}) # observe
return "Stopped: reached the step limit without an answer."
print(run_agent("Do I need to carry water in Chennai today?"))Stop conditions: the part people forget
| Stop condition | Why you need it |
|---|---|
| The model gives a final answer | The normal, happy ending. |
| Step limit (max steps) | Stops runaway loops. Pick a number from real tests, not a guess. |
| Budget limit (tokens, money or time) | Stops one task from using far more than it should. |
| Repeated identical tool call | If the same call with the same arguments fails three times, it will not work the fourth time. |
| A risky action needs approval | Pause and ask a person instead of acting (see Keeping a human in the loop). |
Errors go back to the model
- No step limit:
while True - A tool exception crashes the whole run
- The model's tool name is trusted and called directly
- Stops silently and returns nothing
- A step limit and a budget limit
- Tool errors become readable tool results
- Only names in your
TOOLStable can run - Every stop has a clear reason
tool messages, never as system instructions. See Indirect injection: the invisible note.Watch and read more
Lab
A tiny agent that runs offline with a fake model and a fake train-status tool, with three stop conditions.
The problem
Build a small agent that answers questions like "Is train 12345 running late?" without any API key.
- Copy the starter. It has a fake
train_statustool and a fake model. - Finish
run_agent: call the model, run the tool if asked, add the result as atoolmessage, and return the final answer. - Add three stop conditions: final answer,
max_steps, and "the same tool call with the same arguments happened 3 times". - Test three cases: a known train, an unknown train (the tool returns an error), and a fake model that loops forever. Print the reason each run stopped.
Practice lab
Edit the code and run it. Python runs in your browser — nothing is sent to our server.
No output. Add print(...) to see values.
You are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1Your step limit is 10, but one step can use very different amounts of tokens. Why is a step limit alone not enough to control cost?Reveal
One step can be a short call, and another can include a huge tool result (a long web page or file) that fills the context. Later steps then resend all that text. So cost grows with context size, not only with step count. Add a token or money budget per task and trim or summarise big tool results.
Q2Why is "the same call three times" a better loop detector than "the same tool three times"?Reveal
Calling one tool several times with different arguments is often normal work, for example checking three trains. Calling it with identical arguments and getting the same error means the agent is stuck. Comparing the full call avoids stopping good runs while still catching loops.
Questions people ask
Do I need a framework to build an agent?
No. A basic agent is a loop, a model call and a table of tools, as you saw. Frameworks add useful things such as tracing, retries and many ready-made tools. Write the loop yourself once, so you understand what the framework does for you.
How do real APIs return a tool call?
Most chat APIs with tool or function calling return a structured part in the reply: a tool name and arguments as JSON, often with an id. You send the tool result back with the same id. The exact format differs by provider, so check your provider's current docs and convert it to a simple shape like the dictionary above.
What is a good max_steps value?
It depends on the task. Run your agent on realistic tasks, see how many steps successful runs take, and set the limit a little above that. A simple lookup might need 3 to 5 steps; a long research task may need more. Also add a budget limit, because steps can differ a lot in cost.
Can the model call a tool that is not in my list?
It can write any name, including made-up ones. That is why your code checks the name against your own table and returns an error for anything else. The model never runs code directly.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.