Two durable stores, different jobs:
- This page — the bot pending-approval queue (
ApprovalStoreat~/.praisonai/state/approvals.sqlite) tracks in-flight tap prompts waiting for a decision. - Sibling — the gateway allow-list store (Gateway Scoped Approvals,
ScopedAllowlistStoreat~/.praisonai/state/gateway/approvals.sqlite) tracks “allow always” grants that skip future prompts.
Transport wiring for Telegram is auto-configured — see Telegram Durable Approval. This page covers the underlying store used by that path.
Quick Start
How It Works
EachApprovalRequest carries a stable approval_id (UUID hex) used as the correlation key across restarts.
Persisted approvals can be recorded as reusable command prefixes — see Reusable Approval Scopes for the
reusable_scope=True opt-in.ApprovalStore API
| Method | Description |
|---|---|
persist(approval_id, request, *, expires_at) | Store a pending approval; refuses to overwrite resolved rows |
list_pending() | Return outstanding un-expired approvals |
resolve(approval_id, decision) | Record final decision; returns False if already resolved |
expire_overdue() | Mark past-expiry pending rows as expired |
get(approval_id) | Inspect a row for audit/doctor |
purge() | Delete all entries |
What Gets Stored
Without a store, behaviour is unchanged — in-memory approvals remain the zero-dependency default. The core SDK defines
ApprovalStoreProtocol; the SQLite implementation lives in praisonai.bots.PersistentApproval entries in approvals.json carry a derived: bool field (default False). When derived=True, the pattern was auto-generated by the reusable command-prefix scope derivation — for example bash:git status * instead of the literal bash:git status -s. Derived patterns match the bare prefix too (bash:git status with no trailing args). Approvals from older approvals.json files default to derived=False and behave as before. See Reusable Approval Scopes.
Secure, Actor-Authorised Approval Backend
Thesecure (alias: presentation) backend wraps PresentationApprovalHandler and ApprovalStore together into a single ApprovalProtocol adapter — adding actor authorisation and fail-closed behaviour on top of the base durability layer.
Activate via CLI
Activate via Python
How it differs from the base ApprovalStore
| Feature | ApprovalStore alone | --approval secure |
|---|---|---|
| Persist to SQLite | ✅ | ✅ |
| Rehydrate on restart | ✅ | ✅ |
| Actor allow-list | ❌ | ✅ (PRAISONAI_APPROVAL_ACTORS) |
| LLM classifier for Allow/Deny | Some backends | ❌ (never uses LLM) |
| Fail-closed on empty allow-list | ❌ | ✅ |
| Approval ID binding | ✅ | ✅ (unguessable UUID) |
Configuration
| Setting | How to set | Required |
|---|---|---|
| Allow-list | PRAISONAI_APPROVAL_ACTORS=telegram:123,slack:U456 | Yes (fail-closed) |
| Store path | PRAISONAI_HOME=/path/to/dir (approvals stored in <HOME>/state/approvals.sqlite) | No (default: ~/.praisonai) |
Rehydration guarantee
Allow-list actors are stored alongside each pending approval. On restart, re-hydrated approvals carry the original allow-list — late Allow/Deny taps after a redeploy are still bound to the original requester.Best Practices
Use for long-running bots
Use for long-running bots
Telegram, Slack, and Discord bots that restart for deploys benefit most — users won’t lose half-resolved prompts.
Set a sensible TTL
Set a sensible TTL
Default eviction is 7 days. Tune
ttl_seconds to bound disk growth while keeping an audit trail.Optional, not required
Optional, not required
Console and webhook backends work without a store. Add one only when restart-safety matters.
Related
Approval
Default approval behaviour and backends
Approval Protocol
ApprovalStoreProtocol and backend contracts
Messaging Bots
Deploy agents to chat channels

