Skip to content

Instantly share code, notes, and snippets.

@kausmeows
Last active April 27, 2026 14:40
Show Gist options
  • Select an option

  • Save kausmeows/a8bc8c095c07905dbb5d9e1aad633ced to your computer and use it in GitHub Desktop.

Select an option

Save kausmeows/a8bc8c095c07905dbb5d9e1aad633ced to your computer and use it in GitHub Desktop.

HITL Workflow: Resume API & Payload Examples (Needs Agno version >=2.6.0)

1. API Endpoint to Resume a Paused HITL Workflow

POST /workflows/{workflow_id}/runs/{run_id}/continue

Parameter Type Required Description
step_requirements string (JSON array) Yes Resolved StepRequirement objects from the paused response
session_id string Yes Session ID from the paused run
user_id string No User identifier
stream bool No Enable SSE streaming (default: true)
Response Code Meaning
200 Resumed (SSE stream or JSON)
400 Invalid JSON in step_requirements
404 Workflow or run not found
409 Run is not paused

Where to get step_requirements:

  • Non-streaming (stream=false): The paused JSON response contains step_requirements directly.
  • Streaming (stream=true): The server emits a final event: WorkflowRunOutput SSE event when the workflow pauses. Parse the data JSON of that event to get step_requirements.

Best practice: Echo back the full step_requirements array from the paused response with your decisions filled in.


2. CURL Examples for Step-Level HITL

All examples use:

  • ${wf_id} = workflow ID
  • ${run_id} = run ID from initial run response
  • ${sess_id} = session ID from initial run response
  • ${step_id} = step_requirements[0].step_id from the paused response

2a. Step Confirmation: Approve

Step has requires_confirmation=True. User approves before it executes.

curl -X POST "http://localhost:7777/workflows/${wf_id}/runs/${run_id}/continue" \
  -F "session_id=${sess_id}" \
  -F "stream=false" \
  -F 'step_requirements=[{
    "step_id": "'${step_id}'",
    "step_name": "process_data",
    "step_type": "Step",
    "requires_confirmation": true,
    "confirmed": true
  }]'

Result: Step executes, workflow continues.

2b. Step Confirmation: Reject (Skip)

curl -X POST "http://localhost:7777/workflows/${wf_id}/runs/${run_id}/continue" \
  -F "session_id=${sess_id}" \
  -F "stream=false" \
  -F 'step_requirements=[{
    "step_id": "'${step_id}'",
    "step_name": "process_data",
    "step_type": "Step",
    "requires_confirmation": true,
    "confirmed": false,
    "on_reject": "skip"
  }]'

Result: Step is skipped, workflow continues to the next step.

2c. Step Confirmation: Reject (Cancel)

curl -X POST "http://localhost:7777/workflows/${wf_id}/runs/${run_id}/continue" \
  -F "session_id=${sess_id}" \
  -F "stream=false" \
  -F 'step_requirements=[{
    "step_id": "'${step_id}'",
    "step_name": "process_data",
    "step_type": "Step",
    "requires_confirmation": true,
    "confirmed": false,
    "on_reject": "cancel"
  }]'

Result: Entire workflow is cancelled. Status: CANCELLED.

2d. Step User Input: Provide Custom Fields

Step has requires_user_input=True with a user_input_schema. User fills in the fields.

curl -X POST "http://localhost:7777/workflows/${wf_id}/runs/${run_id}/continue" \
  -F "session_id=${sess_id}" \
  -F "stream=false" \
  -F 'step_requirements=[{
    "step_id": "'${step_id}'",
    "step_name": "collect_preferences",
    "step_type": "Step",
    "requires_user_input": true,
    "user_input": {
      "tone": "formal",
      "length": "short",
      "language": "en"
    }
  }]'

Result: Step executes with user input available in step_input.additional_data["user_input"].


3. CURL Examples for Router HITL Scenarios

Same variables as Section 2.

3a. Router Confirmation: Approve

Selector picks route automatically, user confirms before execution.

curl -X POST "http://localhost:7777/workflows/${wf_id}/runs/${run_id}/continue" \
  -F "session_id=${sess_id}" \
  -F "stream=false" \
  -F 'step_requirements=[{
    "step_id": "'${step_id}'",
    "step_name": "ops_router",
    "step_type": "Router",
    "requires_confirmation": true,
    "confirmed": true
  }]'

Result: Router executes the selector-chosen route, workflow continues.

3b. Router Confirmation: Reject (Skip)

curl -X POST "http://localhost:7777/workflows/${wf_id}/runs/${run_id}/continue" \
  -F "session_id=${sess_id}" \
  -F "stream=false" \
  -F 'step_requirements=[{
    "step_id": "'${step_id}'",
    "step_name": "ops_router",
    "step_type": "Router",
    "requires_confirmation": true,
    "confirmed": false,
    "on_reject": "skip"
  }]'

Result: Router is skipped entirely, workflow continues to the next step.

3c. Router Confirmation: Reject (Cancel)

curl -X POST "http://localhost:7777/workflows/${wf_id}/runs/${run_id}/continue" \
  -F "session_id=${sess_id}" \
  -F "stream=false" \
  -F 'step_requirements=[{
    "step_id": "'${step_id}'",
    "step_name": "ops_router",
    "step_type": "Router",
    "requires_confirmation": true,
    "confirmed": false,
    "on_reject": "cancel"
  }]'

Result: Entire workflow is cancelled. Status: CANCELLED.

3d. Router User Selection: Pick a Route

User selects which route to execute from available_choices in the paused response.

curl -X POST "http://localhost:7777/workflows/${wf_id}/runs/${run_id}/continue" \
  -F "session_id=${sess_id}" \
  -F "stream=false" \
  -F 'step_requirements=[{
    "step_id": "'${step_id}'",
    "step_name": "analysis_router",
    "step_type": "Router",
    "requires_route_selection": true,
    "selected_choices": ["billing"]
  }]'

Result: Only the selected route ("billing") executes, workflow continues.


4. Cookbook References

Step-level HITL:

Scenario Cookbook
Step confirmation (basic) 01_basic_step_confirmation.py
Step confirmation (streaming) 03_step_confirmation_streaming.py
Step user input 02_step_user_input.py
Step user input (streaming) 03_step_user_input_streaming.py

Router-level HITL:

Scenario Cookbook
Router confirmation (approve / reject-skip) 04_router_confirmation.py
Router user selection 01_router_user_selection.py
Router multi-selection 02_router_multi_selection.py

5. Component Configuration Examples

Step with Confirmation

from agno.workflow import OnReject
from agno.workflow.step import Step

Step(
    name="process_data",
    agent=processing_agent,
    requires_confirmation=True,
    confirmation_message="Process the data with these parameters?",
    on_reject=OnReject.skip,  # or OnReject.cancel
)

Supported on_reject: skip (default), cancel

Step with User Input

from agno.workflow.step import Step
from agno.workflow.types import UserInputField

Step(
    name="collect_preferences",
    agent=content_agent,
    requires_user_input=True,
    user_input_message="Provide your content preferences:",
    user_input_schema=[
        UserInputField(name="tone", field_type="str", description="formal / casual / technical", required=True),
        UserInputField(name="length", field_type="str", description="short / medium / long", required=True),
        UserInputField(name="language", field_type="str", description="Language code", required=False),
    ],
)

Router with Confirmation (Approve or Skip/Cancel)

from agno.workflow import OnReject
from agno.workflow.router import Router
from agno.workflow.step import Step

Router(
    name="ops_router",
    choices=[
        Step(name="restart", description="Restart service", executor=restart_handler),
        Step(name="scale", description="Scale service", executor=scale_handler),
    ],
    selector=route_by_intent,
    requires_confirmation=True,
    confirmation_message="Execute this operation?",
    on_reject=OnReject.cancel,  # or OnReject.skip (default)
)

Supported on_reject: skip (default), cancel

Router with User Selection

Router(
    name="analysis_router",
    choices=[
        Step(name="quick", description="Fast analysis (2 min)", executor=quick_fn),
        Step(name="deep", description="Deep analysis (10 min)", executor=deep_fn),
        Step(name="custom", description="Custom analysis", executor=custom_fn),
    ],
    requires_user_input=True,
    user_input_message="Select analysis type:",
    allow_multiple_selections=False,
)

6. Typical E2E Flow

1. POST /workflows/{wf_id}/runs
      -F 'message=...' -F 'stream=false'
   -> Response: status=PAUSED, step_requirements=[...]

   (If streaming: parse the final `event: WorkflowRunOutput` SSE event to get step_requirements)

2. Inspect step_requirements:
   - requires_confirmation=true    -> user approves/rejects (Step or Router)
   - requires_user_input=true      -> user fills in fields from user_input_schema (Step)
   - requires_route_selection=true -> user picks from available_choices (Router)

3. POST /workflows/{wf_id}/runs/{run_id}/continue
      -F 'session_id=...' -F 'step_requirements=[{...resolved...}]' -F 'stream=false'
   -> Response: status=COMPLETED | PAUSED (if next step also needs HITL) | CANCELLED

4. Repeat step 3 if still PAUSED.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment