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.
modelsFlowStateModelsConfig—Shorthand 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.
settingsobject—Read in blocks as ctx.settings. Type it by declaration-merging FlowStateSettings. Process-wide and one shape for every flow; for settings that differ between two registered copies of one flow, use config, read as ctx.flow.config.
voice{ provider? }—Runtime voice provider (TTS/STT). Distinct from a flow's voice speak defaults.
onError(error, { method, path }) => void—HTTP-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) => void—Keep-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.
dispatchDrainTimeoutMsnumber30000How long dispose() waits for dispatch runs still running in this process. 0 skips the wait and still reports what was left. The default is sized for a serverless shutdown grace period, so raise it past your longest expected run on a long-lived host doing slow background work. 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 on a loopback host.
debugAllowAnonymousLocalbooleantrue when FSDEV_DEBUG_ALLOW_ANONYMOUS_LOCAL is 1, else falseLet debug routes accept requests with no Origin header (curl, scripts, same-origin browser GETs). Off, they get 403. A request whose Origin is not loopback is rejected either way; createFlowState has no origin allowlist (createFlowApiRouter takes one as debugAllowedOrigins). fsdev dev turns it on for a loopback host; fsdev serve does not. Only for a server bound to loopback.
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, internal and task cannot be added.
maxChildSessionListLimitnumber100Largest limit the child-session listing route accepts. The listing is all-time history, so it outgrows any fixed page a client would ask for.
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 object—Sweeper for expired suspensions, leases, and orphaned checkpoints. Only with durability on.
workerWorkerAdapter—Execution backend (for example bullmqWorker(...)). Mutually exclusive with dispatcher.
dispatcherFlowDispatcherin-processLow-level "where actions run" hook. Prefer worker.
adaptersinbound adapters—Custom 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 id—Fallback 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.
providerPreferencepreference—Reorder 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 Dispatched work and Background jobs with BullMQ.

See also​