The problem: an automation that doesn't know which way to go
You have a task that a language model cannot close on its own: answering a customer who asks where their order is requires consulting an order system and, if the order does not exist, searching by another key. You can write this logic as a sequence of fixed steps, or you can let the model choose the next step. The first solution is called a workflow, the second an agent. This series is about the second, and this first article exists to help you decide when it is worth choosing: often the answer is "not yet".
The distinction we use is the one from Anthropic's article Building effective agents: workflows are systems where models and tools are orchestrated through predefined code paths; agents are systems where the model dynamically directs its own process and its use of tools, keeping control over how it accomplishes the task. The same source recommends looking for the simplest possible solution and increasing complexity only when needed, which can mean not building an agentic system at all, because these systems usually trade latency and cost for better task performance.
What is an AI agent?
The definition of an agent comes from Russell and Norvig's textbook, Artificial Intelligence: A Modern Approach: anything that can be viewed as perceiving its environment through sensors and acting upon that environment through actuators. A thermostat fits this definition. What makes an agent based on a language model interesting is that the choice of action is not written in the code, but produced by the model at every turn, starting from what it has seen so far.
A model used directly, by contrast, is a function: it receives a prompt, returns text, and the loop ends. To become an agent it needs a ring around it, that is, a program that executes the actions the model proposes, puts the result back into the context and calls the model again until it decides to stop. The concrete capabilities that this ring makes possible are four.
- Multi-step decisions: breaking the goal down and adapting the plan to intermediate results, as in the case of the order that cannot be found.
- External tools: calling APIs, querying databases, reading and writing files.
- Memory across turns: keeping in the context what has already been observed and, if needed, saving it outside the context.
- Error handling: if a tool fails, the model sees the error and can try again another way.
None of these four items is magic: they are all code that you write around a call to the model. The most delicate is the last one, because an error repeated
without a cap on turns is the fastest way to burn budget; this is why the listing below has an explicit limit, max_turns.
Workflow and agent compared, in a few lines
The listing solves the same problem in two ways. workflow is the fixed path: it looks up the order and reports its status, whatever it finds. agent
is a loop in which model returns an (action, argument) pair, the code runs the corresponding tool and appends the result to observations.
The "model" here is fake: it follows a script for each request, but chooses the action based on how many observations it already has, as a real model would with its context. This
way the example is reproducible and calls no service.
# Fake model: it follows a script, so the example is reproducible.
# In a real system this function would be a call to an LLM.
SCRIPTS = {
"A-1042": [("find_order", "A-1042"),
("answer", "Parcel A-1042 is out for delivery, arriving tomorrow.")],
"A-9999": [("find_order", "A-9999"),
("find_customer", "mario@example.com"),
("answer", "I can't find A-9999; Mario's order is A-1042, out for delivery.")],
}
ORDERS = {"A-1042": "out for delivery"}
CUSTOMERS = {"mario@example.com": ["A-1042"]}
def model(request, observations):
"""Returns the next action, chosen from what it has seen so far."""
return SCRIPTS[request][len(observations)]
TOOLS = {
"find_order": lambda k: ORDERS.get(k, "order does not exist"),
"find_customer": lambda k: CUSTOMERS.get(k, []),
}
def workflow(request):
"""Fixed path, decided by the programmer: always and only these two steps."""
status = TOOLS["find_order"](request)
return f"Status of order {request}: {status}", 1
def agent(request, max_turns=5):
"""The model decides the next step; the code runs it and reports back."""
observations = []
for turn in range(1, max_turns + 1):
action, argument = model(request, observations)
if action == "answer":
return argument, turn - 1 # tool calls made
observations.append(TOOLS[action](argument))
return "Turn limit reached", max_turns
for r in ("A-1042", "A-9999"):
print(r, "| workflow:", workflow(r))
print(r, "| agent :", agent(r))
# Output:
# A-1042 | workflow: ('Status of order A-1042: out for delivery', 1)
# A-1042 | agent : ('Parcel A-1042 is out for delivery, arriving tomorrow.', 1)
# A-9999 | workflow: ('Status of order A-9999: order does not exist', 1)
# A-9999 | agent : ("I can't find A-9999; Mario's order is A-1042, out for delivery.", 2)
Run twice with Python 3.14.4 (python3 -I), the program printed identical output both times. For A-1042 the two approaches coincide, and
the workflow is simpler and cheaper: one tool call. The interesting case is A-9999: the workflow answers "order does not exist" and stops,
while the agent sees that observation, chooses find_customer and arrives at a useful answer, with two tool calls instead of one. The price of flexibility
is precisely that second turn: in a real system every turn is a call to the model, with cost and latency. The line you cannot remove is the for with max_turns:
without it, a script (or a model) that never says "answer" runs forever.
The listing is a teaching sketch: the script is not a model and does not prove that a real model would choose well. It only shows the structure, that is, where the decision lives
(inside model) and where the control lives (inside agent). We build the complete loop, with real models and tools, in the following articles,
starting from Fundamentals: OODA Loop, ReAct and Tool Calling.
Agent, LLM and chatbot: the differences that matter
"Chatbot", "LLM" and "agent" are used as synonyms, but they describe different architectures. The table compares them on the points that change design choices.
| Feature | Rule-based chatbot | Direct LLM | Agent |
|---|---|---|---|
| Who decides the step | Hand-written tree | No step: one answer | The model, at every turn |
| External tools | Only those hard-wired in the tree | None, unless code around it | Yes, chosen by the model |
| Memory | Session state | Only the context window | Context plus an optional external store |
| Cost and latency | Low and predictable | One call | Several calls, number not known in advance |
| Predictability | High | Medium | Lower, a cap on turns is needed |
What matters most in the table is the last row: every bit of extra autonomy is paid for in predictability. The rule-based chatbot is the most boring and the easiest to test; the agent is the most capable and the hardest to reproduce. That is why the choice is not "which is more modern", but "how much task variability justifies the cost of not knowing the steps in advance".
When to use an agent, and when not to
A practical criterion: if you can write down on paper the steps, in order, for all the cases you expect, you have a workflow. An agent makes sense when the steps depend on what is discovered along the way and you cannot list them beforehand. Some cases where this really happens:
- Support on irregular cases: the order that cannot be found, the refund that depends on the customer's history.
- Research with multiple sources: the next question depends on what was found in the previous one.
- Code with verification: write, run the tests, read the error, fix, with a cap on attempts.
- Exploratory data analysis: the next hypothesis comes from the result of the previous query.
The common thread is that the number and order of the steps are not known at the start, and that a mistake midway can be recovered. When this does not hold, the agent's advantage disappears and the costs remain. In the Anthropic article cited above, the example uses are precisely customer support and coding agents, where the result can be verified.
Signs that you don't need an agent
- A known algorithm exists: sorting, validating a format, computing an amount. A normal function is faster, free and deterministic.
- Latency must be minimal: a loop with several calls to the model is not compatible with a response in a few milliseconds.
- The outcome must be reproducible: documents with a fixed format or decisions that must be justified identically on every run.
- The data is sensitive and the controls are missing: without minimal permissions, tracing and human approval, giving tools to a model is a risk, not a feature.
These items do not add up: one is enough to stop you. In particular the last one does not depend on the model but on what you allow it to do, and it is the topic of Security and Safety: Jailbreaking and Guardrails. Before building an agent, always try the poorest case: a single call to the model with the right context, then a workflow, and only after that the loop.
The frameworks: three philosophies, not three races
You don't need a framework to write an agent: the listing above is the proof. Frameworks serve what sits around the loop, that is, saved state, resumption after an
error, human approvals, observability. The versions on the PyPI registry on 2026-11-12: langgraph 1.2.21, crewai 1.15.41, ag2 1.3.1,
autogen-agentchat 0.7.5, agent-framework 1.26.0, openai-agents 0.24.0. Frameworks change fast: always check the version before copying an example.
LangGraph
The official documentation defines it as a low-level orchestration framework and a runtime for long-running, stateful agents: you describe the flow as a graph of shared state,
nodes and edges, mixing deterministic steps and model-driven steps. It focuses on durable execution, streaming and human intervention, and imposes neither prompts
nor agent architecture. The higher-level piece is in the langchain package: the create_agent function, described as a minimal, configurable wrapper around
the model loop. The migration guide to version 1 says that langgraph.prebuilt.create_react_agent used to be recommended and now langchain.agents.create_agent is, and that legacy chains such as
LLMChain and other deprecated features are in the langchain-classic package. We look at it in
LangChain and LangGraph: Agents with AgentExecutor and Tools.
CrewAI
CrewAI describes itself as an open source framework that combines Crews, teams of autonomous agents that collaborate on delegated tasks, with Flows, event-driven flows that manage state and execution. It is the natural choice when the problem divides into roles (researcher, analyst, editor) and you want to declare the roles rather than write the loop. The trade-off is that part of the coordination logic lives inside the framework: more convenient, less visible. It is covered in CrewAI: Collaborative Agent Teams.
AutoGen, AG2 and Microsoft Agent Framework
Here the situation has changed recently and must be read from the sources. The AutoGen documentation now reports that the project is archived and no longer maintained by Microsoft, that it discourages use
in production, and that new users should start from the Microsoft Agent Framework, with a migration guide for those already using AutoGen. The ag2ai/ag2 repository instead notes that the
ag2 package no longer includes the autogen import name or the classic classes, now gathered in "AG2 Classic". If you find a tutorial with import autogen, check which
package and which version it refers to. The topic is covered in
AutoGen and AG2: Emergent Multi-Agent Conversation.
| Criterion | LangGraph | CrewAI | AutoGen / AG2 |
|---|---|---|---|
| Paradigm | State graph | Teams (Crews) and flows (Flows) | Conversation between agents |
| When it fits | Custom flow with resumption and approvals | Problem divided into roles | Check the project status first |
| Price to pay | More structural code | Coordination hidden in the framework | Uncertainty about package and maintenance |
The table is not a ranking. If you start from zero and your flow is sequential, none of the three is needed: the loop in the listing is enough. Frameworks are justified when you find yourself rewriting state saving, resumption and human approval, and want to stop doing it by hand.
Anatomy of an agent: five components
Every agent, with any framework, has five parts. In the listing you can see them all: the model (model), the instructions (the script, which in a real system is the system prompt),
the tools (TOOLS), the memory (observations) and the orchestrator (agent).
- The model chooses the next step. A more capable model makes fewer mistakes, a smaller one costs less: the choice is a compromise, not a hierarchy.
- The system prompt gives role, constraints and format. If it is vague, the behavior is unpredictable; if it is too rigid, the model cannot adapt.
- The tools are functions with a name, a description and a parameter schema. The description is what the model reads to decide when to use them.
- The memory is the current context (short term) plus an optional external store (long term).
- The orchestrator is the loop that executes, accumulates and stops: it holds turn limits, timeouts and error handling.
Of the five parts, the one where mistakes are most frequent is the tool: an ambiguous description makes the model call the wrong function, and no model compensates for a badly written schema. Tool design is covered in Advanced Tool Calling: APIs, Web and Custom Tools, and memory in Memory Systems: Context, History and Knowledge Graphs.
Standards for tools: MCP
The Model Context Protocol (MCP) is, according to the specification, an open protocol for integration between applications based on language models and external data sources and tools. The practical reason is to avoid a different adapter for every application-tool pair: an MCP server exposes tools, and every client that speaks MCP can use them. For an agent it means that some of the tools can come from outside instead of being written by you, with a security consequence not to be overlooked: every MCP server you connect widens what the model can do.
Frequently asked questions
What is the difference between a workflow and an agent?
In a workflow the steps are written in the code and the model only fills in fields; in an agent the model chooses the next step at every turn. The practical consequence is that the workflow is predictable and the agent adapts to irregular cases, with more calls to the model.
Do I need a framework to build an agent?
No: a minimal agent is a loop that calls the model, runs the requested tool and puts the result back into the context, as in the listing in this article. Frameworks help when you need saved state, resumption after an error and human approval.
How much does an agent cost compared to a single call?
Every turn of the loop is a call to the model, and the number of turns is not known in advance: that is why a cap is always needed. The real cost depends on the model and the task, so it has to be measured on your case and cannot be deduced from a general rule.
What you can do now
You can tell a workflow from an agent and recognize in the code where the decision lives. You can use the criterion of listable steps to decide whether you really need an agent, and you know the cost to pay: more calls, less predictability, a cap on turns to always impose. In the next article, Fundamentals: OODA Loop, ReAct and Tool Calling, we open the loop and see how the model proposes an action and how the program executes it.
The rest of the series develops what is only sketched here: orchestrating several agents in production, testing and evaluating agents, production deployment, the cost of tokens, a constructed example of a research assistant and, at the end, the capabilities and limits of agents.
Sources
- Building effective agents (Anthropic) - definition of workflow and agent, recommendation to start from the simplest solution, trade-off between latency and cost; consulted on 2026-11-12
- LangGraph overview (LangChain) - description of LangGraph as a low-level stateful runtime; consulted on 2026-11-12
- LangChain v1 migration guide - from
create_react_agenttocreate_agent, legacy code inlangchain-classic; consulted on 2026-11-12 - CrewAI - Introduction - Crews and Flows; consulted on 2026-11-12
- AutoGen - stable documentation - archived-project notice and pointer to the Microsoft Agent Framework; consulted on 2026-11-12
- ag2ai/ag2 repository (README) - the
ag2package no longer includes theautogenname; consulted on 2026-11-12 - Model Context Protocol, specification 2025-06-18 - definition of the protocol; consulted on 2026-11-12
- PyPI registry (package JSON API) - versions cited, read with
curlon the registry on 2026-11-12

Commenti
Caricamento commenti...
Sign in to comment