Boris Cherny, the creator of Claude Code, said something recently that matched my own experience: “I don’t prompt Claude anymore… my job is to write loops.” Peter Steinberger put it more bluntly: “You should be designing loops that prompt your agents.”

I’ve been building LLM-powered pipelines on top of Spring Integration for a while, and those two lines named what I had been doing without quite realizing it. My time had stopped going into prompts. It was going into the loops around them.

I covered what this looks like in practice in I Built an AI Video Pipeline. It’s Mostly Enterprise Integration Patterns, which walks through a concrete eight-stage pipeline and the specific EIP patterns (Splitter, Aggregator, Claim Check) it uses. This post is about the broader shift: why agent work is loop design at all, and why a declarative integration runtime is a good place to do it.

The Shift in Mental Model#

When people talk about “prompt engineering,” the implicit model is a single exchange: I craft an input, the model produces an output, the job is done. The craft is in the wording.

That model breaks down the moment the goal is something non-trivial. A polished piece of writing is not the output of one good prompt; it is the output of a process. First comes an idea, then a structure, then a draft, then a critique, then a revision. The quality comes from the shape of the process, not from any single prompt in isolation.

Writing a loop means specifying that shape explicitly: what steps exist, in what order, what each step receives and emits, and how earlier outputs feed into later inputs. The prompt inside each step can be simple. The interesting engineering is in the pipeline itself.

Keip as a Loop Engine#

Spring Integration, the runtime under Keip, is at its core a framework for describing message flows: channels carry messages, transformers process them, and the wiring between channels determines the shape of the computation. That description maps almost perfectly onto what an agent loop looks like in practice.

Here is a minimal pipeline that takes a raw idea and progressively refines it into a polished script:

<int:chain input-channel="idea-in" output-channel="script-out">
    <int:transformer ref="conceptExpander"/>
    <int:transformer ref="outlinePlanner"/>
    <int:transformer ref="draftWriter"/>
    <int:transformer ref="critiqueEnricher"/>
    <int:transformer ref="revisedDraftWriter"/>
</int:chain>

Each <transformer> is one turn of the loop. It receives the accumulated message, adds to it or transforms it, and passes the enriched result to the next stage. The channel graph is the loop structure made visible in XML, before any code runs.

In practice each of those transformer beans calls an LLM with a stage-specific prompt and a carefully scoped slice of the message payload. conceptExpander only knows about the seed idea. outlinePlanner sees the expanded concept. critiqueEnricher sees the draft and produces structured feedback. revisedDraftWriter sees both the draft and the critique. The prompts are short and focused because each one only has to do one thing. The richness comes from how they chain.

A chain is the simplest shape, a straight line. The moment a stage can reject the work, the loop earns its name. If I pull the draft step out of the chain so its output can be inspected, the reject path becomes explicit: a validation step that fails sends the draft back for another pass, and that feedback edge is a real cycle in the graph, declared rather than coded.

<int:router input-channel="draft-channel"
            expression="payload.valid ? 'script-out' : 'regenerate-channel'"/>

<int:service-activator input-channel="regenerate-channel"
                       output-channel="draft-channel"
                       ref="draftWriter"/>

The router is the decision point. A valid draft moves on; an invalid one returns to the generator and comes back around. In practice I carry an attempt counter in a message header and cap the retries, so a draft the model cannot get right does not circle forever. The cycle is a property of the route, visible in the wiring, not a while loop buried in a method.

The Concrete Shape#

To make this tangible: the input is a single string, a topic or a brief. The pipeline’s job is to turn that string into a finished script. Each stage adds a layer.

Stage Input Output
conceptExpander topic string expanded concept, themes, audience
outlinePlanner expanded concept structured outline with beat points
draftWriter outline rough draft prose
critiqueEnricher rough draft critique with specific notes
revisedDraftWriter draft + critique final revised script

No stage is trying to do everything. Each one receives clear context and has a narrow job. The loop structure enforces that discipline: a stage cannot skip the outline and jump to the draft, because the channels do not connect that way.

Why Declarative Routes Matter#

The pipeline above is checked into version control as XML. That carries several consequences.

The loop structure is auditable without running anything. I can read the route and understand the process shape before touching a single LLM call. Reviewing a change is reading a git diff: someone added a factChecker step between draftWriter and critiqueEnricher, and the change to the process is immediately legible.

Stages are independently swappable. If critiqueEnricher is not producing useful feedback, I replace that one bean and leave the rest untouched. Compare that to a long imperative script where every stage is tangled with the orchestration logic around it.

The loop is also composable. The script-out channel can feed another chain, and the whole pipeline can be invoked from a larger flow. Because each stage has a defined input and output contract through the message payload and headers, stages snap together without coordination overhead.

There is a construction-time benefit too. When I build one of these pipelines with an AI coding assistant, the same isolation that makes stages swappable at runtime makes them tractable to implement one at a time. The assistant does not need the whole pipeline in context to write critiqueEnricher; it only needs the contract, the message that comes in and the result it should produce. Each bean is a focused task with a clear scope, the kind of well-bounded work AI assistance handles well. The decomposition that helps the runtime also helps the developer.

The shape also survives scrutiny over time. Six months from now the route still states what the process is. The loop did not dissolve into a tangle of conditionals that only made sense the week it was written.

The Inversion#

Classical prompt engineering asks: what do I say to the model to get what I want? Loop engineering asks: what shape should the process be?

The first question puts all the weight on a single exchange. The second distributes it across a designed sequence. The first optimizes the prompt; the second optimizes the pipeline.

I spend almost no time now tuning individual prompts. I spend most of my time thinking about stage boundaries: where one step ends and the next begins, what each step should not know about, and what the message looks like at each transition point.

Boris was right. The job is to write loops, and the prompts mostly take care of themselves once the loop is right. I just keep mine in XML, on a cluster, where the shape of the loop is the first thing anyone sees.