NeMo Agent Toolkit supports interactive workflows (Human-in-the-Loop and OAuth) over plain HTTP, without requiring a WebSocket connection. This is useful in deployment environments where WebSocket support is limited.
When enabled, the interactive extensions allow HTTP clients to:
- Start a workflow execution and receive an execution ID
- Poll for execution status (running, interaction required, OAuth required, completed, or failed)
- Submit interaction responses (text, binary choice, radio, checkbox, dropdown, or notification acknowledgment)
- Handle OAuth2 authorization code flows through status polling
Two client integration patterns are supported:
- Polling mode: The server returns
202 Acceptedwith an execution ID. The client pollsGET /executions/{execution_id}until the workflow completes or requires interaction. - Streaming mode (SSE): The server streams workflow output as Server-Sent Events. When an interaction or OAuth flow is needed, a typed SSE event is emitted. The client submits its response, and the stream resumes.
HTTP interactive extensions are enabled by default on all workflow endpoints except for
OpenAI-compatible endpoints (/v1/chat/completions). To force the interactive extension to work
with OpenAI-compatible endpoints, set enable_interactive_extensions to true in the FastAPI
front-end configuration:
general:
front_end:
_type: fastapi
enable_interactive_extensions: trueThe following table describes the relevant configuration parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
enable_interactive_extensions |
boolean | false |
Enable HTTP interactive execution on OpenAI-compatible endpoints. When true, POST requests to chat and OpenAI-compatible endpoints (/v1/chat/completions) return 202 Accepted if the workflow pauses for interaction or OAuth |
disable_legacy_routes |
boolean | false |
Disable legacy endpoint paths (/generate, /chat). When true, only versioned paths with interactive support (/v1/workflow, /v1/chat) are registered |
oauth2_callback_path |
string | /auth/redirect |
Path for the OAuth2 authorization code grant callback endpoint |
:::{note}
Interactive extensions are enabled for versioned workflow and chat endpoints (for example,
/v1/workflow and /v1/chat).
OpenAI-compatible endpoints (/v1/chat/completions) are opt-in only (defaulting to disabled).
:::
An interactive HTTP execution moves through the following states:
flowchart TD
Start(["Start workflow"])
Running(["Running"])
Interaction(["Interaction Required"])
OAuth(["OAuth Required"])
Completed(["Completed"])
Failed(["Failed"])
Start --> Running
Running -->|Workflow has interaction| Interaction
Running -->|Workflow needs authentication| OAuth
Running -->|Workflow execution completes| Completed
Running -->|Workflow execution failed| Failed
Interaction -->|Client submits response to execution endpoint| Running
Interaction -->|Timeout| Failed
OAuth -->|OAuth redirect resolves token| Running
OAuth -->|Timeout| Failed
style Completed fill:#2e7d32,color:#fff,stroke:#1b5e20
style Failed fill:#c62828,color:#fff,stroke:#b71c1c
style Running fill:#006699,color:#fff,stroke:#003366
POST requests to versioned endpoints such as /v1/chat return a 202 Accepted response if the
workflow requires interaction or OAuth before it can complete. Interactive support is enabled by
default on these endpoints; the enable_interactive_extensions flag only gates OpenAI-compatible
endpoints (see Configuration).
Request:
curl -X POST http://localhost:8000/v1/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "Analyze the sales data"}
]
}'Response (202 Accepted, interaction required):
{
"status": "interaction_required",
"status_url": "/executions/550e8400-e29b-41d4-a716-446655440000",
"interaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"prompt": {
"input_type": "text",
"text": "Should I include Q4 projections?",
"placeholder": "Type your response...",
"required": true,
"timeout": null,
"error": null
},
"response_url": "/executions/550e8400-e29b-41d4-a716-446655440000/interactions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/response"
}The error field inside the prompt object is only populated when the prompt times out or becomes
unavailable. Under normal operation it is null.
Response (202 Accepted, OAuth required):
{
"status": "oauth_required",
"status_url": "/executions/550e8400-e29b-41d4-a716-446655440000",
"auth_url": "https://provider.example.com/authorize?client_id=...&state=abc123",
"oauth_state": "abc123"
}If the workflow completes without requiring interaction, a standard 200 OK response with the
workflow result is returned, identical to the non-interactive behavior.
Poll the execution status endpoint to check progress or retrieve the final result.
- Route:
GET /executions/{execution_id} - Description: Returns the current status of an execution.
Request:
curl http://localhost:8000/executions/550e8400-e29b-41d4-a716-446655440000Response (running):
{
"status": "running"
}Response (interaction required):
{
"status": "interaction_required",
"interaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"prompt": {
"input_type": "text",
"text": "Should I include Q4 projections?",
"placeholder": "Type your response...",
"required": true,
"timeout": null,
"error": null
},
"response_url": "/executions/550e8400-e29b-41d4-a716-446655440000/interactions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/response"
}The error field inside the prompt object is only populated when the prompt times out or becomes
unavailable. Under normal operation it is null.
Response (OAuth required):
{
"status": "oauth_required",
"auth_url": "https://provider.example.com/authorize?client_id=...&state=abc123",
"oauth_state": "abc123"
}Response (completed):
{
"status": "completed",
"result": {
"id": "chatcmpl-abc123",
"object": "chat.completion",
"choices": [
{
"message": {
"content": "The analysis is complete. Q4 projections have been included.",
"role": "assistant"
},
"finish_reason": "stop",
"index": 0
}
]
}
}Response (failed):
{
"status": "failed",
"error": "Workflow execution timed out after 300 seconds"
}The following table lists all possible status values:
| Status | Description |
|---|---|
running |
The workflow is actively executing |
interaction_required |
The workflow is paused waiting for a human response |
oauth_required |
The workflow is paused waiting for OAuth2 authorization |
completed |
The workflow finished successfully; the result field contains the output |
failed |
The workflow failed; the error field contains the error message |
When an execution has status interaction_required, submit a response to resume the workflow.
- Route:
POST /executions/{execution_id}/interactions/{interaction_id}/response - Description: Submit a human response to a pending interaction prompt.
Request (text response):
curl -X POST http://localhost:8000/executions/550e8400-e29b-41d4-a716-446655440000/interactions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/response \
-H "Content-Type: application/json" \
-d '{
"response": {
"input_type": "text",
"text": "Yes, include Q4 projections"
}
}'Response: 204 No Content
After submitting a response, the execution transitions back to running. Continue polling
GET /executions/{execution_id} to track progress.
The response field in the request body is a discriminated union based on input_type. All
prompt types supported by Interactive Workflows
are available:
input_type |
Fields | Description |
|---|---|---|
text |
text (string) |
Free-text response |
binary_choice |
selected_option (object) |
One of two options (for example, Continue or Cancel) |
radio |
selected_option (object) |
Single selection from multiple options |
checkbox |
selected_options (array of objects) |
Multiple selections from a list |
dropdown |
selected_option (object) |
Single selection from a dropdown |
notification |
(none) | Acknowledgment of a notification prompt |
Request (radio response):
curl -X POST http://localhost:8000/executions/550e8400-e29b-41d4-a716-446655440000/interactions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/response \
-H "Content-Type: application/json" \
-d '{
"response": {
"input_type": "radio",
"selected_option": {"id": "email", "label": "Email", "value": "email"}
}
}'Request (checkbox response):
curl -X POST http://localhost:8000/executions/550e8400-e29b-41d4-a716-446655440000/interactions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/response \
-H "Content-Type: application/json" \
-d '{
"response": {
"input_type": "checkbox",
"selected_options": [
{"id": "email", "label": "Email", "value": "email"},
{"id": "sms", "label": "SMS", "value": "sms"}
]
}
}'| Status Code | Condition |
|---|---|
204 |
Response accepted successfully |
| Status Code | Condition |
|---|---|
400 |
Interaction has already been resolved |
404 |
Execution or interaction not found |
When using streaming endpoints (/v1/chat/stream or /v1/chat/completions with stream: true),
interactive events are delivered as typed Server-Sent Events within the stream.
When the workflow pauses for human interaction, the following SSE event is emitted:
event: interaction_required
data: {"event_type": "interaction_required", "execution_id": "550e8400-...", "interaction_id": "a1b2c3d4-...", "prompt": {"input_type": "text", "text": "Should I proceed?", ...}, "response_url": "/executions/550e8400-.../interactions/a1b2c3d4-.../response"}
After receiving this event, submit the interaction response through the
POST /executions/{execution_id}/interactions/{interaction_id}/response endpoint.
The SSE stream remains open and resumes sending workflow output once the response is submitted.
When the workflow requires OAuth2 authorization, the following SSE event is emitted:
event: oauth_required
data: {"event_type": "oauth_required", "execution_id": "550e8400-...", "auth_url": "https://provider.example.com/authorize?...", "oauth_state": "abc123"}
Direct the user to the auth_url to complete authorization. After the OAuth redirect callback
is processed, the stream resumes automatically.
The following Python example demonstrates how to consume the SSE stream and handle interactive events:
import httpx
import json
def stream_with_interactions(base_url: str, messages: list[dict]) -> str:
"""Stream a chat request and handle any interactive events."""
with httpx.Client(base_url=base_url, timeout=300) as client:
with client.stream(
"POST",
"/v1/chat/stream",
json={"messages": messages},
) as response:
lines_iter = response.iter_lines()
for line in lines_iter:
if not line:
continue
# Check for typed SSE events
if line.startswith("event: interaction_required"):
# Next line is the data payload
data_line = next(lines_iter)
event = json.loads(data_line.removeprefix("data: "))
# Prompt the user
print(f"Workflow asks: {event['prompt']['text']}")
user_input = input("> ")
# Submit the response
client.post(
event["response_url"],
json={
"response": {
"input_type": "text",
"text": user_input,
}
},
)
continue
if line.startswith("event: oauth_required"):
data_line = next(lines_iter)
event = json.loads(data_line.removeprefix("data: "))
print(f"Please authorize at: {event['auth_url']}")
input("Press Enter after completing authorization...")
continue
# Regular data chunk
if line.startswith("data: "):
chunk = json.loads(line.removeprefix("data: "))
if "value" in chunk:
return chunk["value"]
return ""The OpenAI-compatible endpoint (/v1/chat/completions) also supports interactive extensions when
enable_interactive_extensions is true. The behavior is the same as for the chat endpoints:
- Non-streaming (
stream: false): Returns202 Acceptedwith execution details when interaction or OAuth is required - Streaming (
stream: true): Emitsinteraction_requiredoroauth_requiredSSE events within the stream
curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "nvidia/llama-3.1-8b-instruct",
"messages": [
{"role": "user", "content": "Summarize my documents"}
],
"stream": false
}'If the workflow needs human input, the response is 202 Accepted with the same structure as the
polling mode responses described above.
When a workflow requires OAuth2 authentication (for example, to access a third-party API), the execution pauses and the authorization URL is surfaced through the execution status or SSE event.
The flow works as follows:
- The workflow calls an authenticated tool that requires OAuth2
- The execution status changes to
oauth_requiredwith theauth_url - The client directs the user to the
auth_urlto complete authorization - The OAuth provider redirects the user to the configured callback path (default:
/auth/redirect) - The callback endpoint exchanges the authorization code for a token and resolves the execution
- The execution transitions back to
runningand the workflow continues
:::{note}
The OAuth2 callback endpoint must be reachable by the user's browser. Ensure oauth2_callback_path
is configured correctly for your deployment environment.
:::
- Interactive Workflows Guide for building workflows with human-in-the-loop interactions
- API Server Endpoints for the full list of HTTP and WebSocket endpoints
- WebSocket Message Schema for the WebSocket-based interactive messaging format