TL;DR Almost every agent I have built ends up as one of two shapes: a loop, where the model owns the control flow, or a graph, where you do. Start with a loop to learn the problem, then lift the reliable failure points into an explicit graph.

When people say “agent,” they usually mean a loop: a model that thinks, takes an action, looks at the result, and decides what to do next, over and over, until it is done. It is the simplest thing that works, and for a huge number of tasks it is the right answer. But loops have a failure mode, and the fix for that failure mode is usually a graph. Here is how I decide between them.

The loop

A loop is one model call in a cycle. You give it a goal and a set of tools, and you let it run: call a tool, read the observation, append it to the context, ask the model again. The control flow lives inside the model. You are trusting it to plan, to notice when it is stuck, and to know when to stop.

Diagram of an agent loop: an LLM call feeds a tool action, whose observation is appended to context, then a done-check decides whether to continue the cycle or exit with an answer.
The loop. One model, called repeatedly. The model owns the control flow, so the whole thing is only as reliable as its judgment on any given step.

Loops are wonderful when the path is genuinely open-ended and you cannot enumerate the steps in advance. Research, debugging, and “figure out why this is broken” tasks all fit. The cost is control. Because the model decides everything, it can loop forever, take a shortcut you did not want, or spend twenty tool calls on something a fixed path would have done in two. You feel this most when you try to make a loop reliable enough to put in front of a real user.

The graph

A graph makes the control flow explicit. You define nodes, each a unit of work, and edges, the allowed transitions between them. State flows along paths you drew. A router node might classify the request and branch; a retrieval node fetches context; a critique node checks the draft and, on failure, sends state back along a retry edge you deliberately allowed.

Diagram of an agent graph: intake feeds a router that branches to retrieval, compute, or critique nodes, which converge on a synthesize node, with a bounded dashed retry edge from critique back to the router.
The graph. Nodes and edges you defined. Loops still exist, but they are specific edges you allowed, not an open-ended cycle the model can spin on.

The graph does not remove the model, it constrains where the model gets to make decisions. That is the whole trade. You give up some flexibility and you get back legibility: you can point at a node and say what it does, test it in isolation, cap a retry edge at three passes, and reason about failure without replaying an entire transcript. Notice that a graph can still contain a loop. The difference is that the loop is one labeled edge with a bound on it, not the entire architecture.

How I choose

I start with a loop when I am still learning the problem. It is fast to build and it tells me what the model actually needs. Then I watch where it goes wrong. The failures are the design document: every place the loop reliably stalls, over-thinks, or takes the wrong turn is a node and an edge waiting to be drawn. When those failure points become predictable, I lift them out of the model's head and into an explicit graph.

Put simply: a loop is a hypothesis about what the model can handle on its own, and a graph is what you build once you know the answer is “not all of it.” The best systems I have shipped are mostly graph at the edges, where reliability matters, with small loops tucked inside the nodes that genuinely need open-ended reasoning.