# Build a flow

Connect requests and application code in an inspectable, bounded flow.

New to flows? Start with [Flows from scratch](/guides/flow-basics/), which introduces each method with a small example. For exact inputs and return values, use the [API glossary](/api/reference/).

Flows are optional. Start here once a single request appears in the drawer. Keep all `jev-devtools/flow` imports on Node.

## Define first, run later

This flow classifies a message and returns the name of a team. It does not send a ticket or perform a business action.

Hover, focus, or tap a dotted API method or option for a short explanation. Try [`jev`](/api/reference/#flow-jev), [`branch`](/api/reference/#flow-branch), or `state`. Local variables and example-specific names stay plain code. Press Escape to dismiss the tooltip.


```ts twoslash file=flow.ts
import { choice } from "@typesafe-ai/sdk";
import { defineJevFlow } from "jev-devtools/flow";
import { inspector, jev } from "./server.js";

// 1. Define the steps; no SDK request is sent here.
export const triage = defineJevFlow<{ message: string }>("triage", (flow) => {
  const classification = flow.jev("classify", {
    state: flow.input,
    questions: {
      team: choice("Which team can help?", {
        billing: "Charges and payments",
        other: "Everything else",
      }),
    },
  });

  return flow.branch("route", {
    when: classification.answer("team").choice.eq("billing"),
    then: (path) => path.output("billing-team"),
    else: (path) => path.output("support-team"),
  });
});

// 2. Call routeMessage("I was charged twice.") from your Node handler.
export async function routeMessage(message: string) {
  const result = await triage.run({ message }, { client: jev, devtools: inspector });
  console.log(result.outcome); // "billing-team" or "support-team", chosen by the flow
  return result;
}
```


The `triage` definition is built synchronously once; its `jev()` call creates answer references without sending a request. `routeMessage()` uses `await triage.run(...)` to send the request, wait for the selected path, and read the outcome. Call `await routeMessage("I was charged twice.")` from your Node handler. Pass the shared adapter as `devtools` to record the execution and link request traces to steps.

## Read the returned outcome

[`run()`](/api/reference/#flow-run) resolves to `{ runId, outcome, value? }`. In this example, `outcome` is `billing-team` or `support-team`. Your application decides what to do with it. The return value is not inferred as a discriminated union; narrow `value` in application code when needed.

| Returned property | Type | Value in this example |
| --- | --- | --- |
| `runId` | `string` | Unique execution ID. |
| `outcome` | `string` | `"billing-team"` or `"support-team"`. |
| `value?` | `unknown` | Omitted; the terminal paths only name an outcome. |

**Example walkthrough:** `state: flow.input` supplies the message to the Choice question. `answer("team").choice.eq("billing")` creates the routing condition from the chosen label. `branch()` selects one terminal `output()`; the application can then act on that outcome. Passing `devtools: inspector` records the execution with the same adapter as the SDK client.

## Choose the right operation

| If you need to…                         | Use               |
| --------------------------------------- | ----------------- |
| Ask Jev one or more questions           | [`flow.jev()`](/api/reference/#flow-jev)      |
| Run application code or await a service | [`flow.task()`](/api/reference/#flow-task)     |
| Select a path and finish the flow       | [`flow.branch()`](/api/reference/#flow-branch)   |
| Select a value and continue             | [`flow.if()`](/api/reference/#flow-if)       |
| Run independent lanes and join          | [`flow.parallel()`](/api/reference/#flow-parallel) |
| Process a bounded collection            | [`flow.map()`](/api/reference/#flow-map)      |
| Repeat while a state condition holds    | [`flow.loop()`](/api/reference/#flow-loop)     |
| Return a named result                   | [`flow.output()`](/api/reference/#flow-output)   |

See the [flow builder reference](/api/flow/) for options and boundaries, and [expressions](/api/expressions/) for conditions.

## Keep decisions explicit

A returned Choice is used directly. If you introduce thresholds for Noul or Score results, those are application policies. Put them in the flow definition and explain them to readers. A Noul is a probability of true, not a confidence score.

## Handle failure and cancellation

Pass an `AbortSignal` through [`run(input, { client, devtools, signal })`](/api/reference/#flow-run). SDK cancellation propagates to requests. Tasks receive a signal and should cooperate with cancellation. Tasks do not get automatic retries, rollback, or durable execution.

A loop that still needs to continue after its limit rejects with [`FlowLoopLimitError`](/api/reference/#flowlooplimiterror); it does not return a successful partial result. Consult the [full flow guide](https://github.com/diegoag64/jev-devtools/blob/main/docs/flows.md) for error propagation, parallel cancellation, and advanced examples.
