🍳 Cookbook: Human-in-the-Loop Workflows¶
For sensitive actions—like sending emails, executing refunds, or deploying code—you should never let an agent run fully autonomously. This guide shows how to implement an Approval Gate.
🎯 Goal¶
Build a workflow that prepares a draft email but pauses for human approval before sending it.
🏗️ Phase 1: Define the Workflow¶
We use the SpecWorkflow logic but manually construct the Task list to include a dependency break.
# app/services/email_workflow.py
from app.models.thread import Task, ThreadStatus
from app.services.thread_manager import ThreadManager
async def start_email_draft(topic: str, recipient: str):
tm = ThreadManager()
# Define tasks
tasks = [
# Task 1: Auto-execute
Task(
id="draft_email",
agent="writer",
input=f"Write a polite email about {topic} to {recipient}"
),
# Task 2: This task depends on 'draft_email'
# Crucially, we will configuring the executor to PAUSE here.
Task(
id="send_email",
agent="executor", # Assuming executor has the send_email tool
input="Send the email drafted in the previous step.",
dependencies=["draft_email"],
requires_human_approval=True # 👈 The magic flag
)
]
thread = await tm.create(goal="Send email", tasks=tasks)
return thread
⏸️ Phase 2: The Approval Trap¶
In your execution loop, you check for the flag.
# app/services/run_loop.py
async def execute_thread(thread_id):
thread = get_thread(thread_id)
for task in thread.tasks:
if task.status == "pending":
# CHECK FOR APPROVAL
if task.requires_human_approval and not task.approved:
print(f"⏸️ Task {task.id} requires approval. Pausing thread.")
thread.status = ThreadStatus.PAUSED
save_thread(thread)
return # <--- STOP EXECUTION HERE
# Otherwise, execute...
execute_task(task)
🚦 Phase 3: The Human Interface¶
The thread is now saved in the database in a PAUSED state. You can build a simple CLI or UI to list and approve tasks.
1. List Pending Approvals¶
poetry run agentic approvals list
# Found 1 paused thread:
# Thread ID: th_123
# Pending Task: "send_email"
# Context: "Subject: Hello..." (Draft content)
2. Approve Execution¶
This command flips the boolean flag and resumes the loop.
poetry run agentic approvals approve th_123 task_send_email
# ✅ Task approved. Resuming thread execution...
# 📧 Email sent!
🛡️ Implementation in Traylinx Template¶
The AsyncExecutor in the Traylinx template has native support for this.
- Mark the Task: When creating a
Taskmodel, setrequire_approval=True. - Run: The executor automatically halts when it hits that task.
- Resume: Call
executor.resume_thread(thread_id, approval_data={...}).
This pattern ensures that no critical action happens without an explicit human "Yes".