Human-in-the-Loop Approvals
Hold an agent's writes and DDL until a human approves them. Approve or reject in the dashboard, set auto-approve rules for safe changes, and auto-expire stale requests.
Approvals hold a statement before it reaches your database and wait for a human to say yes. Turn it on for a credential, and every write or DDL statement it runs is parked as an approval request instead of executing. A reviewer approves or rejects it in the dashboard, the agent's session blocks until then, and the statement runs only on approval.
This is the middle ground between read-only (no writes ever) and read-write (any write, immediately). The agent stays productive: it can draft a change and hand it to a person, rather than being told no.
Approvals apply to agent credentials and to human credentials. A junior analyst's write can be held for a senior reviewer the same way an agent's is.
Turn it on
Add an approval rule to a policy. It names which statement kinds are held.
pgbeam policies create --name writer-supervised \
--mode read_write \
--allow public.orders \
--approval-mode all \
--approval-timeout-seconds 3600On the policy profile, open Approvals, choose which statement kinds to hold (writes, DDL, or both), and set an expiry. Pending requests show up on the Approvals tab with the full context a reviewer needs (see below).
{
"name": "writer-supervised",
"access_mode": "read_write",
"table_allowlist": ["public.orders"],
"approval_mode": "all",
"approval_timeout_seconds": 3600,
"approval_auto_max_rows": 10
}approval_mode is one of off, writes, ddl, or all, and sets which
statement classes are held for a reviewer.
What the agent sees
When a held statement is parked, the agent gets an LLM-readable notice on the wire and the session waits:
NOTICE: statement held for approval (request apr_7f3a). Waiting for a reviewer.If the request is approved, the statement runs and the agent gets its result. If it is rejected or expires, the agent gets an error explaining why, so it can move on instead of hanging.
ERROR: statement rejected by reviewer (request apr_7f3a)
ERROR: approval request apr_7f3a expired after 1hWhat reviewers see
Each pending request on the Approvals tab carries the context needed to decide without opening a SQL console:
- The full SQL of the held statement and the credential that ran it, shown by
its friendly name rather than an opaque
agt_…id. - The statement kind (for example
update,delete, or a DDL kind) as a badge. - The target tables the statement touches.
- An affected-row estimate, always labeled estimated because the data can change between the estimate and execution.
- A warning when the estimate exceeds the policy's
max_affected_rowscap: even if the request is approved, the gateway still blocks a write that actually affects more rows than the cap.
Approve or reject a request
Reviewers act in the dashboard, or from the API for automation:
# Approve
curl -X POST \
"https://api.pgbeam.com/v1/projects/{projectId}/approvals/{approvalId}/approve" \
-H "Authorization: Bearer $PGBEAM_TOKEN"
# Reject with a reason
curl -X POST \
"https://api.pgbeam.com/v1/projects/{projectId}/approvals/{approvalId}/reject" \
-H "Authorization: Bearer $PGBEAM_TOKEN" \
-H "Content-Type: application/json" \
--data '{"reason": "touches too many rows, scope it down"}'Auto-approve rules
Holding every write on a busy credential is noise. The approval_auto_max_rows
setting lets the safe changes through and reserves human attention for the rest.
It is a single row ceiling for the whole policy: a held write that touches at most
that many rows is approved automatically, and anything larger stays held.
{
"approval_mode": "writes",
"approval_auto_max_rows": 10
}PgBeam counts the rows a write would affect before it commits. A write at or under
the ceiling is approved and recorded; a write over it is parked for a person. Set
approval_auto_max_rows to 0 to auto-approve nothing. DDL and other statements
with no countable row effect are never auto-approved.
Auto-expire
A held request that no one acts on expires after the policy's expiry window. The
statement is rejected, the agent is told, and the request closes. This keeps a
forgotten request from holding an agent session open forever. Every approval,
rejection, and expiry is written to the audit log and can fire
an approval_requested webhook.
Related
- Read-only enforcement: block writes entirely instead of holding them.
- Sandbox writes: let an agent write freely against a throwaway branch.
- Audit log: every approval decision is recorded.
- Audit export: fire a webhook when a request is created.
Local policy testing
Test allowlists, masking, row filters, and statement rules against a local or dev database with the pgbeam-dev tool, before a policy reaches prod.
Sandbox Writes
Let an agent write freely against an instant, isolated branch of your database, or run every write in always-rollback dry-run mode. Production is never touched.