11 Human-in-the-Loop
Human-in-the-loop (HITL) is where agent workflows stop being “cool demos” and start being trustworthy systems. This chapter introduces approvals and reviews as language primitives.
Now that you’ve seen how to structure an agent loop, HITL becomes a practical pattern: gate the risky step, collect missing inputs, and resume the workflow exactly where it left off.
11.1 The HITL Primitives
Tactus includes three blocking primitives you’ll use constantly:
Human.review({ ... })— review an artifact (text, code, config) and optionally edit itHuman.approve({ ... })— yes/no gate before a risky actionHuman.input({ ... })— request missing information the workflow shouldn’t guess
Each is a durable suspend point. When execution reaches one of these calls, the runtime:
- Checkpoints everything up to that point
- Emits a pending request to the human UI/handler
- Suspends execution
- Resumes later (even in a new process) when the human responds
That’s the difference between “agent scripts” and real workflows: you don’t keep a Python process running for 14 hours waiting for someone to click “Approve.”
11.2 Timeouts and “Fail Safe”
All blocking HITL calls can be configured with timeouts and defaults:
timeout = 3600(seconds) to stop waiting eventuallydefault = false(or a default string, etc.) to pick a safe fallback
For risky actions, the safe default is almost always “don’t proceed.”
11.3 Example: Review the Draft, Then Approve Sending
The example for this chapter is code/chapter-10/50-meeting-recap-hitl-send.tac.
It follows a workflow shape you can reuse almost anywhere:
- Generate a structured draft (
subject,body,action_items) Human.review(...)the draft (human can approve, reject, or revise)- If revised, incorporate edits (or re-run the agent with feedback)
Human.approve(...)before calling the send tool- Call
send_email(...)(stubbed here) and returnmessage_id
Run it:
tactus run code/chapter-10/50-meeting-recap-hitl-send.tac \
--param recipient_name="Sam" \
--param recipient_email="sam@example.com" \
--param raw_notes="Discussed Q1 launch timeline. Risks: vendor delays. Action: Sam to confirm dates by Friday."In mock mode, HITL is automatically answered so tests can run without blocking:
tactus test code/chapter-10/50-meeting-recap-hitl-send.tac --mock11.4 How HITL Shows Up in Different Environments
The same Lua code can run with different HITL handlers:
- CLI: prompts in the terminal
- IDE / desktop app: modal dialogs or inbox-style pending requests
- Embedded platform: web UI, Slack, email, or your own integration
The procedure doesn’t change. That portability is the point.
This is also how you move beyond the “everyone uses chat” paradigm. Instead of forcing end users to steer the agent turn-by-turn, your application can surface structured, minimal interactions:
- ask for one missing field at the moment it’s needed (not a long chat)
- present a draft artifact in a review UI (with edits)
- require an explicit approval before a side effect
- show stages/logs so operators can monitor many runs at once
In other words: the human is still in the loop, but they’re an approver/reviewer/operator—not a full-time copilot.
11.5 Where This Takes Us
At this point in Part II, our workflow is:
- Useful (draft + send integration)
- Bounded and deterministic (safe loop shape)
- Trustworthy (human gates before risky actions)
Next, Part III steps back and names the levers of control you have in an agent system—beyond prompts—then turns those into enforceable guardrails: capability control, sandboxing, isolation boundaries, and secretless execution. After that, Part IV covers reliability: specs and evaluations so you can trust the workflow over time.