Skip to Content

State Management

Workflow state is managed through a typed state interface and passed as the first parameter to transition methods. State is returned from each transition and automatically persisted across transitions.

Defining State

Define a state interface and pass it as a generic to BaseWorkflow:

interface MyState { counter: number; llmResult?: LlmGenerateTextResult; items: string[]; } export class MyWorkflow extends BaseWorkflow<Record<string, unknown>, MyState> { // ... }

Writing State

Return updated state from transition methods:

@Transition({ from: 'ready', to: 'processed' }) async process(state: MyState): Promise<MyState> { const result = await this.llmGenerateText.call( {}, { config: { provider: 'claude', model: 'claude-sonnet-4-6' } }, ); return { ...state, llmResult: result.data, counter: (state.counter ?? 0) + 1, items: [...(state.items ?? []), 'new item'], }; }

Reading State

Access state in any transition or guard method:

@Transition({ from: 'processed', to: 'end' }) async display(state: MyState): Promise<unknown> { await this.documentStore.save(MessageDocument, { role: 'assistant', content: `Processed ${state.counter} items. Result: ${state.llmResult?.content}`, }); return {}; } hasToolCalls(state: MyState): boolean { return state.llmResult?.message.stopReason === 'tool_use'; }

Persistence Across Pauses

State survives when a workflow pauses at a wait: true transition and resumes later:

@Transition({ to: 'waiting' }) async setup(state: MyState): Promise<MyState> { return { ...state, counter: 42 }; // Set before pause } @Transition({ from: 'waiting', to: 'end', wait: true }) async onResume(state: MyState): Promise<unknown> { // state.counter is still 42 return {}; }

Accessing Workflow Args

Input arguments are available via ctx.args:

@Workflow({ schema: z.object({ value: z.number().default(150) }), }) export class MyWorkflow extends BaseWorkflow<{ value: number }, MyState> { @Transition({ to: 'ready' }) async setup(state: MyState, ctx: LoopstackContext): Promise<MyState> { const args = ctx.args as { value: number }; console.log(args.value); // 150 return state; } }

Helper Methods

Use regular private methods for reusable logic — no special decorator needed:

export class MyWorkflow extends BaseWorkflow<Record<string, unknown>, MyState> { @Transition({ from: 'data_created', to: 'end' }) async showResults(state: MyState): Promise<unknown> { await this.documentStore.save(MessageDocument, { role: 'assistant', content: this.formatMessage(state.message!), }); return {}; } private formatMessage(text: string): string { return text.toUpperCase(); } }

Registry References

Last updated on