Human-in-the-loop workflows
Not every decision should be fully automated. Human-in-the-loop (HITL) is the pattern where a workflow pauses at a critical decision point, presents structured findings to a reviewer, waits for their input, and then resumes based on that input. It lets you combine the reach of automation with human judgment where judgment matters most.
- Remediation with potential impact. Isolating a host, blocking a user, or deleting data. Pause for analyst approval before the destructive action.
- Ambiguous classifications. When an AI or rule is uncertain, ask a human before proceeding.
- Escalation gates. Page an on-call, wait for acknowledgement and decision, then route accordingly.
- Approval for automation. A new workflow in test mode can pause and ask for approval on each action for the first few runs, then switch to full automation once trusted.
HITL is built on one step type: waitForInput. When the workflow reaches it, execution pauses. The reviewer sees the message (optionally with a form generated from a JSON Schema). When they respond, the workflow resumes with their input available as steps.<step_name>.output.
A HITL message is read by a human mid-incident. Design for speed:
- Lead with the decision. The first line should say what the reviewer needs to decide.
- Include the evidence. Relevant context (alert details, enrichment results, AI rationale) belongs in the message so the reviewer doesn't have to dig.
- Keep the schema small. Three fields is a lot. One boolean plus an optional notes field is often enough.
- Use Markdown. The message supports Markdown, so use headings, bold text, and bullets to make it scannable.
The three ingredients: a preceding step that gathers context, a waitForInput step that presents it, and subsequent steps that branch on the reviewer's decision.
name: isolate-host-with-approval
enabled: true
triggers:
- type: alert
steps:
- name: open_case
type: cases.createCase
with:
title: "Potential compromise: {{ event.alerts[0].host.name }}"
severity: high
tags: ["auto-triage"]
- name: investigate
type: elasticsearch.search
with:
index: "logs-*"
query:
term:
"host.name": "{{ event.alerts[0].host.name }}"
- name: classify
type: ai.classify
connector-id: "my-openai"
with:
input: "${{ steps.investigate.output.hits.hits }}"
categories: ["confirmed_compromise", "likely_benign", "needs_review"]
includeRationale: true
- name: review
type: waitForInput
with:
message: |
## Alert on `{{ event.alerts[0].host.name }}`
**AI classification:** {{ steps.classify.output.category }}
**Rationale:** {{ steps.classify.output.rationale }}
Isolate this host?
schema:
type: object
properties:
approved:
type: boolean
title: "Isolate the host"
notes:
type: string
title: "Analyst notes"
required: ["approved"]
- name: isolate
type: http
if: "steps.review.output.approved : true"
connector-id: "edr-connector"
with:
method: "POST"
url: "https://edr.example.com/isolate"
body:
host: "{{ event.alerts[0].host.name }}"
reason: "{{ steps.review.output.notes }}"
- name: record_decision
type: cases.addComment
with:
case_id: "{{ steps.open_case.output.case.id }}"
comment: |
**Decision:** {{ steps.review.output.approved ? "isolated" : "no action" }}
**Notes:** {{ steps.review.output.notes }}
Execution pauses at review. Until a reviewer responds, the execution state is WAITING_FOR_INPUT. When they respond, execution resumes at isolate, which is gated by an if guard on the approval decision.
Resume a paused workflow using the following methods.
Open the execution view. The paused step renders a form generated from the schema. Fill it in, submit, and the workflow resumes.
Send a POST request to the resume endpoint with the reviewer's input:
POST /api/workflowExecutions/{executionId}/resume
Content-Type: application/json
{
"approved": true,
"notes": "Confirmed malicious. Proceeding with isolation."
}
The input body is available to subsequent steps as steps.<step_name>.output. Reference individual fields like {{ steps.review.output.approved }} and {{ steps.review.output.notes }}.
- The execution is in the
WAITING_FOR_INPUTstate. It appears in the execution history with a resume action. - There's no default timeout on
waitForInput. The workflow waits indefinitely. To limit the wait, set a workflow-levelsettings.timeout. - If the workflow-level
settings.timeoutelapses before the reviewer responds, the execution is cancelled.
waitForInputstep reference: Step parameter details.- AI steps: Pair AI classification or summarization with HITL for uncertain cases.
- Cases action steps: Record decisions and outcomes on the case.