Sooner or later a loop's next step is too big to be a step. It's a whole task with its own sub-tasks — its own loop. Now you have loops inside loops, and the question everyone asks is: what stops it from recursing into oblivion? The usual answer, a depth limit, is the wrong one.

Thesis: Recursion in agents — an iteration whose next step is itself a loop — doesn't need a depth cap to stay finite. It needs a structural floor: a step becomes a sub-loop only when it genuinely spawns sub-work, so the tree bottoms out on its own. Each sub-loop gets its own bar from its parent and is verified independently. And the most dangerous recursion — a loop that finds its own gaps and spawns loops to fix them — is safe only when it can never author its own goal or grade its own work.


This is the final post of "The Loop." The series' three questions were: when does a loop stop (3, 4), what does it remember (5), and — now — can an iteration spawn its own loop?

When a Step Becomes a Loop

Everything so far has treated an iteration as a single step. But real work is fractal. "Ship the feature" contains "write the migration," which contains "handle the backfill," which contains "make the idempotency check pass." At some point a step is too big to do in one pass — it needs its own plan, its own iterations, its own stop condition. It needs to be a loop.

That's recursion: an iteration whose next step, instead of being an action, is another loop. In practice it's a subagent spawning a subagent, or a work item expanding into a sub-ledger of its own. And it's genuinely useful — it's how a loop tackles something bigger than a single context can hold, by delegating a chunk to a fresh sub-loop that returns only a summary.

It's also where the field's oldest fear lives: runaway recursion. Loops spawning loops spawning loops, fanning out exponentially, never bottoming out, melting your token budget and your patience. So the question is real. The standard answer is just wrong.

The Wrong Fix: A Depth Limit

The reflexive guardrail is a maximum recursion depth. "Never nest more than four levels." And it fails for exactly the reason a try-count fails as a stop condition: it's an arbitrary number standing in for the thing you actually care about.

Set the depth limit too low and you guillotine legitimately deep work mid-task — the fifth level was real, and now it's abandoned. Set it too high and it never triggers on the runaway case you were worried about; you just melt down four levels later than you would have. A depth cap, like a try-count, measures the wrong quantity: how nested the work is, not whether the nesting is warranted.

The Right Fix: A Structural Floor

The durable answer makes bottoming-out a property of the structure, not a number you picked. A framework I work with puts it precisely: "a stage is a step by default; it becomes a child cycle only when it spawns real sub-work... that is the recursion floor — presence of sub-work is the only thing that makes a stage a cycle, so recursion cannot run away."

Read that carefully, because the move is subtle. Recursion doesn't descend a fixed number of levels and stop. It descends only where a piece genuinely needs sub-pieces, and it stops wherever a piece doesn't. A step that can be done directly is done directly — it never becomes a loop. The tree bottoms out naturally, at different depths on different branches, exactly where the work stops needing decomposition.

flowchart TD
    Root["Ship the feature<br/>(loop)"] --> A["Write migration<br/>(loop — has sub-work)"]
    Root --> B["Update changelog<br/>(step — done directly, floor)"]
    A --> A1["Backfill<br/>(loop — has sub-work)"]
    A --> A2["Add column<br/>(step — floor)"]
    A1 --> A1a["Idempotency check<br/>(step — floor)"]
    A1 --> A1b["Batch the writes<br/>(step — floor)"]

    style B fill:#2d6a4f,color:#fff
    style A2 fill:#2d6a4f,color:#fff
    style A1a fill:#2d6a4f,color:#fff
    style A1b fill:#2d6a4f,color:#fff

The green nodes are the floor — steps that don't spawn sub-work, so they aren't loops. Recursion can't run away because it only exists where there's genuine sub-work to justify it, and genuine sub-work is finite. You didn't cap the depth; you removed the reason for unwarranted depth to exist.

Self-Similar, All the Way Down

What makes this clean rather than chaotic is that it's the same loop at every level. A sub-loop isn't a different mechanism — it's the whole apparatus from this series, running again, one level down.

flowchart LR
    subgraph parent["Parent loop"]
        P["goal + bar → iterate →<br/>verify gap → done"]
    end
    subgraph child["Child loop (a step that spawned sub-work)"]
        C["sub-goal + sub-bar → iterate →<br/>verify gap → done"]
    end
    P -->|"hands down its own<br/>goal + bar"| C
    C -->|"returns a verified,<br/>compact result"| P

    style P fill:#5a5a8a,color:#fff
    style C fill:#5a5a8a,color:#fff

The framework states it as an invariant: "self-similar all the way down — everything produced receives its own given goal and bar from its parent and runs the same loop." Each sub-piece gets its what and its bar handed down from its parent, runs its own gap-terminated, externally-verified loop, and returns a verified result upward. The done-gates are fractal: a parent's step isn't done until its child loop's bar was independently met. You don't design the recursion separately — you design one loop well, and recursion is that loop pointed at its own sub-work.

The Escape Hatch: Abandon and Surface

A structural floor keeps warranted recursion finite. But there's still the branch that should bottom out and won't — a sub-gap that genuinely can't be closed. The idempotency check that can't be made to pass because the task was underspecified. Left alone, that branch loops forever on a gap that will never close.

This is where the demoted counter from Post 3 earns its place, as a per-node livelock guard. The framework's version: after N consecutive validation failures — N times an independent check said "still not met" — the node is marked abandoned and, critically, surfaced: "gap judged uncloseable by the loop — surfaced for the giver, not silently dropped."

That last clause is the entire ethic of safe recursion. An uncloseable sub-gap has exactly three possible fates, and two of them are bugs:

Fate of an uncloseable sub-gap Verdict
Silently dropped (pretend it wasn't needed) ❌ Corrupts the parent's result invisibly
Faked as success (lie up the tree) The completion lie, now recursive
Abandoned and surfaced to a human ✅ The only honest option

Runaway recursion isn't only infinite descent. It's also infinite retry on one stuck node. The floor handles the first; the surface-don't-drop guard handles the second.

The Scariest Recursion: A Loop That Evolves Itself

Push recursion to its limit and you get a loop pointed at itself: an agent that inspects its own behavior, finds its own gaps, and spawns loops to fix them. No human noticed the gap; the system found and closed it. This is the dream of self-evolving agents — what I've seen described as an emerging research direction, where skill libraries and tool graphs are proposed to update from a system's own deployed runs — and it's genuinely powerful, if still early and largely unproven at scale. It's also where recursion stops being a performance concern and becomes a safety one.

The binding constraint, in both the research and in practice, is the verifier. A self-evolving loop is only as trustworthy as the thing that gates its self-updates — and that thing cannot be the agent itself. A framework I work with encodes it as two absolute prohibitions: never author your own goal, and never author your own bar — because "a self-evolving thing that picks its own mission and grades itself is a hall of mirrors."

flowchart TD
    Agent["Self-evolving loop<br/>finds its own gaps,<br/>spawns fixes"]
    Agent -->|"may choose"| How["the HOW<br/>(methods, sub-loops, edits)"]
    Human["Human / giver"] -->|"owns, immovably"| What["the WHAT<br/>(the goal)"]
    Human -->|"owns, immovably"| Bar["the BAR<br/>(what 'good' means)"]
    What -.->|"agent can't rewrite"| Agent
    Bar -.->|"agent can't grade itself against<br/>a bar it controls"| Agent

    style How fill:#5a5a8a,color:#fff
    style What fill:#2d6a4f,color:#fff
    style Bar fill:#2d6a4f,color:#fff
    style Agent fill:#9d4444,color:#fff

This is the entire "Intent Over Instructions" thesis turned into a safety boundary for recursion. The agent may own the how all the way down — spawn any sub-loop, choose any method, evolve any technique. It may never own the what or the bar. Give it both and self-evolution has no fixed point to converge toward; it optimizes a target it's free to move, which is not optimization at all. Keep the goal and the bar in human hands, and even a loop that rewrites itself stays anchored to something it can't rewrite.

When Recursion Should Stay Shallow

Depth for its own sake is a smell. Prefer a flat loop when:

  • The work isn't actually nested. If your tasks are a list, not a tree, a single loop over the list beats a recursive one. Don't manufacture hierarchy.
  • Sub-results don't compress. Recursion pays when a sub-loop returns a compact verified result. If the parent needs the sub-loop's full context anyway, you haven't saved anything — you've just added a boundary.
  • The overhead dominates. Spinning up a sub-loop (fresh context, its own ledger, its own verification) has real cost. For small sub-work, inline it.

Recursion earns its keep when a piece is genuinely too big for one context and its result can be summarized cleanly on the way back up. Otherwise, stay flat.

The Whole Series in One Loop

Every post in this series was one loop, seen from a different side:

flowchart LR
    Aim["aim at the goal"] --> Gap["find the open gap"]
    Gap --> Bar["lock a bar you can't soften"]
    Bar --> Do["do one step<br/>(fresh context)"]
    Do --> Verify["independent check —<br/>not self-report"]
    Verify -->|"gap open → next step"| Do
    Verify -->|"sub-work needed"| Child["spawn a child loop<br/>(same shape)"]
    Child --> Do
    Verify -->|"bar met"| Done["done — for real"]

    style Done fill:#2d6a4f,color:#fff
    style Verify fill:#5a5a8a,color:#fff
    style Child fill:#5a5a8a,color:#fff

Aim at a goal. Find the gap between goal and reality. Lock a bar you can't soften. Do one step in a fresh context. Have something other than the doer check the gap. If it's open, loop; if the step was too big, spawn a child loop with the same shape; if the bar is met, stop — for real, because a grader said so. That single loop, run honestly and nested only where warranted, is most of what "agentic" actually means. The model supplies the intelligence for one step. Everything that makes it trustworthy across many steps — and many levels — is the loop.

What to Do Next

  • Replace depth limits with a floor. Stop capping recursion depth. Instead, make a step become a sub-loop only when it has genuine sub-work — and let branches bottom out at whatever depth they naturally do.
  • Hand every sub-loop its own bar. A child loop should get its what and its success criterion from its parent, and be verified independently — fractal done-gates, not a parent that trusts its children's say-so.
  • Guard each node against livelock. Per sub-gap, cap consecutive validation failures and, on the cap, abandon-and-surface. Never let a stuck node retry forever, drop silently, or fake success upward.
  • Never let a self-evolving loop own its goal or its bar. It can choose any how, at any depth. The what and the definition of "good" stay in human hands, or you've built a hall of mirrors.
  • Design one loop well. Recursion isn't a separate system. Get termination, memory, and verification right for a single loop, and recursion is that loop pointed at its own sub-work.

This concludes "The Loop." The arc: the loop is the unit of workmap the shapes before diving into the hard partsstop on a verified gap, not a countdon't trust the agent's own "done"keep memory on disk, not in contextdrive it with a ledger, not an orchestrator → and nest it only where the work is genuinely deeper. Companion series: Intent Over Instructions.