What an agent actually is
An agent is three things: a loop, a set of tools, and a stopping condition. That is the whole definition. Everything else — planners, routers, memory layers, multi-agent swarms — is a variation on those three parts, and most of the difficulty in building agents comes from people learning the variations before they have internalized the base case.
So before any framework, build the base case once by hand. It takes about twenty lines and it will permanently change how you read everything else.
The three parts
The loop. You call the model. It responds. If the response asks to use a tool, you run the tool and call the model again with the result appended. Repeat. That repetition is the only thing separating an agent from a single completion — an agent is a chat completion in a while loop.
The tools. Functions you expose to the model, described in a schema it can read. The model never executes anything itself. It emits a structured request saying "call read_file with path=config.yaml," and your code decides whether to honor that. The model is a planner that can only speak; your runtime is the only thing with hands.
The stopping condition. The loop needs an exit. Usually that is the model returning a plain text response with no tool call, meaning it believes it is done. But you always need a second exit — a step cap — because "the model believes it is done" is not a guarantee you can build on.
A complete agent, no framework
def run_agent(user_message, tools, max_steps=10):
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
]
for step in range(max_steps):
response = model.create(messages=messages, tools=tool_schemas)
messages.append(response.message)
if not response.tool_calls:
return response.text # natural stop
for call in response.tool_calls:
result = tools[call.name](**call.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": str(result),
})
raise StepLimitExceeded(step) # forced stop
That is a real agent. Give it a read_file and a write_file and it will refactor code. Give it search and fetch and it will research. The capability is entirely in the tools; the loop never changes.
The thing to notice
messages is rebuilt and re-sent on every single iteration. The model is not accumulating anything on its side. That one detail explains most of what happens next — it gets its own page in The model is stateless.
What frameworks add, and what they hide
Frameworks are worth using. But it is worth knowing what you are buying, because the pitch usually obscures how little of it is essential.
What they genuinely add: retry and backoff, schema generation from type hints, streaming, tracing, provider abstraction, and conversation persistence. That is real plumbing you would otherwise write.
What they hide, and what hurts later: the exact bytes being sent to the model. Nearly every hard agent bug is a context bug — something is in the message array that should not be, or something is missing that should be there. If you cannot print the final payload, you cannot debug it. Before you adopt any framework, find the flag that dumps the raw request. If it does not have one, that is a serious mark against it.
When you do not need an agent
The loop exists to let the model decide what to do next based on what it just learned. If you already know the sequence of steps, the loop is pure cost — you are paying per-token for a control flow you could have written in Python, and accepting non-determinism you did not need.
- Fixed sequence, known in advance — write a script that makes model calls. Not an agent.
- One transformation, one output — a single call. Not an agent.
- Branching on a value you can compute — an
ifstatement. Not an agent. - The next step depends on the content of the previous result — now you need an agent.
That last line is the actual test. If you cannot draw the flowchart in advance because the branches depend on what gets discovered at runtime, you need a loop. If you can draw it, draw it — it will be faster, cheaper, and debuggable.
Where this goes
The next page covers the single most consequential property of the model you are calling: it has no memory, and every appearance of memory is something your code did. After that, the loop gets pulled apart phase by phase.
What to prompt to go deeper
This page gives you the shape. A model will happily fill in the specifics for your stack — these are the prompts worth pasting.
- Write the twenty-line agent loop above in my language and SDK, with no framework. Print the full request payload on every iteration.
- Here is a task I want to automate: [describe it]. Ask me questions until you can tell me whether it needs an agent loop or just a script.
- Take this agent framework I am considering: [name]. Show me exactly how to dump the raw model request it sends.