Do you actually need an agent? A framework for deciding
A single LLM call is one-shot: send a prompt, get text back, with no way to act on the world beyond what it can reason from what it already knows. Tool calling changes that — describe a set of functions the model can invoke, each with a name, a description, and a parameter schema, and instead of only producing text, the model can produce a structured request to call one, with specific arguments. The application executes it for real and returns the result as new context. The model itself decides whether to call a tool, which one, and with what arguments, dynamically, per question — a different thing entirely from a single retrieval-then-generate step.
The loop underneath every agent
The pattern that makes multi-step agents work is ReAct — reason, act, observe, repeat: a chain-of-thought step about what to do next, a tool call, an observation of the result, until the model decides it has enough to answer. In code, that's a while loop: call the model, check whether it asked for a tool, execute it if so and feed the result back, repeat, return the final text when it stops asking. The loop itself is a small amount of code and worth understanding that way first — the real complexity lives around it: managing state as conversation history and intermediate results grow, handling a tool that fails or a model that hallucinates one that doesn't exist, capping iterations so nothing loops forever, and logging every step, since tracing a chain of calls is the same problem as tracking the cost of that chain — not a coincidence that the same tools end up doing both.
Four different bets
LangChain is the original general-purpose framework, and the one that set most of the ecosystem's vocabulary. Its strengths are real — the largest library of integrations, the most documentation, fast to prototype common patterns — and so are its weaknesses: heavy abstraction layers that make debugging opaque, a history of breaking API changes, and a control-flow model (its agent executor, the original ReAct implementation) that gets uncomfortable to force past simple branching.
LangGraph, from the same team, is the direct answer to that limitation: instead of an implicit loop, the agent's workflow is an explicit graph — nodes as steps, edges as transitions, including conditional edges that branch on the current state. That buys conditional branching as a first-class concept instead of nested logic, cycles with state (a "keep refining until it passes a quality check" pattern that's naturally a graph cycle), human-in-the-loop approval steps, and checkpointing a long-running workflow to resume later. The cost is upfront design — a genuinely simple agent, a handful of tools with no real branching, pays overhead for explicitness it doesn't need.
CrewAI takes a higher-level, more opinionated stance: role-based multi-agent orchestration. Instead of one agent with many tools, several agents each get a role, a goal, and only the tools relevant to that role — the reasoning being that a single agent handling every possible task with a giant system prompt tends to perform worse at each individual task than specialists would, the same dilution that happens when you hand a person too many responsibilities at once. It's a real fit when subtasks genuinely reflect distinct roles a human team would divide work by. It's over-engineering when the task is actually one job wearing a multi-agent costume — every additional agent in a chain adds cost, latency, and a new place for a handoff to fail, for no benefit if the underlying task didn't need dividing.
DSPy isn't an orchestration framework at all, and doesn't compete with the other three. It treats prompt design as a program that optimizes itself: declare the input/output shape of a task instead of writing prompt text by hand, compose it from reusable modules (chain-of-thought, ReAct, plain prediction), and — the real substance — give it training examples and a metric, and an optimizer searches for the prompt wording and few-shot examples that score best against it. It operates a level below orchestration, on the problem of writing the prompt for any given step, regardless of what's orchestrating that step. It earns its cost with a clear metric and enough training examples to generalize from; it's overkill for a small, one-off prompt that's genuinely faster to adjust by hand.
A decision tree, not a feature table
The right way to choose isn't comparing feature lists — it's a sequence of questions that each rule something out before the next one matters. Does this need an agent at all, or does a single retrieval-and-generate step solve it — reaching for a framework because they're popular, not because the problem demands one, is the most common miscalibration. If it needs an agent, is it simple — a handful of tools, no real branching, no cycles, no multi-agent handoffs — in which case a hand-written loop or a basic executor is enough, and the explicit graph machinery is overhead without a return. Does control flow genuinely get complicated — conditional branching, retry cycles, approval steps, a long workflow that needs to persist and resume — that's a graph, not a chain. Does the problem actually decompose into distinct roles the way a human team would split it — not "multi-agent sounds impressive" — that's a case for role-based orchestration; if the honest answer is one task in a costume, that's a sign to go back a step. And DSPy sits orthogonal to all of this, not as another item on the list — it applies whenever a specific step's prompt is being tuned by hand through trial and error instead of systematically, regardless of what's orchestrating the rest.
The principle underneath all of it: every additional agent and every additional call in a chain adds cost, latency, and a new place for something to break. The default should point toward the simplest architecture that solves the actual problem, not the most sophisticated one available — asking whether this genuinely needs to be an agent, or a multi-agent system, is the first question, not the last.