Guide
Human approval for AI agents
An approval button is easy. An approval gate that still holds when a permission is revoked mid-flight, a request is retried twice, and the model proposed a path outside its remit is the part worth checking.
Last substantive change:
Four properties, and how to test for them
- Fail closed
- When authorisation is missing, revoked, or expired, the action must be unreachable — not reachable and then blocked. Ask what happens if a permission is revoked between the draft and the approval.
- Re-check at the boundary
- The target of the action must be re-derived from trusted state at the moment of the write, not carried along from whatever the model proposed. Ask where the path or amount is validated, and how many times.
- Idempotent
- A double click, a retried request, or a replayed webhook must converge on the one action that exists rather than performing a second one. Ask what the deduplication key is.
- Recorded
- The approval must name the deciding human and commit in the same transaction as the action it authorises. An approval that can exist without its action — or an action without its approval — is a log, not a gate.
| Property | The question to ask a vendor |
|---|---|
| Fail closed | When authorisation is missing, revoked, or expired, the action must be unreachable — not reachable and then blocked. Ask what happens if a permission is revoked between the draft and the approval. |
| Re-check at the boundary | The target of the action must be re-derived from trusted state at the moment of the write, not carried along from whatever the model proposed. Ask where the path or amount is validated, and how many times. |
| Idempotent | A double click, a retried request, or a replayed webhook must converge on the one action that exists rather than performing a second one. Ask what the deduplication key is. |
| Recorded | The approval must name the deciding human and commit in the same transaction as the action it authorises. An approval that can exist without its action — or an action without its approval — is a log, not a gate. |
Stenlio’s answers to the same four
- Fail closed
- The approval query joins on a live content surface, a connected integration, a connected provider account, and an owner or admin membership. Revoke any of them and the artifact resolves to no row, so approval is refused before a single request reaches GitHub.
- Re-check at the boundary
- The article path is re-derived against the registered directory root inside the approval transaction, and again at the write itself. Neither check trusts the other.
- Idempotent
- Keyed on the artifact, not the attempt. A second approval returns the action that already exists, and the branch name is a pure function of the action ID, so retries land on the same branch and the same pull request.
- Recorded
- One transaction writes the authorisation check, the reserved action, the approval row naming the deciding user, the artifact status change, and the outbox event. Either all of it commits or none of it does.
| Property | Implementation |
|---|---|
| Fail closed | The approval query joins on a live content surface, a connected integration, a connected provider account, and an owner or admin membership. Revoke any of them and the artifact resolves to no row, so approval is refused before a single request reaches GitHub. |
| Re-check at the boundary | The article path is re-derived against the registered directory root inside the approval transaction, and again at the write itself. Neither check trusts the other. |
| Idempotent | Keyed on the artifact, not the attempt. A second approval returns the action that already exists, and the branch name is a pure function of the action ID, so retries land on the same branch and the same pull request. |
| Recorded | One transaction writes the authorisation check, the reserved action, the approval row naming the deciding user, the artifact status change, and the outbox event. Either all of it commits or none of it does. |
One caveat that belongs here rather than in a footnote: GitHub’s contents-write permission is repository-wide, so Stenlio’s directory limit is an application guarantee rather than a permission boundary. Why, and what that means.
Draw the line in three states, not two
Most tools present two: runs on its own, and needs approval. That collapses two very different things — an action the tool can perform once you say yes, and an action it cannot perform at all. Readers fill the gap with the more generous reading. Naming the third state is the cheapest accuracy fix available.
Runs unattended, output stays inside the tool:
- Read the connected repository and website and cite what it read
- Rank content opportunities and write the article draft
- Triage an incoming issue and propose a label
- Score an inbound lead against your ICP
- Categorise an expense and recompute runway
- Run an accessibility audit against WCAG AA
Supported, and only after an explicit approval:
- Open a draft pull request in the directory you registered
Not built — no approval enables them:
- Merging, publishing, or deploying anything
- Writing anywhere outside the registered content directory
- Sending email, posting to social, or spending on ads
- Moving money, issuing refunds, or changing a price
- Modifying infrastructure or running database migrations
Before you connect anything
Install on one repository rather than an organisation. Register the narrowest directory that still works. Confirm the tool opens drafts rather than pushing to your default branch. Ask where a copy of what it read is stored and for how long, because reading is a copy even when nothing is written back. Stenlio’s answers for GitHub, and a free check of your own directory against the guard itself.