Skip to main content

Flow options

defineFlow({ ... }) returns a factory. Call the factory (defineFlow({ ... })() or defineFlow({ ... })({ id: "default" })) to get a registerable instance. The definition is the contract; the instance is what you pass to createFlowState.

import { defineFlow, generator } from "@flow-state-dev/core";
import { z } from "zod";

const inputSchema = z.object({ message: z.string() });

export default defineFlow({
kind: "support",
requireUser: true,
actions: {
chat: {
inputSchema,
block: generator({
name: "chat",
model: "intent/chat",
prompt: "You answer support questions.",
inputSchema,
history: true,
user: (input) => input.message,
itemVisibility: { client: true, history: true },
}),
userMessage: (input) => input.message,
},
},
session: {
stateSchema: z.object({ ticketId: z.string().nullable().default(null) }),
client: { expose: ["ticketId"] },
historyWindow: { turns: 50 },
},
})();

Narrative: Flows, Actions, State and scopes.

defineFlow fields

FieldTypeDefaultWhat it does
kindstringrequiredFlow type id. Becomes the URL segment /api/flows/:kind.
actionsRecord<string, ActionConfig>requiredCaller-addressed entry points (HTTP and, when enabled, MCP).
requireUserbooleantrueShorthand for authentication.requireUser. If both are set, authentication.requireUser wins.
authenticationAuthenticationConfigPer-flow principal resolution. See Authentication.
sessionSessionConfigSession state, client projection, retention, history window.
requestRequestConfigRequest-scoped state, lifecycle hooks, heartbeats, concurrency default.
userUserConfigUser-scoped state and client projection.
orgOrgConfigOrg-scoped state and client projection.
resourcesresource mapFlat map of defineResource / collection declarations. Each resource's own scope decides where it persists.
toolsToolsConfigDefault timeout, concurrency, retry, and lifecycle hooks for tools.
voiceVoiceConfigFlow-level TTS provider and speak defaults.
mcpMcpConfigoffOpt-in MCP exposure for this flow. Definition-only: you cannot override it on the instance.
chatChatConfigChat-transport event bindings. Definition-only.
webhooksWebhookConfigWebhook event bindings. Definition-only.
schedulesSchedulesConfigStatic and dynamic scheduled actions. Definition-only.
tokenCounterTokenCounterCustom token accounting.
costEstimatorCostEstimatorCustom USD cost estimate from model usage.
isolateUserStatebooleanfalseKey user state (and the default for user resources) per flow kind. A resource's own flowIsolation always wins.
isolateOrgStatebooleanfalseOrg-scope equivalent of isolateUserState.
defaultBlockRendererrenderer or falseDefault UI renderer for blocks in this flow.

mcp, chat, webhooks, and schedules belong on the definition. Passing them to the factory call (defineFlow({ ... })({ mcp: ... })) is rejected.

Actions

Each key in actions is a public name. Clients call it with sendAction("chat", { message }) or POST /api/flows/:kind/actions/chat.

FieldTypeDefaultWhat it does
blockBlockDefinitionrequiredThe block that runs.
inputSchemaZod schemathe block's schemaPublic input surface. Set this when the HTTP/MCP contract should differ from the block (richer .describe(), a narrower public shape).
descriptionstringRequired when the action is MCP-exposed. DevTool uses it for tooltips either way.
userMessage(input) => stringUser-visible message recorded for the turn.
concurrencyConcurrencyConfigflow request.concurrency, else "allow"What happens if another request on the same key is in flight. See Concurrency policies.
durablebooleanfalseCheckpoint at step boundaries and allow ctx.suspend(). Needs durable: true on createFlowState.
tokenBudget{ maxTotalTokens, warnAt?, onExceeded? }Cap tokens for the action. onExceeded is "error", "stop", or "warn".
onCompleted / onErroredBlockDefinitionAction-level hooks after the root block finishes or throws.
mcpActionMcpConfigexposed when the flow enables MCPPer-action MCP overrides.

action.mcp

FieldTypeDefaultWhat it does
enabledbooleantrueSet false to keep the action off the MCP surface.
namestringderived from the action key (recordPaymentrecord_payment)MCP tool name. Must match [A-Za-z0-9_.-]{1,128}.
sessionstring or { fromInput: string }fresh ephemeral session per tools/callHow the adapter picks a flow sessionId. A string is a mint template (* → random token). { fromInput } reads a field from the tool input. The principal still comes from resolvePrincipal.

Authentication

FieldTypeDefaultWhat it does
resolvePrincipal(ctx) => principal | nullMap the inbound request to { userId, orgId? }. Throw a PrincipalResolutionError to pick the HTTP status (401/403).
defaultUserIdstringUsed when the resolver returns no userId. Typical for schedules and webhooks.
requireUserbooleantrueReject requests that still have no userId after the fallback. false forbids user-scoped state, client projections, and resources at registration.
requireOrgbooleanReserved. No runtime effect today.

The host verifies credentials. The framework applies defaultUserId and requireUser after your resolver returns. See Authentication.

Session, user, and org

user and org accept stateSchema, cas, and client (same shape as session, minus retention and history).

session

FieldTypeDefaultWhat it does
stateSchemaZod objectSession state shape. Use .nullable().default(null) for fields that start empty.
client{ expose?, derived? }privateWhat crosses to the browser under clientData.session. expose copies named fields verbatim. derived computes named projections from { state, resources }. Names must not collide.
metadataZod schemaDeclares the session metadata shape (title, tags, and so on) for typing. Not enforced at runtime today: neither the session-metadata route nor ctx.session.setMetadata() parses against it, so a value outside the schema is persisted unchanged.
retention{ maxItems?, maxAge? }unboundedBounds the persisted item log. maxAge is milliseconds or a duration string ("7d"). Oldest completed requests evict first.
historyWindow{ turns: number }50Caps cross-turn history loaded per request. 0 or a negative number disables it. Per-call history({ limit }) can only shrink this window.
casCASOptionsOptimistic-concurrency options for this scope.

clientData on a scope still works and logs a deprecation warning. Use client.derived.

request

FieldTypeDefaultWhat it does
stateSchemaZod objectRequest-scoped state.
onStarted / onCompleted / onErrored / onFinished / onStepErroredBlockDefinitionRequest lifecycle hooks.
heartbeatIntervalMsnumber10000Active-request heartbeat. 0 disables the heartbeat and cross-process abort delivery.
sseHeartbeatMsnumber15000SSE : ping cadence. 0 disables.
concurrencyConcurrencyConfig"allow"Default for actions that omit concurrency.
mutationTimeoutMsnumber30000Budget for in-memory state writes. Infinity disables. Persistent stores use CAS retries instead.
cleanupCheckpointsOnTerminalbooleanfalseDelete durable sequencer checkpoints when the request finishes.

Tools defaults

tools.defaults applies to tools on generators in this flow.

FieldTypeDefaultWhat it does
defaults.timeoutMsnumberTool timeout.
defaults.concurrency"parallel" | "serial"Whether tools in a step run together or one at a time.
defaults.retryRetryPolicy{ maxAttempts?, baseDelayMs?, maxDelayMs?, retryableErrors? }.
onToolStarted / onToolCompleted / onToolErroredhook or blockObserve tool lifecycle. Cache hits still fire started/completed; errors are never cached.

A generator can override these with flowTools.

Inbound transports

These maps live on the flow definition. The matching adapter has to be mounted on the runtime for anything to listen. See Engine setup and the transport pages linked below.

mcp

FieldTypeDefaultWhat it does
enabledbooleanfalseMount MCP for this flow.
exposeResourcesbooleantrueInclude flow resources in resources/list and resources/read, honoring client.content.read.

See MCP.

schedules

FieldTypeDefaultWhat it does
staticRecord<id, ScheduleConfig>Cron entries looked up by id first.
resolve(id, ctx) => ScheduleConfig | nullDynamic lookup when static[id] is missing. Return null to 404.

Each ScheduleConfig extends the action core (block, inputSchema, hooks, durable, …) plus:

FieldTypeDefaultWhat it does
cronPOSIX 5-field stringrequiredDisplay-only. The host scheduler fires; the framework does not.
inputvalue or () => inputPassed to the handler.
principal{ userId, orgId? }gateway principalWho the action runs as.
timezoneIANA string"UTC"Metadata for the host scheduler.
onOverlap"skip" | "allow""skip"What to do if the same schedule id is already running.
descriptionstringListing and DevTool.
enabledbooleantrueDisabled static schedules list but 404 on dispatch.

See Scheduled actions.

chat and webhooks

chat.on and each provider's webhooks.<provider>.on map event keys to a binding that extends the action core (block plus execution policy). They are not entries in actions. See Chat and Webhooks.

voice

voice.provider sets the TTS/STT implementation for this flow, and voice.tts carries the speak defaults (model, voice, speed). Those three are catalogued with the rest of the voice surface on Voice.

Resources

Declare resources on the flow (or on a block / capability). scope on defineResource decides the storage layer.

FieldTypeDefaultWhat it does
scope"session" | "user" | "org"requiredWhere state and content persist.
stateSchemaZod objectrequiredResource state shape.
refstringaccessor keyStorage namespace id.
defaultJSON valueInitial state.
flowIsolationbooleanfalsePer-flow keying for user/org resources. Rejected on session scope.
sharedToWorkstreambooleanfalseSession resources resolve against the lineage root so child workstreams share them. Session-scope only.
prefetchMode"eager" | "lazy""eager"When the runtime loads the resource.
llmReadable / llmWritablebooleanWhether generators may read or write the resource.
clientResourceClientConfigomitted — state stays privateOpens the resource to clients. For a single resource, declaring expose, exclude, or data (mutually exclusive) is the opt-in; a client carrying only content keeps state private. Collections gate state on state.read and ship the full item state when no projection is set. See Client access.
content / contentFile / contentTemplatecontent sourceMutually exclusive ways to supply a body.

Collections add pattern, maxInstances, eviction ("none" | "lru" | "oldest"), and create/delete hooks. See Resources and Collections.

See also