Skip to Content
DocumentationFeaturesHuman-in-the-Loop

Human-in-the-Loop

Pause workflows for user input, review, or confirmation using wait: true transitions and document UI actions.

Wait Transition Pattern

A transition with wait: true pauses the workflow until externally triggered by user interaction:

@Transition({ from: 'waiting_for_user', to: 'ready', wait: true, schema: z.object({ message: z.string() }), }) async userMessage(payload: { message: string }) { await this.repository.save(ClaudeMessageDocument, { role: 'user', content: payload.message, }); }

Document Action Buttons

Documents can include buttons that trigger wait: true transitions:

# Document YAML type: document ui: widgets: - widget: form options: properties: text: title: Text widget: textarea actions: - type: button transition: userResponse # Must match the method name label: 'Submit'

When the user clicks Submit, the workflow’s userResponse method fires with the document’s current content as the payload.

Chat Input Widget

For conversational UIs, use the prompt-input widget:

ui: widgets: - widget: prompt-input enabledWhen: - waiting_for_user options: transition: userMessage
@Transition({ from: 'waiting_for_user', to: 'ready', wait: true, schema: z.string(), }) async userMessage(payload: string) { await this.repository.save(ClaudeMessageDocument, { role: 'user', content: payload, }); }

Confirmation Pattern

Show AI-generated content for user review before proceeding:

@Workflow({ uiConfig: __dirname + '/meeting-notes.ui.yaml', schema: z.object({ inputText: z.string().default('...') }), }) export class MeetingNotesWorkflow extends BaseWorkflow { @InjectTool() claudeGenerateDocument: ClaudeGenerateDocument; @Initial({ to: 'waiting_for_response' }) async createForm() { const args = this.ctx.args as { inputText: string }; await this.repository.save( MeetingNotesDocument, { text: args.inputText, }, { id: 'input' }, ); } // Wait for user to edit and submit @Transition({ from: 'waiting_for_response', to: 'response_received', wait: true }) async userResponse() { const payload = this.ctx.runtime.transition!.payload; await this.repository.save(MeetingNotesDocument, payload, { id: 'input' }); } // AI generates structured output @Transition({ from: 'response_received', to: 'notes_optimized' }) async optimizeNotes() { await this.claudeGenerateDocument.call({ claude: { model: 'claude-sonnet-4-6' }, response: { id: 'final', document: OptimizedNotesDocument }, prompt: `Structure these notes...`, }); } // Wait for user to confirm @Final({ from: 'notes_optimized', wait: true }) async confirm() { const payload = this.ctx.runtime.transition!.payload; await this.repository.save(OptimizedNotesDocument, payload, { id: 'final' }); } }

enabledWhen — Conditional Widgets

Show/hide widgets based on the current workflow place:

ui: widgets: - widget: form enabledWhen: - review - editing options: properties: summary: title: Summary widget: textarea actions: - type: button transition: confirm label: 'Confirm'

The widget only appears when the workflow is at the review or editing place.

Registry References

Last updated on