Loop returns a StepFunc that runs body repeatedly until a stop condition is met or the iteration cap is reached, whichever comes first — the agentic "loop": keep working until the goal is done, with a guaranteed ceiling so it can never run away. Compose it as a flow step. The carried State flows fr
(body StepFunc, opts ...LoopOption)
| 72 | // before the stop condition fires, the loop returns the latest state rather |
| 73 | // than erroring — the guardrail did its job. |
| 74 | func Loop(body StepFunc, opts ...LoopOption) StepFunc { |
| 75 | o := LoopOptions{Max: 10} |
| 76 | for _, op := range opts { |
| 77 | op(&o) |
| 78 | } |
| 79 | if o.Max <= 0 { |
| 80 | o.Max = 10 |
| 81 | } |
| 82 | return func(ctx context.Context, in State) (State, error) { |
| 83 | if body == nil { |
| 84 | return in, fmt.Errorf("flow: Loop requires a body step") |
| 85 | } |
| 86 | cur := in |
| 87 | for iter := 1; iter <= o.Max; iter++ { |
| 88 | out, err := body(ctx, cur) |
| 89 | if err != nil { |
| 90 | return cur, fmt.Errorf("loop iteration %d: %w", iter, err) |
| 91 | } |
| 92 | cur = out |
| 93 | if o.OnIter != nil { |
| 94 | o.OnIter(iter, cur) |
| 95 | } |
| 96 | done, err := loopDone(ctx, o, cur, iter) |
| 97 | if err != nil { |
| 98 | return cur, err |
| 99 | } |
| 100 | if done { |
| 101 | return cur, nil |
| 102 | } |
| 103 | } |
| 104 | return cur, nil |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | // loopDone evaluates the stop conditions: a code-defined Until predicate |
| 109 | // and/or an LLM judgement. Either firing stops the loop. |
searching dependent graphs…