Course Content
AutoGen Essentials
7 sections · 28 lessons
How do you implement human-in-the-loop approvals for high-risk actions?
What you need to know
Where to put the human
| Option (0.4+) | How it works | Good for |
|---|---|---|
UserProxyAgent(input_func=...) in the team | The team waits while your function gets an answer | Local tools, quick demos, answers within seconds |
CodeExecutorAgent(approval_func=...) | Your function approves or rejects each code block | Reviewing code before it runs |
| Approval inside a tool | The tool refuses unless a valid approval record exists | Money, deletes, external messages |
HandoffTermination / max_turns + save_state | The run stops, state is saved, the app asks the human, run resumes later | Approvals that take minutes or hours |
UserProxyAgent blocks the team while it waits, so AutoGen's own docs suggest ending the run and resuming for slow human responses.
Approval enforced in the tool
1async def refund_order(order_id: str, amount_inr: int) -> dict:2 """Refund a delivered order. Amounts above Rs 5,000 need approval."""3 if amount_inr > 5000:4 approval = await approvals.find(order_id=order_id, amount=amount_inr,5 status="approved")6 if approval is None:7 req = await approvals.create(order_id=order_id, amount=amount_inr,8 requested_by=session.agent_name)9 return {"ok": False, "status": "pending_approval", "request_id": req.id}10 return await payments.refund(order_id, amount_inr, idempotency_key=order_id)The tool, not the agent, decides whether approval is needed. The refunds agent then hands off to "user" (a HandoffTermination ends the run), the app saves state and posts to Slack, and when a supervisor clicks Approve, the app loads state and runs the team again.
What the approver sees
A decision-ready card, not a transcript: "Refund ₹12,400 for order 55102 to card ending 4471. Reason: item damaged, photo attached. Declining sends the customer to a human agent." Buttons: Approve, Reject with reason. The reason goes back to the agent as a message.
Design rules
- Tier by risk. Auto-approve small amounts, one approver in the middle band, two above a high threshold.
- Default deny on timeout. No answer in 4 hours means no refund, plus a message to the customer.
- Log everything. Approver, time, exact payload approved. That record is the point of the control.
- Bind the approval to the payload. An approval for ₹12,400 on order 55102 must not also allow ₹14,000.
Legacy 0.2: UserProxyAgent(human_input_mode="ALWAYS") asked a human every turn, "TERMINATE" only at the end; approvals in chat were blocking input() calls.
A real-life example
A marketplace's customer-support triage team issues UPI refunds. Version one used a UserProxyAgent with human_input_mode="ALWAYS" in 0.2: supervisors approved every message, about 30 a day each, and after two weeks they clicked Approve on everything in under 3 seconds.
Version two (0.4+) gates only refunds over ₹5,000 and account closures, about 4% of chats. Approval requests arrive in Slack with the card above; the run is saved and the pod is freed. Median approval time is 11 minutes, the customer sees "a supervisor is reviewing your refund", and unanswered requests are declined after 4 hours. Supervisors now reject about 9% of requests, a sign they are actually reading them.
Follow-up questions to expect
- "Why not approve every step?" — Approval fatigue: people stop reading. Few, meaningful approvals keep the control real.
- "How do you stop the agent bypassing approval?" — The tool checks for an approval record tied to the exact payload; the agent has no tool that pays without it.
- "What about Microsoft Agent Framework?" — Its workflows have built-in request-and-response pauses and checkpointing for this; in AutoGen you assemble it from termination,
save_stateandrun.