Anchors & Routing
Anchors give you fine-grained control over flow execution order. Any step can jump to a named anchor by returning "#anchorName", enabling loops, retries, and conditional skips without needing the .loop() primitive.
Defining an Anchor
typescript
flow
.anchor("retry")
.then(processStep)
.then(async (s) => {
if (s.failed) return "#retry"; // jump back to the anchor
})
.then(finalStep);anchor(name) inserts a no-op marker. The for-loop in the executor increments the step index past the anchor, so the next step after the anchor is the effective entry point.
How goto Works
When a fn step returns a string starting with #, the executor:
- Strips the
#prefix to get the anchor name. - Looks up the anchor's step index.
- Sets the iteration counter to that index — the next iteration lands on the anchor step, skips it (since it's a no-op), and continues with the following step.
Branching with Anchors
Anchors work in .branch() too:
typescript
flow.branch((s) => s.action, {
retry: async (s) => {
s.retries++;
return "#start";
},
finish: async (s) => {
s.done = true;
},
});Forward Jumps (Skip Ahead)
Anchors can also be placed after the jumping step for forward skips:
typescript
flow
.then(async (s) => {
if (s.skipProcessing) return "#save";
})
.then(expensiveProcessing)
.anchor("save")
.then(saveResults);Guard Against Infinite Loops
Use withCycles to cap the number of anchor jumps:
typescript
const AppFlow = FlowBuilder.extend([withCycles]);
const flow = new AppFlow()
.withCycles(50) // max 50 total jumps per run
.withCycles(5, "retry") // max 5 jumps to the "retry" anchor
.anchor("retry")
.then(doWork)
.then((s) => {
if (!s.ok) return "#retry";
});Anchors in Generator Steps
Generator steps can also return an anchor:
typescript
.then(async function* (s) {
for await (const token of stream(s.prompt)) {
s.output += token;
yield token;
}
if (s.output.includes("ERROR")) return "#retry";
})The generator's return value (not yield) is used for routing.