Bot Chat Commands
Built-in chat commands — status, model switching, usage, context compression, queuing — plus Python-registered, file-based, and entry-point custom commands for Telegram, Discord, Slack, and WhatsApp bots.
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="You are a helpful assistant.")
agent.start("What commands are available?")The user types a slash command in chat; the bot runs built-in handlers for status, model, usage, and session control.
Command Picker
graph TB
Q{What do you need?} --> A[Cancel current answer only]
Q --> A2[Cancel current answer AND my queued messages]
Q --> B[Free context space]
Q --> M[See where we left off]
Q --> C[Change LLM]
Q --> D[See cost/tokens]
Q --> E[Queue a follow-up]
Q --> F[Author a skill from sources]
Q --> G[Rewind the last turn]
Q --> H[List or switch sessions]
Q --> I[Re-run your last message]
Q --> J[Toggle reasoning visibility]
Q --> K[Accept a suggested automation]
Q --> L[Create from a template]
Q --> M[Run a project command file]
Q --> N[Inspect background tasks]
A --> A1[/stop]
A2 --> A3[/stop all · /cancel]
B --> B1[/compress]
M --> M1[/recap]
C --> C1[/model]
D --> D1[/usage]
E --> E1[/queue]
F --> F1[/learn]
G --> G1[/undo]
H --> H1[/sessions, /resume]
I --> I1[/retry]
J --> J1[/reasoning]
K --> K1[/automations]
L --> L1[/blueprint]
N --> N1[/tasks]
M --> M1[".praisonai/commands/*.md → /name"]
classDef agent fill:#8B0000,color:#fff
classDef tool fill:#189AB4,color:#fff
classDef source fill:#6366F1,color:#fff
class Q agent
class A,A2,B,C,D,E,F,G,H,I,J,K,L,N,A1,A3,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,N1 tool
class M,M1 sourceFile-based (.praisonai/commands/*.md) and entry-point commands surface alongside built-ins — see File-based & Entry-point Commands.
Every PraisonAI bot comes with built-in chat commands. Just type a command in your chat — no setup required.
Built-in Commands
Shows agent name, model, platform, and uptime.
/newResets the conversation session. Starts a fresh chat with the agent.
/stopCancels the current agent task. Requires run control to be enabled.
/stop allCancels the current task AND drains any messages the user queued behind it. Session-scoped stop.
/cancelAlias for /stop all. Cancels the current task AND drains queued follow-ups.
Lists all available commands (built-in + custom) with descriptions. Output is filtered to commands the caller is allowed to run.
/whoamiShows your user ID, role, and the commands you are allowed to run.
/modelSwitch the LLM model for this conversation (session-scoped).
/usageShow token usage and estimated cost for this session.
/compressSummarise older messages to free context window space.
/recapShow a read-only “where were we” summary of the session. Never mutates history or triggers compaction.
/queueQueue a follow-up message to run after the current task.
/tasksList background tasks and background subagents (/tasks), inspect one (/tasks <id>), or cancel (/tasks cancel <id>). Per-user scoped.
Distil a grounded skill from sources you describe (repo, docs, PDFs, or this chat). Privileged — admin-only by default when a policy is configured. Authored via skill_manage with the same approval gate as other agent-created skills. See Learn a Skill from Sources and Command Access Control.
Undo the last turn’s file changes. Requires the agent to track changes (autonomy with track_changes).
List the caller’s saved sessions/checkpoints. Scoped to the requesting user — never enumerates other users’ sessions.
/resumeSwitch to a saved session: /resume <id>. Use /sessions to list available IDs.
Re-run your last message through the normal chat path.
/reasoningToggle whether extended-thinking output is shown in this conversation (per user).
/automationsList pending automation suggestions with inline Accept/Dismiss buttons. Telegram only today.
/blueprintCreate an automation from a template: /blueprint <name> [slot=value ...]. Telegram only today.
Per-Command Access Control
Restrict which commands each user may run with admin_users and user_allowed_commands on Telegram, Slack, and Discord — admins get full access, regular users only run commands you list. All built-in commands, including the new runtime controls, are gated by this policy.
Command classification is now gated by two layers: content-trust (new — BotMessage.allow_control, so an untrusted producer’s /-prefixed body is never treated as a command) and identity ACL (existing — admin_users / user_allowed_commands). Content-trust fires first. See Control-Trust Provenance.
Admin users, per-command allowlists, and /whoami
Quick Start
- Install PraisonAI
bash pip install praisonai - Set Your Bot Token
bash export TELEGRAM_BOT_TOKEN=your_token_here - Start the Bot
bash praisonai bot telegram --token $TELEGRAM_BOT_TOKEN - Send a Command
Open your bot chat and type:
/statusYou’ll see the agent name, model, platform, and uptime.
Platform Examples
praisonai bot telegram --token $TELEGRAM_BOT_TOKENpraisonai bot discord --token $DISCORD_BOT_TOKENpraisonai bot slack --token $SLACK_BOT_TOKEN --app-token $SLACK_APP_TOKENCommand Details
/status
Shows current bot information:
| Field | Description |
|---|---|
| Agent | Name of the AI agent |
| Model | LLM model being used |
| Platform | telegram, discord, slack, or whatsapp |
| Uptime | How long the bot has been running |
| Running | Whether the bot is currently active |
/whoami
Shows permission information for the caller:
| Field | Description |
|---|---|
| User ID | Platform-native user ID |
| Username | Display name when available |
| Role | Admin or User |
| Allowed commands | Commands this user may run |
Example output:
User Information
User ID: 456
Username: alice
Role: User
Allowed commands: help, status, whoamiA command outside your allowed list — or one that doesn’t exist — falls through to the agent rather than vanishing. See Unknown or Misspelled Commands.
/new
Resets the conversation:
- Clears all previous messages from the agent’s session
- Replies with a confirmation message
- Next message starts a completely new conversation
- Clears any per-user
/modeloverride — so a “start fresh” also un-does a bad or unavailable model switch (a stuck/model gpt-9-imaginaryno longer persists past/new)
/stop
Cancels the current agent task immediately. Pick the scope that matches what you want stopped.
graph TB
Q{What do you want to stop?} --> A[Just the running answer]
Q --> B[The running answer AND my queued follow-ups]
A --> A1[/stop · stop]
B --> B1[/stop all · /cancel]
classDef q fill:#8B0000,stroke:#7C90A0,color:#fff
classDef turn fill:#189AB4,stroke:#7C90A0,color:#fff
classDef session fill:#F59E0B,stroke:#7C90A0,color:#fff
class Q q
class A,A1 turn
class B,B1 sessionTurn-scoped (default):
| Command | What it stops | Reply payload |
|---|---|---|
/stop (or stop) | Current turn only. Queue preserved. | scope: "turn", pending_cancelled: 0 |
/stop all | Current turn + drains queued messages. | scope: "session", pending_cancelled: N |
/cancel | Alias for /stop all. | Same as /stop all. |
/stop:
- Immediately interrupts the current agent processing
- Replies with confirmation that the task was cancelled
- Next message starts a fresh task
/stop all and /cancel additionally:
- Drain any pending messages the user queued behind the running turn
- Report the drained count as
pending_cancelled: Nso nothing is silently dropped
/help
Lists all available commands — both built-in and custom — with their descriptions.
/model
Switches the LLM model for your conversation, or reports which model is currently active.
Show current model — /model with no argument:
Current model: gpt-4o-mini
Use /model <name> to switch (e.g., /model gpt-4o)Switch model — /model <name>:
/model gpt-4o
✅ Model switched to gpt-4o for this conversation.Restart-required guard — when the gateway’s code on disk has changed since the process started (e.g. you ran git pull or pip install -U against the running checkout), /model <name> refuses the switch instead of risking a half-loaded module:
/model gpt-4o
⚠️ Gateway code changed on disk since it started (2aa28d1 → def5678). Restart the gateway to apply updates before switching models.Restart the gateway/bot process and try again. The check is best-effort: when the fingerprint can’t be determined (no git checkout, unreadable files) the switch proceeds normally.
To disable the guard (e.g. on a sandboxed CI runner where in-place updates are deliberate):
session_manager.code_skew_guard = False/usage
Reports token usage and a rough estimated cost for the session.
| Field | Description |
|---|---|
| Total | Total tokens used in the session |
| Prompt | Tokens spent on prompts (when tracked) |
| Completion | Tokens generated by the model (when tracked) |
| Est. cost | Approximate cost using a flat per-token rate — not model-specific |
Example output:
📊 Token Usage
Total: 18,204 tokens
Prompt: 12,800 | Completion: 5,404
Est. cost: $0.0036/compress
Compresses your conversation history to free context-window space.
- Requires at least 10 messages — shorter histories get:
"ℹ️ Conversation too short to compress (need at least 10 messages)." - Targets ~50% token reduction, always preserving the most recent 10 messages and all system messages
- Reports messages removed and tokens freed:
✅ Compressed 32 messages, freed ~12,400 tokens.- If the context optimizer library is unavailable, falls back to keeping system messages and the last 10 messages
- On failure the history is left untouched:
"❌ Compression failed: {error}"
/recap
Renders a read-only “where were we” summary of the session without changing anything.
Example output:
📌 Recap — where we were:
[Previous conversation summary]
User asked about deploy runbook; we outlined steps 1–3.
Recent:
• user: what about step 4?
• assistant: step 4 covers rollback…- Read-only. Never mutates the transcript and never triggers compaction — safe to run mid-conversation.
- Reuses a persisted summary when present. If
/compresshas already produced a compaction summary,/recapshows it verbatim instead of re-summarising. - Otherwise it runs the naive summariser over a copy of the history, so the original is untouched and no compaction event is emitted.
- Always preserves the recent tail — the last few messages are appended verbatim.
- Output is bounded to ~3500 characters so Telegram deliveries never overflow the 4096-char message limit; when trimming is needed the older summary is truncated, never the recent tail.
Empty history returns:
Nothing to recap yet — this session has no messages.When the access policy denies the command, the bot replies:
⛔ You are not permitted to run /recapThe same recap is available as a Python primitive when you build a custom surface:
from praisonaiagents.compaction import build_recap
history = [
{"role": "user", "content": "Outline the deploy runbook."},
{"role": "assistant", "content": "Steps 1–3 cover build, test, ship."},
{"role": "user", "content": "What about step 4?"},
{"role": "assistant", "content": "Step 4 covers rollback."},
]
print(build_recap(history))| Option | Type | Default | Description |
|---|---|---|---|
history | List[Dict[str, Any]] | — | Conversation messages. Not modified. |
tail | int | 4 | Number of recent messages to append verbatim. |
max_chars | int | 3500 | Upper bound on rendered recap length. <= 0 disables the cap. |
/queue
Adds a follow-up message that runs after the current task completes.
Check pending queue — /queue with no argument:
📝 Pending follow-up: summarise the key findingsOr if nothing is pending:
No follow-up queued. Use /queue <message> to add one while a task is running.Queue a message — three possible outcomes depending on bot state:
| Outcome | When | Bot reply |
|---|---|---|
| Nothing to queue | No task running | "ℹ️ No task is currently running, so there's nothing to queue behind. Send your message normally to run it now." |
| Merged | Task running, message parked in pending slot | "⏳ Added to your pending follow-up — it will run after the current task completes." |
| Steered | Task running, message injected as live guidance | "🧭 Injected into the running task as live guidance." |
/undo
Rewinds the last turn’s file changes via Agent.undo().
- Requires change tracking and the pre-run snapshot —
autonomy=AutonomyConfig(track_changes=True, snapshot=True)(see File Snapshot) - Returns
"✅ Reverted the last turn."when files were restored,"ℹ️ Nothing to undo."when there is nothing to revert - Degrades gracefully when undo is unavailable:
"❌ This agent does not support undo (change tracking not enabled)."
/sessions
Lists recent saved sessions for the requesting user only. Session lookups are scoped via the per-user storage key so one caller cannot enumerate another user’s sessions.
Example output:
📁 Recent sessions:
• telegram:123456789
• telegram:123456789:project-a
Use /resume <id> to switch to one.Returns "No saved sessions yet." when nothing is stored.
/resume
Switches to a previously saved session.
/resume telegram:123456789:project-a
✅ Resumed session telegram:123456789:project-a.- Usage:
/resume <id>— run/sessionsfirst to see IDs - Returns
"❌ Session {id} not found."when the ID is unknown - When the session manager lacks a
resume_sessionprimitive, the bot reports honestly rather than claiming a switch succeeded
/retry
Re-runs your most recent user message through the normal session.chat(...) path.
🔁 Retrying your last message:
Summarise the deployment runbook in three bullets.Returns "ℹ️ Nothing to retry — no previous message found." when the history is empty.
/reasoning
Toggles whether extended-thinking output is shown for this user in this conversation. The preference is stored per user on the session manager.
/reasoning
🧠 Reasoning output will be shown for this conversation.Run again to hide:
/reasoning
🙈 Reasoning output will be hidden for this conversation.Defaults to hidden when no preference is set. Adapters consult is_reasoning_visible() when rendering responses.
/tasks
Inspect background tasks from inside the chat — no need to switch to a terminal. /tasks merges two sources into one list: background tasks (from BackgroundRunner) and background subagents spawned from chat (from BackgroundJobManager).
| Command | Purpose |
|---|---|
/tasks | List your background tasks and background subagents |
/tasks <id> | Show detail for one task or subagent job (incl. result / error) |
/tasks cancel <id> | Cancel a running background task or subagent job |
Example output:
/tasks
🧩 Background tasks:
• aa1b2c… research — running (35%)
• ff9d3e… summary — completed (100%)
• 3f8a91c2 Research the latest Python 3.13 features — running
Use /tasks <id> for detail or /tasks cancel <id>.
/tasks 3f8a91c2
🧩 Task 3f8a91c2 — Research the latest Python 3.13 features
Status: completed
Result: Python 3.13 adds an experimental free-threaded build…The list shows at most 20 entries: background tasks first, then background-subagent jobs newest-first in the remaining slots. Subagent detail shows Result: or Error: trimmed to the first 200 characters.
/tasks cancel <id> on a background subagent calls BackgroundJobManager.cancel_job(id) and only reports success when the job was actually cancelled.
Handler strings:
| Situation | Reply |
|---|---|
| No tasks | 💤 No background tasks. |
| Cancel needs an id | ℹ️ Usage: /tasks cancel <id> |
| Not found (or not owned) | ❌ Task not found: <id> |
| Cancel success | ✅ Cancelled task <id>. |
| Cancel too late | ❌ Could not cancel task <id> (already finished?). |
- Background Tasks — how tasks get submitted in the first place
- Background Subagents — chat-spawned jobs that
/tasksalso lists - Command Access Control —
/tasksrespects the same policy, so admins can restrict it
/learn
Authors a grounded, reusable SKILL.md from sources you name — a codebase, API docs, PDFs, configs, or the current chat.
/learn deploy steps from this repo and the runbook PDF- Calls
agent.learn_skill(request)which gathers the named sources with the agent’s existing tools and writes one groundedSKILL.mdviaskill_manage - Admin-only by default when a
CommandAccessPolicyis configured (admin_usersoruser_allowed_commandsset) — it belongs toPRIVILEGED_COMMANDSbecause it reads host sources and alters future agent behaviour - Available to all users when no policy is configured (backward-compatible)
Full guide: sources, grounding rules, async API, bot usage
/automations
Lists pending automation suggestions, one message per suggestion, each with its own ✓ Accept / ✕ Dismiss inline keyboard. Telegram only today.
- Header when suggestions exist:
💡 You have 1 pending automation suggestion:(pluralised by count) - Header when empty:
You have no pending automation suggestions.\nUse /blueprint to create one from a template. - Each Accept button carries
sug:accept:<id>; each Dismiss carriessug:dismiss:<id> - The
automationspermission is re-checked on every button tap. A denied user sees⛔ You are not permitted to manage automationsappended to the message - When the scheduler extra isn’t installed:
❌ Automations are not available (scheduler not installed).
/blueprint
Creates an automation directly from a blueprint template. Telegram only today.
Three call shapes:
/blueprint(no args) → lists available blueprints + usage:
📋 Create an automation from a template:
• morning-brief — Daily morning briefing with news and priorities
• important-mail — Check for important emails at a regular interval
• weekly-review — End-of-week summary and review
Usage: /blueprint <name> [slot=value ...]
Example: /blueprint morning-brief hour=8 weekdays=mon-fri/blueprint <name>→ creates a job with blueprint defaults/blueprint <name> hour=8 weekdays=mon-fri→ slot overrides
Slot overrides use key=value grammar. Integer-looking values are coerced to int, so hour, minute, and interval_minutes resolve correctly (_parse_slot_args).
Success and error strings from _automations.create_from_blueprint:
✅ Automation scheduled from 'morning-brief'. Added job 'morning-brief' (schedule cron:0 8 * * mon,tue,wed,thu,fri)❌ Blueprint 'unknown' not found. Available: morning-brief, important-mail, weekly-reviewUnknown or Misspelled Commands
An unknown slash command — a typo like /statu, or a custom command name that isn’t registered on this deployment — is dispatched to the agent as the raw text, the same as any free-form message. The bot never silently drops a command.
| Platform | Behavior on unknown /command |
|---|---|
| Telegram | Dispatched to agent as raw text (PR #4320). |
| Discord | Dispatched to agent as raw text (PR #4320). |
| Slack | Dispatched to agent as raw text (reference adapter — unchanged). |
Custom Commands
Register your own commands using register_command(). Available on all bot adapters.
from praisonai.bots import TelegramBot
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="Be helpful")
bot = TelegramBot(token="YOUR_TOKEN", agent=agent)
# Register a custom command
async def handle_ping(message):
return "Pong! Bot is alive."
bot.register_command("ping", handle_ping, description="Check if bot is alive")
import asyncio
asyncio.run(bot.start())
# Now /ping is available alongside /status, /new, /stop, /helpregister_command() API
bot.register_command(
name="mycommand", # Command name (without /)
handler=my_handler, # Async or sync callable
description="What it does", # Shown in /help
usage="/mycommand <arg>", # Optional usage string
channels=["telegram"], # Optional: restrict to specific platforms
)| Parameter | Type | Required | Description |
|---|---|---|---|
name | str | Yes | Command name without / prefix |
handler | Callable | Yes | Function to call. Receives a BotMessage argument |
description | str | No | Human-readable description (shown in /help) |
usage | str | No | Usage example string |
channels | list[str] | No | Restrict to specific platforms (e.g., ["telegram", "slack"]). Empty = all platforms |
The description you pass here IS the label users see in the native / menu on Telegram and Discord. See Slash Command Menu.
list_commands()
Get all registered commands:
commands = bot.list_commands()
# Returns: [ChatCommandInfo(name="status", ...), ChatCommandInfo(name="new", ...), ...]
# Filter by platform
telegram_commands = bot.list_commands(platform="telegram")File-based and Entry-point Commands in Bot Chats
One .praisonai/commands/{name}.md file authored once runs in praisonai code and in every bot chat — no duplication, no per-platform wiring.
Quick Start — one file, every chat
Author a single command file:
---
description: Summarise the current channel
argument-hint: <topic?>
---
Summarise recent messages about $ARGUMENTS.Start the bot — nothing new to wire. The resolver discovers the file automatically once bot commands are enabled in config:
from praisonai.bots import TelegramBot
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="Be helpful.")
bot = TelegramBot(token="YOUR_TOKEN", agent=agent)
import asyncio
asyncio.run(bot.start())
# /summary is now available in Telegram — same file also works in `praisonai code`.The user runs it in chat:
User: /summary Q3 revenue
Bot: [agent replies with a Q3 revenue summary]One command source, every surface
The same .praisonai/commands/*.md files already documented for Custom Agents & Commands now reach bot chats too — the resolver reuses that loader through the sanctioned _code_bridge seam instead of reimplementing it.
Configuration — BotCommandsConfigSchema
Enable and tune the bridge with a commands block on each channel.
YAML
# bots.yaml
channels:
telegram:
token: env:TELEGRAM_BOT_TOKEN
commands:
allow_shell: false # default; opt-in per deployment (see safety)
expose: [summary, deploy] # optional allow-list; omit → all project commands
include_user_scope: false # default; user ~/.praisonai commands excludedPython
from praisonai_bot.bots._config_schema import (
ChannelConfigSchema,
BotCommandsConfigSchema,
)
channel = ChannelConfigSchema(
platform="telegram",
token="${TELEGRAM_BOT_TOKEN}",
commands=BotCommandsConfigSchema(
allow_shell=False,
expose=["summary", "deploy"],
include_user_scope=False,
),
)Env
export TELEGRAM_BOT_TOKEN=your_token_here
# Author .praisonai/commands/summary.md, then start the bot with a config
# whose channel enables commands. With an empty `expose`, every project
# command is surfaced automatically.
praisonai bot serve --config bots.yaml| Option | Type | Default | Description |
|---|---|---|---|
allow_shell | bool | false | Enables live ` !cmd substitution in bot chats. Also requires the command's own allow_shell: true` frontmatter — a deployment can never upgrade a command that declared shell off. |
expose | list[str] | [] | Optional allow-list of command names surfaced in chat. Empty → every project-scope command is exposed. |
include_user_scope | bool | false | When true, also loads commands from ~/.praisonai/commands/. Off by default — bots serve the project, not the operator’s home dir. |
Safety posture: double opt-in for shell
Shell substitution needs both the deployment and the command to say yes.
When either is false, the resolver drops the ! marker so the segment becomes plain backticks — the safe $ARGUMENTS and @file parts still interpolate normally instead of the whole command failing.
Precedence & collision rules
When two sources define the same command name, one wins.
| Source | Precedence | Notes |
|---|---|---|
Built-in commands (/status, /help, …) | highest — always wins | Live commands (/automations, /blueprint) step aside if you register a same-name handler. |
register_command(...) (Python) | high | Existing path, unchanged. |
File-based .praisonai/commands/{name}.md | medium | Wins over entry-point on collision. Project scope by default; user scope opt-in. |
Entry-point praisonai.bot_commands | lowest | Installed packages ship bot commands; file/project commands override. |
/help and the native / menu are populated through the shared ChatCommandMixin, so file and entry-point commands appear in both automatically — no per-adapter wiring.
Packaged commands via entry-point
Installed packages add bot commands through the praisonai.bot_commands entry-point group — no forking.
# pyproject.toml
[project.entry-points."praisonai.bot_commands"]
mypkg = "mypkg.bot_commands:commands"# mypkg/bot_commands.py
commands = {
"greet": {
"description": "Say hi",
"template": "Say hello to $ARGUMENTS.",
"allow_shell": False, # optional; safe default
},
}
# Or expose a zero-argument callable that returns the same dict.The loader accepts three spec shapes per command:
| Spec shape | Example | Meaning |
|---|---|---|
| Bare string | "greet": "Say hello to $ARGUMENTS." | Template only; no description, allow_shell off. |
| Dict | {"template": ..., "description": ..., "allow_shell": ...} | Full record. |
| Object | any object with .template / .description / .allow_shell attributes | Full record from a class instance. |
User interaction flow
- A teammate authors
.praisonai/commands/deploy.mdwithdescription: "Run the deploy checklist"and opens a PR. - After merge, everyone using
praisonai codegets/deploy— no restart of code. - On the next bot restart,
/deployalso appears in Telegram’s native/menu with the same description, and running it in chat replies with the interpolated template through the normalsession.chatpath. - Same file. Two surfaces. No duplication.
Security & Allowlists
Built-in commands (/status, /new, /help) and custom commands registered via register_command() go through the same security pipeline as regular text messages. If allowed_users is set and the sender is not in it, the command is silently dropped with no reply. If unknown_user_policy="pair", unknown senders get the pairing flow before the command runs. If group_policy="mention_only", commands in groups still need a mention. observe behaves like mention_only for command dispatch — slash-commands in groups still need a mention; only free-form messages are passively recorded. Under command_only on Telegram, Discord and Slack, a plain @mention without a slash-command does not run the agent — only slash-commands do.
from praisonai.bots import TelegramBot
from praisonaiagents import BotConfig
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="Be helpful")
bot = TelegramBot(
token="YOUR_TOKEN",
agent=agent,
config=BotConfig(allowed_users=["123456789"]), # Only this user
)
# /help, /status, /new from any other user are now silently ignoredFor full allowlist setup, see the Bot Security guide. This security hardening was implemented in PR #1835.
Python Usage
Start bots programmatically — built-in commands are always available:
from praisonai.bots import TelegramBot
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="Be helpful")
bot = TelegramBot(token="YOUR_TOKEN", agent=agent)
import asyncio
asyncio.run(bot.start())
# /status, /new, /stop, /model, /usage, /compress, /recap, /queue, /tasks, /help available automatically
# /stop all and /cancel (session-scoped stop) are handled by the gateway when the bot is gateway-routedfrom praisonai.bots import DiscordBot
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="Be helpful")
bot = DiscordBot(token="YOUR_TOKEN", agent=agent)
import asyncio
asyncio.run(bot.start())from praisonai.bots import SlackBot
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="Be helpful")
bot = SlackBot(token="YOUR_TOKEN", app_token="YOUR_APP_TOKEN", agent=agent)
import asyncio
asyncio.run(bot.start())from praisonai.bots import WhatsAppBot
from praisonaiagents import Agent
agent = Agent(name="assistant", instructions="Be helpful")
bot = WhatsAppBot(mode="web", agent=agent)
# /stop, /status, /new, /help registered automatically
import asyncio
asyncio.run(bot.start())Native / Autocomplete (Telegram + Discord)
Typing / in Telegram or Discord surfaces the bot’s commands with descriptions through each platform’s native menu — no configuration needed.
The bot publishes the menu automatically on startup — no user code required:
from praisonai.bots import TelegramBot
from praisonaiagents import Agent
agent = Agent(name="Support", instructions="Answer customer questions.")
bot = TelegramBot(token="YOUR_TOKEN", agent=agent)
import asyncio
asyncio.run(bot.start()) # /-menu appears in Telegram automaticallyCustom commands flow through the same registry, so they appear alongside the built-ins on the next restart:
from praisonai.bots import TelegramBot
from praisonaiagents import Agent
agent = Agent(name="Support", instructions="Answer customer questions.")
bot = TelegramBot(token="YOUR_TOKEN", agent=agent)
async def summary(message):
return "Latest summary..."
bot.register_command("summary", summary, description="Get the latest summary")
import asyncio
asyncio.run(bot.start()) # /summary appears alongside /status, /model, /usage in the / menuDesign Guarantees
The menu is a discoverability layer that never changes how commands run.
Per-Platform Behaviour
| Platform | API | Name rules | Description limit |
|---|---|---|---|
| Telegram | set_my_commands(BotCommand(...)) | [a-z0-9_]{1,32} | ≤ 256 chars |
| Discord | CommandTree.sync() (application commands) | [a-z0-9_-]{1,32} | ≤ 100 chars |
Out-of-range names are skipped; over-length descriptions are truncated.
Best Practices
Restrict privileged commands
Configure admin_users and user_allowed_commands before enabling /learn, /stop, or /queue in production. See Command Access Control.
Enable run control for /stop and /queue
/stop and /queue need Bot Run Control with busy_mode for mid-run cancellation and follow-up queuing — without it, users get informational fallbacks only.
Choose the right stop scope
Use /stop (turn-scoped) when the user wants to interrupt only the current answer and let their queued follow-ups run. Use /stop all or /cancel (session-scoped) when the user regrets the whole line of questions — a session stop drains every message they queued behind the running turn, and the reply’s pending_cancelled count tells them exactly how many were dropped.
Compress before long sessions
Run /compress when conversations exceed ~20 turns. It preserves recent context whilst reclaiming window space for research or coding tasks.
Recap first — it's free
/recap never changes your history. Run it first to see the state of the conversation; only reach for /compress when you actually need to free window space.
Document custom commands in /help
Always pass a description to register_command(). Users discover extensions through /help, which filters to commands they are allowed to run.
Share one command file across code and bots
Author .praisonai/commands/{name}.md once and commit it. It runs in praisonai code and — once a channel’s commands block is enabled — in every bot chat, with no per-platform duplication. See File-based & Entry-point Commands.
Keep shell double-opted-in
Live ` !cmd substitution runs only when both the deployment (commands.allow_shell: true) and the command's own allow_shell: true` frontmatter agree. Leave both off unless a specific command genuinely needs shell — the segment degrades to inert backticks otherwise, so the rest of the command still works.
Isolate /automations and /blueprint per user
The scheduler’s suggestion store supports per-user isolation via a principal owner key (see Automation Suggestions → Multi-tenant isolation and Per-User Scheduler Isolation). When your bot passes the resolved chat identity to the store, each user sees and can accept/dismiss only their own suggestions, and one tenant cannot exhaust another’s pending cap or dedup window.
Until the adapter you use threads that identity through (or if you prefer a simpler policy), gate the whole command behind an admin allowlist by adding automations and blueprint to admin_users (not user_allowed_commands) in CommandAccessPolicy.
Related
Connect your agent to Telegram, Discord, Slack, and WhatsApp.
CardUnderstand per-platform message limits, editing, and rate-limit behaviour.
Slash Command MenuNative / autocomplete for Telegram and Discord.
Restrict who can run which commands.
CardTranscribe inbound voice messages on Telegram, Slack, WhatsApp, Discord, and Signal.
Custom Agents & CommandsAuthor .praisonai/commands/*.md files once — they run in praisonai code and bot chats.
The double-opt-in shell rule and the wider shell-approval model.
CardHow tasks get submitted, configured, and made durable — the substrate /tasks inspects.
Skills that declare their own schedule and delivery target — install once, run themselves.