Standalone Bot YAML
Describe a standalone gateway + agent + channels declaratively
Describe your agent, gateway, and channel platforms in a single YAML file — no Python required.
from praisonaiagents import Agent
agent = Agent(name="bot-agent", instructions="Reply to users across chat channels.")
agent.start("Answer incoming messages from Telegram and Discord.")The user defines a bot.yaml; the gateway loads it and connects the agent to every configured channel.
How It Works
Config Structure
Quick Start
- Install
bash pip install praisonaiagents "praisonai-bot[gateway,bot]" - Set credentials
bash export OPENAI_API_KEY=your_openai_api_key export TELEGRAM_BOT_TOKEN=your_telegram_token - Create bot.yaml
Leave the model unset to resolve it from whichever provider credential is present — no OpenAI key required:
yaml # bot.yaml — auto-detect provider from whichever API key is set agent: name: assistant instructions: You are a helpful assistant. # model omitted ⇒ resolves to the detected provider at load time. # Equivalent to: model: auto platform: telegram token: ${TELEGRAM_BOT_TOKEN}Or pin a specific model explicitly — an explicit value always wins:
yaml agent: name: assistant instructions: You are a helpful assistant. model: gpt-4o-mini gateway: host: 127.0.0.1 port: 8765 platforms: telegram: token: ${TELEGRAM_BOT_TOKEN} - Start the gateway with your YAML
bash praisonai-bot gateway start --config bot.yamlThe gateway registers the agent and starts all configured channel bots automatically.
Config Options Reference
agent section
| Key | Type | Default | Description |
|---|---|---|---|
agent.name | str | required | Agent display name, used as the agent_id for gateway routing |
agent.instructions | str | required | System prompt / instructions for the agent |
agent.model | str | None → provider-detected (last resort "gpt-4o-mini") | LLM model identifier. Leave unset or set to "auto" to resolve to the detected provider’s default (Anthropic / Gemini / Ollama / LM Studio / OpenAI). An explicit value always wins. |
agent.llm | str | alias of model | Backward-compatible alias for agent.model. llm wins if both are set — the schema’s handle_model_aliases copies it onto model. |
Provider auto-detection (model: auto)
Omitting model: and writing model: auto are equivalent — both defer the choice to load time, where the gateway picks a model that matches whichever provider credential is actually present. This is the same detection ladder praisonai setup uses (default_model_for_available_provider), so a bot config is no longer tied to an OpenAI key.
Detection walks the available credentials in this order and returns the first match:
| Order | Credential | Resolved model (example) |
|---|---|---|
| 1 | OPENAI_API_KEY | gpt-4o-mini |
| 2 | ANTHROPIC_API_KEY | anthropic/claude-3-5-sonnet-latest |
| 3 | GEMINI_API_KEY / GOOGLE_API_KEY | gemini/gemini-1.5-flash |
| 4 | Local endpoint (Ollama / LM Studio) | the local default |
| … | other catalogued providers (Groq, Cohere, OpenRouter, …) | that provider’s default |
| last | none present | gpt-4o-mini (fallback) |
A detected non-OpenAI provider yields a provider-prefixed id (e.g. anthropic/claude-3-5-sonnet-latest). The list above is illustrative — the SDK helper default_model_for_available_provider is the ground truth, so new providers are picked up automatically.
Precedence (highest wins):
An explicit model: on the agent always wins; a default_model on the gateway block wins over detection; model: auto (or an unset model) triggers detection; gpt-4o-mini is used only when nothing else resolves.
gateway section
| Key | Type | Default | Description |
|---|---|---|---|
gateway.host | str | "127.0.0.1" | Host to bind the WebSocket server |
gateway.port | int | 8765 | Port to listen on |
platforms section
Each key under platforms is a platform name. Use ${VAR_NAME} for environment variable interpolation.
| Key pattern | Type | Description |
|---|---|---|
platforms.<name>.token | str | Bot token for the platform. Supports ${ENV_VAR} substitution |
platforms.slack.app_token | str | Slack App-Level Token (xapp-...) for Socket Mode. Required for Slack when Socket Mode is used. Supports ${ENV_VAR} and secret references. |
Supported platforms: telegram, discord, slack, whatsapp, and any platform registered via entry points.
events — inbound platform events (opt-in)
Add an events: list under a channel to receive reactions, edits, deletions, member changes, and thread creations:
channels:
my-discord-bot:
platform: discord
token: "${DISCORD_BOT_TOKEN}"
events: [reactions, edits, members, threads]See Inbound Platform Events for the full list of accepted classes and what each one enables.
Single-platform shorthand
For one platform, declare a top-level platform: (and optionally token:) instead of a platforms: map:
platform: telegram
token: "${TELEGRAM_BOT_TOKEN}"token: is optional — omit it and the declared platform: resolves the platform’s own documented env var (TELEGRAM_BOT_TOKEN, DISCORD_BOT_TOKEN, etc.):
platform: telegramThe declared platform always wins. If other platform credentials are exported in the environment, bot start refuses to auto-enable them and logs a warning:
WARNING Config declares 'platform: telegram'; NOT auto-enabling discord, slack
despite their credentials being present in the environment. The declared
platform wins. To run them too, list them under 'channels:' explicitly.Admission policy (unknown_user_policy / owner_user_id)
Control who may DM a single-bot instance directly from bot.yaml. Since PR #5111, praisonai bot start wires unknown_user_policy and owner_user_id into the running bot for telegram / discord / slack — matching the gateway path exactly.
# bot.yaml — single-bot admission policy
platform: telegram
token: ${TELEGRAM_BOT_TOKEN}
unknown_user_policy: allow # deny (default) | pair | allow
owner_user_id: 987654321 # required when unknown_user_policy: pair
agent:
name: Assistant
instructions: Reply to incoming messages.Both fields also work at the channel level for the multi-channel shape:
# bot.yaml — channel-level admission policy
channels:
telegram:
token: ${TELEGRAM_BOT_TOKEN}
unknown_user_policy: pair
owner_user_id: 987654321
agent:
name: Assistant
instructions: Reply to incoming messages.| Value | Meaning |
|---|---|
deny | Default. Drop inbound DMs from users not on allowed_users. Since PR #5112 every drop logs a WARNING with the sender’s user_id, platform, and conversation so operators can distinguish a misconfigured allowlist from a broken bot. |
pair | Route unknown users through the owner-approval / pairing flow. Requires owner_user_id. |
allow | Open DMs to everyone. Trusted networks / open bots only. |
Values are case-insensitive on load (ALLOW normalises to allow) and whitespace is trimmed. An unquoted numeric owner_user_id (e.g. 987654321) is coerced to a string automatically.
A misspelled policy (alow, deney, …) fails closed at load time rather than silently reverting to deny:
Invalid unknown_user_policy 'alow'. Must be one of: allow, deny, pairTreat this error as intended — fix the typo and restart. The same fail-closed validation now applies to the multi-channel gateway.yaml schema too (PR #5112).
Full owner-approval flow, inline buttons, and CLI fallback.
Bot-to-bot inbound (allow_bots / bot_loop_protection)
Wire the bot-to-bot opt-in and the runaway-loop guard directly from bot.yaml — since PR #5242, praisonai bot start reads both allow_bots and bot_loop_protection for telegram / discord / slack and folds them into BotConfig, exactly matching the gateway path.
# bot.yaml
name: Group Helper
platform: telegram
allow_bots: true
bot_loop_protection:
max_events_per_window: 20
window_seconds: 60
cooldown_seconds: 60Default-off preserves today’s “drop bot-authored inbound messages” behaviour. See Bot Loop Protection for the full policy reference.
Slack single-bot shorthand
Slack Socket Mode also needs an app-level token, so add a top-level app_token: alongside token::
platform: slack
token: "${SLACK_BOT_TOKEN}"
app_token: "${SLACK_APP_TOKEN}"app_token: accepts the same three forms as token: — plaintext, ${ENV} interpolation, and the { source, id } secret reference.
app_token is Slack-specific. When platform: is anything other than slack, a top-level app_token: is ignored — this keeps a strict non-Slack adapter from rejecting an unexpected keyword. For the multi-channel form, set app_token under the Slack entry in channels: (see Messaging Bots).
Run a Slack bot in three lines plus the agent block:
# bot.yaml — Slack Socket Mode single-bot shorthand
platform: slack
token: "${SLACK_BOT_TOKEN}"
app_token: "${SLACK_APP_TOKEN}"
agent:
name: assistant
instructions: Reply to Slack mentions.
llm: gpt-4o-miniexport SLACK_BOT_TOKEN=xoxb-...
export SLACK_APP_TOKEN=xapp-...
praisonai bot start --config bot.yamlBot startup exit codes: praisonai bot start now exits 1 on any startup failure (bad config path, missing token, unknown platform). This makes systemd Restart=on-failure and if praisonai bot start ... ; then scripting work as expected.
Shell execution keys
Add these per-channel keys to let a bot run shell commands with approval routing.
| Key pattern | Type | Default | Description | |
|---|---|---|---|---|
<name>.allow_shell | bool | False | Opt in shell execution. Injects execute_command, lifts the deny-list entry, and appends a permission-grant line to instructions. | |
<name>.auto_approve_shell | bool | True | Runs every shell call immediately only on a loopback-bound gateway + non-group channel. On externally-bound gateways or any channel with a group_policy, the gateway automatically downgrades this to an approval backend (see <name>.auto_approve_shell_acknowledge_exposed). | |
<name>.auto_approve_shell_acknowledge_exposed | bool | False | Explicit acknowledgement that blanket auto-approval is safe on this externally-exposed or multi-user channel. Set to true (with auto_approve_shell: true) to keep the blanket behaviour on 0.0.0.0 binds or group channels. Off by default — the gateway fails closed to approval. | |
<name>.approval_channel | str | None | Where the approval prompt is delivered (platform-specific ID). | |
<name>.approval_users | str \ | list[str] | None | Approver allowlist. Comma-separated string or list. |
<name>.approval_mode | str | None (= channel) | Backend selector: channel, gateway, http, webhook. Typos raise ValueError. | |
<name>.approval_webhook_url | str | None | HTTPS URL for approval_mode: webhook. | |
<name>.approval_http_host | str | "127.0.0.1" | HTTP dashboard host for approval_mode: http. | |
<name>.approval_http_port | int | 8899 | HTTP dashboard port for approval_mode: http. |
Full routing ladder, per-platform ID resolution, and recipes.
Environment Variable Interpolation
Values in the form ${VAR_NAME} are resolved from the current environment at startup:
platforms:
telegram:
token: ${TELEGRAM_BOT_TOKEN} # reads os.environ["TELEGRAM_BOT_TOKEN"]
discord:
token: ${DISCORD_BOT_TOKEN} # reads os.environ["DISCORD_BOT_TOKEN"]Tokens stored in ~/.praisonai/.env (written by praisonai-bot onboard) are loaded automatically before interpolation.
When to Pick YAML vs Python
| Scenario | Use |
|---|---|
| Operations team manages tokens | YAML — credentials stay out of code |
| Static single-agent setup | YAML — fewer moving parts |
| Dynamic agents (created at runtime) | Python — full control over register_agent() |
| Custom gateway middleware or hooks | Python — subclass or extend WebSocketGateway |
| Multiple environments (dev / staging / prod) | YAML — per-environment files with env vars |
Common Patterns
Multi-platform setup
agent:
name: assistant
instructions: You are a helpful assistant.
llm: gpt-4o-mini
gateway:
host: 0.0.0.0
port: 8765
platforms:
telegram:
token: ${TELEGRAM_BOT_TOKEN}
discord:
token: ${DISCORD_BOT_TOKEN}
slack:
token: ${SLACK_BOT_TOKEN}Custom port from environment
gateway:
host: 0.0.0.0
port: 9000Or set the GATEWAY_PORT environment variable — the CLI reads it automatically when --port is not passed.
Best Practices
Always use ${…} for tokens
Never hardcode tokens in YAML. Use environment variables and store them in ~/.praisonai/.env (written by praisonai-bot onboard) or your secrets manager.
# Good
platforms:
telegram:
token: ${TELEGRAM_BOT_TOKEN}
# Never do this
platforms:
telegram:
token: 1234567890:ABCdef...Run praisonai-bot gateway doctor before going live
praisonai-bot gateway doctor --config bot.yamlThis validates every channel token before starting, so a bad token fails fast instead of silently reconnecting. It also prints a credential availability table for token, app_token, and verify_token — including the secret-reference form — without revealing any value.
Use separate YAML files per environment
Keep dev.yaml, staging.yaml, and prod.yaml — each referencing different environment variables. Pass the right one with --config.