Skip to content

Human-in-the-Loop

FlowDrop’s interrupt system enables workflows to pause execution and request user input before continuing. This is essential for approval workflows, data collection, decision points, and quality control.

Simple yes/no prompt for binary decisions:

{
"interrupt_type": "confirmation",
"message": "Do you approve sending this email to 150 recipients?",
"confirm_label": "Yes, send email",
"cancel_label": "No, cancel"
}

Response: boolean

Single or multiple selection from predefined options:

{
"interrupt_type": "choice",
"message": "Select the output format:",
"options": [
{ "value": "json", "label": "JSON", "description": "Structured data" },
{ "value": "csv", "label": "CSV", "description": "Spreadsheet format" }
],
"multiple": false
}

Response: string (single) or string[] (multiple)

Free-form text entry:

{
"interrupt_type": "text_input",
"message": "Provide additional context:",
"placeholder": "Enter your notes...",
"multiline": true,
"max_length": 1000
}

Response: string

Complex data entry using JSON Schema:

{
"interrupt_type": "form",
"message": "Complete the configuration:",
"schema": {
"type": "object",
"properties": {
"priority": { "type": "string", "enum": ["low", "medium", "high"] },
"notify": { "type": "boolean", "title": "Send notification" }
}
},
"default_values": { "priority": "medium", "notify": true }
}

Response: object (matching schema structure)

Review proposed field changes with per-field accept/reject decisions and visual diffs:

{
"interrupt_type": "review",
"message": "Review these proposed changes:",
"changes": [
{
"field": "title",
"label": "Page Title",
"original": "About Us",
"proposed": "About Our Company"
},
{
"field": "body",
"label": "Body Content",
"original": "<p>Welcome to our site.</p>",
"proposed": "<p>Welcome to our company website.</p>"
}
]
}

Response: ReviewResolution with per-field decisions and summary counts.

UserFrontendBackendWorkflow ExecutionUserFrontendBackendWorkflow ExecutionPause & create interruptSend interrupt requestRender promptSubmit responseResolve interruptResume workflow

The ChatPanel automatically detects and renders interrupts in messages. For manual integration:

import {
interruptService,
interruptActions,
getPendingInterrupts,
getInterrupt
} from '@flowdrop/flowdrop/playground';
// Read pending interrupts reactively
const pending = $derived(getPendingInterrupts());
// Resolve an interrupt
async function resolveInterrupt(interruptId: string, value: unknown) {
const result = interruptActions.startSubmit(interruptId, value);
if (!result.valid) return;
try {
await interruptService.resolveInterrupt(interruptId, value);
interruptActions.submitSuccess(interruptId);
} catch (error) {
interruptActions.submitFailure(interruptId, String(error));
}
}
<script lang="ts">
import { ConfirmationPrompt } from '@flowdrop/flowdrop/playground';
</script>
<ConfirmationPrompt
config={{
message: 'Do you approve this action?',
confirm_label: 'Approve',
cancel_label: 'Reject'
}}
status="pending"
allowCancel={true}
onConfirm={() => handleConfirm()}
onCancel={() => handleCancel()}
/>

When a workflow requires input, the backend sends a message with interrupt metadata:

{
"id": "msg-123",
"role": "assistant",
"content": "I need your approval to proceed.",
"metadata": {
"type": "interrupt_request",
"interrupt_id": "int-456",
"interrupt_type": "confirmation",
"message": "Do you approve this action?",
"confirm_label": "Approve",
"cancel_label": "Reject"
}
}
EndpointMethodPurpose
/interrupts/{id}GETGet interrupt details
/interrupts/{id}POSTResolve interrupt
/interrupts/{id}/cancelPOSTCancel interrupt
/playground/sessions/{id}/interruptsGETList session interrupts

The interrupt store uses a state machine with these transitions:

  • idle — Awaiting user input
  • submitting — User response being sent
  • resolved — Successfully processed
  • error — Submission failed (can retry)

Resolved interrupts remain visible but disabled, showing the user’s selection.

  1. Clear messages — Write actionable prompts (“Do you approve sending this email to 150 recipients?” not “Proceed?”)
  2. Meaningful labels — Use descriptive button labels (“Yes, send email” not “Yes”)
  3. Default values — Provide sensible defaults for form fields
  4. Cancel behavior — Only set allowCancel: false for mandatory interrupts
  5. Error handling — Always handle resolution failures gracefully