Human-in-the-Loop (HITL)

Enable procedures to collaborate with human operators through approval, input, review, and notifications.

Core Principles

  • Blocking by default: Approval, input, and review operations block workflow execution
  • Non-blocking notifications: Progress updates and alerts don't block execution
  • Timeout support: All blocking operations support timeouts with fallback behavior
  • Context-rich: Provide structured data to help humans make informed decisions
  • Stage integration: Procedure status reflects when waiting for human interaction

HITL Primitives

Human.approve()

Request yes/no approval (blocking)

local approved = Human.approve({
  message = "Deploy to production?",
  context = {
    version = "2.1.0",
    environment = "production",
    changes = change_summary
  },
  timeout = 3600,  -- 1 hour
  default = false
})

if approved then
  deploy()
else
  Log.info("Deployment cancelled")
end

Human.input()

Request text input (blocking)

local topic = Human.input({
  message = "What topic should I research?",
  placeholder = "Enter a topic...",
  timeout = nil  -- Wait forever
})

if topic then
  Procedure.run("researcher", {topic = topic})
end

Human.review()

Request work product review (blocking)

local review = Human.review({
  message = "Please review this report",
  artifact = report_content,
  artifact_type = "document",
  options = {"approve", "edit", "reject"},
  timeout = 86400  -- 24 hours
})

if review.decision == "approve" then
  publish(report_content)
elseif review.decision == "edit" then
  publish(review.edited_artifact)
else
  Log.warn("Report rejected")
end

Human.notify()

Send notification (non-blocking)

Human.notify({
  message = "Processing phase 2 of 5",
  level = "info",  -- info, warning, error
  context = {
    progress = "40%",
    items_processed = 200
  }
})

-- Execution continues immediately

System.alert()

Send system alert (non-blocking)

System.alert({
  message = "Memory usage exceeded threshold",
  level = "warning",  -- info, warning, error, critical
  source = "batch_processor",
  context = {
    memory_mb = 8500,
    threshold_mb = 8000
  }
})

Declarative HITL Points

For predictable workflows, declare HITL points in YAML:

hitl: review_content: type: review message: "Review the generated content" timeout: 86400 options: [approve, edit, reject] confirm_publish: type: approval message: "Publish to production?" timeout: 3600 default: false workflow: | -- Generate content Worker.turn() -- Use declared review point local review = Human.review("review_content", { artifact = State.get("content") }) -- Use declared approval point if review.decision == "approve" then local approved = Human.approve("confirm_publish") if approved then publish() end end

Common Patterns

Pre-Action Approval

Request approval before critical operations:

workflow: | -- Analyze impact analyze_impact() -- Request approval local approved = Human.approve({ message = "Execute database migration?", context = { affected_tables = State.get("affected_tables"), estimated_duration = "15 minutes", rollback_plan = "Available" }, timeout = 1800 }) if approved then migrate_database() end

Progress with Alerts

Monitor operations and alert on issues:

workflow: | Human.notify({message = "Starting batch job"}) local processed = 0 local failed = 0 for i, item in ipairs(params.items) do local ok = process(item) if ok then processed = processed + 1 else failed = failed + 1 end -- Alert if failure rate too high local failure_rate = failed / i if failure_rate > 0.1 and i > 10 then System.alert({ message = "High failure rate detected", level = "warning", source = "batch_processor", context = {failure_rate = failure_rate} }) local continue = Human.approve({ message = "Continue processing?", default = false }) if not continue then break end end end

Complete Example

name: content_pipeline version: 1.0.0 params: topic: type: string required: true outputs: published: type: boolean required: true stages: - drafting - review - publishing hitl: review_content: type: review message: "Review generated content" timeout: 86400 confirm_publish: type: approval message: "Publish?" timeout: 3600 default: false agents: writer: system_prompt: "Write about: {params.topic}" tools: [research, write, done] workflow: | -- Generate Stage.set("drafting") Human.notify({message = "Generating content"}) repeat Writer.turn() until Tool.called("done") -- Review Stage.set("review") local review = Human.review("review_content", { artifact = State.get("draft") }) if review.decision == "reject" then return {published = false} end -- Publish Stage.set("publishing") local approved = Human.approve("confirm_publish") if approved then publish(review.edited_artifact or State.get("draft")) Human.notify({message = "Published successfully"}) return {published = true} else return {published = false} end