Decide which actions an automation may take on its own, which need a person, and build the approval gate and audit trail that make that decision stick.
Module 10 ~50 min read + lab No codePrerequisites: Module 9: Multi-agent workflows, and the triage flow you built in Module 6.
Modules 5 to 9 taught you to make things happen automatically. This module teaches you to decide, on purpose, what should not happen without a person. That order is deliberate. Until you have watched an automation act on its own, the need for a gate feels theoretical. After the first wrong email goes to a customer, it feels obvious.
"Human-in-the-loop" (HITL) is the general name for any design where a person can see, approve, change or stop what an automated system does. It is not a single feature. It is a set of decisions about where the person sits, what they see and how much they are asked to do. Get those decisions right and the automation is trusted and used. Get them wrong and one of two things happens: the gate is ignored because it fires too often, or the gate is missing where it mattered.
For every action in a workflow, ask: If this ran wrongly a hundred times before anyone noticed, what would it cost? The answer decides the tier, and the tier decides the control.
Every tool an agent can call falls into one of five tiers. The tier is about the consequence of a wrong call, not about how clever the step is.

| Tier | What it does | Examples | Control needed |
|---|---|---|---|
| 1. Read-only | Looks something up, changes nothing | Search a sheet, read a calendar, fetch a ticket | None beyond logging. Let it run. |
| 2. Reversible write | Changes internal data that can be undone | Add a row, set a label, create a draft, move a card | Review after. Spot-check a sample weekly. |
| 3. Irreversible internal | Cannot be undone, but stays inside the organisation | Delete a record, overwrite a file, close a ticket | Approve before, or convert to reversible (archive instead of delete). |
| 4. External / customer-facing | Leaves the organisation | Send an email, post to a customer portal, reply on social media | Approve before, or edit-then-approve. Always. |
| 5. Money, people, legal | Commits funds, affects a person's status, or creates legal exposure | Issue a refund, approve leave, sign a contract, change pay | The system prepares; a named person decides. No auto-run, ever. |
Two habits make tiering easy. First, tier the tool, not the workflow: an "email" tool is Tier 4 wherever it appears. Second, look for a cheaper tier that gives the same result. "Delete" becomes "archive" (Tier 3 to Tier 2). "Send email" becomes "create draft in the sender's mailbox" (Tier 4 to Tier 2), with the human pressing send. Most approval gates can be avoided by choosing a lower-tier tool.
An agent that "usually" only reads but "occasionally" sends is a Tier 4 agent. Controls follow the highest tier the agent can reach, not the average.
Once you know the tier, pick the pattern. There are only four, and each answers a different question: before or after, and every time or only when unsure.

The workflow pauses and shows the person exactly what it is about to do. Nothing happens until they say yes. Use for Tier 3 and 4 actions. Cost: one click per run, so volume must be manageable.
The action runs, and the person sees a digest (daily or weekly) of what happened, with an undo path. Use for Tier 2 writes at high volume. Cost: near zero per run, but mistakes live for a while before they are caught.
The system drafts, the person adjusts and sends. The best pattern for customer-facing text: the person stays the author, the machine removes the blank page. Cost: a minute per item, so it suits tens per day, not thousands.
The workflow runs alone when the model is sure, and routes to a person when it is not. Use when most cases are easy and a few are hard. Requires the AI step to report a confidence or a category such as "unsure".
Real workflows combine them. The Module 6 triage flow, for example, can run alone for labelling (Tier 2), escalate anything the model marks "unsure", and use edit-then-approve for every outgoing reply (Tier 4).
Under every approval gate is the same mechanism, whatever tool you use. The workflow reaches a checkpoint, saves its state (everything it knows so far), stops, and waits for a signal. When a person sends the signal, the workflow resumes from exactly where it stopped, with the person's decision added to what it knows.

In developer frameworks this is explicit. LangGraph, for example, lets a designer mark a step as "interrupt before": the graph pauses just before that step, a checkpoint is stored, and a human call is needed to continue. The same idea appears in every no-code tool under a friendlier name:
| Tool | How the pause is built | How the human resumes it |
|---|---|---|
| n8n | Wait node set to "resume on webhook call" or "on form submission" | Clicks an approve or reject link in Slack or email; the link calls the resume address |
| n8n (simpler) | Gmail or Slack node with "Send and wait for approval" | Presses Approve or Decline in the message |
| Power Automate | Approvals action ("Start and wait for an approval") | Responds in Teams, Outlook or the Approvals app |
| Make / Zapier | Split into two scenarios: one ends by sending the request, a second starts from the approval webhook | Clicks a link that fires the second scenario |
Three design details decide whether a gate is any good:
![An e-mail titled Approve this reply? Re: [S10 test] Refund for unused licences, with the recipient, subject, category, the full reply between reply and end markers, the time limit, and two buttons: Reject and Approve and send](images/m10-approval_email.jpg)
If you want to see the mechanism in code, the LangGraph tutorial on this site walks through checkpointing and interrupt-before step by step. You do not need it for the labs; it is there for the curious.
Approval gates control which actions run. Two more controls limit how much can go wrong between gates.

An agent (Module 8) loops until it decides it is done. Give it a ceiling: at most 10 tool calls per task, at most a fixed model spend per run, at most 50 runs per hour for the whole workflow. In n8n's AI Agent node this is the "max iterations" option; in most builders it is a similar setting. When the ceiling is hit the agent must stop and hand the task to a person with what it has so far, not silently give up.
An audit trail answers, months later, "what did the system do, and who said it could?" At minimum log one row per gated action:
A Google Sheet or Airtable base is enough to start. The important properties are that the log is append-only (nobody edits history), complete (every gated action, including rejections and timeouts), and readable by someone who was not there.
A gate that fires 200 times a day gets rubber-stamped by lunchtime, and a rubber-stamped gate is worse than none, because it creates false confidence. Warning signs: median approval time under five seconds, approval rate above 98%, the same person approving everything. Fixes: lower the tier of the tool (draft instead of send), batch approvals into a digest, escalate only low-confidence cases, rotate approvers, and review the numbers monthly.
Everything below was built and tested on n8n Cloud (2.43.6) on 23 October 2026: the Module 6 triage flow with a drafted reply behind an approval gate, Gemini 2.5 Flash on the trial's Gateway credits, temperature 0. The three customers in the test e-mails are fictional; Gmail addresses are blurred.



| Test e-mail | Classified | Decision | Where it ended |
|---|---|---|---|
| Khaled: cannot log in after a password reset | technical | approved | Reply sent; Log sent |
| Lina: refund for unused licences, "today" | billing | rejected | Label needs-human; Log not sent |
| Nadia: can we book a demo next week? | sales | timed out (3-minute limit) | Label needs-human; Log not sent; approver "nobody (timed out)" |
Why reject Lina's reply? It reads well, but anything that sounds like a refund is Tier 5: money. A person should answer that one personally.

You will add an approval gate to the e-mail triage workflow from Module 6 so that no reply leaves without a person seeing it, and every decision, including rejections and timeouts, is logged. Every setting below was verified on n8n Cloud 2.43.6 on 23 October 2026; the three trap boxes are what went wrong on the way.
const i = 0, 1, 2)Upload the Approval log template and Save as Google Sheets; create the Gmail label needs-human; duplicate your M6 Inbox triage as M10 Triage with approval gate; import the test sender and paste your address into Send to myself.
Our first Approval log was a new, empty sheet. The Append node ignored the nine mapped columns and wrote the incoming item's own fields as the header: id, threadId, labelIds. On a sheet that already has its header row the mapping works. Start from the template.
Fill the first tab of the tier worksheet: the trigger, Classify and Draft reply are Tier 1 (nothing leaves the run), the label and the log are Tier 2, Send reply is Tier 4. Only one node needs a gate.
New email (Gmail Trigger), Simplify off, Filters, Search:
Without the two exclusions the trigger also matches the approval request ("Approve this reply? Re: [S10 test] ...") and the reply the workflow sends ("Re: [S10 test] ..."). Our second test run picked up the sent reply. Published, the flow would draft replies to its own replies.
From Clean label, a Basic LLM Chain named Draft reply, prompt in Expression mode, the same Gemini model:
An Edit Fields node, Include Other Input Fields off: message_id = {{ $('New email').item.json.id }}, to = {{ $('New email').item.json.from.text }}, subject = Re: {{ $('New email').item.json.subject }}, label = {{ $('Clean label').item.json.label }}, reply = {{ $json.text.trim() }}, approver = your Gmail address.
Gmail, operation Send and Wait for Response: To {{ $json.approver }}, Subject Approve this reply? {{ $json.subject }}, Response Type Approval, Approval Options Approve and Disapprove (labels Approve and send, Reject), Options, Limit Wait Time 3 minutes for the lab (24 hours in real use). Message, in Expression mode:
An Edit Fields node Decision: decision =
plus message_id, to, subject, reply from $('Keep reply'), and approver = {{ $json.data?.approved === undefined ? 'nobody (timed out)' : $('Keep reply').item.json.approver }}. Then an IF Approved?: {{ $json.decision }} is equal to approved.
Measured on 23 October 2026: approved gives {data: {approved: true, respondedAt}}, rejected gives approved: false, and when the time limit runs out the node passes its input item through unchanged, with no data, on the same output. An IF that tests "is not false" would send the reply when nobody answered. Always test for approved.
True: Gmail, Reply to {{ $json.message_id }} with {{ $json.reply }}, then Google Sheets Append Row to the Approval log. False: Gmail, Add Label needs-human, then Append Row. Map the nine columns (timestamp, workflow, run_id, action_proposed, tier, approver, decision, final_content, notes) from $('Decision'), Cell Format Let n8n format. Rejections and timeouts are rows too.
In the test sender set const i = 0;, Execute workflow; in M10, Execute workflow; open the approval e-mail and Approve and send. Then i = 1 and Reject. Then i = 2 and do nothing for 3 minutes. The Approval log should show approved, rejected and timed out.
The node's output is approved and the time of the answer; it does not say who clicked, and anyone who has the e-mail can press the button. Send approval requests to one person's own mailbox, never a shared one or a forwarding list, and use a channel with sign-in (Slack, Teams, a form behind login) for Tier 5 decisions. Module 11 picks this up.
An export of the gated workflow, a screenshot of the approval message showing the full draft, and the Approval log sheet with at least one approved, one rejected and one timed-out row. Save as M10-approval-gate.
Pick one answer per question, then check your score. These mirror the style of the final exam.
Answer in your own words first, then open the model answer.
Read-only (look up a record), reversible write (add a row or label), irreversible internal (delete or overwrite), external (email a customer), and money/people/legal (approve a refund or leave). Any concrete mapping is acceptable if the consequence matches the tier.
The workflow reaches a checkpoint and saves everything it knows. It stops and waits for a signal from a person. When the signal arrives, it continues from the checkpoint with the person's decision included.
Timestamp, workflow and run id, the proposed action and tier, the approver, the decision including rejections and timeouts, and the final content. Append-only so that history cannot be edited after the fact and the record is trusted.
Further reading: LangGraph: checkpointing and human-in-the-loop · n8n guide: error handling and approval steps