Skip to main content
Pending tool approvals normally live in process memory — a gateway restart loses in-flight decisions. A durable approval store persists them so a late Allow/Deny tap still resolves correctly.
Two durable stores, different jobs:
  • This page — the bot pending-approval queue (ApprovalStore at ~/.praisonai/state/approvals.sqlite) tracks in-flight tap prompts waiting for a decision.
  • Sibling — the gateway allow-list store (Gateway Scoped Approvals, ScopedAllowlistStore at ~/.praisonai/state/gateway/approvals.sqlite) tracks “allow always” grants that skip future prompts.
Under the hood: approval decisions are journalled as KIND_APPROVAL events in the Run-State Journal. Combined with interrupted_runs(), this is what enables durable pause-for-approval HITL: a run that stops for approval can be safely resumed later — the loop replays journalled decisions rather than re-invoking tools.
Transport wiring for Telegram is auto-configured — see Telegram Durable Approval. This page covers the underlying store used by that path.
The user taps Allow or Deny on a channel; the durable store resolves the pending approval even after a gateway restart.

Quick Start

1

Create a store

2

Pass it to your bot

3

Restart safely

On startup, outstanding pending approvals are re-hydrated. Users can tap Allow/Deny on messages sent before the restart.

How It Works

Each ApprovalRequest 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


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

The secure (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.
--approval secure is fail-closed: if PRAISONAI_APPROVAL_ACTORS is empty or not set, the gateway refuses to start. This prevents an empty allow-list from silently permitting any actor to approve.

Activate via CLI

Activate via Python

How it differs from the base ApprovalStore

Configuration

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

Telegram, Slack, and Discord bots that restart for deploys benefit most — users won’t lose half-resolved prompts.
Default eviction is 7 days. Tune ttl_seconds to bound disk growth while keeping an audit trail.
Console and webhook backends work without a store. Add one only when restart-safety matters.

Approval

Default approval behaviour and backends

Approval Protocol

ApprovalStoreProtocol and backend contracts

Messaging Bots

Deploy agents to chat channels