Skip to content

Human-in-the-loop

Set interrupt: true on an agent to grant it the built-in ask_user tool. When the agent calls it, the run pauses and the question surfaces to the client; on resume the same call returns the user's answer.

review_team (delegate):
  coordinator ─▶ field_agent (interrupt: true, asks the user)

Config

agents:
  field_agent:
    interrupt: true          # grants the built-in ask_user tool
    system_prompt: |
      Before finalizing anything risky, call ask_user to confirm. If several
      details are unclear, ask them together in one ask_user call.

orchestrations:
  review_team:
    mode: delegate
    entry_name: coordinator
    connections:
      - { agent: field_agent, description: "Gated field work with user confirmation" }

entry: review_team

Full runnable project: examples/18_hitl.

Key behaviours

  • Interrupts bubble up. A gate fired by a nested sub-agent surfaces to the single top-level handler and pauses the entire run — no matter how deep it is.
  • Parallel gated tool calls. An agent can raise several gates in one turn (a multi-question form, or two tool calls). Each becomes its own interrupt with a distinct tool-call id, and the run resumes once all are answered.
  • Positions. HITL works for a plain entry agent, a delegate sub-agent, and swarm/graph nodes alike.

Run

HITL needs a client with a resume UI — serve it and connect a CopilotKit frontend:

OPENROUTER_API_KEY=... uv run kaboo-serve examples/18_hitl/config.yaml

Resume protocol

On pause, the run finishes with an interrupt outcome listing the pending interrupts (each with an id). The client resumes by sending, per interrupt, either {"status": "resolved", "payload": ...} or {"status": "cancelled"} (see errors & rejection).

Proven by

  • tests/e2e/test_cross_cutting.py::test_ask_user_interrupt_then_resume (plain / delegate / swarm / graph positions).
  • tests/e2e/test_complex.py::test_parallel_interrupts_surface_together_and_resume (two gates in one step, distinct ids, both resumed).