> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Bot Gateway

> Run multiple bots (Telegram, Discord, Slack) from a single gateway server with multi-agent routing.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Bot Gateway"
        Request[📋 User Request] --> Process[⚙️ Bot Gateway]
        Process --> Result[✅ Result]
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class Request input
    class Process process
    class Result output
```

<Note>
  Bot platform adapters now ship in the `praisonai-bot` package. `praisonai bot serve` still works exactly as documented here; for a standalone install see [praisonai-bot Migration](/docs/guides/praisonai-bot-migration).
</Note>

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(name="gateway-bot", instructions="Handle messages from Telegram, Slack, and Discord.")
agent.start("Set up the bot gateway for all chat platforms.")
```

The user messages on Telegram, Slack, or Discord; the gateway routes each channel to the right agent.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Config[gateway.yaml] --> Gateway[Gateway Server]
    Gateway --> TG[Telegram Bot]
    Gateway --> DC[Discord Bot]
    Gateway --> SL[Slack Bot]
    TG --> AgentA[Agent A]
    DC --> AgentA
    SL --> AgentB[Agent B]
    
    classDef agent fill:#8B0000,color:#fff
    classDef tool fill:#189AB4,color:#fff
    
    class AgentA,AgentB agent
    class Config,Gateway,TG,DC,SL tool
```

Run all your bots from one command. The Gateway Server manages multiple bot connections and routes messages to the right AI agent.

**Parity with Bot()**: `praisonai gateway start` now applies the same smart defaults as `praisonai bot start`, resolving the previous "zero tools in daemon mode" issue. Both entry points produce identical behavior with safe tools auto-injected and auto-approval enabled by default.

Each channel bot gets an isolated agent via `Agent.clone_for_channel()` — fresh locks, fresh interrupt controller, and no shared `handoffs` state — so configuring tools or memory on one channel never leaks into another.

## Quick Start

<Steps>
  <Step title="Install PraisonAI">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    pip install praisonai
    ```
  </Step>

  <Step title="Create gateway.yaml">
    Create a `gateway.yaml` file in your project (Gateway YAML files are read as UTF-8 — non-ASCII characters work on all platforms including Windows):

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    gateway:
      host: "127.0.0.1"
      port: 8765

    agents:
      personal:
        instructions: "You are a helpful personal assistant"
        model: gpt-4o-mini
        tools:
          - internet_search
          - get_current_time
        reflection: true
      support:
        instructions: "You are a customer support agent"
        model: gpt-4o
        role: "Customer Support Specialist"
        goal: "Resolve customer issues quickly and accurately"
        backstory: "Expert in troubleshooting and customer care"
        tools:
          - internet_search
        tool_choice: auto
        allow_delegation: false

    channels:
      telegram:
        token: ${TELEGRAM_BOT_TOKEN}
        allowed_users: []                        # or a list of allowed user IDs
        unknown_user_policy: deny                # deny (default, secure) | pair | allow
        owner_user_id: ${TELEGRAM_OWNER_ID}      # required if unknown_user_policy: pair
        streaming:
          mode: draft       # off | draft | progress — see /features/bot-streaming-replies
        routes:
          dm: personal
          group: support
          default: personal
      discord:
        token: ${DISCORD_BOT_TOKEN}
        allowed_users: []
        unknown_user_policy: deny
        owner_user_id: ${DISCORD_OWNER_ID}
        routes:
          default: personal
    ```

    <Tip>
      **Even simpler:** if you have a platform token in the environment, you can skip the `channels:` block entirely. `export TELEGRAM_BOT_TOKEN=... && praisonai gateway` brings up a working bot with no config. See [Zero-Config Gateway](/docs/features/gateway-auto-enable-from-env).
    </Tip>
  </Step>

  <Step title="Set Environment Variables">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export TELEGRAM_BOT_TOKEN=your_telegram_token
    export DISCORD_BOT_TOKEN=your_discord_token
    export OPENAI_API_KEY="${OPENAI_API_KEY:?Set OPENAI_API_KEY in your shell}"
    ```
  </Step>

  <Step title="Start the Gateway">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai gateway start --config gateway.yaml
    ```

    All bots start together. Messages are routed to the correct agent automatically.
  </Step>
</Steps>

## Supported Channels

<CardGroup cols={2}>
  <Card title="Telegram" icon="paper-plane">
    Full support for DMs, groups, commands, voice, and media.
  </Card>

  <Card title="Discord" icon="discord">
    Guild channels, DMs, slash commands, and embeds.
  </Card>

  <Card title="Slack" icon="slack">
    Socket Mode, channels, DMs, slash commands, and threads.
  </Card>

  <Card title="WhatsApp" icon="whatsapp">
    Cloud API and Web mode. DMs, groups, media support.
  </Card>
</CardGroup>

<Note>
  **Windows users:** Gateway Telegram error replies are automatically sanitized to ASCII-safe text — non-ASCII exception content (warning symbols, emoji, accented characters) no longer crashes the error handler with a `charmap` codec error. Real underlying errors (quota, rate limit, auth) surface cleanly.
</Note>

## Configuration Reference

### Gateway Section

| Field    | Type    | Default     | Description                        |
| -------- | ------- | ----------- | ---------------------------------- |
| **host** | string  | `127.0.0.1` | Address to bind the gateway server |
| **port** | integer | `8765`      | Port for the WebSocket server      |

### Agents Section

Each agent is defined by a unique ID and its configuration:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
agents:
  my_agent_id:
    instructions: "Your system prompt here"
    model: gpt-4o-mini
    memory: true
    tools:
      - internet_search
      - get_current_time
    reflection: true
    role: "Research Analyst"
    goal: "Find accurate information"
    backstory: "Expert researcher"
    tool_choice: auto
    allow_delegation: false
```

| Field                 | Type    | Default | Description                               |
| --------------------- | ------- | ------- | ----------------------------------------- |
| **instructions**      | string  | `""`    | System prompt for the agent               |
| **model**             | string  | `None`  | LLM model (e.g., `gpt-4o-mini`, `gpt-4o`) |
| **memory**            | boolean | `false` | Enable conversation memory                |
| **tools**             | list    | `[]`    | Tool names resolved via ToolResolver      |
| **reflection**        | boolean | `true`  | Enable self-reflection / interactive mode |
| **role**              | string  | `None`  | Agent role (CrewAI-style)                 |
| **goal**              | string  | `None`  | Agent goal (CrewAI-style)                 |
| **backstory**         | string  | `None`  | Agent backstory (CrewAI-style)            |
| **tool\_choice**      | string  | `None`  | `auto`, `required`, or `none`             |
| **allow\_delegation** | boolean | `false` | Allow task delegation to other agents     |

### Channels Section

Each channel maps to a bot platform:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
channels:
  telegram:
    token: ${TELEGRAM_BOT_TOKEN}    # Environment variable
    unknown_user_policy: deny        # deny (default) | pair | allow
    owner_user_id: ${TELEGRAM_OWNER_ID}  # required if unknown_user_policy: pair
    routes:
      dm: personal                   # DMs → personal agent
      group: support                 # Groups → support agent
      default: personal              # Fallback agent
```

<Tip>
  Use `${ENV_VAR_NAME}` syntax in token fields. The gateway automatically reads from your environment variables.
</Tip>

### Channel Security

Each channel enforces the same access-control pipeline as standalone bots. Every key below is read per-channel from `gateway.yaml` — including `unknown_user_policy` and `owner_user_id`, which the gateway now wires through at start and on hot-reload ([PR #2856](https://github.com/MervinPraison/PraisonAI/pull/2856)).

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Same effect from Python — for consumers building custom adapters
from praisonaiagents.bots import BotConfig

config = BotConfig(
    token="...",
    allowed_users=["telegram:123456"],
    unknown_user_policy="pair",     # deny | pair | allow
    owner_user_id="telegram:987654",
)

# is_explicitly_allowed skips the pairing flow for whitelisted users
config.is_explicitly_allowed("telegram:123456")  # True
config.is_explicitly_allowed("telegram:99999")   # False → policy decides
```

The same configuration comes straight from `gateway.yaml`:

| YAML Key              | Type                          | Default                  | Purpose                                                                                                                                                                                                                   |
| --------------------- | ----------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`               | `str`                         | required\*               | Bot auth token. \*Optional for `whatsapp` web mode and `email`/`agentmail`.                                                                                                                                               |
| `platform`            | `str`                         | channel name             | Override channel platform (e.g. `telegram`).                                                                                                                                                                              |
| `routing` / `routes`  | `dict`                        | `{"default": "default"}` | Route map: `dm`, `group`, `default` → agent id.                                                                                                                                                                           |
| `allowed_users`       | `list[str]` \| `str`          | `[]`                     | User-ID allowlist. Comma-separated string also accepted (env-expansion friendly).                                                                                                                                         |
| `allowed_channels`    | `list[str]` \| `str`          | `[]`                     | Channel/group-ID allowlist. Same string handling.                                                                                                                                                                         |
| `unknown_user_policy` | `"deny" \| "pair" \| "allow"` | `"deny"`                 | What to do with users not in `allowed_users`. `deny` silently drops. `pair` runs the owner-approval flow (`owner_user_id` required). `allow` lets everyone through (trusted networks only).                               |
| `owner_user_id`       | `str`                         | `null`                   | Platform user ID of the channel owner. Required when `unknown_user_policy: "pair"` — the owner receives approval prompts for new users.                                                                                   |
| `group_policy`        | `str`                         | `"mention_only"`         | One of `"mention_only"`, `"command_only"`, `"respond_all"`, `"observe"`. Sets `mention_required` automatically. `observe` records unmentioned group messages as passive context instead of dropping them (Telegram only). |
| `auto_approve_tools`  | `bool` \| `str`               | `True`                   | Auto-approve safe tools. Strings `"1"/"true"/"yes"/"on"` are truthy.                                                                                                                                                      |
| `default_tools`       | `list[str]`                   | (BotConfig default)      | Per-channel override for default safe tools.                                                                                                                                                                              |

### `observe`: passive group context

`observe` records unmentioned group messages as passive context so the bot can answer with full conversational awareness when it's finally mentioned.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Msg[💬 Unmentioned msg] --> Gate{🚦 Policy gate}
    Gate -->|mention_only| Drop[🗑️ Dropped]
    Gate -->|observe| Store[💾 Session store]
    Store --> History[📜 Visible on next mention]

    classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef gate fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef store fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff

    class Msg input
    class Gate gate
    class Store,History store
    class Drop result
```

With `mention_only` (the default), messages that don't `@mention` the bot are dropped and leave no session record. With `observe`, those messages are appended to the session transcript as ordinary `user` turns — the bot still does **not** answer them, but the next time it is addressed (e.g. `@bot summarise what we just decided`) the preceding conversation is in its history.

Pair `observe` with `session_scope: per_chat` so passive messages route to the shared per-chat key that a later addressed turn reads from. Under the default `per_user` scope, passive turns are retained only in that sender's own history — they do not leak into any single user's `per_user` history when `per_chat` is used.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
channels:
  telegram:
    token: ${TELEGRAM_BOT_TOKEN}
    group_policy: observe
    session_scope: per_chat
```

<Note>
  `observe` is Telegram-only today. Other adapters ignore it and fall back to their default group handling.
</Note>

**When to use it:** a busy group where users want to `@bot` and have it see prior discussion; the bot stays quiet until addressed.

**When not to use it:** noise-sensitive or high-PII channels — every unmentioned message is stored, increasing the volume of retained conversation text.

See [Session Scopes](/docs/features/multi-channel-bots) and [Intentional Silence](/docs/features/bot-intentional-silence).

### Shell execution

Set `allow_shell: true` on any channel to turn its bot into a shell-capable agent, with approval routed per platform. Policy is per-channel — one channel can auto-approve while another requires owner sign-off. On channels with routing rules, routed agents inherit `execute_command` too — the shell setup is re-applied to each routed agent, cloned and cached per `(channel, agent)`. See [Bot Shell Execution](/docs/docs/features/bot-shell-execution#with-routing-rules).

<Note>
  **Routing rules apply to Slack @mentions.** Slack `handle_mention` dispatches through registered `on_message` handlers, so per-route agent selection and `allow_shell` opt-in reach @mentions the same way they reach DMs and channel messages. On older builds, @mentions bypassed routing and ran the raw default agent. See [Messaging Bots → Slack](/docs/features/messaging-bots).
</Note>

| YAML Key                                 | Type                 | Default              | Purpose                                                                                                                                                                                                                                                                                  |
| ---------------------------------------- | -------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allow_shell`                            | `bool`               | `False`              | Opt in shell execution for this channel. Injects `execute_command`, lifts the deny-list entry, and appends a permission-grant line to the agent's instructions.                                                                                                                          |
| `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 `auto_approve_shell_acknowledge_exposed`).          |
| `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. |
| `approval_channel`                       | `str`                | `None`               | Where the approval prompt is delivered (Slack user/channel ID, Telegram chat ID, Discord channel ID — falls back to `home_channel`).                                                                                                                                                     |
| `approval_users`                         | `str` \| `list[str]` | `None`               | Approver allowlist. Comma-separated string or list. Falls back to `SLACK_APPROVERS` / `TELEGRAM_APPROVERS` / `DISCORD_APPROVERS`.                                                                                                                                                        |
| `approval_mode`                          | `str`                | `None` (= `channel`) | Backend selector: `channel`, `gateway`, `http`, or `webhook`. Validated at load — typos raise `ValueError`.                                                                                                                                                                              |
| `approval_webhook_url`                   | `str`                | `None`               | HTTPS URL for `approval_mode: webhook`. Falls back to `APPROVAL_WEBHOOK_URL`. Missing → warns and falls through to the gateway queue.                                                                                                                                                    |
| `approval_http_host`                     | `str`                | `"127.0.0.1"`        | HTTP dashboard host for `approval_mode: http`. Set `"0.0.0.0"` to expose.                                                                                                                                                                                                                |
| `approval_http_port`                     | `int`                | `8899`               | HTTP dashboard port for `approval_mode: http`.                                                                                                                                                                                                                                           |

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
channels:
  telegram:
    token: "${TELEGRAM_BOT_TOKEN}"
    # allow_shell: true
    # auto_approve_shell: false
    # approval_channel: "123456789"
```

<Note>
  See [Bot Shell Execution](/docs/features/bot-shell-execution) for the full routing ladder, per-platform ID resolution, env-var reference, and copy-paste recipes.
</Note>

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
channels:
  telegram_support:
    token: ${TELEGRAM_BOT_TOKEN}
    allowed_users:                # User-ID allowlist (empty list → unknown_user_policy decides)
      - "123456789"
      - "987654321"
    allowed_channels:             # Group/channel allowlist (empty = anywhere)
      - "-1001234567890"
    unknown_user_policy: deny     # deny (default, secure) | pair | allow
    owner_user_id: ${TELEGRAM_OWNER_ID}  # required if unknown_user_policy: pair
    group_policy: "mention_only"  # mention_only | command_only | respond_all | observe
    auto_approve_tools: true      # Auto-approve safe tools (default true)
    routes:
      dm: personal
      group: support
      default: personal
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    subgraph "Inbound Security Pipeline"
        Message[📨 Inbound Update] --> Extract[📝 Extract Text]
        Extract --> Convert[🔄 Convert to BotMessage]
        Convert --> Check1{🔍 Channel Allowed?}
        Check1 -->|No| Drop1[❌ Drop Message]
        Check1 -->|Yes| Check2{👤 User Allowed?}
        Check2 -->|No| Pairing[🤝 Unknown User Handler]
        Pairing -->|Denied| Drop2[❌ Drop Message]
        Pairing -->|Approved| Check3{🏷️ Group Policy?}
        Check2 -->|Yes| Check3
        Check3 -->|DM| Process[✅ Process Message]
        Check3 -->|Group + mention_only| Mention{💬 @mentioned?}
        Check3 -->|Group + command_only| Command{⚡ Command?}
        Check3 -->|Group + respond_all| Process
        Check3 -->|Group + observe| Mention2{💬 @mentioned?}
        Mention -->|Yes| Process
        Mention -->|No| Drop3[❌ Drop Message]
        Command -->|Yes| Process
        Command -->|No| Drop4[❌ Drop Message]
        Mention2 -->|Yes| Process
        Mention2 -->|No| Record[📝 Record as passive context]
        Record --> Drop5[❌ No agent run]
    end
    
    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef check fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff
    classDef drop fill:#8B0000,stroke:#7C90A0,color:#fff
    
    class Message,Extract,Convert input
    class Check1,Check2,Check3,Mention,Command,Mention2,Record,Pairing check
    class Process success
    class Drop1,Drop2,Drop3,Drop4,Drop5 drop
```

<Warning>
  **`allowed_users` interacts with `unknown_user_policy`.** With an empty `allowed_users` list, every Telegram user is treated as "unknown" and the `unknown_user_policy` decides their fate:

  * `"deny"` (default) — all messages silently dropped (recommended for production).
  * `"pair"` — unknown users go through the owner-approval pairing flow.
  * `"allow"` — every user is let through (only set this in trusted networks).

  Earlier releases incorrectly treated empty `allowed_users` as "allow everyone" on Telegram, bypassing the policy entirely. Fixed in [PR #1885](https://github.com/MervinPraison/PraisonAI/pull/1885). Discord and Slack were already correct.
</Warning>

### Startup warnings

An empty `allowed_users` triggers a policy-aware warning at gateway start and on hot-reload. Grep these exact strings in your logs:

<CodeGroup>
  ```text deny (default — secure) theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  Channel 'telegram' has no allowed_users AND unknown_user_policy='deny' — inbound DMs from unknown users are SILENTLY DROPPED. Set unknown_user_policy: pair (with owner_user_id) or allow to change this.
  ```

  ```text pair theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  Channel 'telegram' has no allowed_users AND unknown_user_policy='pair' — unknown users go through the owner-approval pairing flow. Ensure owner_user_id is set.
  ```

  ```text allow theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  Channel 'telegram' has no allowed_users AND unknown_user_policy='allow' — bot accepts messages from everyone. This is intentional only in trusted networks.
  ```
</CodeGroup>

If you see the `deny` warning and expected messages to flow through, your config is secure-by-default and DMs are intentionally dropped. Add IDs to `allowed_users`, or set `unknown_user_policy: pair` (with `owner_user_id`) or `allow`.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    subgraph "Unknown user hits the bot"
        In[📨 Message from user_id] --> Check{On allowed_users?}
        Check -->|Yes| Pass[✅ Runs as normal user]
        Check -->|No — empty or missing| Pol{unknown_user_policy?}
        Pol -->|deny default| Drop[⛔ Silently drop]
        Pol -->|pair| Pair[📨 Notify owner_user_id → approve/reject]
        Pol -->|allow| Trust[✅ Runs as unknown user]
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef gate fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef ok fill:#10B981,stroke:#7C90A0,color:#fff
    classDef fail fill:#8B0000,stroke:#7C90A0,color:#fff

    class In input
    class Check,Pol gate
    class Pass,Pair,Trust ok
    class Drop fail
```

<Note>
  As of PR #1791, gateway-mode bots enforce the same security pipeline as standalone bots (`praisonai bot start`). Previous versions silently bypassed `allowed_users`, pairing, and `group_policy` in gateway mode.
</Note>

<Note>
  Pair with `unknown_user_policy: "deny"` for the most secure default. To intentionally allow everyone (e.g. internal staging), leave `allowed_users` empty **and** set `unknown_user_policy: "allow"` — both are required.
</Note>

### Interactive presentations (optional)

`GatewayMessage` carries an optional `presentation: MessagePresentation` field. When set, channel adapters that implement [`SupportsPresentation`](/docs/features/bot-presentations) render it as a native widget (Telegram inline keyboard, Slack Block Kit, Discord components). Channels that do not implement the protocol fall back to the message's plain-text `content`. See [Interactive Bot Messages](/docs/features/bot-presentations) for the full presentation model.

## Hot-Reload

Editing `gateway.yaml` while the gateway runs triggers a diff-driven reload — only affected agents or channels restart. The WebSocket server stays up.

See [Gateway Hot-Reload](/docs/features/gateway-hot-reload) for the full restart-scope table.

## CLI Commands

<CodeGroup>
  ```bash Start Gateway theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  praisonai gateway start --config gateway.yaml
  ```

  ```bash Start with Custom Host/Port theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  praisonai gateway start --config gateway.yaml --host 0.0.0.0 --port 9000
  ```

  ```bash Check Gateway Status theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  praisonai gateway status
  ```

  ```bash Stop Gateway theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  praisonai gateway stop
  ```
</CodeGroup>

<Note>
  `gateway stop` won't release the PID lock if the running gateway is owned by another user — it reports the stop as not confirmed and leaves the lock in place so a duplicate gateway can't start on top of it. See [Gateway CLI — stop](/docs/features/gateway-cli#commands).
</Note>

## Python Usage

<Tabs>
  <Tab title="From YAML">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai.gateway import WebSocketGateway

    # Load gateway config, agents, and channels from YAML
    gateway = WebSocketGateway.from_config_file("gateway.yaml")

    import asyncio
    asyncio.run(gateway.start())
    ```

    <Tip>
      `from_config_file()` automatically resolves `${ENV_VAR}` syntax in your YAML, creates agents with tools, and configures channel bots.
    </Tip>
  </Tab>

  <Tab title="Programmatic">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import asyncio
    from praisonai.gateway import WebSocketGateway
    from praisonaiagents import Agent
    from praisonaiagents import GatewayConfig

    # Create agents
    personal = Agent(name="personal", instructions="You are a helpful assistant")
    support = Agent(name="support", instructions="You are a support agent")

    # Create gateway
    config = GatewayConfig(host="127.0.0.1", port=8765)
    gateway = WebSocketGateway(config=config)

    # Register agents (use overwrite=False to prevent accidental replacement)
    gateway.register_agent(personal, agent_id="personal")
    gateway.register_agent(support, agent_id="support", overwrite=False)

    # Start gateway
    asyncio.run(gateway.start())
    ```
  </Tab>

  <Tab title="Inspect Bots">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # After starting the gateway, inspect connected channel bots
    gateway.list_channel_bots()      # ['telegram', 'discord']
    gateway.get_channel_bot("telegram")  # Returns bot instance
    gateway.has_channel_bot("slack")     # False

    # Inspect registered agents
    gateway.list_agents()            # ['personal', 'support']
    gateway.get_agent("personal")    # Returns Agent instance
    ```
  </Tab>
</Tabs>

<Accordion title="Advanced: Health Endpoint">
  The gateway exposes a health endpoint at `http://host:port/health`:

  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  curl http://127.0.0.1:8765/health
  ```

  Returns:

  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "status": "healthy",
    "uptime": 120.5,
    "agents": 2,
    "sessions": 3,
    "clients": 1,
    "channels": {
      "telegram": {
        "platform": "telegram",
        "running": true
      },
      "discord": {
        "platform": "discord", 
        "running": false
      }
    },
    "push": {
      "enabled": true,
      "push_channels": 2,
      "online_clients": 5,
      "redis_connected": true
    }
  }
  ```

  The `push` section is only included when push notifications are enabled.
</Accordion>

<Accordion title="Advanced: Channel isolation">
  The gateway calls `_create_bot()` to build independent clones for each channel:

  ```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  flowchart TB
      Base[🤖 Base Agent] --> Gateway[Gateway Server]
      Gateway --> Clone1[📱 Telegram Clone]
      Gateway --> Clone2[💬 Discord Clone] 
      Gateway --> Clone3[📺 Slack Clone]
      
      style Base fill:#8B0000,color:#fff
      style Gateway fill:#189AB4,color:#fff
      style Clone1,Clone2,Clone3 fill:#10B981,color:#fff
  ```

  Each clone has fresh locks and interrupt controllers, preventing cross-channel interference. Learn more about [Agent Cloning](/docs/features/agent-cloning).
</Accordion>

<Accordion title="Advanced: Standalone Bot Mode">
  You can still run a single bot without the gateway:

  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  praisonai bot telegram --token $TELEGRAM_BOT_TOKEN
  ```

  This starts just one bot connected to a single agent — no gateway needed.
</Accordion>

<Warning>
  Never commit bot tokens to version control. Always use environment variables or a `.env` file.
</Warning>

## BotOS Options

When creating a `BotOS` instance in Python, the following options control gateway-level behaviour:

| Field                | Type                        | Default | Description                                                                                                                                                                    |
| -------------------- | --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `bots`               | `list[Bot]`                 | `[]`    | Pre-built Bot instances to orchestrate                                                                                                                                         |
| `agent`              | `Agent`                     | `None`  | Shared agent for auto-created bots (used with `platforms`)                                                                                                                     |
| `platforms`          | `list[str]`                 | `None`  | Platform names; creates one Bot per platform using the shared `agent`                                                                                                          |
| `health_monitor`     | `HealthMonitorConfig`       | `None`  | Channel health monitoring configuration                                                                                                                                        |
| `enable_supervision` | `bool`                      | `True`  | Enable automatic channel supervision and recovery                                                                                                                              |
| `idle_policy`        | `GatewayIdlePolicyProtocol` | `None`  | When set, schedules an idle-dormancy loop that quiesces transports after inactivity. Default `None` = always-on. See [Scale-to-Zero Gateway](/docs/features/gateway-scale-to-zero). |

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai.bots import BotOS, Bot
from praisonaiagents.gateway import ScaleToZeroPolicy

agent = Agent(name="assistant", instructions="Help users")

botos = BotOS(
    bots=[Bot("telegram", agent=agent)],
    idle_policy=ScaleToZeroPolicy(
        idle_timeout_minutes=5,
        wake_url="https://my-bot.fly.dev/_wake",
    ),
)
botos.run()
```

## Observability

<CardGroup cols={2}>
  <Card icon="chart-line" href="/docs/features/gateway-metrics" title="Gateway Metrics">
    Scrape `GET /metrics` for Prometheus-format counters and gauges on every
    hop of the message flow — no extra dependencies.
  </Card>

  <Card icon="link" href="/docs/features/correlation-ids" title="Correlation IDs">
    One stable id joins ingress, session, and agent-run logs for every turn.
    Read it inside any tool with `current_correlation_id()`.
  </Card>

  <Card icon="route" href="/docs/features/gateway-tracing-hook" title="Gateway Tracing Hook">
    Open a distributed-tracing span around each pipeline stage — inbound,
    admit, agent.run, llm.call, tool.call, outbox.enqueue, delivery.
  </Card>
</CardGroup>

## Code-Skew Guard

After running `git pull` or `pip install -U`, the gateway's `/model` command detects that the installed code version has changed and blocks the model switch with a "restart required" message. This prevents the model from changing when the running code is out of sync with the installed libraries.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User as 👤 User
    participant Bot as 🤖 Bot
    participant Guard as 🔍 code_skew_guard

    User->>Bot: /model gpt-4o
    Bot->>Guard: detect_code_skew(fingerprint)
    Guard-->>Bot: skew detected
    Bot-->>User: ⚠️ Gateway restart required before switching models
```

To opt out (useful in development):

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
session_manager.code_skew_guard = False
```

Helpers for custom tooling:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.gateway import detect_code_skew, read_code_fingerprint

fingerprint = read_code_fingerprint()
if detect_code_skew(fingerprint):
    print("Code has changed since gateway started — restart recommended")
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Lock down empty allowlists">
    Leave `allowed_users` empty only with `unknown_user_policy: "deny"` for production. Pairing mode is fine for personal bots; public gateways need explicit allowlists.
  </Accordion>

  <Accordion title="Route by channel context">
    Map `dm`, `group`, and `default` to different agents when support and personal assistants should not share memory or tools. Isolated clones prevent cross-channel leakage.
  </Accordion>

  <Accordion title="Use environment variables for tokens">
    Reference tokens as `${TELEGRAM_BOT_TOKEN}` in YAML — never commit secrets. Gateway resolves env vars at load time on all platforms including Windows.
  </Accordion>

  <Accordion title="Monitor via /health and /metrics">
    Poll the health endpoint during deploys and wire [Gateway Metrics](/docs/features/gateway-metrics) into Prometheus for message-flow visibility.
  </Accordion>
</AccordionGroup>

<Note>
  `/undo` in a bot chat reverts files in that chat's workspace, not the gateway process cwd. This happens automatically — `apply_bot_smart_defaults()` calls `Agent.set_snapshot_root(workspace.root)` after attaching the `Workspace`. See [File Snapshot → Bot / Gateway Workspaces](/docs/docs/features/file-snapshot#bot-gateway-workspaces).
</Note>

## Related

<CardGroup cols={2}>
  <Card icon="shield" href="/docs/features/gateway-bind-aware-auth" title="Bind-Aware Auth">
    Token auth plus the bind-aware loopback bypass (permissive on loopback by default) for gateway operational endpoints.
  </Card>

  <Card icon="triangle-alert" href="/docs/features/gateway-error-handling" title="Gateway Error Handling">
    Supervision, restarts, and error recovery in the gateway.
  </Card>

  <Card icon="chart-line" href="/docs/features/gateway-metrics" title="Gateway Metrics">
    Prometheus-format message-flow metrics from `GET /metrics`.
  </Card>

  <Card icon="link" href="/docs/features/correlation-ids" title="Correlation IDs">
    Join ingress, session, and agent-run logs on one stable id per message.
  </Card>

  <Card icon="arrows-rotate" href="/docs/features/gateway-restart-continuation" title="Restart Continuation">
    Re-drive interrupted turns and notify the originating channel after a restart.
  </Card>
</CardGroup>
