Skip to content

Code Flow

Code Flow is one of Orvanta’s flow authoring models, alongside the node-based DAG Flow canvas and BPMN Flow. Instead of wiring nodes on a canvas, you write a single orchestration function in Python or TypeScript and mark it and its steps with a small set of decorators/wrappers. Orvanta checkpoints each step so the function can suspend, hand its worker slot back, and resume exactly where it left off.

  1. Mark the orchestration function

    Python uses the @workflow decorator; TypeScript wraps the function with workflow().

    from orvanta import workflow, task
    @task
    async def extract(url: str):
    ...
    @workflow
    async def main(url: str):
    data = await extract(url)
    return data
    import { workflow, task } from "orvanta";
    const extract = task(async (url: string) => {
    // ...
    });
    export const main = workflow(async (url: string) => {
    const data = await extract(url);
    return data;
    });
  2. Mark each unit of work

    Python uses @task (or @task(path=..., timeout=..., tag=..., ...) for options); TypeScript uses task(fn, options?). A task called from inside a workflow dispatches as a checkpointed step rather than running inline.

  3. Call other runnables as tasks

    task_script(path, ...) / taskScript(path, options?) dispatch to an existing Orvanta script by path. task_flow(path, ...) / taskFlow(path, options?) dispatch to an existing flow. Both return a callable you invoke like any other task inside the workflow.

A @workflow/workflow() function must be deterministic: given the same inputs, it must call tasks in the same order on every replay. Branching on a task’s result is fine — results are served from the checkpoint on replay. Branching on external state (the current time, randomness, an uncheckpointed API call) is not, because the replay would see different values than the original run and diverge from the recorded step sequence.

To checkpoint a plain value without spawning a task, use step(name, fn). It runs fn inline once, stores the result in the checkpoint, and on replay returns the cached value without re-running fn.

Each task runs as a separate job, with its own logs and its own entry in the run’s timeline — the same visibility you get from a node in a DAG Flow, not a single merged log stream for the whole function.

Between dispatching a task and receiving its result, the parent workflow suspends and releases its worker slot. It doesn’t hold a worker idle while a task runs; it resumes (replaying completed steps from the checkpoint) once the task completes. This is what lets a Code Flow orchestrate long-running or highly parallel work without pinning a worker for the whole duration.

Other primitives follow the same suspend/checkpoint model:

  • sleep(seconds) — suspends the workflow for a duration without holding a worker, then auto-resumes.
  • wait_for_approval(...) / waitForApproval(...) — suspends until an external approval is recorded.
  • parallel(items, fn, concurrency?) — dispatches fn (a task) over a list of items in concurrency-limited batches.

Reach for Code Flow when the orchestration logic is easier to express as a program than as a graph — dynamic branching, loops with non-trivial exit conditions, or dispatching a variable number of tasks computed at runtime. Reach for DAG Flow when the shape of the flow is fixed and the visual canvas is the more legible representation.

Code Flow still produces the same kind of durable artifact described in Flows as artifacts — the difference is how you author it, not whether it’s version-controlled or reviewable.