{"course":{"slug":"building-with-ai","title":"Building with AI","url":"https://addaly.com/learn/building-with-ai","level":"intermediate","track":"build"},"lesson":{"slug":"human-in-the-loop-handoff","title":"Approval gates, undo, and handing over to a person","url":"https://addaly.com/learn/building-with-ai/human-in-the-loop-handoff","position":31,"of":80,"minutes":8,"answer":"Gate on reversibility rather than importance, put the gate in the tool where the model cannot argue past it.","keyIdea":"Gate on reversibility rather than importance, put the gate in the tool where the model cannot argue past it, and watch the approval and override rates because a gate approved 99% of the time is theatre."},"body":{"format":"markdown","text":"## Decide by reversibility, not by importance\n\nThe useful axis for deciding what needs a human is not how important an action is. It is **how hard it is to undo**.\n\n- **Reversible in one click**: draft an email, add a tag, create an unpublished record. Let the model do it. Requiring approval here trains people to click Approve without reading, which destroys the value of every gate you have.\n- **Reversible with effort**: publish a post, update a customer record, cancel a booking. Let it act, log it clearly, and provide a visible undo.\n- **Not reversible**: send money, send a message to a customer, delete data, change permissions, sign anything. Human approval, every time, with the specifics on screen.\n\nA useful sharpening: anything a stranger sees, anything that moves money, and anything you cannot get back. Those three cover almost every case.\n\n## What a good approval screen shows\n\nApproval fatigue is the failure mode of this whole area. A dialog that says *The assistant wants to run send_email. Approve?* teaches people to click yes.\n\nShow the thing itself:\n\n> Figure: Gate on how hard it is to undo, not on how important it feels. Layers, top to bottom: Reversible in one click: let the model act (Draft an email, add a tag, create an unpublished record. No approval.); Reversible with effort: act, log clearly, offer undo (Publish a post, update a customer record, cancel a booking.); Not reversible: a person approves, every time (Send money, message a customer, delete data, change permissions, sign anything.). A gate on the top layer trains people to click Approve without reading, which destroys the value of the gate on the bottom one. Watch the approval rate: at 99 per cent the gate is theatre and the action should be automated or the gate narrowed.\n\n- **The exact action, rendered**: the full email with recipient, subject and body; the amount and the account; the rows that will be deleted, with a count\n- **Why**, in one line: the user request or the rule that led here\n- **What changes and what does not**: \"this refunds ₹2,400 to the original card; it does not cancel the subscription\"\n- **Approve, edit, reject**: with edit as a real option. Most rejections are of a nearly right action, and forcing the person to reject and start again wastes the work.\n\nOne approval per action. Batch approval of twelve heterogeneous actions is a single click that nobody reads.\n\n## The gate belongs in the tool, not the prompt\n\nThis is the mistake worth naming explicitly. \"Always ask the user before sending an email\" in a system prompt is a *preference*, not a control. It will hold most of the time and fail exactly when something unusual is happening, which is when you needed it.\n\nThe gate goes in code, at the point of effect:\n\n```python\ndef send_email(to, subject, body):\n    if not approvals.granted(action_id(to, subject, body)):\n        raise NeedsApproval(to=to, subject=subject, body=body)\n    return mail.send(to, subject, body)\n```\n\nThe model cannot talk its way past that, because the model is not involved. `NeedsApproval` comes back through the loop as a recoverable result, the run pauses, and the pending action is stored for a person. This is the same principle as authorising against the session rather than the arguments: **anything that must always happen is code, and anything in the prompt is a suggestion.**\n\n## Pausing a run properly\n\nAn agent waiting for approval may wait for hours, so the run cannot live in a request handler.\n\nPersist the whole state: messages so far, pending tool call, run id, who may approve, an expiry. When approval arrives, resume from that state rather than starting again. Two rules that save you: **expire pending approvals** (a refund approved from a two-week-old context is approving something nobody remembers), and **re-validate at execution time** (the order may have been cancelled while the request sat in a queue).\n\n## Handing to a person, and handing back\n\nEscalation is a normal outcome, not a failure. It should be triggered by rules you write, not by the model's judgement about its own competence:\n\n- The user asked twice for a human\n- Sentiment or complaint language crossed a threshold\n- The topic is on your list: legal, medical, safety, a named regulator\n- Confidence checks failed, or retrieval returned nothing\n- The loop hit a budget\n\nWhen it fires, hand over properly. A person picking up a conversation needs a two-line summary of what was asked, what was tried and what failed, not a 40-turn transcript to read while a customer waits. Carry the context, keep the ticket id, and do not make the customer repeat themselves. Most people's anger at automated support comes from that one thing.\n\nAnd give the human a way back. A conversation that escalated may return to the assistant once the specific issue is resolved. A one-way door means every escalation permanently consumes a person.\n\n## Count the gate\n\nInstrument it like anything else: how often approval is requested, how often it is granted unchanged, how often edited, how often rejected.\n\nA 99 per cent approval rate means the gate is theatre and the reviewer is rubber-stamping: either automate the action or narrow the gate to the cases that are actually contested. A 40 per cent rejection rate means the model should not be proposing these at all. The useful range is in between, and knowing where you sit is the difference between oversight and its appearance."},"check":{"question":"An assistant's system prompt says it must always ask before issuing a refund. Refunds are occasionally issued without a prompt appearing. What is the structural fix?","options":[{"text":"Restate the rule more emphatically and repeat it at the end of the prompt.","correct":false,"tempting":"Increases the salience of an instruction that remains advisory; a rule the model can weigh is a rule it can sometimes not weigh."},{"text":"Raise the model tier so instruction-following is more reliable.","correct":false,"tempting":"Reduces the frequency of the failure without removing the possibility, which is not an acceptable property for an irreversible money movement."},{"text":"Make the refund tool refuse to execute without a stored approval token, so the check happens in code at the point of effect.","correct":true},{"text":"Log every refund so unapproved ones can be reversed afterwards.","correct":false,"tempting":"Provides detection and cleanup after the fact, which is worth having but does not prevent the action the gate exists to prevent."}]},"prev":{"slug":"multi-agent-when-worth-it","title":"Multiple agents, and the honest cost","url":"https://addaly.com/learn/building-with-ai/multi-agent-when-worth-it"},"next":{"slug":"retrieval-that-helps","title":"Retrieval: what it fixes and what it does not","url":"https://addaly.com/learn/building-with-ai/retrieval-that-helps"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Approval gates, undo, and handing over to a person', in Building with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-with-ai/human-in-the-loop-handoff (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Approval gates, undo, and handing over to a person","courseTitle":"Building with AI","module":{"index":3,"slug":"structure-tools-and-the-loop","title":"Structure, tools and the loop"},"answer":null,"keyIdea":"Gate on reversibility rather than importance, put the gate in the tool where the model cannot argue past it, and watch the approval and override rates because a gate approved 99% of the time is theatre.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"stack","title":"Gate on how hard it is to undo, not on how important it feels","layers":[{"label":"Reversible in one click: let the model act","detail":"Draft an email, add a tag, create an unpublished record. No approval."},{"label":"Reversible with effort: act, log clearly, offer undo","detail":"Publish a post, update a customer record, cancel a booking."},{"label":"Not reversible: a person approves, every time","detail":"Send money, message a customer, delete data, change permissions, sign anything."}],"caption":"A gate on the top layer trains people to click Approve without reading, which destroys the value of the gate on the bottom one. Watch the approval rate: at 99 per cent the gate is theatre and the action should be automated or the gate narrowed."},"alt":"Figure: Gate on how hard it is to undo, not on how important it feels. Layers, top to bottom: Reversible in one click: let the model act (Draft an email, add a tag, create an unpublished record. No approval.); Reversible with effort: act, log clearly, offer undo (Publish a post, update a customer record, cancel a booking.); Not reversible: a person approves, every time (Send money, message a customer, delete data, change permissions, sign anything.). A gate on the top layer trains people to click Approve without reading, which destroys the value of the gate on the bottom one. Watch the approval rate: at 99 per cent the gate is theatre and the action should be automated or the gate narrowed."}},"blocks":[{"t":"h2","id":"decide-by-reversibility-not-by-importance","runs":[{"text":"Decide by reversibility, not by importance"}]},{"t":"p","runs":[{"text":"The useful axis for deciding what needs a human is not how important an action is. It is "},{"text":"how hard it is to undo","b":true},{"text":"."}]},{"t":"ul","items":[[{"text":"Reversible in one click","b":true},{"text":": draft an email, add a tag, create an unpublished record. Let the model do it. Requiring approval here trains people to click Approve without reading, which destroys the value of every gate you have."}],[{"text":"Reversible with effort","b":true},{"text":": publish a post, update a customer record, cancel a booking. Let it act, log it clearly, and provide a visible undo."}],[{"text":"Not reversible","b":true},{"text":": send money, send a message to a customer, delete data, change permissions, sign anything. Human approval, every time, with the specifics on screen."}]]},{"t":"p","runs":[{"text":"A useful sharpening: anything a stranger sees, anything that moves money, and anything you cannot get back. Those three cover almost every case."}]},{"t":"h2","id":"what-a-good-approval-screen-shows","runs":[{"text":"What a good approval screen shows"}]},{"t":"p","runs":[{"text":"Approval fatigue is the failure mode of this whole area. A dialog that says "},{"text":"The assistant wants to run send_email. Approve?","i":true},{"text":" teaches people to click yes."}]},{"t":"p","runs":[{"text":"Show the thing itself:"}]},{"t":"ul","items":[[{"text":"The exact action, rendered","b":true},{"text":": the full email with recipient, subject and body; the amount and the account; the rows that will be deleted, with a count"}],[{"text":"Why","b":true},{"text":", in one line: the user request or the rule that led here"}],[{"text":"What changes and what does not","b":true},{"text":": “this refunds ₹2,400 to the original card; it does not cancel the subscription”"}],[{"text":"Approve, edit, reject","b":true},{"text":": with edit as a real option. Most rejections are of a nearly right action, and forcing the person to reject and start again wastes the work."}]]},{"t":"p","runs":[{"text":"One approval per action. Batch approval of twelve heterogeneous actions is a single click that nobody reads."}]},{"t":"h2","id":"the-gate-belongs-in-the-tool-not-the-prompt","runs":[{"text":"The gate belongs in the tool, not the prompt"}]},{"t":"p","runs":[{"text":"This is the mistake worth naming explicitly. “Always ask the user before sending an email” in a system prompt is a "},{"text":"preference","i":true},{"text":", not a control. It will hold most of the time and fail exactly when something unusual is happening, which is when you needed it."}]},{"t":"p","runs":[{"text":"The gate goes in code, at the point of effect:"}]},{"t":"code","lang":"python","label":"Python","text":"def send_email(to, subject, body):\n    if not approvals.granted(action_id(to, subject, body)):\n        raise NeedsApproval(to=to, subject=subject, body=body)\n    return mail.send(to, subject, body)"},{"t":"p","runs":[{"text":"The model cannot talk its way past that, because the model is not involved. "},{"text":"NeedsApproval","code":true},{"text":" comes back through the loop as a recoverable result, the run pauses, and the pending action is stored for a person. This is the same principle as authorising against the session rather than the arguments: "},{"text":"anything that must always happen is code, and anything in the prompt is a suggestion.","b":true}]},{"t":"h2","id":"pausing-a-run-properly","runs":[{"text":"Pausing a run properly"}]},{"t":"p","runs":[{"text":"An agent waiting for approval may wait for hours, so the run cannot live in a request handler."}]},{"t":"p","runs":[{"text":"Persist the whole state: messages so far, pending tool call, run id, who may approve, an expiry. When approval arrives, resume from that state rather than starting again. Two rules that save you: "},{"text":"expire pending approvals","b":true},{"text":" (a refund approved from a two-week-old context is approving something nobody remembers), and "},{"text":"re-validate at execution time","b":true},{"text":" (the order may have been cancelled while the request sat in a queue)."}]},{"t":"h2","id":"handing-to-a-person-and-handing-back","runs":[{"text":"Handing to a person, and handing back"}]},{"t":"p","runs":[{"text":"Escalation is a normal outcome, not a failure. It should be triggered by rules you write, not by the model’s judgement about its own competence:"}]},{"t":"ul","items":[[{"text":"The user asked twice for a human"}],[{"text":"Sentiment or complaint language crossed a threshold"}],[{"text":"The topic is on your list: legal, medical, safety, a named regulator"}],[{"text":"Confidence checks failed, or retrieval returned nothing"}],[{"text":"The loop hit a budget"}]]},{"t":"p","runs":[{"text":"When it fires, hand over properly. A person picking up a conversation needs a two-line summary of what was asked, what was tried and what failed, not a 40-turn transcript to read while a customer waits. Carry the context, keep the ticket id, and do not make the customer repeat themselves. Most people’s anger at automated support comes from that one thing."}]},{"t":"p","runs":[{"text":"And give the human a way back. A conversation that escalated may return to the assistant once the specific issue is resolved. A one-way door means every escalation permanently consumes a person."}]},{"t":"h2","id":"count-the-gate","runs":[{"text":"Count the gate"}]},{"t":"p","runs":[{"text":"Instrument it like anything else: how often approval is requested, how often it is granted unchanged, how often edited, how often rejected."}]},{"t":"p","runs":[{"text":"A 99 per cent approval rate means the gate is theatre and the reviewer is rubber-stamping: either automate the action or narrow the gate to the cases that are actually contested. A 40 per cent rejection rate means the model should not be proposing these at all. The useful range is in between, and knowing where you sit is the difference between oversight and its appearance."}]}],"check":{"question":[{"text":"An assistant’s system prompt says it must always ask before issuing a refund. Refunds are occasionally issued without a prompt appearing. What is the structural fix?"}],"options":[{"text":[{"text":"Restate the rule more emphatically and repeat it at the end of the prompt."}],"correct":false,"tempting":[{"text":"Increases the salience of an instruction that remains advisory; a rule the model can weigh is a rule it can sometimes not weigh."}]},{"text":[{"text":"Raise the model tier so instruction-following is more reliable."}],"correct":false,"tempting":[{"text":"Reduces the frequency of the failure without removing the possibility, which is not an acceptable property for an irreversible money movement."}]},{"text":[{"text":"Make the refund tool refuse to execute without a stored approval token, so the check happens in code at the point of effect."}],"correct":true,"tempting":null},{"text":[{"text":"Log every refund so unapproved ones can be reversed afterwards."}],"correct":false,"tempting":[{"text":"Provides detection and cleanup after the fact, which is worth having but does not prevent the action the gate exists to prevent."}]}],"order":[3,2,0,1],"why":null},"prev":{"slug":"multi-agent-when-worth-it","title":"Multiple agents, and the honest cost","minutes":9},"next":{"slug":"retrieval-that-helps","title":"Retrieval: what it fixes and what it does not","minutes":8}}}