jev / devtoolsDocumentation

Guides

Build a flow

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

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.

ts · API help
import { choice } from "@typesafe-ai/sdk";
import { defineJevFlow } from "jev-devtools/flow";
import { inspector, jev } from "./server.js";

export const triage = <{ message: string }>("triage", (flow) => {
  const classification = flow.("classify", {
    : flow.,
    : {
      team: choice("Which team can help?", {
        billing: "Charges and payments",
        other: "Everything else",
      }),
    },
  });

  return flow.("route", {
    : classification.("team")..("billing"),
    : (path) => path.("billing-team"),
    : (path) => path.("support-team"),
  });
});

export async function routeMessage(message: string) {
  return triage.({ message }, { : jev, : inspector });
}

API 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.

Jev DevTools is an independent, open-source community project. It is not affiliated with TypeSafe AI.