Approval¶
A flow that pauses for a human decision and resumes with the answer.
Source: examples/approval.py. Run it with python examples/approval.py while cereyan serve examples/ is running.
wait_for_input parks the run in Paused with a question. The engine is released
while the run waits; answering through the UI, the API, the client, or the MCP
resume_run tool schedules a new attempt that replays the flow from the top, where
tasks marked cache=INPUTS return their stored results and wait_for_input
returns the answer. This example runs against a server: start
cereyan serve examples/ first.
import time
from datetime import date
from cereyan import INPUTS, flow, get_run_logger, task, wait_for_input
from cereyan import client
@task(cache=INPUTS, persist_result=True)
def prepare(day: date) -> int:
get_run_logger().info("preparing %s", day)
return 1200
@task
def release(rows: int) -> str:
return f"released {rows} rows"
The flow¶
Each question carries a JSON schema, which the run page turns into a form. The flow
asks twice: whether to load the day, then whether to publish it. Every answer is
stored against the question it answered, so the replay after the second answer gets
approve for the first question and publish for the second. The flow is listed in
the releases group of its project.
APPROVE = {"type": "object", "properties": {"approve": {"type": "boolean"}}, "required": ["approve"]}
@flow(group="releases")
def publish(day: date) -> str:
rows = prepare(day)
if not wait_for_input(f"Release {rows} rows for {day}?", schema=APPROVE)["approve"]:
return "held"
released = release(rows)
if not wait_for_input(f"Publish {day} to the dashboard?", schema=APPROVE)["approve"]:
return f"{released}, not published"
return f"{released}, published"
Driving it from a script¶
Start the run, answer each question as the run pauses on it, and wait for the result.
def wait_for(run_id: int, predicate, timeout: float = 30.0) -> dict:
deadline = time.time() + timeout
while time.time() < deadline:
run = client.get_run(run_id)
if predicate(run):
return run
time.sleep(0.1)
raise TimeoutError(f"run {run_id} did not reach the expected state")
if __name__ == "__main__":
run = client.run("publish", day="2026-03-01")
for question in ("Release", "Publish"):
paused = wait_for(
run["id"],
lambda r: r["state"]["type"] == "Paused" and r["state"]["details"]["prompt"].startswith(question),
)
print("question:", paused["state"]["details"]["prompt"])
client.default_client().resume(run["id"], {"approve": True})
done = wait_for(run["id"], lambda r: r["state"]["type"] in ("Completed", "Failed", "Crashed", "Cancelled"))
assert done["state"]["type"] == "Completed", done["state"]
print("state:", done["state"]["type"])