Quick Start
Enable durable scoped approvals
Durable, agent-scoped grants are on by default — just launch the gateway. The allow-list opens a SQLite store at
~/.praisonai/state/gateway/approvals.sqlite.Grant Once, Auto-Approve Forever (per Agent)
Resolve a pending request with
allow_always=True. The grant is scoped to the resolving agent by default — one agent’s approval never authorises another.Scope even tighter to arguments
Add
scope_to_args=True so only subsequent calls with the same argument shape auto-approve; a different argument re-prompts.How It Works
A matching grant short-circuitsregister — the future resolves immediately with approved=True and no prompt is shown.
Which Scope to Use
Pick the narrowest scope that still saves the operator from re-approving.The Grant Key
Every grant is stored as(agent_id, tool_name, arg_signature).
| Key part | Type | Meaning |
|---|---|---|
agent_id | str | Resolved agent name; sentinel "*" means “any agent” |
tool_name | str | Registered tool name |
arg_signature | str | None | First 32 chars of SHA-256 over json.dumps(args, sort_keys=True); None = argument-agnostic |
Resolver Options
Resolution carries the resolver’s decision and how broadly to persist it.
| Field | Type | Default | Description |
|---|---|---|---|
approved | bool | (required) | Whether the request is approved |
reason | str | "" | Human-readable reason |
allow_always | bool | False | Persist the grant; when False the resolution applies only to this one request |
scope_to_agent | bool | True | Grant is scoped to the requesting agent; set False for pre-#2622 global grants |
scope_to_args | bool | False | Also key the grant on the argument SHA — subsequent calls with different arguments will re-prompt |
Silent-Skip Warning
Storage Layout
Grants live in one SQLite file — safe to back up, rotate, or delete.| Aspect | Value |
|---|---|
| Default path | ~/.praisonai/state/gateway/approvals.sqlite |
| Env override | PRAISONAI_HOME (whole state root moves) |
| Programmatic override | ExecApprovalManager(allowlist_path=...) |
| Journal mode | WAL |
| Default TTL | 90 days (evicted lazily on read / list()) |
| Safe to delete | Yes — next restart re-prompts on the next request |
If the store can’t be opened (permissions, disk full, corruption) the gateway logs and falls back to in-memory only — grants won’t survive restart, but the gateway does not crash.
HTTP Endpoint
GET/POST/DELETE /api/approval/allow-list manages grants over HTTP. POST and DELETE accept an optional agent_id; GET returns a grants view alongside the legacy allow_list.
- GET
- POST
- DELETE
Mutations (
POST/DELETE) require OperatorScope.APPROVALS and pass the per-IP rate limiter. See Gateway Operator Scopes.Legacy Behaviour Preserved
I use allowlist.add('tool_name') directly
I use allowlist.add('tool_name') directly
Still works. It creates an any-agent (
agent_id="*") grant with an empty argument signature. "tool" in allowlist, allowlist.remove("tool"), and allowlist.list() also keep their original meaning.I POST /api/approval/allow-list without agent_id
I POST /api/approval/allow-list without agent_id
Still works. Omitting
agent_id creates an any-agent grant, exactly as before.I want the old cross-agent leaky behaviour
I want the old cross-agent leaky behaviour
Set
scope_to_agent=False on the Resolution. The grant then applies to every agent.I need to purge everything
I need to purge everything
Delete the SQLite file, or call
store.revoke_tool(name) to drop every grant for one tool across all agents.Best Practices
Keep scope_to_agent=True
Keep scope_to_agent=True
One agent’s approval should never authorise another. The default scoping prevents accidental cross-agent leakage.
Use scope_to_args=True for high-blast-radius tools
Use scope_to_args=True for high-blast-radius tools
For
shell_exec or apply_patch, add scope_to_args=True so the operator’s approval only covers the exact command they saw.Rotate or shorten TTL for security-sensitive state
Rotate or shorten TTL for security-sensitive state
Rotate the SQLite file periodically, or shorten the window via
ScopedAllowlistStore(ttl_seconds=...).Watch the silent-skip log line
Watch the silent-skip log line
“Skipping scoped allow-always grant” means a resolver is sending
scope_to_agent=True on a request with no agent identity — the grant was not persisted.This is defence-in-depth, not a security boundary: a resolver that can approve requests can still choose
scope_to_agent=False. The default scoping prevents accidental cross-agent leakage.Related
Gateway
Gateway overview and deployment
Approval Backends
How approval requests are routed
Gateway Operator Scopes
Authorisation for admin endpoints
Durable Approvals
Restart-safe bot pending-approval queue

