> ## 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 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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Bot Chat Commands"
        Request[📋 User Request] --> Process[⚙️ Bot Chat Commands]
        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>

<Note>
  On Telegram and Discord, every command below appears in the native `/` autocomplete menu automatically. See [Slash Command Menu](/docs/features/bot-slash-command-menu).
</Note>

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Bot Chat Commands"
        In[📝 /command] --> Handler[⚙️ Command Handler]
        Handler --> Agent[🤖 Agent]
        Agent --> Out[✅ Chat Reply]
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class In input
    class Handler process
    class Agent agent
    class Out output
```

### Command Picker

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Q{What do you need?} --> A[Cancel a running task]
    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]
    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,B,C,D,E,F,G,H,I,J,K,L,N,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,N1 tool
    class M,M1 source
```

File-based (`.praisonai/commands/*.md`) and entry-point commands surface alongside built-ins — see [File-based & Entry-point Commands](#file-based-and-entry-point-commands-in-bot-chats).

Every PraisonAI bot comes with built-in chat commands. Just type a command in your chat — no setup required.

## Built-in Commands

<CardGroup cols={2}>
  <Card title="/status" icon="circle-info">
    Shows agent name, model, platform, and uptime.
  </Card>

  <Card title="/new" icon="rotate">
    Resets the conversation session. Starts a fresh chat with the agent.
  </Card>

  <Card title="/stop" icon="octagon-pause">
    Cancels the current agent task. Requires run control to be enabled.
  </Card>

  <Card title="/help" icon="circle-question">
    Lists all available commands (built-in + custom) with descriptions. Output is filtered to commands the caller is allowed to run.
  </Card>

  <Card title="/whoami" icon="user">
    Shows your user ID, role, and the commands you are allowed to run.
  </Card>

  <Card title="/model" icon="arrows-rotate">
    Switch the LLM model for this conversation (session-scoped).
  </Card>

  <Card title="/usage" icon="chart-column">
    Show token usage and estimated cost for this session.
  </Card>

  <Card title="/compress" icon="compress">
    Summarise older messages to free context window space.
  </Card>

  <Card title="/recap" icon="bookmark">
    Show a read-only "where were we" summary of the session. Never mutates history or triggers compaction.
  </Card>

  <Card title="/queue" icon="list-ol">
    Queue a follow-up message to run after the current task.
  </Card>

  <Card title="/tasks" icon="list-check">
    List background tasks (`/tasks`), inspect one (`/tasks <id>`), or cancel (`/tasks cancel <id>`). Per-user scoped.
  </Card>

  <Card title="/learn" icon="graduation-cap">
    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](/docs/features/learn-skill) and [Command Access Control](/docs/features/bot-command-access-control).
  </Card>

  <Card title="/undo" icon="rotate-left">
    Undo the last turn's file changes. Requires the agent to track changes (`autonomy` with `track_changes`).
  </Card>

  <Card title="/sessions" icon="folder-open">
    List the caller's saved sessions/checkpoints. Scoped to the requesting user — never enumerates other users' sessions.
  </Card>

  <Card title="/resume" icon="play">
    Switch to a saved session: `/resume &lt;id&gt;`. Use `/sessions` to list available IDs.
  </Card>

  <Card title="/retry" icon="rotate-right">
    Re-run your last message through the normal chat path.
  </Card>

  <Card title="/reasoning" icon="brain">
    Toggle whether extended-thinking output is shown in this conversation (per user).
  </Card>

  <Card title="/automations" icon="wand-magic-sparkles">
    List pending automation suggestions with inline Accept/Dismiss buttons. Telegram only today.
  </Card>

  <Card title="/blueprint" icon="file-code">
    Create an automation from a template: `/blueprint <name> [slot=value ...]`. Telegram only today.
  </Card>
</CardGroup>

<Note>
  Scheduled deliveries pick up their home channel from `~/.praisonai/state/home_channels.json`, the `{PLATFORM}_HOME_CHANNEL` env var, or an explicit `deliver:` target in YAML — no chat command is involved. See [Proactive Delivery](/docs/features/proactive-delivery) and [Scheduler Delivery](/docs/features/scheduler-delivery).
</Note>

## 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.

<Card title="Command Access Control" icon="shield-keyhole" href="/docs/features/bot-command-access-control">
  Admin users, per-command allowlists, and /whoami
</Card>

## Quick Start

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

  <Step title="Set Your Bot Token">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export TELEGRAM_BOT_TOKEN=your_token_here
    ```
  </Step>

  <Step title="Start the Bot">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai bot telegram --token $TELEGRAM_BOT_TOKEN
    ```
  </Step>

  <Step title="Send a Command">
    Open your bot chat and type:

    ```
    /status
    ```

    You'll see the agent name, model, platform, and uptime.
  </Step>
</Steps>

## Platform Examples

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

  ```bash Discord theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  praisonai bot discord --token $DISCORD_BOT_TOKEN
  ```

  ```bash Slack theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  praisonai bot slack --token $SLACK_BOT_TOKEN --app-token $SLACK_APP_TOKEN
  ```
</CodeGroup>

<Note>
  Voice notes are transcribed automatically — see [Voice Notes](/docs/features/gateway-voice-notes).
</Note>

## Command 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, whoami
```

A command outside your allowed list — or one that doesn't exist — falls through to the agent rather than vanishing. See [Unknown or Misspelled Commands](#unknown-or-misspelled-commands).

### /new

Resets the conversation:

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
flowchart LR
    User[User] -->|/new| Bot[Bot]
    Bot -->|Clear History| Agent[AI Agent]
    Agent -->|Fresh Start| User
    
    style User fill:#8B0000,color:#fff
    style Bot fill:#189AB4,color:#fff
    style Agent fill:#189AB4,color:#fff
```

* 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 `/model` override — so a "start fresh" also un-does a bad or unavailable model switch (a stuck `/model gpt-9-imaginary` no longer persists past `/new`)

<Tip>
  `/new` also clears any per-user `/model` override applied earlier in the session. If a `/model` switch left you on a broken or unavailable model, `/new` restores the agent's configured model on your next turn. See [`/model`](#model).
</Tip>

### /stop

Cancels the current agent task immediately:

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
flowchart LR
    User[User] -->|/stop| Bot[Bot]
    Bot -->|Request Cancel| RunControl[Run Control]
    RunControl -->|Interrupt| Agent[AI Agent]
    Agent -->|Task Cancelled| User
    
    style User fill:#8B0000,color:#fff
    style Bot fill:#189AB4,color:#fff
    style RunControl fill:#F59E0B,color:#fff
    style Agent fill:#189AB4,color:#fff
```

* Immediately interrupts the current agent processing
* Clears any pending messages in the queue
* Replies with confirmation that the task was cancelled
* Next message starts a fresh task

<Note>
  `/stop` works out of the box on any bot using the default `BotSessionManager` — Telegram, Discord, Slack, and WhatsApp all register it. Enabling [Bot Run Control](/docs/features/bot-run-control) with `busy_mode` adds mid-run interruption plus pending-message handling on top of basic cancellation.
</Note>

<Tip>
  When your bot routes through the gateway (Telegram, Discord, Slack, WhatsApp with `praisonai_bot`), `/stop` is handled server-side by the gateway's abort path introduced in [PraisonAI#3472](https://github.com/MervinPraison/PraisonAI/pull/3472) — it cancels the in-flight turn cooperatively without requiring `busy_mode`. The `busy_mode` path is still what enables pending-message handling and mid-run steering; `/stop` alone works with any gateway-routed bot. See [Gateway Abort and Per-Turn Timeout](/docs/features/gateway-abort-and-timeout).
</Tip>

<Note>
  Prior to [PraisonAI PR #4301](https://github.com/MervinPraison/PraisonAI/pull/4301) (release after 2026-08-24), `/stop` reported "✅ Current task cancelled" but did nothing on agents using the **default** OpenAI-native LLM path — `Agent(...)`, `Agent(llm="gpt-4o-mini")`, or any model string without a `/`. Remaining tool calls kept firing and the agent returned its normal final answer with no exception. The gateway/bot layers were correct; the token was dropped inside `chat_mixin` before it reached `OpenAIClient`. Upgrade to get real mid-run cancellation on the default path. Provider strings like `openai/gpt-4o-mini` (LiteLLM path) were already working.
</Note>

### /help

Lists all available commands — both built-in and custom — with their descriptions.

<Tip>
  Commands work the same way on all platforms — Telegram, Discord, Slack, and WhatsApp. The `/` prefix is the default command prefix. Typed a command that isn't listed? See [Unknown or Misspelled Commands](#unknown-or-misspelled-commands).
</Tip>

### /model

Switches the LLM model for your conversation, or reports which model is currently active.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
flowchart LR
    User[User] -->|/model gpt-4o| Bot[Bot]
    Bot -->|Per-user override| Session[Session]
    Session -->|Applied next turn| Agent[AI Agent]
    Agent -->|Reply with new model| User

    style User fill:#8B0000,color:#fff
    style Bot fill:#189AB4,color:#fff
    style Session fill:#F59E0B,color:#fff
    style Agent fill:#189AB4,color:#fff
```

**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):

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

<Note>
  The override is scoped to your conversation only — other users keep their own model. On each turn the original model is restored after your message is processed, so a shared agent never leaks one user's model to another concurrent user.
</Note>

<Note>
  The override lives on the session and is cleared by [`/new`](#new) (per-user) and by a whole-gateway `reset_all()` (all users). If a model you set with `/model` turns out to be unavailable and every following turn fails at the provider, `/new` is the honest remedy — it wipes the override alongside the history. The same clearing happens on an idle-triggered or scheduled [session reset](/docs/features/bot-session-reset).
</Note>

<Warning>
  **Code-skew guard.** After an in-place update (`git pull` / `pip install -U`), `/model` detects that the running gateway code differs from the installed code and replies with a "restart required" message instead of switching the model. This prevents stale-code model switches that could produce inconsistent behaviour.

  To disable the guard (e.g. in development):

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

  Power users can also call the guard helpers directly:

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

  fingerprint = read_code_fingerprint()
  skewed = detect_code_skew(fingerprint)
  ```
</Warning>

### /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
```

<Warning>
  The cost estimate uses a flat `$0.00002 / token` rate — it is **not** model-specific pricing. Treat it as a rough order-of-magnitude guide, not a billing figure.
</Warning>

### /compress

Compresses your conversation history to free context-window space.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
flowchart LR
    User[User] -->|/compress| Bot[Bot]
    Bot -->|SummarizeOptimizer| Context[Context Manager]
    Context -->|Summary + last 10| History[Compacted History]
    History -->|Tokens freed| User

    style User fill:#8B0000,color:#fff
    style Bot fill:#189AB4,color:#fff
    style Context fill:#F59E0B,color:#fff
    style History fill:#10B981,color:#fff
```

* 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}"`

<Tip>
  Run `/compress` before a long research task to reclaim context window space without losing your conversation thread.
</Tip>

<Note>
  `/compress` is destructive — it shrinks context. For a non-destructive "where were we" view that changes nothing, use [`/recap`](#recap) instead.
</Note>

### /recap

Renders a read-only "where were we" summary of the session without changing anything.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
flowchart LR
    User[User] -->|/recap| Bot[Bot]
    Bot -->|read history| Recap[build_recap]
    Recap -->|📌 summary + recent tail| User

    style User fill:#8B0000,color:#fff
    style Bot fill:#189AB4,color:#fff
    style Recap fill:#F59E0B,color:#fff
```

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 `/compress` has already produced a compaction summary, `/recap` shows 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 /recap
```

<Tip>
  Returning to a long session (a bot chat or `--continue`)? Run `/recap` first as a cheap re-entry aid — unlike `/compress` it costs nothing against your context window.
</Tip>

The same recap is available as a Python primitive when you build a custom surface:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Bot
    participant RunControl as Run Control
    participant Agent

    User->>Bot: /queue follow-up message
    Bot->>RunControl: submit(user_id, text)
    alt No active run
        RunControl-->>Bot: RUN_NOW
        Bot-->>User: "Send normally — nothing to queue behind"
    else Active run, merged into pending slot
        RunControl-->>Bot: MERGED
        Bot-->>User: "Added to pending follow-up"
        Note right of Agent: Current turn finishes first
        Agent->>Agent: Process queued message
    else Steered into running turn
        RunControl-->>Bot: STEERED
        Bot-->>User: "Injected as live guidance"
    end
```

**Check pending queue** — `/queue` with no argument:

```
📝 Pending follow-up: summarise the key findings
```

Or 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."`                                                            |

<Note>
  `/queue` requires [Bot Run Control](/docs/features/bot-run-control) to be enabled with `busy_mode`. Without it, the bot replies: `"ℹ️ Message queuing isn't enabled for this bot. Just send your message and it will be processed."`
</Note>

### /undo

Rewinds the last turn's file changes via `Agent.undo()`.

* Requires change tracking — enable `autonomy` with `track_changes` on the agent
* 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 `/sessions` first to see IDs
* Returns `"❌ Session {id} not found."` when the ID is unknown
* When the session manager lacks a `resume_session` primitive, 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.

| Command              | Purpose                                            |
| -------------------- | -------------------------------------------------- |
| `/tasks`             | List background tasks (id, name, status, progress) |
| `/tasks <id>`        | Show detail for one task (incl. result / error)    |
| `/tasks cancel <id>` | Cancel a running background task                   |

Example output:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
/tasks
🧩 Background tasks:
• aa1b2c… research — running (35%)
• ff9d3e… summary  — completed (100%)

Use /tasks <id> for detail or /tasks cancel <id>.
```

<Warning>
  **Per-user scoping.** `/tasks` only shows tasks whose `metadata["user_id"]` matches the caller. Tasks submitted from the CLI or REPL without a `user_id` are hidden from bot users. One user can never enumerate or cancel another user's work through `/tasks`.
</Warning>

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](/docs/features/background-tasks) — how tasks get submitted in the first place
* [Command Access Control](/docs/features/bot-command-access-control) — `/tasks` respects 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 grounded `SKILL.md` via `skill_manage`
* **Admin-only by default** when a `CommandAccessPolicy` is configured (`admin_users` or `user_allowed_commands` set) — it belongs to `PRIVILEGED_COMMANDS` because it reads host sources and alters future agent behaviour
* Available to all users when no policy is configured (backward-compatible)

<Card title="Learn Skill" icon="graduation-cap" href="/docs/features/agent-learn-skill">
  Full guide: sources, grounding rules, async API, bot usage
</Card>

### /automations

Lists pending automation suggestions, one message per suggestion, each with its own `✓ Accept` / `✕ Dismiss` inline keyboard. **Telegram only today.**

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Bot
    participant Engine as SuggestionEngine
    participant Scheduler as schedule_add

    User->>Bot: /automations
    Bot->>Engine: list pending
    Engine-->>Bot: suggestions
    Bot-->>User: 💡 header + Accept/Dismiss buttons
    User->>Bot: tap ✓ Accept (sug:accept:<id>)
    Bot->>Scheduler: schedule_add(...)
    Scheduler-->>User: ✅ Automation scheduled
```

* 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 carries `sug:dismiss:<id>`
* The `automations` permission is re-checked on every button tap. A denied user sees `⛔ You are not permitted to manage automations` appended 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:

1. `/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
```

2. `/blueprint <name>` → creates a job with blueprint defaults
3. `/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-review
```

<Note>
  `/automations` and `/blueprint` are generic names, so if your app registers a custom `@bot.on_command` handler of the same name, that handler wins and the built-in steps aside — unlike every other built-in, which always takes precedence. See [Automation Suggestions](/docs/features/automation-suggestions).
</Note>

### Unknown 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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Bot
    participant Registry as CommandRegistry
    participant Agent

    User->>Bot: /statu
    Bot->>Registry: match "statu"
    Registry-->>Bot: no match
    Note over Bot: Falls through to agent dispatch<br/>(matches Slack reference adapter)
    Bot->>Agent: session.chat(user_id, "/statu")
    Agent-->>Bot: reply
    Bot-->>User: reply
```

| Platform | Behavior on unknown `/command`                                                                      |
| -------- | --------------------------------------------------------------------------------------------------- |
| Telegram | Dispatched to agent as raw text (PR [#4320](https://github.com/MervinPraison/PraisonAI/pull/4320)). |
| Discord  | Dispatched to agent as raw text (PR [#4320](https://github.com/MervinPraison/PraisonAI/pull/4320)). |
| Slack    | Dispatched to agent as raw text (reference adapter — unchanged).                                    |

<Note>
  Prior to PraisonAI PR [#4320](https://github.com/MervinPraison/PraisonAI/pull/4320), unknown slash commands on Telegram and Discord were silent no-ops — indistinguishable from "the bot is down" or "my message didn't send". Slack was already correct; the fix aligns Telegram and Discord to that shape.
</Note>

<Tip>
  Users who type a made-up command still get an agent reply — often the agent notes it doesn't recognise the command and suggests `/help`. For stricter behavior, register a custom handler that returns a "did you mean" message.
</Tip>

***

## Custom Commands

Register your own commands using `register_command()`. Available on all bot adapters.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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, /help
```

### register\_command() API

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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](/docs/features/bot-slash-command-menu).

### list\_commands()

Get all registered commands:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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:

```markdown theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
---
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:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    File[📄 .praisonai/commands/*.md] --> Resolver[🔀 CustomCommandResolver]
    EP[📦 praisonai.bot_commands entry-points] --> Resolver
    Resolver --> Code[💻 praisonai code REPL/TUI]
    Resolver --> Bot[💬 Telegram / Slack / Discord]

    classDef source fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef proc fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef surface fill:#10B981,stroke:#7C90A0,color:#fff

    class File,EP source
    class Resolver proc
    class Code,Bot surface
```

The same `.praisonai/commands/*.md` files already documented for [Custom Agents & Commands](/docs/features/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.

<Tabs>
  <Tab title="YAML">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # 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 excluded
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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,
        ),
    )
    ```
  </Tab>

  <Tab title="Env">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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
    ```
  </Tab>
</Tabs>

| 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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Cmd["Command with !`cmd` in template"] --> D{Deployment: allow_shell?}
    D -->|false<br/>default| Inert[Render segment inert<br/>rest of command still runs]
    D -->|true| F{Command frontmatter:<br/>allow_shell?}
    F -->|false| Inert
    F -->|true| Exec[Execute shell substitution]

    classDef q fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef safe fill:#10B981,stroke:#7C90A0,color:#fff
    classDef exec fill:#8B0000,stroke:#7C90A0,color:#fff

    class D,F q
    class Inert safe
    class Exec exec
```

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.

<Warning>
  A shell-carrying command whose deployment forbids shell renders that segment **inert** rather than erroring. This safe-by-default degradation keeps the rest of the command working. See [Bot Shell Execution](/docs/features/bot-shell-execution) for the wider shell-approval model.
</Warning>

### 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.

```toml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# pyproject.toml
[project.entry-points."praisonai.bot_commands"]
mypkg = "mypkg.bot_commands:commands"
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# 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

1. A teammate authors `.praisonai/commands/deploy.md` with `description: "Run the deploy checklist"` and opens a PR.
2. After merge, everyone using `praisonai code` gets `/deploy` — no restart of code.
3. On the next bot restart, `/deploy` also appears in Telegram's native `/` menu with the same description, and running it in chat replies with the interpolated template through the normal `session.chat` path.
4. Same file. Two surfaces. No duplication.

<Note>
  File and entry-point commands pass through the same [Command Access Control](/docs/features/bot-command-access-control) policy as built-in and Python-registered commands — `admin_users` and `user_allowed_commands` gate them identically, and restricted commands stay hidden from the `/` menu for users who cannot run them.
</Note>

***

## 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.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 ignored
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant DisallowedUser as Disallowed User
    participant AllowedUser as Allowed User
    participant Bot as Bot
    participant SecurityPipeline as Security Pipeline
    participant Agent as Agent
    
    DisallowedUser->>Bot: /help
    Bot->>SecurityPipeline: Check user
    SecurityPipeline-->>Bot: User not allowed
    Note right of Bot: Silently dropped (no response)
    
    AllowedUser->>Bot: /help
    Bot->>SecurityPipeline: Check user
    SecurityPipeline-->>Bot: User allowed
    Bot->>Agent: Process command
    Agent-->>Bot: Help response
    Bot-->>AllowedUser: Help message
```

For full allowlist setup, see the [Bot Security](/docs/best-practices/bot-security) guide. This security hardening was implemented in [PR #1835](https://github.com/MervinPraison/PraisonAI/pull/1835).

***

## Python Usage

Start bots programmatically — built-in commands are always available:

<CodeGroup>
  ```python Telegram theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  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
  ```

  ```python Discord theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from 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())
  ```

  ```python Slack theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  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())
  ```

  ```python WhatsApp theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  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())
  ```
</CodeGroup>

## 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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Client as Telegram/Discord
    participant Bot as PraisonAI Bot
    participant Registry as CommandRegistry

    Note over Bot: Startup
    Bot->>Registry: build_command_menu_entries(user_id)
    Registry-->>Bot: [(name, description), ...]
    Bot->>Client: publish_command_menu (set_my_commands / tree.sync)

    Note over User: Chat
    User->>Client: types "/"
    Client-->>User: shows /status, /model, ...
    User->>Client: taps /status
    Client->>Bot: message "/status"
    Bot-->>Client: reply
    Client-->>User: reply
```

The bot publishes the menu automatically on startup — no user code required:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 automatically
```

Custom commands flow through the same registry, so they appear alongside the built-ins on the next restart:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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 / menu
```

### Design Guarantees

The menu is a discoverability layer that never changes how commands run.

<Info>
  **Additive.** The native menu sits on top of the existing text-command path. `on_message` still dispatches every command exactly as before — the menu only makes commands easier to find. A command it doesn't recognise falls through to the agent rather than being dropped. See [Unknown or Misspelled Commands](#unknown-or-misspelled-commands).
</Info>

<Tip>
  **Zero-config.** Menu publishing fires at startup: Telegram after the bot is running, Discord on `on_ready`. New custom commands appear on the next restart.
</Tip>

<Note>
  **Best-effort.** Publishing is wrapped in `try/except`. A missing SDK, network error, or unsupported platform is logged at `debug` and swallowed, so startup is never blocked.
</Note>

<Warning>
  **Policy-filtered.** When a `CommandAccessPolicy` is set, `build_command_menu_entries(user_id)` returns only the commands that user may run, so restricted commands stay hidden from users who cannot run them. See [Command Access Control](/docs/features/bot-command-access-control).
</Warning>

### 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.

<Note>
  **Discord shim caveat.** Registered Discord slash commands are thin shims. Tapping `/status` in Discord replies with `Type /status as a message to run this command.` — the text-command path remains the execution route. This is intentional so the change stays additive; the slash menu never runs the handler directly.
</Note>

## Best Practices

<AccordionGroup>
  <Accordion title="Restrict privileged commands">
    Configure `admin_users` and `user_allowed_commands` before enabling `/learn`, `/stop`, or `/queue` in production. See [Command Access Control](/docs/features/bot-command-access-control).
  </Accordion>

  <Accordion title="Enable run control for /stop and /queue">
    `/stop` and `/queue` need [Bot Run Control](/docs/features/bot-run-control) with `busy_mode` for mid-run cancellation and follow-up queuing — without it, users get informational fallbacks only.
  </Accordion>

  <Accordion title="Compress before long sessions">
    Run `/compress` when conversations exceed \~20 turns. It preserves recent context whilst reclaiming window space for research or coding tasks.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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](#file-based-and-entry-point-commands-in-bot-chats).
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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](/docs/docs/features/automation-suggestions#multi-tenant-isolation) and [Per-User Scheduler Isolation](/docs/features/scheduler-multi-tenant)). 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`.
  </Accordion>
</AccordionGroup>

<Note>
  Bot tokens should never be hardcoded. Use environment variables or a `.env` file to store them securely.
</Note>

## Related

<CardGroup cols={2}>
  <Card icon="robot" href="/docs/features/messaging-bots">
    Connect your agent to Telegram, Discord, Slack, and WhatsApp.
  </Card>

  <Card icon="puzzle-piece" href="/docs/features/bot-platform-capabilities">
    Understand per-platform message limits, editing, and rate-limit behaviour.
  </Card>

  <Card title="Slash Command Menu" icon="list" href="/docs/features/bot-slash-command-menu">
    Native `/` autocomplete for Telegram and Discord.
  </Card>

  <Card title="Command Access Control" icon="shield-keyhole" href="/docs/features/bot-command-access-control">
    Restrict who can run which commands.
  </Card>

  <Card icon="microphone" href="/docs/features/gateway-voice-notes">
    Transcribe inbound voice messages on Telegram, Slack, and WhatsApp.
  </Card>

  <Card title="Custom Agents & Commands" icon="folder-tree" href="/docs/features/custom-agents-commands">
    Author `.praisonai/commands/*.md` files once — they run in `praisonai code` and bot chats.
  </Card>

  <Card title="Bot Shell Execution" icon="terminal" href="/docs/features/bot-shell-execution">
    The double-opt-in shell rule and the wider shell-approval model.
  </Card>

  <Card icon="clock" href="/docs/features/background-tasks">
    How tasks get submitted, configured, and made durable — the substrate `/tasks` inspects.
  </Card>

  <Card title="Skill Automation" icon="clock" href="/docs/features/skill-automation">
    Skills that declare their own schedule and delivery target — install once, run themselves.
  </Card>
</CardGroup>
