New to flows? Start with Flows from scratch, which introduces each method with a small example. For exact inputs and return values, use the API glossary.
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, branch, or state. Local variables and example-specific names stay plain code. Press Escape to dismiss the tooltip.
import { choice } from "@typesafe-ai/sdk";
import { defineJevFlow } from "jev-devtools/flow";
import { inspector, jev } from "./server.js";
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"),
});
});
export async function routeMessage(message: string) {
return triage.run({ message }, { client: jev, devtools: inspector });
}Describe a flow once, then run it with different inputs. Defining it does not send requests.
defineJevFlow<Input>(name, build)- name
- The flow name shown in DevTools.
- build
- Receives a builder. Return an output or a terminal branch.
Add a Jev request step. It returns references to answers you can use in later steps.
flow.jev(id, { state, questions }, display?)- id
- A unique step ID within this flow.
- state
- Context sent to Jev for this request. Flow references are resolved when the step runs.
- questions
- Named questions to ask. Their keys identify the returned answers.
- display?
- Optional labels and display metadata for DevTools.
Context sent to Jev for this request. Flow references are resolved when the step runs.
stateA reference to the data passed to run(). Inside map/loop programs, it refers to the item or iteration input instead.
flow.input: FlowExpression<Input>Named questions to ask. Their keys identify the returned answers.
questionsFinish the flow by choosing one of two paths. Each path must return an output or another branch.
flow.branch(id, { when, then, else }, display?)- id
- A unique step ID within this flow.
- when
- A flow condition evaluated when the branch runs.
- then
- The path used when the condition is true.
- else
- The path used when the condition is false.
- display?
- Optional labels and display metadata for DevTools.
A flow condition evaluated when the branch runs.
whenReference the result of a named question. The name must match a key in questions; answer fields can be used in later steps.
request.answer(name)A reference to the label selected by a Choice question. Its allowed labels come from that question’s alternatives.
choice: FlowExpression<Label>Build an equality condition. It compares matching scalar values when the flow runs; it does not compare values during definition.
expression.eq(value)The path used when the condition is true.
thenFinish this path with a named outcome and, optionally, a value for your application.
flow.output(name, value?, display?)- name
- The outcome string returned by run().
- value?
- Optional result data. Narrow its type in your application before using it.
- display?
- Optional labels and display metadata for DevTools.
The path used when the condition is false.
elseExecute the flow with new input. Resolves to { runId, outcome, value? }.
program.run(input, { client, devtools?, signal? })- input
- Data matching the input type declared in defineJevFlow.
- client
- The TypeSafe-compatible client that executes Jev requests.
- devtools?
- An optional observer, such as the shared server adapter, that records execution.
- signal?
- An optional AbortSignal to cancel the run.
The TypeSafe-compatible client that executes Jev requests.
clientAn optional observer, such as the shared server adapter, that records execution.
devtoolsAPI reference: defineJevFlow() · flow.jev() · flow.input · flow.branch() · answer.choice · expression.eq() · flow.output() · program.run()
The triage definition is built once. routeMessage() runs it with new input each time. Pass the shared adapter as devtools to record the execution and link request traces to steps.
Read the returned outcome#
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() |
| Run application code or await a service | flow.task() |
| Select a path and finish the flow | flow.branch() |
| Select a value and continue | flow.if() |
| Run independent lanes and join | flow.parallel() |
| Process a bounded collection | flow.map() |
| Repeat while a state condition holds | flow.loop() |
| Return a named result | flow.output() |
See the flow builder reference for options and boundaries, and 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 }). 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; it does not return a successful partial result. Consult the full flow guide for error propagation, parallel cancellation, and advanced examples.