Orchestration
BotOS lets you deploy your AI agent to multiple messaging platforms with just a few lines of code. One agent brain, many channels.Quick Start
How It Works
Architecture
Design principles
Design principles
- Protocol-driven:
BotOSProtocollives in the lightweight core SDK; heavy implementations live in the wrapper - Lazy loading: Platform libraries (telegram, discord, etc.) are only imported when
start()is called - Agent-centric: Every bot is powered by an Agent, AgentTeam, or AgentFlow
- Extensible: Register custom platforms at runtime with
register_platform(), or distribute packaged connectors via thepraisonai.channelsentry-point group for zero-code auto-registration
Token Setup
Tokens are resolved automatically from environment variables. Set them once, use everywhere.| Platform | Environment Variable |
|---|---|
| Telegram | TELEGRAM_BOT_TOKEN |
| Discord | DISCORD_BOT_TOKEN |
| Slack | SLACK_BOT_TOKEN + SLACK_APP_TOKEN |
WHATSAPP_ACCESS_TOKEN + WHATSAPP_PHONE_NUMBER_ID |
Usage Examples
Single Agent on Telegram
AgentTeam on Discord
AgentFlow on Multiple Platforms
YAML Configuration
Create abotos.yaml file:
Environment variables in
${VAR_NAME} format are automatically resolved.Smart Defaults
When you create aBot(), it automatically enhances your agent with useful defaults — no configuration needed:
| Default | What Happens | When Applied |
|---|---|---|
| Safe tools | search_web, schedule_add, schedule_list, schedule_remove | Only if agent has no tools |
Smart defaults only apply to plain
Agent instances. AgentTeam and AgentFlow are left untouched — they already have their own configuration.Auto-Recovery & Health Monitoring
EveryBotOS instance is supervised by default — crashed bots are restarted with backoff, and proactive health checks catch hung connections before users notice.
Supervision is on automatically — no extra arguments needed:
Multi-Process / HA Deployments
Running more than oneBotOS process — for high availability, horizontal scaling, or a brief rolling-restart overlap — used to double-fire every scheduled job, double-posting messages, double-charging tokens, and duplicating side effects.
Each due job is now claimed atomically before it runs, so exactly one BotOS process fires it; the others skip silently. This needs no config change as long as the schedule store supports atomic claim — the default FileScheduleStore does, via an advisory-locked claim_due.
Verify the guarantee is engaged from the startup log — every ticker prints one line when its schedule loop starts:
atomic_claim=True confirms cross-process at-most-once is active. atomic_claim=False means the current store does not implement claim_due, so the legacy non-atomic path is in use and a job can fire once per process.
Start two processes against the same store and watch the log — only one fires each job:
| Store | At-most-once | How |
|---|---|---|
FileScheduleStore (default) | ✅ | Advisory-locked claim_due |
| Custom store | Depends | Only when it implements claim_due on ScheduleStoreProtocol |
If a
BotOS process crashes mid-run, its 300-second lease expires and another ticker re-claims the job — no permanent job loss. Single-process behaviour is unchanged.Operator Controls
When supervision is enabled, use Python API controls (BotOS has no CLI for these):Aggregated Health
await botos.health() into a /healthz endpoint or Prometheus exporter — one dict per platform, no custom wiring per adapter.
Unified Per-User Session
Link multiple platforms so one human gets one conversation across all channels:Health Checks
Every bot adapter supportsprobe() and health() for diagnostics:
Extending Platforms
Add your own messaging platform in 3 steps:Adapter contract
Adapter contract
Your custom adapter class must implement:
| Method | Required | Description |
|---|---|---|
__init__(**kwargs) | Yes | Accept token, agent, and platform-specific params |
async start() | Yes | Start the bot (connect, listen for messages) |
async stop() | Yes | Gracefully stop the bot |
async send_message(channel_id, content) | Optional | Send a message programmatically |
on_message(handler) | Optional | Register a message handler |
on_command(command) | Optional | Register a command handler |
is_running (property) | Optional | Whether the bot is currently running |
Managing Bots
API Reference
Bot class
Bot class
Platform name:
"telegram", "discord", "slack", "whatsapp", or any registered custom platform.The AI agent that powers the bot.
Explicit token. Falls back to
{PLATFORM}_BOT_TOKEN env var.Cross-platform identity resolver for unified sessions.
StoreBackedIdentityResolver.from_env() (from praisonai.bots) reuses the gateway pairing store so paired users share one session out of the box — see Cross-Platform Sessions.**kwargs
Platform-specific parameters (e.g.,
app_token for Slack, mode for WhatsApp).bot.run()— Start the bot (sync, blocks)await bot.start()— Start the bot (async)await bot.stop()— Stop the botawait bot.send_message(channel_id, content)— Send a messageawait bot.probe()— Test channel connectivity (returnsProbeResult)await bot.health()— Get detailed health status (returnsHealthResult)bot.register_command(name, handler)— Register a custom chat commandbot.list_commands()— List all registered commands
BotOS class
BotOS class
Pre-built Bot instances to orchestrate.
Shared agent — used with
platforms to auto-create Bots.Platform names — auto-creates a Bot per platform using
agent.Cross-platform identity resolver for unified sessions.
StoreBackedIdentityResolver.from_env() (from praisonai.bots) reuses the gateway pairing store so paired users share one session out of the box — see Cross-Platform Sessions.Proactive health sweep settings (
interval, startup_grace, stale_after, stuck_after, max_restarts_per_hour).Run each bot through
ChannelSupervisor with auto-recovery. Set False when an external supervisor owns restarts.Reliability preset:
"production" (15 s graceful drain + CPU-scaled admission ceiling + bounded fair queue), "default" / None (5 s drain, no admission ceiling), or "off" (immediate teardown, no admission). Explicit fields like drain_timeout and max_concurrent_runs always override the preset. See Gateway Reliability Presets for details.botos.run()— Start all bots (sync, blocks)await botos.start()— Start all bots (async)await botos.stop()— Stop all botsbotos.add_bot(bot)— Register a botbotos.remove_bot("platform")— Remove a botbotos.list_bots()— List platform namesbotos.get_bot("platform")— Get a bot by platformawait botos.health()— AggregatedDict[str, HealthResult]across all botsbotos.get_supervisor_status()— Supervisor + health-monitor snapshotbotos.pause_bot("platform")— Pause a supervised botbotos.resume_bot("platform")— Resume a paused botbotos.reconnect_bot("platform")— Force reconnect and reset error stateawait botos.deliver(target, text, origin=None)— Proactive outbound delivery (see Proactive Delivery)botos.configure_channels(config)— Set home channels and aliasesbotos.delivery_router— Direct access toDeliveryRouterandChannelDirectoryBotOS.from_config("path.yaml")— Load from YAML
Platform Registry
Platform Registry
try/except blocks or test assertions that matched the old "Unknown platform" text.Best Practices
Set tokens via environment variables
Set tokens via environment variables
Use
TELEGRAM_BOT_TOKEN, DISCORD_BOT_TOKEN, and related env vars so secrets never live in source code.Keep supervision enabled in production
Keep supervision enabled in production
Default
BotOS supervision restarts crashed bots with backoff — disable only when systemd or Kubernetes owns restarts.Link identities for cross-channel users
Link identities for cross-channel users
Use
FileIdentityResolver or StoreBackedIdentityResolver so one person gets one session across Telegram, Discord, and Slack.Probe tokens before deploy
Probe tokens before deploy
Call
await bot.probe() in CI to catch invalid tokens before users hit your bot.Related
Messaging Bots
Platform-specific bot setup and capabilities.
Bot Gateway
Gateway routing, pairing, and channel supervision.
Cross-Platform Sessions
Unified sessions across Telegram, Discord, and more.
Proactive Delivery
Push outbound messages with
botos.deliver().
