Skip to content

Loops and branches

Two control-flow primitives cover most workflow patterns: generator ops for iteration, and BranchOp for conditional routing. Use GraphOp.loop for feedback loops where the next iteration depends on the previous one.

Iterate with a generator op

from operonx.core import GraphOp, op, START, END, PARENT

@op
def each_item(items: list):
    for item in items:
        yield {"value": item}

@op
def double(value: int):
    return {"result": value * 2}

with GraphOp(name="map") as graph:
    gen = each_item(items=PARENT["numbers"])
    step = double(value=gen["value"])
    START >> gen >> step >> END

Three items in, three frames downstream — running in parallel by default.

Feedback loop with GraphOp.loop

from operonx.core import GraphOp, op, START, END, PARENT

@op
def increment(counter: int):
    return {"counter": counter + 1}

with GraphOp.loop(until="count >= 5", count=0) as loop:
    inc = increment(counter=PARENT["count"])
    inc["counter"] >> PARENT["count"]
    START >> inc >> END

until= is evaluated against the loop's local state after each iteration. inc["counter"] >> PARENT["count"] writes the new value back so the next iteration sees it.

Branch with hard vs soft edges

>> is a hard edge — the downstream op runs unconditionally. >>~ is a soft edge — the downstream op runs only when the upstream branch op selects this output.

from operonx.core import BranchOp

@op
def is_long(text: str):
    return {"long": len(text) > 100}

with GraphOp(name="route") as graph:
    check = is_long(text=PARENT["text"])
    branch = BranchOp(
        condition=check["long"],
        outputs=["summary", "passthrough"],
    )
    summary_op = summarize(text=PARENT["text"])
    passthrough_op = identity(text=PARENT["text"])

    START >> check >> branch
    branch >>~ summary_op       # soft — runs only when condition is true
    branch >>~ passthrough_op   # soft — runs only when condition is false
    summary_op >> END
    passthrough_op >> END

Soft edges do not count toward ready_count. The downstream ops fire only when the branch routes to them.

Combining

You can nest loops inside iterations and branches inside loops. Frame state is per-iteration, so each frame in an outer loop carries its own inner-loop state.

Where to go next