Skip to main content

Overview

flow-state.dev is a block-based AI workflow framework for TypeScript. You compose five block kinds into flows. The framework runs them, streams the output, and manages state. This page is a map, not a textbook.

Five block kinds​

Every piece of logic is exactly one of five kinds:

  • Handler — Pure logic. Validate input, transform data, mutate state, implement tool logic. No LLM, no streaming. Silent by default.
  • Generator — LLM calls. The framework manages prompt assembly, tool loops, streaming, and structured output. This is where AI happens.
  • Evaluator — Typed questions to a model: which option, what score, yes or no. The answers come back typed, ready for your code to branch on.
  • Sequencer — Compose blocks into pipelines. Chain steps, run work in parallel, add rescue boundaries for error recovery.
  • Router — Dispatch to different blocks at runtime based on input or state. Mode switching, intent routing, conditional flows.

All blocks share the same typed contract: input in, output out. Any block composes with any other. Chain them in sequencers, nest them in routers, pass them as tools to generators.

Flows tie everything together​

A flow defines your API: actions (entry points), state schemas, resources, client projections, lifecycle hooks. Register a flow with the server and you get REST endpoints for action execution, session management, SSE streaming, and state snapshots. No route wiring.

See Flows and Actions.

State in four scopes​

State lives in four scopes, each its own record:

The four state scopes
Request, session, user and org state are four separate records, none inside another. Request state belongs to one action run and the next request cannot see it. Session state belongs to one conversation: its state, items, metadata and journal, and the resources an action declares, keyed by the session id and by the tenant when one is sent. User state follows one user across conversations and flows inside one organization, keyed by the user and the organization; with isolateUserState it is one record per flow copy instead. Org state is shared by everyone in the organization, keyed by the org id, or one record per flow copy with isolateOrgState. A session is bound to one organization when it is created; it names its user and org but holds neither one's state. A tenant id splits sessions only. Blocks read and write all four; the browser sees only the fields a scope's client config exposes4 SEPARATE RECORDS · NONE SITS INSIDE ANOTHERSCOPE 01Requestone action runctx.requestScratch space betweenthe blocks of one runKEYthe requestThe next requestcannot see it.SCOPE 02Sessionone conversationctx.sessionIts state, items,metadata and journal,and the resources anaction declaresKEYthe session id, andthe tenant when sentSCOPE 03Userone personctx.userFollows the personacross conversationsand flows in oneorganizationKEYthe userId and orgIdisolateUserStateone record perflow copy insteadSCOPE 04Orgeveryone in the organizationctx.org?Settings and knowledgethe whole team sharesKEYthe orgIdisolateOrgStateone record perflow copy insteadNAMES · HOLDS NO COPY01A session is bound to one organization when it is created. It points at its user and org; it does not copy theirstate.02A tenant id splits sessions only. User and org records are the same across tenants.03Blocks read and write all four. The browser sees only the fields a scope's client config exposes.LASTS ONE ACTION RUNOUTLIVES THE REQUESTNAMES (A REFERENCE)

Each scope supports patchState, setState, incState, pushState, atomicState. Concurrent writers don't corrupt state, but they don't all merge either: on the built-in stores increments and appends both land, and a plain field write is last-write-wins. State Operations has the rule per call. Blocks declare only the state fields they need.

See State and Scopes.

Streaming is structural​

Streaming is not raw text. The framework streams typed items — messages, reasoning, tool calls, state changes, custom components. Each item has a type, a lifecycle (added → content.delta → done), and a sequence number. Clients can disconnect and resume from a cursor. No data loss.

See Items and Streaming.

Everything composes​

Blocks inside blocks. Sequencers as tools. Routers dispatching to sequencers. Type safety from flow definition through the client SDK to React hooks. One Zod schema, validated at runtime and inferred at compile time.

Next steps​