Reference
Flow
The abstract Flow class: subclass hooks, the model declaration, the step registry, the restore hook, and the flow-owned data helpers that steps read through.
Flow is the durable workflow boundary. One Flow subclass corresponds to one registered
name, one default model selection, one step registry, one tool registry, and exactly one
flow envelope inside a session document.
export abstract class Flow {
static get id(): string; // defaults to the class name
public constructor();
protected abstract configModel(): ModelSelection;
protected configLlmCallPolicy(): LlmCallPolicy;
}
A flow instance is constructed fresh for every request and thrown away when the turn ends. Nothing on the instance survives except what is written into the session document.
Subclass hooks
| Hook | Signature | Default | Override when |
|---|---|---|---|
configModel() |
protected abstract configModel(): ModelSelection |
Abstract — you must implement it | Always |
configLlmCallPolicy() |
protected configLlmCallPolicy(): LlmCallPolicy |
{} — no deadline |
Model invocation attempts need a Flow-wide wall-clock budget |
init() |
public async init(): Promise<void> |
No operation | Deterministic per-instance setup is needed before steps are collected |
defineSteps() |
protected defineSteps(): Step[] |
[new TerminateSessionStep(this).useMemory('temp')] |
The flow declares its own stages |
initialStep() |
protected initialStep(): StepClassType | null |
null — the first step from defineSteps() starts the session |
The initial cursor depends on runtime context |
defineTool() |
public defineTool(): ToolType[] |
[] |
Several steps share one tool definition |
onRestoreSessionDoc() |
protected onRestoreSessionDoc(sessionDoc: SessionType): Promise<SessionType | null> |
Accepts only the current document version | The stored document needs migration, an idle-time reset, or another stricter policy |
spawnSteps() |
protected spawnSteps(): Promise<string> |
Returns '' |
config._concurrent should coordinate worker sessions |
run() |
public run(message: string): Promise<RunResponseType> |
Dispatches to spawnSteps() or the current step, then builds the response envelope |
The whole dispatch contract intentionally differs |
isBatch() |
public isBatch(): boolean |
false |
An extra pre-run session checkpoint is required |
configModel()
protected abstract configModel(): ModelSelection;
Declares the flow’s default provider, model, parameters, and runner retry policy, independently from step
composition. It is resolved lazily on first use, validated through PicoModelCatalog, and
then validated again against the registered provider adapter during bootstrap.
protected configModel() {
return {
provider: "openai",
name: "gpt-4o",
params: { temperature: 0.2 },
retryAttempts: 3,
} as const;
}
Every step without its own useModel(...) override inherits this selection, including
retryAttempts. See
Model catalog for the typing rules.
configLlmCallPolicy()
protected configLlmCallPolicy(): LlmCallPolicy;
Declares code-owned policy for each model invocation attempt independently from provider
parameters. { timeoutMs: 60_000 } applies to every Step by default. A Step may override
the same hook with another positive integer, or return { timeoutMs: null } to remove the
inherited deadline. The policy is not persisted in the session document.
init()
public async init(): Promise<void>;
Called by FlowCreator.create() after the request config has been added as context, but
before collectSteps() and before bootstrap() binds the FlowEngine. getFlowEngine()
therefore throws inside init(). Use it only for setup that needs neither the engine nor a
loaded session.
defineSteps()
protected defineSteps(): Step[];
Constructs the step registry. Steps are keyed by Step.id, which defaults to the class name,
so the returned array is a registry rather than a graph — transitions live in the handlers.
protected defineSteps(): Step[] {
return [
new CollectCustomerStep(this).useMemory("customer"),
new TerminateSessionStep(this).useMemory("end"),
];
}
Any step reachable by go(...), runStep(...), runSteps(...), or a logic response must
appear here. Registering a different set of steps between turns can strand a restored
session whose flow.currentStep no longer resolves.
initialStep()
protected initialStep(): StepClassType | null;
Returns the class whose id becomes flow.currentStep for a new session. Returning null
selects the first entry of defineSteps(). A returned class that is not registered fails
during document creation with Initial step '<name>' is not defined in flow '<id>'.
protected initialStep() {
return this.getContext<boolean>("config.isPresident")
? PresidentStep
: WeatherStep;
}
defineTool()
public defineTool(): ToolType[];
Flow-level tool definitions. They are merged with every step’s defineTool() into one
flow-wide registry during bootstrap(). Duplicate names throw
Duplicate tool '<name>' registered in flow '<id>'. See Defining and handling tools.
onRestoreSessionDoc()
protected async onRestoreSessionDoc(
sessionDoc: SessionType,
): Promise<SessionType | null>;
The compatibility boundary for a stored session. Return the (optionally mutated) document to
continue restoring it; return null to abandon it and create a fresh session document.
The default implementation is:
if (!this.isSessionCurrent(sessionDoc)) return null;
return sessionDoc;
isSessionCurrent(doc) compares doc.version against K.sessionDocVersion.
sessionIdleMs(doc) returns the time since saveOn, allowing an override to
enforce its own idle policy. Both are protected and overridable.
When the hook returns a document, bootstrap() saves it immediately through the normal
compare-and-swap path before reading step state. The hook runs only for a document that
already exists, is not completed or aborted, belongs to this flow, and satisfies the
one-flow invariant. See Session document migration.
spawnSteps()
protected async spawnSteps(): Promise<string>;
Called by run() instead of the current step when getContext<boolean>('config._concurrent')
is truthy. Pair it with concurrentSteps(...):
public async concurrentSteps<T>(options: {
items: T[];
batchSize: number;
onConfig: (item: T) => object;
onBotResponse: (item: T, response: any) => void;
}): Promise<void>;
concurrentSteps() slices items into sequential batches, runs each batch with
Promise.all, and issues one SelfClient POST per item to SELF_URL carrying
{ flowName, config }. Each item therefore gets its own session document. Returning a string
from spawnSteps() does not complete the coordinator session; call markCompleted() or
sessionCompleted() explicitly. See Concurrent batch mode.
run()
public async run(message: string): Promise<RunResponseType>;
type RunResponseType = {
success: boolean;
completed: boolean;
message: string;
session: string;
contentType: HttpContentType;
};
completed is requireCurrentStep().isEnd() and contentType is that same step’s
contentType. Overriding run() takes over cursor selection, completion reporting, and
content conversion; prefer step hooks.
isBatch()
public isBatch(): boolean;
When true, FlowEngine saves the session once before calling run(). It does not select
the spawnSteps() path — that is decided by config._concurrent.
Flow-owned data and helpers
Cursor movement
| Method | Signature | Notes |
|---|---|---|
goto |
goto(stepClass: StepClassType): Promise<Step> |
Moves the one durable cursor. Calls onExit() on the old step and onEnter() on the new one, and appends a sequence entry |
gotoByName |
gotoByName(stepName: string): Promise<Step> |
The string form used by go("StepName") |
Both throw if called from inside a nested execution frame:
Cannot goto '<name>' from a child execution frame. A child returns a result to its owner
instead. Moving to the step that is already current is a no-op.
Context
| Method | Signature |
|---|---|
getContext |
getContext<T = JsonObject>(key?: string): T |
addContext |
addContext(json: object): void |
setContext |
setContext(context: object): void |
Context is seeded from the first request’s config, stored as { config: ... }, and
persisted in flow.context. getContext is a lodash path read, so getContext<string>('config.tenantId')
works. A restored session keeps its stored context; a new config on a later turn does not
replace it.
Step state
| Method | Signature |
|---|---|
getStepState |
getStepState<T = JsonObject>(stepClass: StepClassType, key?: string): T |
saveStepState |
saveStepState(stepClass: StepClassType, json: JsonObject, stateType?: SaveStateType): void |
saveTransientStepState |
saveTransientStepState(stepClass: StepClassType, json: JsonObject): void |
SaveStateType is an enum with transient and persistent; persistent is the default.
Transient state is stored under the _transient key and stripped on write. All three throw
if the class is not registered.
Memory
public getMemory(): Memory;
public getMemory(nameSpace: string): MessageTypes[];
The no-argument form returns the flow’s Memory container, which is where summary policy is
configured:
this.getMemory()
.setSummaryModel({ provider: "openai", name: "gpt-4o" })
.setSummaryConfig({ minMessages: 16, recentMessages: 8 })
.enableSummary("conversation");
The string form returns the raw message array for one namespace. Namespaces must match
/^[A-Za-z][A-Za-z0-9_-]{0,127}$/. Compaction runs inside saveSession(); a failure is
recorded as a session warning rather than failing the turn.
Session access and completion
| Method | Signature | Purpose |
|---|---|---|
getSessionDoc |
getSessionDoc(): SessionType |
The whole document. Throws before bootstrap |
getSessionId |
getSessionId(): string |
The session ID |
markCompleted |
markCompleted(): void |
Sets runStatus to completed |
getFlowEngine |
getFlowEngine(): FlowEngine |
Throws until bootstrap() has bound the engine |
id |
static get id(): string / protected get id(): string |
The registered name; defaults to the class name |
Overriding static get id() decouples the public flow name from a TypeScript class rename.
The registered name must equal FlowClass.id — see FlowEngine.
Step and tool lookup
| Method | Signature | Throws when missing |
|---|---|---|
getCurrentStep |
getCurrentStep(): Step | null |
No |
requireCurrentStep |
requireCurrentStep(): Step |
Yes |
getExecutingStep |
getExecutingStep(): Step | null |
No |
requireExecutingStep |
requireExecutingStep(): Step |
Yes |
getStep |
getStep(stepName: string): Step | undefined |
No |
requireStep |
requireStep(stepName: string): Step |
Yes |
getTool |
getTool(name: string): DynamicStructuredTool | undefined |
No |
requireTool |
requireTool(name: string): DynamicStructuredTool |
Yes |
getCurrentStep() reads the durable cursor. getExecutingStep() returns the innermost step
of the current nested execution frame, falling back to the durable cursor when no frame is
open — that is the accessor the runner uses.
Use getCurrentStep() or requireCurrentStep() for the durable cursor. Inside nested execution, use getExecutingStep() or requireExecutingStep() when you need the step owning the current async frame.
Runtime plumbing
These are public because the runtime uses the same class. Application flows should not call
or override them: bootstrap, collectSteps, saveSession, tallyToken, setMemory,
withNestedSequence, and enterChild.