---
title: "Loops vs. Graphs: The Only Architecture Decision in Claude Automation"
description: "Every Claude automation is one of two shapes: a loop where the model decides what happens next, or a graph where your code does. Here is how each one fails, why a 40-turn loop costs 55x what a 5-turn loop costs, how I decide between them, and the hybrid I actually ship."
date: "2026-09-04"
tags: [ai, claude-code, automation, agents, architecture, orchestration, subagents, developer-tools]
sources:
  - title: "Claude Code — subagents"
    url: "https://code.claude.com/docs/en/sub-agents"
  - title: "Claude Code — hooks reference"
    url: "https://code.claude.com/docs/en/hooks"
  - title: "Claude Code — skills"
    url: "https://code.claude.com/docs/en/skills"
  - title: "Anthropic — prompt caching"
    url: "https://docs.claude.com/en/docs/build-with-claude/prompt-caching"
  - title: "Anthropic — tool use overview"
    url: "https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview"
  - title: "Claude Agent SDK — overview"
    url: "https://docs.claude.com/en/api/agent-sdk/overview"
  - title: "LangGraph — graph concepts"
    url: "https://langchain-ai.github.io/langgraph/concepts/low_level/"
---
Every Claude automation you build is one of two shapes.

Either you hand Claude a goal and let it decide what to do next until it is done — a **loop**. Or you lay out the steps yourself as nodes and edges and let Claude fill in the boxes — a **graph**.

That is it. Those are the options. Everything else is a detail.

The reason it matters is that most people pick the shape by accident — based on which tutorial they read first, or which framework had the nicer landing page — and then spend three weeks fighting the consequences. A loop that should have been a graph will turn a $6/month job into a $400/month job without a single line of code changing. A graph that should have been a loop becomes forty boxes of if-else that still cannot handle a slightly weird input.

Neither shape is better. They fail in opposite directions, which is exactly why the choice is worth making on purpose.

## Two Shapes, One Job {#two-shapes}

Say the job is: *every morning, look at yesterday's error logs and open a GitHub issue for anything new.*

**The loop version** is one prompt and a set of tools:

```
Goal: Review yesterday's errors. Open a GitHub issue for each new
recurring problem. Skip anything already tracked.

Tools: read_logs, search_issues, create_issue
Stop when: every new recurring error has an issue, or you are stuck.
```

Claude decides how many times to call `read_logs`. Claude decides what "recurring" means today. Claude decides when it is finished. You wrote maybe fifteen lines.

**The graph version** is the same job, drawn out:

```
fetch_logs → cluster_errors → [for each cluster] → is_it_new?
                                                   ├─ yes → draft_issue → create_issue
                                                   └─ no  → skip
```

Claude still does the interesting parts — clustering messy stack traces, writing a decent issue body, judging whether two crashes are the same bug. But *the order is yours*. The `for each` is a real loop in real code. The `is_it_new?` check is a database query, not a vibe.

Same job. Same model. Completely different systems.

## The Real Difference Is Who Owns Control Flow {#control-flow}

Strip away the frameworks and one question sits underneath all of this:

> **When the automation decides what happens next, is that decision made by the model or by your code?**

In a loop, control flow lives inside the model's head. Every turn, Claude looks at everything that has happened and picks the next move. That is the entire value proposition — you do not have to enumerate the paths, because Claude finds them.

In a graph, control flow lives in your code. The model is a very good function you call at specific points. It transforms things, judges things, writes things. It does not decide what happens next.

Everything else follows from that one difference:

| | Loop | Graph |
|---|---|---|
| **Who picks the next step** | The model | Your code |
| **Handles inputs you did not anticipate** | Yes, that is the point | No, it falls off the edge |
| **Same input, same path** | Rarely | Almost always |
| **Where you debug** | Transcripts | Stack traces and node state |
| **Cost per run** | Variable, sometimes wildly | Predictable within a range |
| **Adding a capability** | A sentence in the prompt | A node, an edge, and a test |
| **Failure style** | Wanders, retries, quietly gives up | Stops loudly at a known node |

Look at that last row, because it decides most real projects. Loops fail *softly*. A loop that goes wrong will often produce a confident, plausible, wrong result and report success. A graph that goes wrong throws an exception at node seven at 4:02am and you know exactly where to look.

If you can tolerate soft failure, loops buy enormous flexibility for almost no code. If you cannot, no amount of prompt engineering will make a loop safe, and you should stop trying.

## When the Loop Wins {#loop-wins}

Reach for a loop when:

**The path is genuinely unknown ahead of time.** Debugging is the perfect example. You cannot draw a graph for "find out why the test is flaky," because the whole job is discovering which of a hundred branches you are on. Any graph you draw is a guess, and a wrong guess is worse than no graph.

**The task is one-shot or low-frequency.** A migration you run once. A research sweep. A cleanup across 200 files. The loop costs more per run, but you are running it once, and writing a graph would take longer than letting it churn.

**Failure is cheap and visible.** A human reviews the output before it matters — pull requests, drafts, proposals, reports. If a person sees every result before it does anything, soft failure stops being scary.

**The step count is small and bounded.** Loops that finish in five to fifteen turns behave well. Loops that run eighty turns are where the ugly stuff lives.

**You are still figuring out what the automation should even do.** This one is underrated. Build the loop first, on purpose, as a discovery tool. Watch what it actually does across twenty real inputs. *That transcript is your graph.* You will see the same five steps every time, and now you know what to hard-code.

## When the Graph Wins {#graph-wins}

Reach for a graph when:

**It runs on a schedule, unattended.** Nobody is watching the 3am run. It needs to either work or page you — not improvise.

**Steps have side effects you cannot take back.** Sending email. Charging cards. Posting publicly. Deleting things. A loop that calls `send_email` a second time because it was not sure the first one worked is a real failure mode, and "I told it not to" is not a control.

**You need the same input to produce the same output.** Compliance, audit trails, anything where "why did it do that in March?" is a question you will have to answer. Graphs give you a run log with node names. Loops give you a transcript and a shrug.

**Cost has to be predictable.** A graph's ceiling is roughly the sum of its nodes. A loop's ceiling is whatever you set max-turns to, and most people set it high and forget.

**Steps can run in parallel.** Fan-out is trivial in a graph and awkward in a loop. Twenty independent items become twenty parallel nodes and one join. A loop grinds through them one at a time, carrying all nineteen previous results in context while it works on the twentieth.

**Different steps want different models.** In a graph you route: cheap and fast for classification, expensive and smart for the one hard judgment call. In a loop, every turn runs on the same model, so you pay top rates for "call the API again."

That last point is the most common source of silent waste I see, and it leads straight into the math.

## The Cost Math Nobody Shows You {#cost-math}

Here is the thing about loops that the demos never mention: **a loop re-sends its entire history on every single turn.**

Turn one sends the prompt. Turn two sends the prompt plus turn one. Turn three sends the prompt plus turns one and two. The context does not reset — that is what makes it a loop and not a graph.

So if each turn adds roughly `k` tokens of history, a loop of `N` turns does not cost `N × k`. It costs closer to:

```
k × (1 + 2 + 3 + ... + N)  =  k × N(N+1)/2
```

Quadratic. Doubling the turn count roughly **quadruples** the input tokens.

Run the numbers on a plausible case — a loop where each turn adds about 2,000 tokens of tool output and reasoning:

| Turns | Input tokens (approx) | Relative cost |
|---|---|---|
| 5 | 30,000 | 1× |
| 10 | 110,000 | 3.7× |
| 20 | 420,000 | 14× |
| 40 | 1,640,000 | 55× |

A loop that takes 40 turns instead of 5 does not cost 8× more. It costs about 55× more. And nothing in your logs says "this run took 40 turns" unless you went looking. It just says it succeeded.

Multiply by a daily cron and that is how a $6/month job becomes a $400/month job while the code sits untouched. The loop simply started getting a messier input.

Two honest caveats:

**Prompt caching flattens this a lot.** Cached input tokens are dramatically cheaper, and a loop's history is the ideal cache shape — a stable prefix that only grows at the end. If you are running loops without caching, fix that before anything else in this post. It is the single highest-leverage change available.

**A graph is not free either.** Ten nodes means ten calls, each with its own system prompt and setup overhead. For short jobs a graph can genuinely cost *more* than the loop. The quadratic only bites when N gets large.

The rule that falls out: **loops are cheap when they are short and expensive when they are long, and you usually do not control which one you get.** Graphs cost what they cost. That predictability is the product.

## How Each One Fails {#failure-modes}

Both shapes have a signature failure. Learn to recognize yours.

**Loops drift.** Turn 30 is operating on a summary of a summary of the original goal. Still working, still confident, still burning tokens — on a subtly different task than the one you asked for. Long loops do not crash, they wander.

**Loops retry things that already worked.** The tool call succeeded, the response was ambiguous, so Claude calls it again to be sure. Fine for `read_file`. Not fine for `create_invoice`. If a tool has side effects, it needs an idempotency key. The model's good intentions are not a safety mechanism.

**Loops declare victory early.** "I have reviewed the main files and everything looks good." Which files? A loop with a fuzzy stopping condition finds the nearest plausible exit. Vague stop conditions are the root cause of most "it said it was done but it was not" complaints.

**Graphs get brittle.** You built for the shape of the data in March. In September a field is null, or the log format changed, or there are two of something you assumed was one. Node four throws and the whole run stops. That is *better* than the loop failure — loud and localized — but it means graphs need maintenance that loops do not.

**Graphs sprawl.** Every edge case becomes a node. Six months in you have forty boxes, twelve of which have run exactly once, and nobody remembers what `handle_edge_case_3` is for. A graph that has grown past about fifteen nodes is usually telling you that some region of it wanted to be a loop.

**Graphs cannot handle genuine novelty.** By construction. If the input does not fit a path you drew, there is no path. Sometimes that is the correct behavior — refuse and escalate. Sometimes it means you drew the wrong picture.

## The Hybrid That Actually Works {#hybrid}

Here is what I have landed on after building enough of both:

> **Graph on the outside. Loops on the inside. A loop is a node, not an architecture.**

The outer graph owns everything you want to be boring: scheduling, ordering, retries, side effects, cost ceilings, what happens when a step fails. The stuff you would never want a model improvising.

Inside a node, where the work is genuinely open-ended, you run a loop — a *bounded* one. Clear goal, small tool set, hard turn limit, structured output. It gets to be creative inside a box you built.

The morning triage job, done properly:

```
[graph] fetch_logs
        ↓
[graph] cluster_errors           ← deterministic, no model
        ↓
[graph] for each cluster (parallel):
            ↓
        [loop] investigate this cluster
               tools: search_code, read_file, search_issues
               max 8 turns
               returns: {is_new, severity, summary, suspect_files}
            ↓
[graph] filter to is_new && severity >= medium
        ↓
[graph] create_issue              ← the side effect, in your code
        ↓
[graph] post summary to Slack
```

The investigation is a loop, because "why is this error happening" is unknowable in advance. Everything with a consequence — creating issues, posting to Slack — is a plain function call in the graph, where it runs once, deterministically, or not at all.

Three rules make this work in practice:

**Every loop has a turn cap and a typed output schema.** If it cannot finish in budget, it returns a failure the graph can route on. It does not get to try harder.

**Side effects never live inside a loop.** The loop may *recommend* creating an issue. The graph creates it. This one rule eliminates the entire "it sent the email three times" category.

**Loop nodes get their own model choice.** Clustering can run on something small and fast; the one hard judgment call gets the expensive model. In a monolithic loop you cannot make that distinction, so you pay premium rates for every trivial step.

## How I Actually Build, and When I Switch {#how-i-build}

I start almost everything as a loop. Not because loops are better — because I usually do not know what I am building yet.

My default first move is a single prompt, a small set of tools, and a goal written the way I would say it out loud. No nodes, no framework, no orchestration layer. Fifteen minutes of work. Then I run it against real inputs — not one or two, closer to fifteen or twenty — and I read every transcript.

That reading is the actual design work, because after twenty runs the transcripts tell me things I could not have guessed:

- **Which steps happened every single time.** Those are graph nodes. They were never really decisions.
- **Which step it got wrong, and how.** Usually one specific judgment call, and usually the same one.
- **How many turns it took, and how much that varied.** If run #3 took 6 turns and run #14 took 31, that variance is my cost problem and my correctness problem at once.
- **Where it invented work I never asked for.** Loops are enthusiastic. They find adjacent tasks.

So the loop is not the product. The loop is the research — a cheap way to discover the shape of a problem I could not draw on a napkin yet.

### The four things that make me switch

I do not convert on a schedule. I convert when one of these shows up, and it is usually the first one:

**I want to stop watching it.** The moment I would consider putting something on a timer and going to bed, it needs to be a graph. Unattended plus improvised is the combination that ruins your week. Anything I have running on a schedule — monitors, checkers, scheduled reports — is a graph with a loop tucked inside at most one step.

**It is about to touch something real.** The first time an automation goes from *drafting* something to *sending* it, the side effect comes out of the model's hands and into a plain function. The loop is allowed to say "you should open this issue." My code opens it. That boundary is non-negotiable, and it has saved me more than the cost math has.

**The turn count starts varying wildly.** Consistent six-turn runs, I leave alone. When I see runs that finish in 5 and runs that grind through 30, that is not the model having a bad day — that is the task having branches I never named. Time to name them.

**I want to change one step without re-testing everything.** This is the maintenance tell. In a loop, every change is a prompt edit, and every prompt edit means re-running the whole thing against all your cases to see what else moved. In a graph, I rewrite one node and test one node. Once I am editing something weekly, that difference is the whole ballgame.

### What stays a loop, permanently

Plenty of things never graduate, and that is correct:

- **Debugging anything.** The path is the point. I would be drawing a fake graph.
- **One-off migrations and sweeps.** A graph would take longer to write than the loop takes to run.
- **Exploration and research.** Open-ended by definition.
- **Anything where I read the output before it matters.** If a human gate is already in the design, soft failure is a non-issue.

The honest summary: **loops are how I learn the problem, graphs are how I stop thinking about it.** If a job is still interesting, it is probably still a loop. If I want it boring and invisible and never paging me, it gets converted — and the conversion is easy, because the transcripts already drew the picture.

## How This Plays Out in Claude Code {#claude-code}

Everything above is architecture-level, but if you live in Claude Code the same distinction shows up in tools you already have — and most people only use the loop half.

**The loop primitives.** The main agent session is a loop. That is what it *is*. Every "just fix the failing tests" is you choosing the loop shape. `/loop` is the explicit version: a prompt and an interval, or no interval and it paces itself. Genuinely useful for "watch this and tell me when it changes," and equally capable of quietly running up a bill if you point it at something open-ended and walk away.

**The graph primitives, which people skip.**

*Subagents are nodes.* This is the big one. Spawning parallel agents for independent tasks is a fan-out graph: each subagent gets a fresh, bounded context, does one thing, returns a result. That is the quadratic-cost fix, structurally — five subagents at 10 turns each is dramatically cheaper than one agent at 50 turns, because none of them carry the other four's history.

*Hooks are edges with guaranteed execution.* A hook that runs your linter after an edit is not a suggestion the model might follow. It is a deterministic edge. That is how you enforce "tests run after every change" without hoping.

*Skills are node definitions.* A reusable, versioned specification of how one kind of work gets done. Invoking one is closer to calling a function than to writing a prompt.

*Slash commands are the graph's entry points.* A `/spec → /plan → /build → /review → /ship` pipeline is a graph drawn in commands. Each phase is a bounded node whose output the next phase consumes. A human triggering each transition does not make it less of a graph — it makes it a graph with human-approved edges, which for anything touching `main` is exactly right.

The practical translation:

| If you are doing this | Use |
|---|---|
| "Figure out why X is broken" | Main loop. Unknown path, human reviews. |
| "Apply the same refactor to 12 files" | Parallel subagents. Independent, bounded fan-out. |
| "Poll until the deploy finishes" | `/loop` with a real interval. |
| "Never let unformatted code get committed" | Hook. A deterministic edge, not a request. |
| "Ship a feature end to end" | The slash-command pipeline. A graph with checkpoints. |
| "Run something unattended on a schedule" | A scheduled task calling a bounded prompt. Never an open-ended loop. |

If you use Claude Code and have never spawned a subagent or written a hook, you are running the whole thing as one big loop. That works fine until it does not, and the day it stops working is usually the day the context got long.

## The Three-Question Test {#three-questions}

Standing in front of a new automation, ask three things in order.

**1. Can I draw the steps right now, on a napkin, without hedging?**

If yes, graph. You already know the shape; encoding it costs an hour and buys determinism forever. If you catch yourself writing "and then it figures out..." — that is a loop, and specifically that is a loop *node*.

**2. What happens if it does the wrong thing at 3am and nobody notices for a week?**

If the answer is "we lose a little time," a loop is fine. If the answer involves money, customers, data, or anything public, graph — with the side effects in your code, not the model's.

**3. Is this running once, or ten thousand times?**

Once, loop; writing a graph for a one-off is engineering theater. Ten thousand times, graph — because the quadratic will find you, and because at that volume you want to change one step without re-testing the whole thing.

The meta-answer, the one I would actually give over coffee: **start with a loop, watch it work twenty times, then promote the parts that never varied into a graph.** The loop is your prototype. The transcript is your spec. You get flexibility while you are learning the problem and determinism once you understand it.

The mistake is not picking wrong. The mistake is picking by default and never revisiting it while the bill climbs.

## TL;DR

| Question | Honest answer |
|---|---|
| **What is the actual difference?** | Who owns control flow. In a loop the model decides what is next; in a graph your code does. Everything else follows. |
| **Which is cheaper?** | Depends on length. Loops re-send their whole history every turn, so cost grows roughly quadratically — 40 turns costs about 55× what 5 turns costs, not 8×. Graphs cost the sum of their nodes. |
| **Which fails worse?** | Loops fail softly: they drift, retry side effects, and report success on incomplete work. Graphs fail loudly at a named node. Loud is easier to fix. |
| **When do I want a loop?** | Unknown path (debugging, research), one-off runs, a human reviews the output, and while you are still learning what the automation should do. |
| **When do I want a graph?** | Unattended schedules, irreversible side effects, reproducibility requirements, parallel steps, and anywhere different steps want different models. |
| **What should I actually build?** | Graph on the outside, bounded loops as nodes. Turn caps and typed outputs on every loop. Side effects live in the graph, never inside a loop. |
| **How do I decide in practice?** | Start as a loop, read twenty real transcripts, convert when one of four things happens: I want it unattended, it is about to cause a real side effect, turn counts start varying wildly, or I am editing it weekly. |
| **In Claude Code?** | The main session and `/loop` are loops. Subagents, hooks, skills, and the slash-command pipeline are the graph primitives — and they are the half most people never use. |
| **First thing to fix today** | Turn on prompt caching for your loops, and put a hard turn cap on anything running unattended. |

Start with the loop. Keep the transcript. Promote what repeats.
