Skip to content

Loops

Some work is iterative by nature. Three rounds of research, each refining the last. A list of items, each handled the same way. A back-and-forth refinement that stops when the output is good enough. A loop node handles all three patterns.

Until a condition is met

The most common loop. Run the body, check a condition, run again if the condition isn't met yet:

yaml
nodes:
  - id: refine-draft
    type: loop
    max_iterations: 5
    until:
      type: prompt
      prompt: |
        Is this draft ready to ship? Respond yes or no.

        $iteration.output
    body:
      - id: write-draft
        type: prompt
        prompt: |
          Write a draft based on the brief: $BRIEF

          {{#previous}}
          The previous draft had this feedback: $previous.feedback
          Address it directly.
          {{/previous}}

Z.E.N. runs the body, checks the until: condition, runs the body again if the condition returned "no." The loop stops when until: returns yes or when max_iterations: is reached, whichever comes first.

A fixed number of times

For research workflows, content batches, anything where you know up front how many rounds you want:

yaml
  - id: research-rounds
    type: loop
    iterations: 3
    body:
      - id: search-round
        type: prompt
        prompt: |
          Round $iteration of 3. Find sources we haven't seen yet on "$TOPIC".

          Already seen: $previous.sources

Three rounds. Each round can see what the previous round did via $previous.<field>.

Per item in a list

To run the body once for each thing in a list:

yaml
  - id: process-each
    type: loop
    over: $items.output
    body:
      - id: handle-item
        type: prompt
        prompt: |
          Handle this item: $item

          Return the handled version.

If $items.output is a list of ten things, the body runs ten times. The current item is $item. Z.E.N. runs them serially by default; add concurrency: 4 on the loop node to fan out.

What $iteration and $previous mean

Inside a loop body, you have two extra variables:

  • $iteration is the current iteration index (1-based) plus the output of the body so far this round.
  • $previous is the body output from the prior iteration, if there was one.

You use these to write a body that builds on what the prior round did, instead of starting from scratch each time.

When the loop fails

If a body iteration fails, by default the whole loop fails. Override with on_failure: continue to keep going past failures:

yaml
  - id: process-each
    type: loop
    over: $items.output
    on_failure: continue
    body:
      - id: handle-item
        type: prompt
        ...

When the loop finishes, the loop node's output is the list of body outputs (with null entries for any iteration that failed).

When to use a loop vs. just more nodes

If you know up front you want exactly three rounds and each round is different, write three nodes. If the iteration count depends on something dynamic, or all iterations do the same thing, use a loop. The line is "if I'd be copy-pasting nodes, I should use a loop."

AI that follows a recipe, not a conversation.