Skip to content

How to chain flows

Run one flow after another with after=. The server watches for upstream runs to end and creates the downstream run. Dependencies need a running server.

One upstream

from datetime import date
from cereyan import App

app = App("sales")

@app.flow
def load_orders(day: date) -> int:
    return 42

@app.flow(after="load_orders")
def build_report(day: date) -> str:
    return f"report for {day}"

assert build_report.after == {"flow": "load_orders", "flows": ["load_orders"], "key": None, "parameters": {}}

Every time load_orders ends Completed or Skipped, a build_report run is created with the same day, created_by = run:<id>, and a link to the upstream run in its details. A failed upstream creates nothing.

Rename or derive parameters

Parameters are copied by name. To map them, give a template for each downstream parameter; the context is the same as for rules, with run, flow, state, payload, and parameters:

from datetime import date
from cereyan import App

app = App("sales2")

@app.flow
def load_orders(day: date) -> int:
    return 1

@app.flow(after=("load_orders", {"for_day": "{{ run.parameters.day }}", "source": "'orders'"}))
def notify(for_day: date, source: str = "unknown") -> str:
    return f"{source} {for_day}"

assert notify.after["parameters"]["for_day"] == "{{ run.parameters.day }}"

Fan in: wait for several upstreams

When the downstream needs every upstream to have finished the same batch, list them and name the parameter that identifies the batch:

from datetime import date
from cereyan import App

app = App("reporting")

@app.flow
def sales(day: date) -> None: ...

@app.flow
def inventory(day: date) -> None: ...

@app.flow(after=["sales", "inventory"], batch_key="day")
def report(day: date) -> str:
    return f"report {day}"

assert report.after["flows"] == ["sales", "inventory"] and report.after["key"] == "day"

report runs once per day, after both sales and inventory have a Completed or Skipped run for that day. A day that already has a report never gets a second one, however the first was created, and a failed upstream blocks the batch until it is rerun. Each creation records a flow.fan_in event with the key value and the upstream run ids, and the flow page's dependency graph draws one edge per upstream.

When to use a rule instead

after= covers "run B when A finishes". For anything conditional, a rule with a run_flow action gives you the full match clause: only on failure, only for a tag, only in a project, with templated parameters and guards.

Check the wiring

At start, the server reports an after= naming a flow it has not registered as a flow error, visible on the Flows page and in GET /api/flows, without stopping other flows. The flow page lists Triggered by and Triggers.

Related: Dependencies.