Skip to main content
Set approval="presentation" on your Agent and run a Telegram bot — Allow/Deny buttons render on chat, resolve against a SQLite store, and survive a restart.

Quick Start

1

Auto-wire from the agent

Set approval="presentation" on the Agent — the bot wires the durable backend to the chat on start(). No register_approval_backend() call needed.
from praisonaiagents import Agent
from praisonai.bots import Bot

agent = Agent(
    name="Ops",
    instructions="Ask before destructive tools.",
    approval="presentation",   # durable + actor-authorised
    tools=["execute_command"],
)

bot = Bot("telegram", agent=agent)
bot.run()
The bot detects the backend on the agent and connects it to the chat automatically. Unknown decision values (anything outside allow, deny, always) are rejected before reaching the backend — fail closed.
2

Manual wire (advanced)

Wire a backend you constructed separately — for tests or custom bootstrap where the backend is built by hand.
from praisonaiagents import Agent
from praisonai.bots import Bot, PresentationApprovalBackend, ApprovalStore

backend = PresentationApprovalBackend(
    store=ApprovalStore(path="~/.praisonai/state/approvals.sqlite"),
    allowed_actors={"12345"},
)

agent = Agent(name="Ops", instructions="Ask before destructive tools.")
bot = Bot("telegram", agent=agent)
bot.register_approval_backend(backend, target="chat-1")
bot.run()
register_approval_backend gives the backend the channel renderer and target, then enables the /approve callback route.

How It Works

An agent tool call persists to the store, renders Allow/Deny buttons, and unblocks only when an authorised tap resolves the approval ID. Each decision binds to an unguessable approval_id, not a message id — so a late tap after a redeploy still resolves against the SQLite store instead of falling through as an unhandled callback.

What the user sees

The whole point of the feature — a non-developer only ever sees two buttons and a result. The original message updates in place: ✅ Approved, ❌ Denied, or ⚠️ Approval could not be processed.

Configuration Options

Backend construction lives on the secure-backend page — this page only wires it to Telegram.

Secure Approval Backend

PresentationApprovalBackend constructor args (store, allowed_actors, timeout)

Durable Approvals

The SQLite ApprovalStore schema behind the durable path

Common Patterns

Auto-wire from agent (default). Set approval="presentation" on the Agent and run the bot — nothing else. This is the Quick Start path.
agent = Agent(name="Ops", instructions="Ask first.", approval="presentation")
bot = Bot("telegram", agent=agent)
bot.run()
Manual wire with explicit target. Address approval buttons to a specific chat.
bot.register_approval_backend(backend, target="chat-1")
Restart drill. Restart the bot; a late Allow tap on a message sent before the restart still resolves. rehydrate_approvals() runs automatically on start() and returns the restored count.
restored = await bot.rehydrate_approvals()
print(f"Restored {restored} pending approvals")

Best Practices

The backend is fail-closed — set the actor allowlist so only your approvers can resolve a tap. Without it, no actor is authorised.
Do not pass an already-wired backend from Slack to Telegram. When a backend already owns a sender, the coupling guard leaves it untouched so it never renders on one transport while addressing another.
Reserve register_approval_backend() for tests or custom bootstrap. In normal deployments, approval="presentation" on the Agent is enough.
start() logs how many approvals were restored. Watch that number to catch a broken deploy where pending approvals never come back.

Durable Approvals

The SQLite store behind the durable path

Secure Approval Backend

The PresentationApprovalBackend this page wires

Gateway Approval Durability

The gateway sibling of this transport wiring

Messaging Bots

Deploy agents to chat channels