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

# Messaging Gateway

> Chat with Mibyan from Telegram, Discord, Slack, WhatsApp, Signal, SMS, Email, Home Assistant, Mattermost, Matrix, DingTalk, Yuanbao, Microsoft Teams, LINE, Raft, Webhooks, or any OpenAI-compatible frontend via the API server — architecture and setup overview

Chat with Mibyan from Telegram, Discord, Slack, WhatsApp, Signal, SMS, Email, Home Assistant, Mattermost, Matrix, DingTalk, Feishu/Lark, WeCom, Weixin, BlueBubbles (iMessage), QQ, Yuanbao, Microsoft Teams, LINE, ntfy, or your browser. The gateway is a single background process that connects to all your configured platforms, handles sessions, runs cron jobs, and delivers voice messages.

For the full voice feature set — including CLI microphone mode, spoken replies in messaging, and Discord voice-channel conversations — see [Voice Mode](/desktop/user-guide/features/voice-mode) and [Use Voice Mode with Mibyan](/desktop/guides/use-voice-mode-with-mibyan).

<Tip>
  Bots need both a model provider and tool providers (TTS, web). A Nous Portal subscription bundles all of them.
</Tip>

## Messaging status in Desktop and the dashboard

Messaging status belongs to the selected profile on the selected machine. Credentials
saved by `mibyan gateway setup` can enable a credential-based platform without a
`platforms` entry in `config.yaml`; an explicit `platforms.<name>.enabled: false`
still disables it. A different profile never inherits the server process's credentials.
Platforms without required credential fields are not enabled merely because that list
is empty.

Naming the server's own profile explicitly (for example `profile=default` on a
default-profile server) gives the same status as an unscoped request. **Saved** means
credentials are stored, not that the messaging gateway is running or the platform is
connected. An enabled platform can correctly show **Messaging gateway stopped**.

## Platform Comparison

| Platform | Voice | Images | Files | Threads | Reactions | Typing | Streaming |
| - | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| Telegram | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ |
| Discord | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Slack | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Google Chat | — | ✅ | ✅ | ✅ | — | ✅ | — |
| WhatsApp | — | ✅ | ✅ | — | — | ✅ | ✅ |
| WhatsApp Cloud API | ✅ | ✅ | ✅ | — | — | ✅ | — |
| Signal | — | ✅ | ✅ | — | — | ✅ | — |
| SMS | — | — | — | — | — | — | — |
| Email | — | ✅ | ✅ | ✅ | — | — | — |
| Home Assistant | — | — | — | — | — | — | — |
| Mattermost | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ |
| Matrix | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| DingTalk | — | ✅ | ✅ | — | ✅ | — | ✅ |
| Feishu/Lark | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| WeCom | ✅ | ✅ | ✅ | — | — | — | — |
| WeCom Callback | — | — | — | — | — | — | — |
| Weixin | ✅ | ✅ | ✅ | — | — | ✅ | — |
| BlueBubbles | — | ✅ | ✅ | — | ✅ | ✅ | — |
| Photon (iMessage) | ✅ | ✅ | ✅ | — | ✅ | ✅ | — |
| QQ | ✅ | ✅ | ✅ | — | — | ✅ | — |
| Yuanbao | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
| Microsoft Teams | — | ✅ | — | ✅ | — | ✅ | — |
| LINE | — | ✅ | ✅ | — | — | ✅ | — |
| ntfy | — | — | — | — | — | — | — |
| Raft | — | — | — | — | — | — | — |
| IRC | — | — | — | — | — | — | — |
| Buzz | — | ✅ | — | ✅ | — | — | — |
| SimpleX | ✅ | ✅ | ✅ | — | — | ✅ | — |

**Voice** = TTS audio replies and/or voice message transcription. **Images** = send/receive images. **Files** = send/receive file attachments. **Threads** = threaded conversations. **Reactions** = emoji reactions on messages. **Typing** = typing indicator while processing. **Streaming** = progressive message updates via editing.

<Note>
  **Mibyan Relay**

  [Mibyan Relay](/desktop/user-guide/messaging/relay) (experimental) is not a chat platform itself — it is a connector system that fronts platforms like Discord, Telegram, Slack, and WhatsApp through an external connector that owns the platform credentials. Capabilities (media, native approval/clarify prompts, reactions, threads, typing, streaming) are negotiated per connector at handshake rather than fixed in the table above.
</Note>

## Architecture

```mermaid theme={null}
flowchart TB
    subgraph Gateway["Mibyan Gateway"]
        subgraph Adapters["Platform adapters"]
            tg[Telegram]
            dc[Discord]
            wa[WhatsApp]
            sl[Slack]
            gc[Google Chat]
            sig[Signal]
            sms[SMS]
            em[Email]
            ha[Home Assistant]
            mm[Mattermost]
            mx[Matrix]
            dt[DingTalk]
    fs[Feishu/Lark]
    wc[WeCom]
    wcb[WeCom Callback]
    wx[Weixin]
    bb[BlueBubbles]
    qq[QQ]
    yb[Yuanbao]
    ms[Microsoft Teams]
    api["API Server<br/>(OpenAI-compatible)"]
    wh[Webhooks]
        end

        store["Session store<br/>per chat"]
        agent["AIAgent<br/>run_agent.py"]
        cron["Cron scheduler<br/>ticks every 60s"]
    end

    tg --> store
    dc --> store
    wa --> store
    sl --> store
    gc --> store
    sig --> store
    sms --> store
    em --> store
    ha --> store
    mm --> store
    mx --> store
    dt --> store
    fs --> store
    wc --> store
    wcb --> store
    wx --> store
    bb --> store
    qq --> store
    yb --> store
    ms --> store
    api --> store
    wh --> store
    store --> agent
    cron --> store
```

Each platform adapter receives messages, routes them through a per-chat session store, and dispatches them to the AIAgent for processing. The gateway also runs the cron scheduler, ticking every 60 seconds to execute any due jobs.

## Intentional Silence Tokens

For group chats, hooks, and automation flows, Mibyan supports explicit silence tokens. If the agent's final response is exactly one supported token, the gateway suppresses outbound delivery and sends nothing to the chat.

Supported tokens:

* `[SILENT]`
* `SILENT`
* `NO_REPLY`
* `NO REPLY`
* `[静默]` / `静默` and `[沉默]` / `沉默` — the Chinese renderings a model produces when it translates the sentinel instead of emitting it literally

Whitespace and case are normalized, but the whole final response must be the token. A sentence like "Use `[SILENT]` when nothing changed" is delivered normally.

Silence is a delivery decision only. Mibyan keeps the assistant silence turn in the session transcript, so the conversation still alternates normally:

```text theme={null}
user: side-channel chatter
assistant: [SILENT]   # stored, not delivered
user: next message
```

Failed turns still surface as errors; Mibyan does not hide failures just because the text resembles a silence token.

On a message from a person, a bare silence token is replaced by a short notice, because a message that needed a reply must not vanish. Internal wakes such as background-process notifications may stay silent, and so may a message the platform adapter reports as not addressed to the bot. Slack reports this for messages that open by @mentioning someone else and for unmentioned top-level messages that start a new thread in a free-response channel; other platforms always get the notice.

## Quick Setup

The easiest way to configure messaging platforms is the interactive wizard:

```bash theme={null}
mibyan gateway setup        # Interactive setup for all messaging platforms
```

This walks you through configuring each platform with arrow-key selection, shows which platforms are already configured, and offers to start/restart the gateway when done.

## Gateway Commands

```bash theme={null}
mibyan gateway              # Run in foreground
mibyan gateway setup        # Configure messaging platforms interactively
mibyan gateway install      # Install as a user service (Linux) / launchd service (macOS)
sudo mibyan gateway install --system   # Linux only: install a boot-time system service
mibyan gateway start        # Start the default service
mibyan gateway stop         # Stop the default service
mibyan gateway status       # Check default service status
mibyan gateway status --system         # Linux only: inspect the system service explicitly
```

### Stack dump on demand (`SIGUSR2`)

On Linux and macOS, `kill -USR2 <gateway pid>` appends a dump of every thread's
stack to `~/.mibyan/logs/gateway_faulthandler.log` and the gateway keeps
running — use it to see what a stalled or misbehaving gateway is doing without
restarting it.

### Built-in event-loop liveness watchdog

On every platform the gateway runs an out-of-loop watchdog thread that probes
the asyncio loop (`gateway.loop_watchdog_probe_interval_s`, default 30 s). When
the loop stops dispatching for `gateway.loop_watchdog_max_strikes` consecutive
probes (default 3), housekeeping, the cron scheduler and the embedded kanban
dispatcher have all frozen with it, so the watchdog dumps every thread's stack
to the log, stamps `gateway_state.json` with `gateway_state: degraded` and
`exit_reason: loop_liveness_watchdog`, and exits with code `75` so the service
supervisor restarts the process. `mibyan gateway status` renders that record as
`⚠ Gateway exited degraded: event loop stopped dispatching …` until a new
gateway process overwrites it, and the dashboard's gateway badge shows
**Degraded** with the same reason. Set `gateway.loop_watchdog: false` in
`config.yaml` to disable the watchdog.

Housekeeping also re-stamps `gateway_state.json`'s `updated_at` every tick
(60 s), so it doubles as a heartbeat: when the process is still alive but that
stamp is more than 120 s old, `mibyan gateway status` prints
`⚠ Gateway heartbeat stale: housekeeping has not refreshed gateway_state.json
for N s …` and the dashboard badge reads **Heartbeat stale** — the "looks
running but nothing is scheduled" case. Restart the gateway.

### Optional Linux event-loop watchdog

A systemd-managed gateway can opt into process recovery when Python's asyncio
event loop stops receiving scheduling time. This covers whole-process stalls
that also prevent platform-specific liveness tasks from running:

```yaml title="~/.mibyan/config.yaml" theme={null}
gateway:
  systemd_watchdog_seconds: 120
```

Regenerate the service unit after changing this setting:

```bash theme={null}
mibyan gateway install --force
```

A positive value makes the generated unit use `Type=notify`,
`NotifyAccess=main`, and the matching `WatchdogSec`. Mibyan sends heartbeats
only while its event loop is making timely progress; systemd restarts the
process when they stop. The default `0` keeps the existing `Type=simple`
behavior. This setting is Linux/systemd-only and does not treat an ordinary
platform network disconnect as an event-loop failure.

## Chat Commands (Inside Messaging)

| Command | Description |
| - | - |
| `/new` or `/reset` | Start a fresh conversation |
| `/model [provider:model]` | Show or change the model (supports `provider:model` syntax) |
| `/personality [name]` | Set a personality (`none` to reset) |
| `/retry` | Retry the last message |
| `/undo` | Remove the last exchange |
| `/status` | Show session info |
| `/whoami` | Show your slash command access on this scope (admin / user / unrestricted) |
| `/stop` | Stop the running agent |
| `/approve` | Approve a pending dangerous command |
| `/deny` | Reject a pending dangerous command |
| `/sethome` | Set this chat as the home channel |
| `/compress` | Manually compress conversation context |
| `/title [name]` | Set or show the session title |
| `/resume [name]` | Resume a previously named session |
| `/sessions [all] [search <query>]` | List previous sessions; `search <query>` filters by title or id |
| `/usage` | Show token usage for this session (`/usage reset [--force]` redeems a banked Codex limit reset) |
| `/insights [days]` | Show usage insights and analytics |
| `/reasoning [level\|show\|hide]` | Change reasoning effort or toggle reasoning display |
| `/voice [on\|off\|tts\|join\|leave\|status]` | Control messaging voice replies and Discord voice-channel behavior |
| `/rollback [number]` | List or restore filesystem checkpoints |
| `/bg <prompt>` | Run a prompt in a separate background session |
| `/btw <question>` | Ask a side question about the current conversation without interrupting it |
| `/reload-mcp` | Reload MCP servers from config |
| `/update` | Update Mibyan to the latest version |
| `/help` | Show available commands |
| `/<skill-name>` | Invoke any installed skill |

## Session Management

### Session Persistence

Sessions persist across messages until they reset. The agent remembers your conversation context.

### Finding Past Sessions (`/sessions`)

`/sessions` lists your previous sessions for the current chat — including the one you're in now, marked `(current)` — and `/sessions <name>` resumes one (shorthand for `/resume`). When the list grows long, `/sessions search <query>` (alias `find`) filters by title or session-id match, ordered by most recently active. Cross-origin listing with `/sessions all` is admin-only — regular users get a notice explaining the list stayed chat-scoped, and only ever see sessions from their own chat origin.

### Persistent `/model` Overrides

A `/model` switch in a gateway chat applies to that session and now **survives gateway restarts**: the model/provider choice is persisted to the session store and rehydrated on first use after a restart (credentials are re-resolved at load time and never written to disk). `/new` (or `/reset`) clears the override, and `/model <name> --global` writes it through to `config.yaml` instead. `/model <name> --once` applies for a single turn only.

### Delivery Reliability

Final agent responses are recorded in a durable **delivery ledger**
(`state.db`) around each platform send. If the gateway crashes or restarts
between producing a response and the platform confirming receipt, the next
boot redelivers the stored response instead of losing it — or re-running the
whole turn. The ledger lives in the home the gateway was started from; a
multiplexed gateway keeps every served profile's replies there too.

Semantics are honest at-least-once:

* A response whose send **never started** is redelivered as-is.
* A response that was **mid-send** when the gateway died (the platform may or
  may not have received it), including a redelivery an earlier boot was
  still sending, is redelivered with a visible
  "♻️ Recovered reply — … may be a duplicate" prefix. Ambiguity is labeled,
  never silently resent.
* A final send refused by **flood control** (such as Telegram rate limits) is retried automatically
  after the recorded penalty expires, without requiring a reconnect or restart.
  A restart during the penalty adopts the stored reply without spending a retry
  attempt or re-running the agent. Retries retain the original bot profile, chat
  and thread. A rate-limit recovery prefix warns that earlier chunks may already
  have arrived; the ledger cannot infer partial delivery from message length.
* Any other rejected final send (a platform 5xx, an unclassified error) is retried the same
  way after a growing backoff (30 s, then 2 min); the last budgeted attempt is left for the
  next gateway start, so an outage that outlasts the timer never strands the reply. A
  permanently unreachable chat (blocked bot, deleted group) is not retried.
* Redelivery is bounded: 3 attempts, 24-hour freshness, then the row is
  abandoned. Delivered rows are pruned after 7 days.

Disable with `gateway.delivery_ledger: false` in `config.yaml` (restores the
old behavior: in-flight responses are lost on crash).

### Session continuity

Gateway conversations do not reset after inactivity or at a daily boundary. Use `/new`
or `/reset` for an explicit new conversation; context compression remains automatic.
Core ignores legacy `session_reset` settings, reset-policy overrides and reset-timer
environment variables. If your config still sets `session_reset.mode` to `idle`, `daily`
or `both`, gateway startup and `mibyan doctor` warn about it. To keep time-based resets,
install the catalog plugin that reads the same block unchanged:
`mibyan plugins install mibyan-session-reset-policy`. Cached agents may be released to reclaim resources without
replacing the durable conversation. Restart-recovery freshness limits automatic
continuation, not the history loaded when you send a message.

## Per-Channel Model & System Prompt Overrides

Different channels can run different models and personas from a **single gateway** — e.g. a cheap fast model in `#daily` and a frontier model with a specialist prompt in `#dev`. Configure `channel_overrides` under the platform in `~/.mibyan/config.yaml`:

```yaml theme={null}
platforms:
  discord:
    enabled: true
    channel_overrides:
      "123456789012345678":        # channel/thread id
        model: anthropic/claude-sonnet-4.6
        provider: anthropic
        system_prompt: "You are the #dev channel code-review specialist."
      "987654321098765432":
        model: openai/gpt-5-mini
```

Details:

* All three keys are optional — set only `model`, only `system_prompt`, or any combination. Unset fields fall back to the global defaults.
* Lookup order is exact channel/thread id first, then the **parent** channel/forum id — so Discord threads inherit their parent channel's override automatically.
* Resolution priority for the model is: session `/model` override → `channel_overrides` → global config. A user running `/model` in a chat still wins over the channel default.
* The `system_prompt` override replaces the global gateway prompt for that channel (it is ephemeral — injected per turn, not stored in history).

## Security

**By default, the gateway denies all users who are not in an allowlist or paired via DM.** This is the safe default for a bot with terminal access.

```bash theme={null}
# Restrict to specific users (recommended):
TELEGRAM_ALLOWED_USERS=123456789,987654321
DISCORD_ALLOWED_USERS=123456789012345678
SIGNAL_ALLOWED_USERS=+155****4567,+155****6543
SMS_ALLOWED_USERS=+155****4567,+155****6543
EMAIL_ALLOWED_USERS=trusted@example.com,colleague@work.com
MATTERMOST_ALLOWED_USERS=3uo8dkh1p7g1mfk49ear5fzs5c
MATRIX_ALLOWED_USERS=@alice:matrix.org
DINGTALK_ALLOWED_USERS=user-id-1
FEISHU_ALLOWED_USERS=ou_xxxxxxxx,ou_yyyyyyyy
WECOM_ALLOWED_USERS=user-id-1,user-id-2
WECOM_CALLBACK_ALLOWED_USERS=user-id-1,user-id-2
TEAMS_ALLOWED_USERS=aad-object-id-1,aad-object-id-2

# Or allow
GATEWAY_ALLOWED_USERS=123456789,987654321

# Or explicitly allow all users (NOT recommended for bots with terminal access):
GATEWAY_ALLOW_ALL_USERS=true
```

### DM Pairing (Alternative to Allowlists)

Instead of manually configuring user IDs, unknown users receive a one-time pairing code when they DM the bot. Email is the exception: unknown email senders are ignored unless email pairing is explicitly enabled.

```bash theme={null}
# The user sees: "Pairing code: XKGH5N7P"
# You approve them with:
mibyan pairing approve telegram XKGH5N7P

# Other pairing commands:
mibyan pairing list          # View pending + approved users
mibyan pairing revoke telegram 123456789  # Remove access
```

Pairing codes expire after 1 hour, are rate-limited, and use cryptographic randomness.

### Admins vs Regular Users

Allowlists answer "can this person reach the bot at all?" The **admin / user split** answers "now that they're in, what are they allowed to do?"

Every allowed user falls into one of two tiers per scope (DM vs group/channel):

* **Admin** — full access. Can run every registered slash command (built-in + plugin) and use every gated capability.
* **Regular user** — restricted access. Can chat with the agent normally, but can only run the slash commands you explicitly enable. The always-allowed floor is `/help` and `/whoami`.

The tiers are configured per platform and per scope. DM admin status does not imply group/channel admin status — each scope has its own admin list.

**What the tiers gate today:** slash commands. The split runs through the live command registry, so it covers built-ins and plugin-registered commands without per-feature wiring. Plain chat is not affected — non-admins can still talk to the agent.

**What may be gated in the future:** more capability surfaces (tool access, model switching, expensive operations) will hang off the same admin / user distinction as we add them. Configuring the split now means those future restrictions land cleanly without you having to re-model who's an admin.

#### Configuration

```yaml theme={null}
gateway:
  platforms:
    discord:
      extra:
        allow_from: ["111", "222", "333"]
        allow_admin_from: ["111"]                    # admins → all slash commands
        user_allowed_commands: [status, model]       # what non-admins may run
        # Optional: separate group/channel scope
        group_allow_admin_from: ["111"]
        group_user_allowed_commands: [status]
```

**Backward compat:** if `allow_admin_from` is not set for a scope, the tier split is disabled for that scope and every allowed user has full access. Existing installs keep working with no changes — opt in when you want the distinction.

#### Inspecting your access

Use `/whoami` from any platform to see the active scope, your tier (admin / user / unrestricted), and which slash commands you can run. When an admin list is configured, `/help` and `/commands` show a non-admin only the commands they can actually run (`/help`, `/whoami`, plus `user_allowed_commands`); admins see the full catalog. See the [Telegram](/desktop/user-guide/messaging/telegram#slash-command-access-control) and [Discord](/desktop/user-guide/messaging/discord#slash-command-access-control) pages for platform-specific examples.

## Redirecting the Agent

Send a message while the agent is working to correct the active turn:

* **Model generation restarts with context** — reasoning already shown and visible partial text are retained as an ordinary assistant checkpoint
* **Completed work stays available** — prior tool calls and results remain in the turn
* **Running tools finish safely** — the correction is applied at the next tool-result boundary instead of killing the tool
* **`/stop` remains a hard stop** — use it to cancel the active turn and foreground work

### Queue vs interrupt vs steer (busy-input mode)

By default, messaging a busy agent redirects its active turn (a running foreground terminal command is moved to the background rather than killed, so your message is read immediately). Two other modes are available:

* `queue` — follow-up messages wait and run as the next turn after the current task finishes. Each follow-up (text, voice note, video, document) gets its own turn in arrival order; only a rapid photo burst is merged into one album turn.
* `steer` — follow-up messages are injected into the current run via `/steer`, arriving at the agent after the next tool call. No interrupt, no new turn. Falls back to `queue` behavior if the agent hasn't started yet.

Gateway steers (including explicit `/steer`) and active-turn redirects carry the requesting event's available platform, chat, thread, sender, message, profile, and scope identifiers as per-message JSON context. With `privacy.redact_pii: true`, identifiers in this model-visible context are hashed on supported platforms, including alternate and parent identifiers; the original event identifiers remain internal for routing. Otherwise identifiers are preserved exactly. Neither mode changes the session's system prompt or chooses a fallback reply destination. The context is routing data, not authorization or a guarantee of automatic delivery.

```yaml theme={null}
display:
  busy_input_mode: steer   # or queue, or interrupt (default)
  busy_ack_enabled: true   # set to false to suppress the ⚡/⏳/⏩ chat reply entirely
  busy_text_debounce_seconds: 0.35   # quiet window before merged busy text is delivered
  busy_text_hard_cap_seconds: 1.0    # never hold merged busy text longer than this
```

All four keys are read from each profile's own `config.yaml`, so multiplexed profiles keep independent busy policies; there is no process-environment override.

The first time you message a busy agent on any platform, Mibyan appends a one-line reminder to the busy-ack explaining the knob (`"💡 First-time tip — …"`). The reminder fires once per install — a flag under `onboarding.seen.busy_input_prompt` latches it. Delete that key to see the tip again.

If you find the busy acknowledgment noisy, set `display.busy_ack_enabled: false`. Input handling is unchanged; only the confirmation message is hidden.

## Clarify Questions (Multi-Select)

When the agent uses the `clarify` tool to ask you a question, the gateway renders the choices as a numbered prompt (or native buttons on platforms that support them). Clarify supports **multi-select** questions too — the agent can let you pick several options at once:

* **Messaging platforms** — the prompt says "Multiple selections allowed"; reply with the numbers separated by commas or spaces (e.g. `1, 3`), the option text, or your own free-form answer.
* **Classic CLI / TUI** — multi-select renders as checkboxes: **Space** toggles an option, **Enter** submits the selection.

Single-select prompts behave as before: pick one option by number, button, or text, or type your own answer via the "Other" path.

## Tool Progress Notifications

Control how much tool activity is displayed in `~/.mibyan/config.yaml`:

```yaml theme={null}
display:
  tool_progress: all    # off | new | all | verbose | log
  tool_progress_command: false  # set to true to enable /verbose in messaging
  # How progress is grouped on platforms that support message editing:
  #   accumulate (default) — edit one bubble in place as tools run
  #   separate             — send one message per tool (pre-v0.9 style; noisier)
  # Only applies where tool_progress is already enabled.
  tool_progress_grouping: accumulate   # accumulate | separate
```

### `log` mode — audit file instead of chat messages

Setting `display.tool_progress: log` sends **no** progress bubbles to chat. Instead, each tool call is appended as a line to `~/.mibyan/logs/tool_calls.log` — a rotating audit file (5 MB × 3 backups) run through the same secret-redacting formatter as regular logs, so credentials never land on disk. Use it when you want a full tool-call trail without any chat noise.

### Configurable status phrases

Long-running gateway status lines ("still working…"-style heartbeats) draw from a phrase catalog. Built-in defaults ship in `gateway/assets/status_phrases.yaml`; you can add your own with profile-portable files under `mibyan_HOME`:

* `~/.mibyan/status_phrases.yaml` or any `*.yaml` in `~/.mibyan/status_phrases/` (conventional paths, auto-loaded), or
* point config at a relative path:

```yaml theme={null}
display:
  status_phrases:
    path: status_phrases/whatsapp.yaml  # relative to mibyan_HOME
    mode: append                        # append (default) or replace
```

Phrase files map a surface (`status`, `generic`) to a list of strings (max 80 phrases per surface, 160 chars each). Absolute paths and `..` escapes are ignored so config stays profile-portable. Only your configured phrase strings are used — raw tool arguments, commands, and reasoning text are never interpolated into a status phrase.

### Message timestamps in model context

Off by default. When enabled, Mibyan prepends a human-readable timestamp
(e.g. `[Tue 2026-04-28 13:40:53 CEST]`) onto each **user** message *in the
model's context* so the agent knows when messages were sent — useful for
temporal reasoning ("you asked this morning…", noticing a long gap). It is
**not** added to assistant messages or the system prompt.

```yaml theme={null}
gateway:
  message_timestamps:
    enabled: false   # set true to show send-times to the model
```

Persisted transcripts always stay clean — the timestamp is stored as message
metadata regardless of this toggle, so enabling it later also surfaces
send-times for past messages, and replay never accumulates duplicate prefixes.

When enabled, the bot sends status messages as it works:

```text theme={null}
💻 `ls -la`...
🔍 web_search...
📄 web_extract...
🐍 execute_code...
```

## Background Sessions

Run a prompt in a separate background session so the agent works on it independently while your main chat stays responsive:

```
/bg Check all servers in the cluster and report any that are down
```

Mibyan confirms immediately:

```
🔄 Background task started: "Check all servers in the cluster..."
   Task ID: bg_143022_a1b2c3
```

### How It Works

Each `/bg` prompt spawns a **separate agent instance** that runs asynchronously:

* **Isolated session** — the background agent has its own session with its own conversation history. It has no knowledge of your current chat context and receives only the prompt you provide.
* **Same configuration** — inherits your model, provider, toolsets, reasoning settings, and provider routing from the current gateway setup.
* **Non-blocking** — your main chat stays fully interactive. Send messages, run other commands, or start more background tasks while it works.
* **Result delivery** — when the task finishes, the result is sent back to the **same chat or channel** where you issued the command, prefixed with "✅ Background task complete". If it fails, you'll see "❌ Background task failed" with the error.

### Background Process Notifications

When the agent running a background session uses `terminal(background=true)` to start long-running processes (servers, builds, etc.), the gateway can push status updates to your chat. Control this with `display.background_process_notifications` in `~/.mibyan/config.yaml`:

```yaml theme={null}
display:
  background_process_notifications: concise    # concise | all | result | error | off
```

| Mode | What you receive |
| - | - |
| `concise` | One-line status message on completion; failures append a short output tail (default) |
| `all` | Running-output updates **and** the final status message with the output tail |
| `result` | Only the final status message with the output tail (regardless of exit code) |
| `error` | Only the final status message with the output tail when the exit code is non-zero |
| `off` | No process watcher messages at all. Also honored by the CLI, TUI and Desktop: background-process completions and heartbeats no longer wake the agent (subagent results still do) |

You can also set this via environment variable:

```bash theme={null}
mibyan_BACKGROUND_NOTIFICATIONS=result
```

With `terminal(background=true, notify_on_complete=true)` the finished process starts a new agent turn and the agent reports the result itself, so no separate status line is sent. The exception is a process that finishes while the turn that launched it is still running: the completion is queued as the agent's next turn and you get the one-line `concise` status right away (unless the mode is `off`, or `error` with a zero exit code), instead of silence until that turn ends.

### Use Cases

* **Server monitoring** — "/bg Check the health of all services and alert me if anything is down"
* **Long builds** — "/bg Build and deploy the staging environment" while you continue chatting
* **Research tasks** — "/bg Research competitor pricing and summarize in a table"
* **File operations** — "/bg Organize the photos in \~/Downloads by date into folders"

<Tip>
  Background tasks on messaging platforms are fire-and-forget — you don't need to wait or check on them. Results arrive in the same chat automatically when the task finishes.
</Tip>

## Service Management

### Linux (systemd)

```bash theme={null}
mibyan gateway install               # Install as user service
mibyan gateway start                 # Start the service
mibyan gateway stop                  # Stop the service
mibyan gateway status                # Check status
journalctl --user -u mibyan-gateway -f  # View logs

# Enable lingering (keeps running after logout)
sudo loginctl enable-linger $USER

# Or install a boot-time system service that still runs as your user
sudo mibyan gateway install --system
sudo mibyan gateway start --system
sudo mibyan gateway status --system
journalctl -u mibyan-gateway -f
```

Use the user service on laptops and dev boxes. Use the system service on VPS or headless hosts that should come back at boot without relying on systemd linger.

<Warning>
  **Don't add a custom `ExecStopPost` kill drop-in**

  The unit Mibyan installs already shuts the gateway down cleanly with `KillMode=mixed` + `KillSignal=SIGTERM`, and uses `Restart=always` with `RestartForceExitStatus` so updates and `/restart` respawn correctly. Do **not** add a systemd drop-in such as `ExecStopPost=/bin/kill -9 $MAINPID` — `ExecStopPost` fires on *every* stop, including clean restarts, so it `SIGKILL`s the freshly spawned instance before it stabilizes and `Restart=always` immediately respawns it. The result is an infinite restart loop (and, on Telegram, a flood of restart messages). If you've added such a drop-in, remove it: `systemctl --user edit mibyan-gateway` (or `sudo systemctl edit mibyan-gateway` for a system service) and delete the `ExecStopPost` line, then `systemctl --user daemon-reload`.
</Warning>

### Direct `systemctl restart` / `stop` exits cleanly

The installed unit declares `ExecStop=` to record a planned-stop marker for `$MAINPID` before `SIGTERM` is delivered, so stopping or restarting the service directly is classified as intentional: the gateway drains, persists `gateway_state=stopped`, and exits `0` — the journal shows a clean stop/start with no `Failed with result exit-code` line.

```bash theme={null}
systemctl --user restart mibyan-gateway   # or: sudo systemctl restart mibyan-gateway
```

Prefer `mibyan gateway restart` when in-flight agent turns matter: it asks the gateway to drain first (`SIGUSR1`, honoring the restart wait budget) and waits for the replacement, while a raw `systemctl restart` stops the current process on systemd's schedule. After updating Mibyan, run `mibyan gateway restart` once so the running service picks up the regenerated unit that contains the `ExecStop=` line (`mibyan gateway status` warns while the installed unit is outdated).

The installed unit also maps `systemctl reload mibyan-gateway` to `SIGUSR1`. For Mibyan, `reload` therefore means a graceful drain, process exit, and supervisor relaunch; it is **not** an in-process configuration reload. Use `mibyan gateway restart` when you want the CLI to wait for and verify the replacement process.

<Tip>
  **Headless VMs: user service + linger avoids root prompts**

  A system service needs root for every restart — including the automatic gateway restart at the end of `mibyan update`. When `mibyan update` runs as a non-root user, it tries passwordless `sudo systemctl`; if that's unavailable, it skips the restart and prints the manual `sudo systemctl restart mibyan-gateway` command (it never blocks on an interactive password prompt).

  For a headless VM you never log into, a **user** service with lingering enabled gives you the same start-at-boot behavior with zero root involvement:

  ```bash theme={null}
  mibyan gateway install          # user service
  sudo loginctl enable-linger $USER   # one-time: start at boot, survive logout
  ```

  After that, `mibyan update` can restart the gateway without any privileges. If you prefer to keep the system service, either run updates with `sudo mibyan update`, or grant the service account passwordless sudo for systemctl, e.g. in `sudo visudo -f /etc/sudoers.d/mibyan-gateway`:

  ```
  mibyan ALL=(root) NOPASSWD: /usr/bin/systemctl --no-ask-password reset-failed mibyan-gateway*, /usr/bin/systemctl --no-ask-password start mibyan-gateway*, /usr/bin/systemctl --no-ask-password restart mibyan-gateway*
  ```
</Tip>

Avoid keeping both the user and system gateway units installed at once unless you really mean to. Mibyan will warn if it detects both because start/stop/status behavior gets ambiguous.

<Note>
  **Inside a container, only the system scope is offered**

  `mibyan gateway install` (and the `mibyan gateway setup` wizard) refuse to install a **user** service when Mibyan detects it is running inside a container. A user unit lands in `~/.config/systemd/user`, and when that home is bind-mounted from the host (podman/distrobox), the host's own `systemd --user` enables and starts the same unit — a second gateway polling the same bot token. Run the gateway as the container's main process (`mibyan gateway run`, with a container restart policy), or in a systemd container (systemd as PID 1) install the isolated system scope: `sudo mibyan gateway install --system --run-as-user <user>`.
</Note>

<Info>
  **Multiple installations**

  If you run multiple Mibyan installations on the same machine (with different `mibyan_HOME` directories), each gets its own systemd service name. The default `~/.mibyan` uses `mibyan-gateway`; other installations use `mibyan-gateway-<hash>`. The `mibyan gateway` commands automatically target the correct service for your current `mibyan_HOME`.
</Info>

### macOS (launchd)

```bash theme={null}
mibyan gateway install               # Install as launchd agent
mibyan gateway start                 # Start the service
mibyan gateway stop                  # Stop the service
mibyan gateway status                # Check status
tail -f ~/.mibyan/logs/gateway.log   # View logs
```

The generated plist lives at `~/Library/LaunchAgents/ai.mibyan.gateway.plist`. It includes three environment variables:

* **PATH** — your full shell PATH at install time, with the venv `bin/` and `node_modules/.bin` prepended. This ensures user-installed tools (Node.js, ffmpeg, etc.) are available to gateway subprocesses like the WhatsApp bridge.
* **VIRTUAL\_ENV** — points to the Python virtualenv so tools can resolve packages correctly.
* **mibyan\_HOME** — scopes the gateway to your Mibyan installation.

<Tip>
  **PATH changes after install**

  launchd plists are static — if you install new tools (e.g. a new Node.js version via nvm, or ffmpeg via Homebrew) after setting up the gateway, run `mibyan gateway install` again to capture the updated PATH. The gateway will detect the stale plist and reload automatically.
</Tip>

<Info>
  **Installing without starting**

  The plist sets `RunAtLoad`, so loading it starts the gateway. `mibyan gateway install --no-start-now`, like answering No to "Start the gateway now?" in `mibyan gateway setup`, writes the plist without loading it: the gateway starts at your next login, or when you run `mibyan gateway start`. A gateway that launchd is already running is reloaded onto the new plist, not stopped.
</Info>

<Info>
  **Local Network access (LAN devices fail with "No route to host")**

  macOS Local Network Privacy attributes a socket to the executable launchd spawned for the job. A bare venv Python has no application identity, so a launchd-run gateway could not reach LAN hosts (Home Assistant, local model servers) — every connect failed with `errno 65 No route to host` while the same URL worked from Terminal, and no prompt was ever shown to grant it. The generated plist therefore runs the gateway through `/usr/bin/osascript`; a JXA `system()` call starts the gateway without an interactive event-polling loop, and macOS treats its children as osascript's own — an Apple platform binary, exempt from the check. `ps` shows `osascript → stderr_timestamp → gateway run`; stop/restart/KeepAlive behave exactly as before. A plist installed by an older Mibyan is refreshed by `mibyan gateway install` (or on the next `mibyan gateway start`).
</Info>

<Tip>
  **Picking up new credentials after `mibyan auth add` / `mibyan auth reset`**

  Agents run as threads inside the one gateway process; the only child processes are tool subprocesses (terminal commands, browsers), which never hold provider credentials. A running gateway also re-reads the `openai-codex` login it seeded from `auth.json` the next time its pool selects that entry after it had gone `exhausted` or `dead` (entries added with `mibyan auth add openai-codex` are independent accounts and are not resynced). When you want every session on the fresh login at once, restart the gateway — but prefer the drain-aware path over a bare kill:

  * `mibyan gateway restart` asks the gateway (SIGUSR1) to refuse new turns, waits up to `agent.restart_after_turn_timeout` (default 1800 s) for in-flight turns to finish, exits, and lets launchd's `KeepAlive` relaunch it; the new process reads `auth.json` from scratch.
  * `launchctl kickstart -k gui/$UID/ai.mibyan.gateway` sends SIGTERM instead: the gateway interrupts in-flight chat turns after `agent.restart_drain_timeout` (default `0` — immediately; the user is told and the turn resumes on their next message), gives cron runs `agent.cron_drain_timeout` (default 30 s), kills tool subprocesses and exits, then launchd relaunches it. Nothing from the old process survives, so a session that still fails with `401` after the relaunch is talking to a different gateway process — check `mibyan gateway status` (and `launchctl list | grep mibyan`) for a second PID, such as a manually started `mibyan gateway run`, and stop that one too.
</Tip>

<Info>
  **Multiple installations**

  Like the Linux systemd service, each `mibyan_HOME` directory gets its own launchd label. The default `~/.mibyan` uses `ai.mibyan.gateway`; other installations use `ai.mibyan.gateway-<suffix>`.
</Info>

### Windows (Task Scheduler)

```powershell theme={null}
mibyan gateway install               # Register the Mibyan_Gateway Scheduled Task (runs at logon)
mibyan gateway start                 # Start the gateway hidden, without a console window
mibyan gateway stop                  # Drain and stop the service
mibyan gateway status                # Check status, including registration drift
```

The Scheduled Task runs `wscript.exe` on a generated `.vbs` launcher under `%USERPROFILE%\.mibyan\gateway-service\`. The launcher starts `python.exe -m mibyan_cli.main gateway run` with a hidden window and **exits immediately** — by design: `wscript.exe` has no console, so at logon it never receives the `CTRL_CLOSE_EVENT` that kills a `cmd.exe`-hosted gateway, and the gateway inherits one hidden console instead of every subprocess flashing its own (see `mibyan_cli/gateway_windows.py::_build_gateway_vbs_script`).

<Warning>
  **RestartOnFailure covers the launcher, not the gateway**

  Because the launcher returns as soon as the gateway is spawned, Task Scheduler only ever sees the launcher's exit code. The `<RestartOnFailure>` policy in the registered task therefore fires only when `wscript.exe` itself fails to start the gateway — it does **not** restart a gateway that crashes or is killed later. Gateway auto-restart on Windows relies on the gateway's own in-process restart path (`/restart`, updates, and the `mibyan gateway restart` command); a gateway killed from outside stays down until `mibyan gateway start` or `schtasks /Run /TN <task>`.
</Warning>

`mibyan gateway install` writes the task from the current template; a task registered by an older build would otherwise keep its old settings (no `RestartOnFailure`, no logon `Delay`, an older launcher command line) indefinitely. `mibyan gateway status` compares the registered task with the current template and warns when it predates it:

```
⚠ Scheduled Task registration predates the current template (missing: RestartOnFailure, LogonTrigger Delay; version 1.3 vs 1.4)
  Repair: mibyan gateway start  (or: mibyan gateway install)
```

`mibyan gateway start` and `mibyan update` run the same comparison and re-register a drifted task from the current template automatically (like the systemd unit refresh on Linux); when `schtasks` refuses without elevation, re-run `mibyan gateway install`, which can request administrator approval. The check is silent when the task cannot be queried, and it only inspects a few settings Mibyan owns (task version, `RestartOnFailure`, the logon trigger delay and the launcher arguments), so deliberate local edits elsewhere in the task are not flagged.

## Platform-Specific Toolsets

Each platform has its own toolset:

| Platform | Toolset | Capabilities |
| - | - | - |
| CLI | `mibyan-cli` | Full access |
| Telegram | `mibyan-telegram` | Full tools including terminal |
| Discord | `mibyan-discord` | Full tools including terminal |
| WhatsApp | `mibyan-whatsapp` | Full tools including terminal |
| WhatsApp Cloud API | `mibyan-whatsapp` | Full tools including terminal (shares toolset with the Baileys bridge) |
| Slack | `mibyan-slack` | Full tools including terminal |
| Google Chat | `mibyan-google_chat` | Full tools including terminal |
| Signal | `mibyan-signal` | Full tools including terminal |
| SMS | `mibyan-sms` | Full tools including terminal |
| Email | `mibyan-email` | Full tools including terminal |
| Home Assistant | `mibyan-homeassistant` | Full tools + HA device control (ha\_list\_entities, ha\_get\_state, ha\_call\_service, ha\_list\_services) |
| Mattermost | `mibyan-mattermost` | Full tools including terminal |
| Matrix | `mibyan-matrix` | Full tools including terminal |
| DingTalk | `mibyan-dingtalk` | Full tools including terminal |
| Feishu/Lark | `mibyan-feishu` | Full tools including terminal |
| WeCom | `mibyan-wecom` | Full tools including terminal |
| WeCom Callback | `mibyan-wecom-callback` | Full tools including terminal |
| Weixin | `mibyan-weixin` | Full tools including terminal |
| BlueBubbles | `mibyan-bluebubbles` | Full tools including terminal |
| QQBot | `mibyan-qqbot` | Full tools including terminal |
| Yuanbao | `mibyan-yuanbao` | Full tools including terminal |
| Microsoft Teams | `mibyan-teams` | Full tools including terminal |
| API Server | `mibyan-api-server` | Full tools (drops `clarify`, `text_to_speech` — programmatic access doesn't have an interactive user) |
| Webhooks | `mibyan-webhook` | Full tools including terminal |
| Raft | `mibyan-raft` | Wake-only channel; agent uses Raft CLI for message I/O |

## Operating a multi-platform gateway

A gateway typically runs several adapters at once (Telegram + Discord + Slack, etc.). The sections below cover day-2 operations that span all platforms.

### `/platform` command

Once the gateway is running, use the `/platform` slash command from any connected CLI session or chat to inspect and steer individual adapters without restarting the whole gateway:

```
/platform list                  # show all adapters and their state
/platform pause <name>          # stop dispatching new messages to one adapter
/platform resume <name>         # re-enable a paused adapter
```

`/platform list` shows whether each adapter is `running`, `paused` (manually), or `paused-by-breaker` (see below). Pausing keeps the adapter loaded and its background loops alive — incoming messages are dropped on the floor, but the connection itself stays open so resume is instant.

See also the broader status summary command [`/platforms`](/desktop/reference/slash-commands#info).

### Disabling a platform whose credentials are still in `.env`

`platforms.<name>.enabled: false` in `~/.mibyan/config.yaml` is authoritative.
Credentials for that platform left in the environment (`TELEGRAM_BOT_TOKEN`,
`WEIXIN_TOKEN`, `HASS_TOKEN`, `EMAIL_*`, `TWILIO_ACCOUNT_SID`, ...) are still
wired into the platform's config so send-only tooling keeps working, but they
no longer start the adapter:

```yaml title="~/.mibyan/config.yaml" theme={null}
platforms:
  weixin:
    enabled: false   # wins over WEIXIN_TOKEN in .env
```

Earlier releases let the mere presence of credentials re-enable twelve
platforms (Weixin, WhatsApp Cloud, Home Assistant, Email, SMS, DingTalk, Feishu,
WeCom, WeCom callback, BlueBubbles, QQ Bot, Yuanbao) regardless of that key. If
you relied on that, the gateway now logs one WARNING per affected platform at
startup so it does not just go dark:

```
Platform 'weixin' is explicitly disabled by platforms.weixin.enabled: false in config.yaml,
so the credentials found in the environment (WEIXIN_TOKEN, WEIXIN_ACCOUNT_ID) will NOT start
its adapter. Environment credentials no longer override an explicit disable. Remove the key
or set platforms.weixin.enabled: true to turn it back on.
```

Omitting the `enabled` key entirely keeps the env-only behaviour: credentials
present → adapter starts.

### Ignoring an inherited proxy (`gateway.trust_env`)

By default every platform adapter honors `HTTP_PROXY` / `HTTPS_PROXY` /
`NO_PROXY` (and `SSL_CERT_FILE`) from the gateway's environment, and
auto-detects the macOS system proxy. A gateway started by a Windows Scheduled
Task or a service manager can inherit a proxy the interactive shell never
sees — a local Clash/V2Ray listener that isn't running yet — and log
`Cannot connect to host 127.0.0.1:7890` on every poll. Turn the inherited
proxy off for all adapters at once:

```yaml title="~/.mibyan/config.yaml" theme={null}
gateway:
  trust_env: false
```

Explicit per-platform proxy variables (`DISCORD_PROXY`, `TELEGRAM_PROXY`,
`MATRIX_PROXY`, ...) are still honored. Restart the gateway after changing it.

### Automatic circuit breaker

Each adapter is wrapped in a circuit breaker. Repeated retryable failures (network blips, rate-limit replies, 5xx upstream responses, websocket disconnects) cause the breaker to trip — the adapter is auto-paused, an operator notification is sent to the home channel of another live platform when one is configured, and a structured log line is emitted.

The breaker does **not** auto-resume — it stays open until you run `/platform resume <name>` manually. This is intentional: if a platform is in a sustained outage, you don't want the gateway thrashing reconnects.

### Where to look when a platform is paused

When an adapter is paused, check:

1. **Gateway log** (`~/.mibyan/logs/gateway.log` or the systemd / launchd unit log). Search for the platform name and `circuit breaker`, `paused`, or `disabled`. The trip event includes the failure count and the last error.
2. **`/platform list`** output — shows the current state and last reason.
3. **The provider's status page** (Telegram bot API status, Discord status, etc.). The breaker tripped because the platform was unhealthy; don't try to resume until it's back.

Once upstream is healthy, `/platform resume <name>` clears the breaker and re-arms the adapter.

### Restart notifications

When the gateway restarts (or is shut down with in-flight sessions), it can send a one-shot "the agent is back" / "the agent was interrupted" message to each platform's home channel. This is controlled per-platform by the `gateway_restart_notification` flag in `config.yaml`, which defaults to `true`:

```yaml theme={null}
gateway:
  platforms:
    telegram:
      home_chat_id: "123456789"
      gateway_restart_notification: false   # opt out for this platform
    discord:
      home_chat_id: "987654321"
      # gateway_restart_notification omitted → defaults to true
```

Disable it on noisy or low-priority platforms while leaving it on for your primary chat. The notification is sent once per restart, regardless of how many sessions were in flight.

### Typing indicators

While the agent is processing a message, the gateway shows a live typing status on platforms that support it — a "typing…" bubble on Telegram/Discord/Signal, or the "is thinking…" assistant status on Slack. This is controlled per-platform by the `typing_indicator` flag in `config.yaml`, which defaults to `true`:

```yaml theme={null}
gateway:
  platforms:
    slack:
      typing_indicator: false   # don't show "is thinking…" on Slack
    telegram:
      # typing_indicator omitted → defaults to true
```

Set `typing_indicator: false` on any platform where the indicator is unwanted. Some users find Slack's "is thinking…" status noisy (it also briefly disables the compose box while shown, since it uses Slack's Assistant API). Disabling it only suppresses the indicator — message delivery and everything else is unchanged. The flag is generic, so the same key works for every platform.

### Session resume across gateway restarts

When the gateway shuts down with an in-flight tool call or generation, the affected sessions are flagged as `restart_interrupted`. On the next startup, the gateway schedules an auto-resume for each one — the user gets a short heads-up in the chat ("Send any message after restart and I'll try to resume where you left off.") and the session picks up from the last committed turn when they reply.

Only turns that were actually in flight are resumed, and each resumes once. A chat whose turn had already finished is never answered again just because it was active shortly before a crash. If the gateway was killed after the agent finished a reply but before it was sent, the stored reply is delivered (with a "Recovered reply" notice) instead of being regenerated.

This behaviour is on by default and is logged at gateway start:

```
Scheduled auto-resume for N restart-interrupted session(s)
```

No configuration is required. If you don't want the heads-up, set `gateway_restart_notification: false` on the platform.

### Mobile-friendly progress defaults

Telegram is usually a mobile inbox, so the defaults are tuned for that surface:

* **`tool_progress`** defaults to **`off`** — no per-tool breadcrumb stream filling up the chat.
* **`busy_ack_detail`** defaults to **`off`** — busy-state acknowledgments and long-running heartbeats stay terse (no `iteration 21/60` debug detail).
* **`interim_assistant_messages`** stays **on** — real mid-turn assistant commentary (the model literally telling you what it's about to do) is signal, not noise.
* **`long_running_notifications`** stays **on** — a single edit-in-place "⏳ Working — N min" bubble updates every few minutes so you have a heartbeat instead of staring at `typing…` for half an hour.

These per-platform defaults apply only while the same key is unset directly under `display:`. A global `display.tool_progress`, `display.show_reasoning`, `display.busy_ack_detail`, `display.interim_assistant_messages` or `display.long_running_notifications` applies to every platform and replaces its default. A `config.yaml` copied from an older `cli-config.yaml.example` sets all five globally, and an older first-time `mibyan setup` wrote `tool_progress: all`; delete those lines to get the per-platform defaults back.

Opt out of either of the kept-on defaults or opt back into verbose progress per platform:

```yaml theme={null}
display:
  platforms:
    telegram:
      # Re-enable the tool-progress stream
      tool_progress: new
      # Show "iteration N/M, running: tool" in heartbeats and busy acks
      busy_ack_detail: true
      # Or quiet them entirely
      interim_assistant_messages: false
      long_running_notifications: false
```

### Warning and error notifications (opt-in suppression)

Automatic warning and error notifications are shown by default. To suppress
these notifications, enable `suppress_warning_notifications` globally or for
an individual surface:

```yaml theme={null}
display:
  suppress_warning_notifications: true
  platforms:
    telegram:
      suppress_warning_notifications: false
```

This example suppresses notifications globally while keeping them visible on
Telegram. Omit the setting or use `false` to preserve normal delivery. Platform
overrides take precedence; `null` inherits. Invalid values do not enable
suppression.

The setting controls automatic engine warnings, retry/fallback diagnostics,
watchdog and database notices, cron failure notifications, Kanban failure
notifications, background/delegation diagnostics, and adapter-generated error
notices. It applies to messaging platforms, CLI/TUI presentation and API
notification presentation. Classification belongs to the producer: warning-like
text in a user request or an ordinary result is not filtered by its wording.

Suppression changes presentation, not execution. Existing logs, stored diagnostic
content, retry decisions, failure state, scheduler bookkeeping and notification
cursors remain available. A diagnostic-only internal wake (a subagent or credit
failure, a Kanban crash notice) still runs its agent turn — so the agent can act on
the failure and the session history stays consistent — and that turn is billed as
usual; only its unsolicited text, media and streaming presentation are muted. Structured
approval and clarification controls, direct command/API outcomes and requested
results are not converted into success or discarded. API failure flags, status
codes and usage remain truthful even when diagnostic text is hidden.

Cron `failure_deliver` still selects the destination; the destination's warning
policy determines whether an automatic failure notice is presented there.
Suppressed deliveries are settled without claiming a successful send. Already
admitted deliveries retain their delivery identity and outcome.

Policy is resolved for the owning profile and logical destination. Agent turns
use their turn policy; independent notifications and deferred deliveries evaluate
policy at their own delivery boundary. Already delivered messages are not removed.
Suppression does not fix an underlying failure or add another logging destination.

### Progress bubble cleanup (opt-in)

Tool-progress messages, the "still working…" heartbeat, and status-callback bubbles can also be auto-deleted after the final response lands. Enable per-platform via `display.platforms.<platform>.cleanup_progress`:

```yaml theme={null}
display:
  platforms:
    telegram:
      cleanup_progress: true
    discord:
      cleanup_progress: true
```

Defaults to `false`. Only platforms whose adapter implements `delete_message` honor the setting (currently Telegram and Discord). Failed runs **skip** cleanup so the bubbles remain as breadcrumbs.

## Next Steps

* [Telegram Setup](/desktop/user-guide/messaging/telegram)
* [Discord Setup](/desktop/user-guide/messaging/discord)
* [Slack Setup](/desktop/user-guide/messaging/slack)
* [Google Chat Setup](/desktop/user-guide/messaging/google_chat)
* [WhatsApp Setup](/desktop/user-guide/messaging/whatsapp)
* [WhatsApp Business Cloud API Setup](/desktop/user-guide/messaging/whatsapp-cloud)
* [Signal Setup](/desktop/user-guide/messaging/signal)
* [SMS Setup (Twilio)](/desktop/user-guide/messaging/sms)
* [Email Setup](/desktop/user-guide/messaging/email)
* [Home Assistant Integration](/desktop/user-guide/messaging/homeassistant)
* [Mattermost Setup](/desktop/user-guide/messaging/mattermost)
* [Matrix Setup](/desktop/user-guide/messaging/matrix)
* [DingTalk Setup](/desktop/user-guide/messaging/dingtalk)
* [Feishu/Lark Setup](/desktop/user-guide/messaging/feishu)
* [WeCom Setup](/desktop/user-guide/messaging/wecom)
* [WeCom Callback Setup](/desktop/user-guide/messaging/wecom-callback)
* [Weixin Setup (WeChat)](/desktop/user-guide/messaging/weixin)
* [BlueBubbles Setup (iMessage)](/desktop/user-guide/messaging/bluebubbles)
* [Photon Setup (iMessage)](/desktop/user-guide/messaging/photon)
* [QQBot Setup](/desktop/user-guide/messaging/qqbot)
* [Yuanbao Setup](/desktop/user-guide/messaging/yuanbao)
* [Microsoft Teams Setup](/desktop/user-guide/messaging/teams)
* [Teams Meetings Pipeline](/desktop/user-guide/messaging/teams-meetings)
* [Microsoft Graph Webhook Listener](/desktop/user-guide/messaging/msgraph-webhook)
* [LINE Setup](/desktop/user-guide/messaging/line)
* [ntfy Setup](/desktop/user-guide/messaging/ntfy)
* [SimpleX Chat Setup](/desktop/user-guide/messaging/simplex)
* [Open WebUI + API Server](/desktop/user-guide/messaging/open-webui)
* [Raft Setup](/desktop/user-guide/messaging/raft)
* [IRC Setup](/desktop/user-guide/messaging/irc)
* [Buzz Setup](/desktop/user-guide/messaging/buzz)
* [A2A (Agent-to-Agent) Setup](/desktop/user-guide/messaging/a2a)
* [Webhooks](/desktop/user-guide/messaging/webhooks)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.