Skip to main content

Runtime options

createFlowState({ ... }) is the process-level config. It registers flows, builds the model resolver, opens store profiles, and returns a handle you mount as HTTP (and export from fsdev.config.ts for the CLI).

import { createFlowState, inMemoryStores } from "@flow-state-dev/engine";
import chatFlow from "./src/flows/hello-chat/flow";

export const flowstate = createFlowState({
flows: { chat: chatFlow },
models: { default: "openai/gpt-5.4-mini" },
stores: { default: { primary: inMemoryStores() } },
});

The handle builds the router lazily on the first getRouter() / ready() call.

Method / fieldWhat it does
getRouter()Resolve HTTP handlers. First call opens stores.
getRuntime(){ registry, stores, runtimeConfig } for workers and scripts. Same instances the router uses.
ready()Eager warmup. Idempotent.
dispose()Close workers, then store adapters.
activeProfileResolved profile name. Reading it before ready() resolves the profile and throws if FSD_ENV names a missing one.
settingsThe settings bag you passed in.
meta{ flowKeys, profileKeys, declaredSlots, devtool? }.

Narrative: Engine setup, App configuration, Persistence.

createFlowState fields

FieldTypeDefaultWhat it does
flowsRecord<string, FlowInstance>requiredRegistered flows. Keys are stable ids for the registry.
storesStoresConfigrequiredNamed profiles. At least one. See Stores.
modelsFlowStateModelsConfigShorthand model resolver. See Models.
modelResolverModelResolverbuilt from modelsEscape hatch. When set, models is ignored. Use for mocks or a fully custom resolver.
defaultProfilestringfirst declared profileActive profile when FSD_ENV is unset. NODE_ENV is not consulted.
settingsobjectRead in blocks as ctx.settings. Type it by declaration-merging FlowStateSettings.
voice{ provider? }Runtime voice provider (TTS/STT). Distinct from a flow's voice speak defaults.
onError(error, { method, path }) => voidHTTP-level error sink.
errorCapturehandleroffBlock-aware sink: failing block identity plus flow / request / session / user ids. You write the adapter; the framework ships no vendor SDK.
onBackgroundWork(promise) => voidKeep-alive for work that outlives the response. On Vercel, pass (p) => after(() => p).
detectInterruptedOnStartupbooleantrueScan for interrupted requests at boot. Turn off on serverless if the scan contends with the first request.
detachedDrainTimeoutMsnumber30000How long dispose() waits for in-process detached work. 0 skips the wait and still reports what was left. It does not cover the worker shutdown that follows: dispose() then closes the worker, and an adapter like BullMQ waits — outside this ceiling — for jobs this process already claimed. Queued work no worker has claimed is not waited on. Size a host's shutdown grace period for both phases, not this value alone.
debugEndpointsEnabledbooleanfalsePrivileged debug routes. fsdev dev sets FSDEV_DEBUG_ENDPOINTS=1.
defaultSseHeartbeatMsnumber15000Router-level SSE ping when a flow does not set request.sseHeartbeatMs.
staleSweepIntervalMsnumber30000Stale-request sweeper cadence. 0 disables.
staleSweepThresholdMsnumber60000Heartbeat age after which a running request is stale.
queuedGraceMsnumber600000How long an unclaimed queued request may sit before a sweep treats it as lost. Must be finite.
publicReentrySourcesstring[]built-in caller-addressed sourcesExtra inbound source values allowed on retry / continue / resume. webhook and the detached-dispatch source cannot be added.
maxWorkstreamListLimitnumber100Largest limit the workstream list route accepts.
durablebooleanfalseInstall the default checkpoint provider so actions with durable: true can recover and ctx.suspend(). Needs a persistent store to survive a process restart.
durabilityRetentionretention objectSweeper for expired suspensions, leases, and orphaned checkpoints. Only with durability on.
workerWorkerAdapterExecution backend (for example bullmqWorker(...)). Mutually exclusive with dispatcher.
dispatcherFlowDispatcherin-processLow-level "where actions run" hook. Prefer worker.
adaptersinbound adaptersCustom transports forwarded to the router.
resolvePrincipalPrincipalResolverbody userIdHost fallback when a flow has no authentication.resolvePrincipal. Per-flow auth always wins.
chat{ default?: string }fsdev chat target when you omit the flow argument: "<kind>" or "<kind>.<action>". Not the flow's chat subscription map.
devtool{ userId?, bearerToken? }Only fsdev dev reads this. Identity and bearer token for the loopback DevTool page. Production serve ignores it.

Models

models is a reshaped createModelResolver config.

FieldTypeDefaultWhat it does
defaultmodel idFallback when an intent/<name> string cannot resolve. Required once you declare any intents.
intentsRecord<string, string[]>Named ladders. model: "intent/chat" walks intents.chat and takes the first candidate with a key and an installed SDK.
keysRecord<string, string>process.envExplicit API keys.
gatewaysgateway mapauto from envGateway instances or configs.
providersprovider mapauto from envPre-built AI SDK provider instances.
providerPreferencepreferenceReorder intent candidates by provider.
retryPolicyRetryPolicy2 attempts, 1s baseFallback retry when a candidate fails.

Env overrides (FSDEV_INTENT_*, FSDEV_DEFAULT_MODEL) are listed under Environment. Depth: Models.

Stores

stores is Record<profileName, { primary, blobs?, queue?, scheduler? }>.

Profile resolution, first match wins:

  1. process.env.FSD_ENV
  2. defaultProfile
  3. The first key in the stores object
SlotWhat it holds
primaryRequired. Sessions, state, items, requests, content, checkpoints, traces, suspensions, leases.
blobsReserved. Backs no store today.
queueReserved. Backs no store today.
schedulerReserved. Backs no store today.

Only primary resolves into the store registry. blobs, queue, and scheduler are forward-compatible slots: the adapter you name must declare the capability (construction throws if it does not), but declaring one configures nothing. Durable job queues come from a worker adapter; schedules are fired by the host scheduler, not by a store — see Scheduled actions.

Built-in adapters:

FactoryPackageTypical use
inMemoryStores()@flow-state-dev/engineTests and throwaway local runs. Nothing survives dispose().
filesystemStores({ rootDir })@flow-state-dev/engineLocal files under .fsdev/data.
sqliteStores({ filename })@flow-state-dev/store-sqliteSingle-process durable store.
postgresStores({ connectionString })@flow-state-dev/store-postgresMulti-instance production.

inMemoryStores accepts optional cas and traceStore.maxRequests. filesystemStores also accepts developmentOnly and onPersistError. See Persistence.

Workers

worker is an adapter such as bullmqWorker(...) from @flow-state-dev/bullmq. The adapter's mode decides which sides this process runs:

ModeThis process
"colocated" (default)Enqueue and consume. Local-dev default.
"dispatch-only"Enqueue only (web process).
"worker-only"Consume only. Call ready() so the worker starts; you typically do not serve the router.

worker and dispatcher cannot both be set. See Detached work and Background jobs with BullMQ.

See also