Skip to main content
Some writes are too routine to review and too consequential to wave through. An agent that can update feature_flags is doing its job; the same agent running a migration against orders at 2am is a conversation you want to have first. Until now the only answers were allow and refuse, so the choice was between granting a write you’d rather see and denying one the agent legitimately needs. Ask is the third answer (0.16.0+). A write the policy already permits is held, a human decides, and the agent picks the result back up — no credential handed over, no policy loosened. Reach for it when:
  • An agent needs writes you don’t want unsupervised. Grant read_write on orders, then hold row changes so a person sees each one before it lands.
  • Schema changes need a second pair of eyes, but row writes don’t. Hold schema changes only; ordinary UPDATEs keep running.
  • You’re loosening a policy and want a safety period. Turn a table writable and hold its writes for a week to see what the agent actually does.

The three write classes

Every write falls into one of three classes, and each carries its own independent rule (0.17.0+): Each class takes exactly one of refuse, ask, or allow — values of a single rule, not gates stacked on each other, so the two can’t be configured to contradict one another. Two consequences worth knowing before you configure this:
  • Approvals sit under the policy, never over it. The hold runs only after the policy returns ALLOW, so a statement table_access or a guardrail already refused never reaches a reviewer — and no approval can buy a way past one.
  • A statement in more than one class is held if any of its classes is held. WITH d AS (DELETE FROM audit_log) UPDATE orders SET … WHERE id = 1 is both a whole-table write and a row change; running it because only one of the two was set to ask would be the worst available answer.

What happens to a held write

1

The agent's query is held

The engine files an approval request and holds the connection briefly. If a human answers within that window, the write runs inside the agent’s original tool call — the agent never sees a hold at all.
2

A slow decision becomes a ticket

Rather than block past an MCP client’s timeout, the engine hands the agent an approval_id, a deadline, and a review URL. Nothing has run, and nothing has been denied.
3

The agent polls

check_approval reports pending / approved / executed / consumed / denied / expired. Polling is cheap and never consumes the grant.
4

The agent re-runs to collect

Once approved, the agent calls query again with the identical sql and intent. That call is what executes the write.
The grant is bound to (database, sql, intent, token) and is single-use. An approval for DELETE FROM orders WHERE id < 100 cannot authorize … < 100000 — one character’s difference is a different request, needing its own approval. Two retries racing one approval yield exactly one execution.
A held write has not run. The tool result says so first and flatly (executed: false), because the worst thing an agent can do here is report a write that never happened. If you’re writing an agent harness, treat awaiting_approval as an open task, not a result.

An outage is never a refusal

If the approval service can’t be reached, the agent gets approval_unavailable — explicitly retryable, and not a denial. Nothing is audited and no deny webhook fires, so an outage never leaves a refusal nobody made in your compliance record. Real decisions are audited like any other denial, as approval_denied or approval_expired. See denial reasons.

Reviewing a request

Held writes land in Approvals in the dashboard — a cross-project queue, alongside the audit log it mirrors. Anyone on the team can read the queue; approving and denying is owner and admin only. A request nobody answers expires after 30 minutes, and the agent is told to ask again.
  • A denial’s note reaches the agent. Say why you refused, and it can write a better statement instead of retrying the same one.
  • Notifications are link-only. A held DELETE’s WHERE clause routinely carries live values, so the statement is customer data — it renders in the dashboard, never in an email.

Turn it on

In the project’s Database pane, set any row under What a write may do to Ask, then Save. The change hot-reloads the running engine — no restart, and no agent session dropped.
Approvals need a reviewer that can answer. With no gate configured they’d hold every permitted write forever with nothing to review, so the save is refused rather than accepted into a dead end.

Table access & guardrails

Which tables an agent may reach, and the writes refused outright.

`check_approval`

The tool an agent polls, and what each status tells it to do.

Policy schema

The approvals and guardrails keys, with every default.

Denial reasons

How a denied, expired, or held write reaches the agent and the audit log.