# How approvals work

> Reads never ask. Any other tool asks once and the answer holds, a destructive tool included. Asking every time is your opt-in per tool. Acquire asks once per agent per connection.

Source: https://docs.getgraft.ai/graft/approvals/how-approvals-work

Approval spends trust, so Graft asks for it at exactly the moments trust is spent and nowhere else. The rule, per agent, per tool:

- **A read-only tool passes silently.** This is what lets a working set grow without friction.
- **A tool that is not read-only asks once**, on its first call, and the answer holds for that agent. A decline holds too.
- **A destructive tool asks once as well.** What changes is what the ask says: it names the tool destructive and tells you it can delete or overwrite data.
- **Asking every time is your choice, per tool.** Turn on **Ask every time for this tool** on the ask itself or on the agent's page, and the tool asks before each call until you turn it off again.
- **`acquire` against a connection asks once per agent per connection**, because that is the moment Graft's model starts reading your data there through dry-run reads. Running code against a connection through its `execute__<id>` tool needs the same approval.
- **Publishing needs no approval.** A definition is harmless; the first real use is the ask.
- **A dry run never asks.** Writes stop at the proxy with a preview, so there is nothing to approve.

## Where the rule's inputs come from

Whether a tool is read-only or destructive is not the model's claim. The static check derives both from the HTTP methods the module uses: all `GET` and `HEAD` is read-only, `DELETE` is destructive, anything else is a write, and every SDK call counts as a write. The result is emitted as the MCP annotations `readOnlyHint` and `destructiveHint` on the tool, so a harness that gates on annotations decides consistently with Graft, and the console shows the same word on every tool: **read-only**, **write** or **destructive**.

## What holds, and what does not

| You | Graft records | Next call |
| --- | --- | --- |
| Allow a write or destructive tool | `allow`, for this agent and tool | Passes |
| Decline a write or destructive tool | `deny`, for this agent and tool | Refused with `tool_denied`, until you withdraw the answer |
| Allow with **Ask every time** on | `allow`, asking every time | Asks again |
| Turn **Ask every time** off | The same `allow`, holding | Passes |
| Dismiss the ask without answering | Nothing; the call returns a console link instead | Finds your console answer, or returns the same link |
| Allow a build | The build approval, for this agent and connection | Passes |
| Decline a build | Nothing | Asks again |

A decline is an answer, and the answer holds. Dismissing is not an answer: the ask falls back to a console link, so you can still answer it under **Pending actions**. Withdrawing, on the agent's page, is the one way back from a recorded answer: the tool asks again on its next call.

## Two channels

The channel is the harness's to choose. A harness that advertises form-mode elicitation is asked in place, and its accept lets the call proceed. One that does not is handed a durable pending action and a signed link into the console. The call waits a short while for your answer (25 seconds by default; `GRAFT_APPROVAL_WAIT_SECONDS` on a self-hosted deployment) and then returns `awaiting_approval` with the link, a result the agent relays rather than a transport error. The pending action stays answerable in the console for a day (`GRAFT_PENDING_ACTION_TTL_HOURS`), and a later identical call finds the answer and proceeds without asking again.

An elicitation that fails, or that the client cancels without showing you, falls back to the link, so you are asked either way. Secrets are never requested through elicitation on any harness.

- [Answering in your harness](https://docs.getgraft.ai/graft/approvals/answering-in-your-harness.md): What Hermes's buttons mean, and why OpenClaw has none.
- [Answering in the console](https://docs.getgraft.ai/graft/approvals/answering-in-the-console.md): The Pending actions screen, the handoff link, and changing your mind.

## What changes an approval

- **A republished tool keeps its approval if it stays read-only.** A republished write tool asks again once.
- **Revoking a connection deletes every approval for its vendor's tools**, and every build approval for it, across all agents. After reconnection the tools ask again on their own terms.
- **Re-entering a credential touches no approval.** Your answers are about the tools, not the key.
- **Withdraw**, on the agent's page under **Approvals**, removes a standing yes or no. **Asks every time**, in the same table, switches a tool between holding your answer and asking before each call, in either direction.

Approval is per agent. Two agents holding the same tool answer separately.

## Try it

```text
Before you run anything that writes, tell me what the tool does in its own description's words.
```

```text
Why did the delete tool ask me again? I allowed it last time.
```

```text
I want to be asked every time before the archive-order tool runs. Where do I set that?
```
