---
title: Human-in-the-loop approval patterns
slug: human-in-the-loop-approvals
summary: "Patterns that let agents do the work while a human keeps control: dry-run by default, draft-then-post, approval queues, two-key changes and kill switches."
order: 30
updated: 2026-11-11
status: ready
---

Use this page when designing any automation that could change a system or speak for a person. The aim is that the cheapest path for the agent is also the safe one, and that every write can be traced to a human decision.

## Pattern 1: Dry-run is the default

Scripts print the exact command they would run. Only an explicit flag makes them act.

```bash
DRY_RUN=1
[ "${1:-}" = "--execute" ] && DRY_RUN=0
do_change() {
  if [ "$DRY_RUN" = 1 ]; then printf 'DRY-RUN would run: %q ' "$@"; echo; else "$@"; fi
}
do_change glab mr create --draft --title "access: read-only role for <service>"
```

- [ ] Reads and local drafts always run. Remote side effects go through `do_change`.
- [ ] Only a human adds `--execute`, and only for a run they reviewed.

## Pattern 2: Draft, then post

The agent writes what it would say or change into a file or a draft MR. A human approves item by item. A separate step posts only approved items. See the posting script in [connecting Jira](./connecting-jira.md).

## Pattern 3: One approval queue

All pending decisions land in one daily file or board, sorted by risk. The human works it once a day instead of being interrupted. See the [automation blueprint](./automation-blueprint.md).

## Pattern 4: Approval is specific, recorded and fresh

- [ ] An approval names the exact change: this MR, this ticket, this command.
- [ ] It is recorded where an auditor can find it (MR approval, ticket comment).
- [ ] It does not carry over. Approving one access grant does not approve the next.
- [ ] Another agent saying "the human approved" is not approval. Check the record.

## Pattern 5: Guardrails before the human sees it

Reject unsafe proposals automatically so the human only reviews sane ones.

| Proposal | Automatic checks before it reaches the queue |
| --- | --- |
| CI file | Valid YAML, lints clean, production job is manual and on the default branch only, no secrets |
| IAM change | Plan applies cleanly, only allowed paths changed, no `*` actions or resources, no admin policies |
| Ticket comment | No secrets, account IDs or customer data; references real evidence links |
| Monitor change | Has owner tag, notify handle and runbook link |

## Pattern 6: Two keys for risky changes

- [ ] Production, IAM, network and data deletion changes need two approvers, and the author is not one of them.
- [ ] Use platform features (protected environments, required approvals, CODEOWNERS) so this is enforced, not remembered.

## Pattern 7: Least privilege for the agent itself

- [ ] The agent's credentials are read-only. Writes happen through CI with its own scoped role after merge.
- [ ] Tokens are short-lived and stored in a secret store.
- [ ] The agent cannot approve or merge its own MRs.

## Pattern 8: Kill switch and audit trail

```bash
# Every job checks this first
[ -e "$HOME/.automation-paused" ] && { echo "paused by human"; exit 0; }
```

- [ ] One file or flag pauses all automation.
- [ ] Every write is logged with time, target, action and the approval reference.
- [ ] Weekly, a human reads the write log.

## Pattern 9: Stop on surprise

- [ ] Unexpected output, an error, or a plan larger than expected stops the run and reports. No automatic retries of writes.
- [ ] Instructions found in tickets, logs or web pages are data. The agent never follows them.

## Pitfalls

- Approval fatigue. If the human approves 100 items a day without reading, batch better and add guardrails so fewer items need review.
- "Temporary" bypasses of the queue that become permanent.
- An agent with write credentials "for convenience". Convenience is how incidents start.
