----
url: https://docs.openclaw.ai/gateway/health
----

# Health Checks - OpenClaw

## [​](#health-checks-cli)Health Checks (CLI)

Short guide to verify channel connectivity without guessing.

## [​](#quick-checks)Quick checks

* `openclaw status` — local summary: gateway reachability/mode, update hint, linked channel auth age, sessions + recent activity.
* `openclaw status --all` — full local diagnosis (read-only, color, safe to paste for debugging).
* `openclaw status --deep` — also probes the running Gateway (per-channel probes when supported).
* `openclaw health --json` — asks the running Gateway for a full health snapshot (WS-only; no direct Baileys socket).
* Send `/status` as a standalone message in WhatsApp/WebChat to get a status reply without invoking the agent.
* Logs: tail `/tmp/openclaw/openclaw-*.log` and filter for `web-heartbeat`, `web-reconnect`, `web-auto-reply`, `web-inbound`.

## [​](#deep-diagnostics)Deep diagnostics

* Creds on disk: `ls -l ~/.openclaw/credentials/whatsapp/<accountId>/creds.json` (mtime should be recent).
* Session store: `ls -l ~/.openclaw/agents/<agentId>/sessions/sessions.json` (path can be overridden in config). Count and recent recipients are surfaced via `status`.
* Relink flow: `openclaw channels logout && openclaw channels login --verbose` when status codes 409–515 or `loggedOut` appear in logs. (Note: the QR login flow auto-restarts once for status 515 after pairing.)

## [​](#health-monitor-config)Health monitor config

* `gateway.channelHealthCheckMinutes`: how often the gateway checks channel health. Default: `5`. Set `0` to disable health-monitor restarts globally.
* `gateway.channelStaleEventThresholdMinutes`: how long a connected channel can stay idle before the health monitor treats it as stale and restarts it. Default: `30`. Keep this greater than or equal to `gateway.channelHealthCheckMinutes`.
* `gateway.channelMaxRestartsPerHour`: rolling one-hour cap for health-monitor restarts per channel/account. Default: `10`.
* `channels.<provider>.healthMonitor.enabled`: disable health-monitor restarts for a specific channel while leaving global monitoring enabled.
* `channels.<provider>.accounts.<accountId>.healthMonitor.enabled`: multi-account override that wins over the channel-level setting.
* These per-channel overrides apply to the built-in channel monitors that expose them today: Discord, Google Chat, iMessage, Microsoft Teams, Signal, Slack, Telegram, and WhatsApp.

## [​](#when-something-fails)When something fails

* `logged out` or status 409–515 → relink with `openclaw channels logout` then `openclaw channels login`.
* Gateway unreachable → start it: `openclaw gateway --port 18789` (use `--force` if the port is busy).
* No inbound messages → confirm linked phone is online and the sender is allowed (`channels.whatsapp.allowFrom`); for group chats, ensure allowlist + mention rules match (`channels.whatsapp.groups`, `agents.list[].groupChat.mentionPatterns`).

## [​](#dedicated-“health”-command)Dedicated “health” command

`openclaw health --json` asks the running Gateway for its health snapshot (no direct channel sockets from the CLI). It reports linked creds/auth age when available, per-channel probe summaries, session-store summary, and a probe duration. It exits non-zero if the Gateway is unreachable or the probe fails/timeouts. Use `--timeout <ms>` to override the 10s default.

----
url: https://docs.openclaw.ai/cli/devices
----

# devices - OpenClaw

## [​](#openclaw-devices)`openclaw devices`

Manage device pairing requests and device-scoped tokens.

## [​](#commands)Commands

### [​](#openclaw-devices-list)`openclaw devices list`

List pending pairing requests and paired devices.

```
openclaw devices list
openclaw devices list --json
```

Pending request output includes the requested role and scopes so approvals can be reviewed before you approve.

### [​](#openclaw-devices-remove-<deviceid>)`openclaw devices remove <deviceId>`

Remove one paired device entry.

```
openclaw devices remove <deviceId>
openclaw devices remove <deviceId> --json
```

### [​](#openclaw-devices-clear-yes--pending)`openclaw devices clear --yes [--pending]`

Clear paired devices in bulk.

```
openclaw devices clear --yes
openclaw devices clear --yes --pending
openclaw devices clear --yes --pending --json
```

### [​](#openclaw-devices-approve-requestid--latest)`openclaw devices approve [requestId] [--latest]`

Approve a pending device pairing request. If `requestId` is omitted, OpenClaw automatically approves the most recent pending request. Note: if a device retries pairing with changed auth details (role/scopes/public key), OpenClaw supersedes the previous pending entry and issues a new `requestId`. Run `openclaw devices list` right before approval to use the current ID.

```
openclaw devices approve
openclaw devices approve <requestId>
openclaw devices approve --latest
```

### [​](#openclaw-devices-reject-<requestid>)`openclaw devices reject <requestId>`

Reject a pending device pairing request.

```
openclaw devices reject <requestId>
```

### [​](#openclaw-devices-rotate-device-<id>-role-<role>--scope-<scope->)`openclaw devices rotate --device <id> --role <role> [--scope <scope...>]`

Rotate a device token for a specific role (optionally updating scopes).

```
openclaw devices rotate --device <deviceId> --role operator --scope operator.read --scope operator.write
```

### [​](#openclaw-devices-revoke-device-<id>-role-<role>)`openclaw devices revoke --device <id> --role <role>`

Revoke a device token for a specific role.

```
openclaw devices revoke --device <deviceId> --role node
```

## [​](#common-options)Common options

* `--url <url>`: Gateway WebSocket URL (defaults to `gateway.remote.url` when configured).
* `--token <token>`: Gateway token (if required).
* `--password <password>`: Gateway password (password auth).
* `--timeout <ms>`: RPC timeout.
* `--json`: JSON output (recommended for scripting).

Note: when you set `--url`, the CLI does not fall back to config or environment credentials. Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.

## [​](#notes)Notes

* Token rotation returns a new token (sensitive). Treat it like a secret.
* These commands require `operator.pairing` (or `operator.admin`) scope.
* `devices clear` is intentionally gated by `--yes`.
* If pairing scope is unavailable on local loopback (and no explicit `--url` is passed), list/approve can use a local pairing fallback.

## [​](#token-drift-recovery-checklist)Token drift recovery checklist

Use this when Control UI or other clients keep failing with `AUTH_TOKEN_MISMATCH` or `AUTH_DEVICE_TOKEN_MISMATCH`.

1. Confirm current gateway token source:

```
openclaw config get gateway.auth.token
```

2. List paired devices and identify the affected device id:

```
openclaw devices list
```

3. Rotate operator token for the affected device:

```
openclaw devices rotate --device <deviceId> --role operator
```

4. If rotation is not enough, remove stale pairing and approve again:

```
openclaw devices remove <deviceId>
openclaw devices list
openclaw devices approve <requestId>
```

5. Retry client connection with the current shared token/password.

Related:

* [Dashboard auth troubleshooting](https://docs.openclaw.ai/web/dashboard#if-you-see-unauthorized-1008)
* [Gateway troubleshooting](https://docs.openclaw.ai/gateway/troubleshooting#dashboard-control-ui-connectivity)

----
url: https://docs.openclaw.ai/start/wizard-cli-automation
----

# CLI Automation - OpenClaw

## [​](#cli-automation)CLI Automation

Use `--non-interactive` to automate `openclaw onboard`.

`--json` does not imply non-interactive mode. Use `--non-interactive` (and `--workspace`) for scripts.

## [​](#baseline-non-interactive-example)Baseline non-interactive example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice apiKey \
  --anthropic-api-key "$ANTHROPIC_API_KEY" \
  --secret-input-mode plaintext \
  --gateway-port 18789 \
  --gateway-bind loopback \
  --install-daemon \
  --daemon-runtime node \
  --skip-skills
```

Add `--json` for a machine-readable summary. Use `--secret-input-mode ref` to store env-backed refs in auth profiles instead of plaintext values. Interactive selection between env refs and configured provider refs (`file` or `exec`) is available in the onboarding flow. In non-interactive `ref` mode, provider env vars must be set in the process environment. Passing inline key flags without the matching env var now fails fast. Example:

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice openai-api-key \
  --secret-input-mode ref \
  --accept-risk
```

## [​](#provider-specific-examples)Provider-specific examples

Gemini example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice gemini-api-key \
  --gemini-api-key "$GEMINI_API_KEY" \
  --gateway-port 18789 \
  --gateway-bind loopback
```

Z.AI example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice zai-api-key \
  --zai-api-key "$ZAI_API_KEY" \
  --gateway-port 18789 \
  --gateway-bind loopback
```

Vercel AI Gateway example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice ai-gateway-api-key \
  --ai-gateway-api-key "$AI_GATEWAY_API_KEY" \
  --gateway-port 18789 \
  --gateway-bind loopback
```

Cloudflare AI Gateway example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice cloudflare-ai-gateway-api-key \
  --cloudflare-ai-gateway-account-id "your-account-id" \
  --cloudflare-ai-gateway-gateway-id "your-gateway-id" \
  --cloudflare-ai-gateway-api-key "$CLOUDFLARE_AI_GATEWAY_API_KEY" \
  --gateway-port 18789 \
  --gateway-bind loopback
```

Moonshot example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice moonshot-api-key \
  --moonshot-api-key "$MOONSHOT_API_KEY" \
  --gateway-port 18789 \
  --gateway-bind loopback
```

Mistral example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice mistral-api-key \
  --mistral-api-key "$MISTRAL_API_KEY" \
  --gateway-port 18789 \
  --gateway-bind loopback
```

Synthetic example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice synthetic-api-key \
  --synthetic-api-key "$SYNTHETIC_API_KEY" \
  --gateway-port 18789 \
  --gateway-bind loopback
```

OpenCode example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice opencode-zen \
  --opencode-zen-api-key "$OPENCODE_API_KEY" \
  --gateway-port 18789 \
  --gateway-bind loopback
```

Swap to `--auth-choice opencode-go --opencode-go-api-key "$OPENCODE_API_KEY"` for the Go catalog.

Ollama example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice ollama \
  --custom-model-id "qwen3.5:27b" \
  --accept-risk \
  --gateway-port 18789 \
  --gateway-bind loopback
```

Custom provider example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice custom-api-key \
  --custom-base-url "https://llm.example.com/v1" \
  --custom-model-id "foo-large" \
  --custom-api-key "$CUSTOM_API_KEY" \
  --custom-provider-id "my-custom" \
  --custom-compatibility anthropic \
  --gateway-port 18789 \
  --gateway-bind loopback
```

`--custom-api-key` is optional. If omitted, onboarding checks `CUSTOM_API_KEY`.Ref-mode variant:

```
export CUSTOM_API_KEY="your-key"
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice custom-api-key \
  --custom-base-url "https://llm.example.com/v1" \
  --custom-model-id "foo-large" \
  --secret-input-mode ref \
  --custom-provider-id "my-custom" \
  --custom-compatibility anthropic \
  --gateway-port 18789 \
  --gateway-bind loopback
```

In this mode, onboarding stores `apiKey` as `{ source: "env", provider: "default", id: "CUSTOM_API_KEY" }`.

## [​](#add-another-agent)Add another agent

Use `openclaw agents add <name>` to create a separate agent with its own workspace, sessions, and auth profiles. Running without `--workspace` launches the wizard.

```
openclaw agents add work \
  --workspace ~/.openclaw/workspace-work \
  --model openai/gpt-5.2 \
  --bind whatsapp:biz \
  --non-interactive \
  --json
```

What it sets:

* `agents.list[].name`
* `agents.list[].workspace`
* `agents.list[].agentDir`

Notes:

* Default workspaces follow `~/.openclaw/workspace-<agentId>`.
* Add `bindings` to route inbound messages (the wizard can do this).
* Non-interactive flags: `--model`, `--agent-dir`, `--bind`, `--non-interactive`.

## [​](#related-docs)Related docs

* Onboarding hub: [Onboarding (CLI)](https://docs.openclaw.ai/start/wizard)
* Full reference: [CLI Setup Reference](https://docs.openclaw.ai/start/wizard-cli-reference)
* Command reference: [`openclaw onboard`](https://docs.openclaw.ai/cli/onboard)

----
url: https://docs.openclaw.ai/gateway/background-process
----

# Background Exec and Process Tool - OpenClaw

## [​](#background-exec-+-process-tool)Background Exec + Process Tool

OpenClaw runs shell commands through the `exec` tool and keeps long‑running tasks in memory. The `process` tool manages those background sessions.

## [​](#exec-tool)exec tool

Key parameters:

* `command` (required)
* `yieldMs` (default 10000): auto‑background after this delay
* `background` (bool): background immediately
* `timeout` (seconds, default 1800): kill the process after this timeout
* `elevated` (bool): run on host if elevated mode is enabled/allowed
* Need a real TTY? Set `pty: true`.
* `workdir`, `env`

Behavior:

* Foreground runs return output directly.
* When backgrounded (explicit or timeout), the tool returns `status: "running"` + `sessionId` and a short tail.
* Output is kept in memory until the session is polled or cleared.
* If the `process` tool is disallowed, `exec` runs synchronously and ignores `yieldMs`/`background`.
* Spawned exec commands receive `OPENCLAW_SHELL=exec` for context-aware shell/profile rules.

## [​](#child-process-bridging)Child process bridging

When spawning long-running child processes outside the exec/process tools (for example, CLI respawns or gateway helpers), attach the child-process bridge helper so termination signals are forwarded and listeners are detached on exit/error. This avoids orphaned processes on systemd and keeps shutdown behavior consistent across platforms. Environment overrides:

* `PI_BASH_YIELD_MS`: default yield (ms)
* `PI_BASH_MAX_OUTPUT_CHARS`: in‑memory output cap (chars)
* `OPENCLAW_BASH_PENDING_MAX_OUTPUT_CHARS`: pending stdout/stderr cap per stream (chars)
* `PI_BASH_JOB_TTL_MS`: TTL for finished sessions (ms, bounded to 1m–3h)

Config (preferred):

* `tools.exec.backgroundMs` (default 10000)
* `tools.exec.timeoutSec` (default 1800)
* `tools.exec.cleanupMs` (default 1800000)
* `tools.exec.notifyOnExit` (default true): enqueue a system event + request heartbeat when a backgrounded exec exits.
* `tools.exec.notifyOnExitEmptySuccess` (default false): when true, also enqueue completion events for successful backgrounded runs that produced no output.

## [​](#process-tool)process tool

Actions:

* `list`: running + finished sessions
* `poll`: drain new output for a session (also reports exit status)
* `log`: read the aggregated output (supports `offset` + `limit`)
* `write`: send stdin (`data`, optional `eof`)
* `kill`: terminate a background session
* `clear`: remove a finished session from memory
* `remove`: kill if running, otherwise clear if finished

Notes:

* Only backgrounded sessions are listed/persisted in memory.
* Sessions are lost on process restart (no disk persistence).
* Session logs are only saved to chat history if you run `process poll/log` and the tool result is recorded.
* `process` is scoped per agent; it only sees sessions started by that agent.
* `process list` includes a derived `name` (command verb + target) for quick scans.
* `process log` uses line-based `offset`/`limit`.
* When both `offset` and `limit` are omitted, it returns the last 200 lines and includes a paging hint.
* When `offset` is provided and `limit` is omitted, it returns from `offset` to the end (not capped to 200).

## [​](#examples)Examples

Run a long task and poll later:

```
{ "tool": "exec", "command": "sleep 5 && echo done", "yieldMs": 1000 }
```

```
{ "tool": "process", "action": "poll", "sessionId": "<id>" }
```

Start immediately in background:

```
{ "tool": "exec", "command": "npm run build", "background": true }
```

Send stdin:

```
{ "tool": "process", "action": "write", "sessionId": "<id>", "data": "y\n" }
```

----
url: https://docs.openclaw.ai/tools/agent-send
----

# Agent Send - OpenClaw

`openclaw agent` runs a single agent turn from the command line without needing an inbound chat message. Use it for scripted workflows, testing, and programmatic delivery.

## Quick start

## Flags

| Flag                          | Description                                                 |
| ----------------------------- | ----------------------------------------------------------- |
| `--message \<text\>`          | Message to send (required)                                  |
| `--to \<dest\>`               | Derive session key from a target (phone, chat id)           |
| `--agent \<id\>`              | Target a configured agent (uses its `main` session)         |
| `--session-id \<id\>`         | Reuse an existing session by id                             |
| `--local`                     | Force local embedded runtime (skip Gateway)                 |
| `--deliver`                   | Send the reply to a chat channel                            |
| `--channel \<name\>`          | Delivery channel (whatsapp, telegram, discord, slack, etc.) |
| `--reply-to \<target\>`       | Delivery target override                                    |
| `--reply-channel \<name\>`    | Delivery channel override                                   |
| `--reply-account \<id\>`      | Delivery account id override                                |
| `--thinking \<level\>`        | Set thinking level (off, minimal, low, medium, high, xhigh) |
| `--verbose \<on\|full\|off\>` | Set verbose level                                           |
| `--timeout \<seconds\>`       | Override agent timeout                                      |
| `--json`                      | Output structured JSON                                      |

## Behavior

## Examples

----
url: https://docs.openclaw.ai/gateway/local-models
----

# Local Models - OpenClaw

Local is doable, but OpenClaw expects large context + strong defenses against prompt injection. Small cards truncate context and leak safety. Aim high: **≥2 maxed-out Mac Studios or equivalent GPU rig (\~$30k+)**. A single **24 GB** GPU works only for lighter prompts with higher latency. Use the **largest / full-size model variant you can run**; aggressively quantized or “small” checkpoints raise prompt-injection risk (see [Security](https://docs.openclaw.ai/gateway/security)). If you want the lowest-friction local setup, start with [Ollama](https://docs.openclaw.ai/providers/ollama) and `openclaw onboard`. This page is the opinionated guide for higher-end local stacks and custom OpenAI-compatible local servers.

## Recommended: LM Studio + MiniMax M2.5 (Responses API, full-size)

Best current local stack. Load MiniMax M2.5 in LM Studio, enable the local server (default `http://127.0.0.1:1234`), and use Responses API to keep reasoning separate from final text.

```
{
  agents: {
    defaults: {
      model: { primary: "lmstudio/minimax-m2.5-gs32" },
      models: {
        "anthropic/claude-opus-4-6": { alias: "Opus" },
        "lmstudio/minimax-m2.5-gs32": { alias: "Minimax" },
      },
    },
  },
  models: {
    mode: "merge",
    providers: {
      lmstudio: {
        baseUrl: "http://127.0.0.1:1234/v1",
        apiKey: "lmstudio",
        api: "openai-responses",
        models: [
          {
            id: "minimax-m2.5-gs32",
            name: "MiniMax M2.5 GS32",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 196608,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

**Setup checklist**

Keep hosted models configured even when running local; use `models.mode: "merge"` so fallbacks stay available.

### Hybrid config: hosted primary, local fallback

```
{
  agents: {
    defaults: {
      model: {
        primary: "anthropic/claude-sonnet-4-6",
        fallbacks: ["lmstudio/minimax-m2.5-gs32", "anthropic/claude-opus-4-6"],
      },
      models: {
        "anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
        "lmstudio/minimax-m2.5-gs32": { alias: "MiniMax Local" },
        "anthropic/claude-opus-4-6": { alias: "Opus" },
      },
    },
  },
  models: {
    mode: "merge",
    providers: {
      lmstudio: {
        baseUrl: "http://127.0.0.1:1234/v1",
        apiKey: "lmstudio",
        api: "openai-responses",
        models: [
          {
            id: "minimax-m2.5-gs32",
            name: "MiniMax M2.5 GS32",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 196608,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

### Local-first with hosted safety net

Swap the primary and fallback order; keep the same providers block and `models.mode: "merge"` so you can fall back to Sonnet or Opus when the local box is down.

### Regional hosting / data routing

## Other OpenAI-compatible local proxies

vLLM, LiteLLM, OAI-proxy, or custom gateways work if they expose an OpenAI-style `/v1` endpoint. Replace the provider block above with your endpoint and model ID:

```
{
  models: {
    mode: "merge",
    providers: {
      local: {
        baseUrl: "http://127.0.0.1:8000/v1",
        apiKey: "sk-local",
        api: "openai-responses",
        models: [
          {
            id: "my-local-model",
            name: "Local Model",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 120000,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

Keep `models.mode: "merge"` so hosted models stay available as fallbacks.

## Troubleshooting

----
url: https://docs.openclaw.ai/channels/signal
----

# Signal - OpenClaw

Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE.

## Prerequisites

## Quick setup (beginner)

1. Use a **separate Signal number** for the bot (recommended).
2. Install `signal-cli` (Java required if you use the JVM build).
3. Choose one setup path:
4. Configure OpenClaw and restart the gateway.
5. Send a first DM and approve pairing (`openclaw pairing approve signal <CODE>`).

Minimal config:

Field reference:

| Field       | Description                                       |
| ----------- | ------------------------------------------------- |
| `account`   | Bot phone number in E.164 format (`+15551234567`) |
| `cliPath`   | Path to `signal-cli` (`signal-cli` if on `PATH`)  |
| `dmPolicy`  | DM access policy (`pairing` recommended)          |
| `allowFrom` | Phone numbers or `uuid:<id>` values allowed to DM |

## What it is

## Config writes

By default, Signal is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`). Disable with:

## The number model (important)

## Setup path A: link existing Signal account (QR)

1. Install `signal-cli` (JVM or native build).
2. Link a bot account:
3. Configure Signal and start the gateway.

Example:

Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](https://docs.openclaw.ai/gateway/configuration-reference#multi-account-all-channels) for the shared pattern.

## Setup path B: register dedicated bot number (SMS, Linux)

Use this when you want a dedicated bot number instead of linking an existing Signal app account.

1. Get a number that can receive SMS (or voice verification for landlines).
2. Install `signal-cli` on the gateway host:

If you use the JVM build (`signal-cli-${VERSION}.tar.gz`), install JRE 25+ first. Keep `signal-cli` updated; upstream notes that old releases can break as Signal server APIs change.

3. Register and verify the number:

If captcha is required:

1. Open `https://signalcaptchas.org/registration/generate.html`.
2. Complete captcha, copy the `signalcaptcha://...` link target from “Open Signal”.
3. Run from the same external IP as the browser session when possible.
4. Run registration again immediately (captcha tokens expire quickly):

4) Configure OpenClaw, restart gateway, verify channel:

5. Pair your DM sender:

Important: registering a phone number account with `signal-cli` can de-authenticate the main Signal app session for that number. Prefer a dedicated bot number, or use QR link mode if you need to keep your existing phone app setup. Upstream references:

## External daemon mode (httpUrl)

If you want to manage `signal-cli` yourself (slow JVM cold starts, container init, or shared CPUs), run the daemon separately and point OpenClaw at it:

This skips auto-spawn and the startup wait inside OpenClaw. For slow starts when auto-spawning, set `channels.signal.startupTimeoutMs`.

## Access control (DMs + groups)

DMs:

Groups:

## How it works (behavior)

## Typing + read receipts

## Reactions (message tool)

Examples:

Config:

## Delivery targets (CLI/cron)

## Troubleshooting

Run this ladder first:

Then confirm DM pairing state if needed:

Common failures:

Extra checks:

For triage flow: [/channels/troubleshooting](https://docs.openclaw.ai/channels/troubleshooting).

## Security notes

## Configuration reference (Signal)

Full configuration: [Configuration](https://docs.openclaw.ai/gateway/configuration) Provider options:

Related global options:

----
url: https://docs.openclaw.ai/channels/channel-routing
----

# Channel Routing - OpenClaw

## Channels & routing

OpenClaw routes replies **back to the channel where a message came from**. The model does not choose a channel; routing is deterministic and controlled by the host configuration.

## Key terms

## Session key shapes (examples)

Direct messages collapse to the agent’s **main** session:

Groups and channels remain isolated per channel:

Threads:

Examples:

## Main DM route pinning

When `session.dmScope` is `main`, direct messages may share one main session. To prevent the session’s `lastRoute` from being overwritten by non-owner DMs, OpenClaw infers a pinned owner from `allowFrom` when all of these are true:

In that mismatch case, OpenClaw still records inbound session metadata, but it skips updating the main session `lastRoute`.

## Routing rules (how an agent is chosen)

Routing picks **one agent** for each inbound message:

1. **Exact peer match** (`bindings` with `peer.kind` + `peer.id`).
2. **Parent peer match** (thread inheritance).
3. **Guild + roles match** (Discord) via `guildId` + `roles`.
4. **Guild match** (Discord) via `guildId`.
5. **Team match** (Slack) via `teamId`.
6. **Account match** (`accountId` on the channel).
7. **Channel match** (any account on that channel, `accountId: "*"`).
8. **Default agent** (`agents.list[].default`, else first list entry, fallback to `main`).

When a binding includes multiple match fields (`peer`, `guildId`, `teamId`, `roles`), **all provided fields must match** for that binding to apply. The matched agent determines which workspace and session store are used.

## Broadcast groups (run multiple agents)

Broadcast groups let you run **multiple agents** for the same peer **when OpenClaw would normally reply** (for example: in WhatsApp groups, after mention/activation gating). Config:

See: [Broadcast Groups](https://docs.openclaw.ai/channels/broadcast-groups).

## Config overview

Example:

```
{
  agents: {
    list: [{ id: "support", name: "Support", workspace: "~/.openclaw/workspace-support" }],
  },
  bindings: [
    { match: { channel: "slack", teamId: "T123" }, agentId: "support" },
    { match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" },
  ],
}
```

## Session storage

Session stores live under the state directory (default `~/.openclaw`):

You can override the store path via `session.store` and `{agentId}` templating. Gateway and ACP session discovery also scans disk-backed agent stores under the default `agents/` root and under templated `session.store` roots. Discovered stores must stay inside that resolved agent root and use a regular `sessions.json` file. Symlinks and out-of-root paths are ignored.

## WebChat behavior

WebChat attaches to the **selected agent** and defaults to the agent’s main session. Because of this, WebChat lets you see cross‑channel context for that agent in one place.

## Reply context

Inbound replies include:

This is consistent across channels.

----
url: https://docs.openclaw.ai/channels
----

# Chat Channels - OpenClaw

OpenClaw can talk to you on any chat app you already use. Each channel connects via the Gateway. Text is supported everywhere; media and reactions vary by channel.

## Supported channels

* [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe).
* [Discord](https://docs.openclaw.ai/channels/discord) — Discord Bot API + Gateway; supports servers, channels, and DMs.
* [Feishu](https://docs.openclaw.ai/channels/feishu) — Feishu/Lark bot via WebSocket (plugin, installed separately).
* [Google Chat](https://docs.openclaw.ai/channels/googlechat) — Google Chat API app via HTTP webhook.
* [iMessage (legacy)](https://docs.openclaw.ai/channels/imessage) — Legacy macOS integration via imsg CLI (deprecated, use BlueBubbles for new setups).
* [IRC](https://docs.openclaw.ai/channels/irc) — Classic IRC servers; channels + DMs with pairing/allowlist controls.
* [LINE](https://docs.openclaw.ai/channels/line) — LINE Messaging API bot (plugin, installed separately).
* [Matrix](https://docs.openclaw.ai/channels/matrix) — Matrix protocol (plugin, installed separately).
* [Mattermost](https://docs.openclaw.ai/channels/mattermost) — Bot API + WebSocket; channels, groups, DMs (plugin, installed separately).
* [Microsoft Teams](https://docs.openclaw.ai/channels/msteams) — Bot Framework; enterprise support (plugin, installed separately).
* [Nextcloud Talk](https://docs.openclaw.ai/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (plugin, installed separately).
* [Nostr](https://docs.openclaw.ai/channels/nostr) — Decentralized DMs via NIP-04 (plugin, installed separately).
* [Signal](https://docs.openclaw.ai/channels/signal) — signal-cli; privacy-focused.
* [Synology Chat](https://docs.openclaw.ai/channels/synology-chat) — Synology NAS Chat via outgoing+incoming webhooks (plugin, installed separately).
* [Slack](https://docs.openclaw.ai/channels/slack) — Bolt SDK; workspace apps.
* [Telegram](https://docs.openclaw.ai/channels/telegram) — Bot API via grammY; supports groups.
* [Tlon](https://docs.openclaw.ai/channels/tlon) — Urbit-based messenger (plugin, installed separately).
* [Twitch](https://docs.openclaw.ai/channels/twitch) — Twitch chat via IRC connection (plugin, installed separately).
* [WebChat](https://docs.openclaw.ai/web/webchat) — Gateway WebChat UI over WebSocket.
* [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) — Most popular; uses Baileys and requires QR pairing.
* [Zalo](https://docs.openclaw.ai/channels/zalo) — Zalo Bot API; Vietnam’s popular messenger (plugin, installed separately).
* [Zalo Personal](https://docs.openclaw.ai/channels/zalouser) — Zalo personal account via QR login (plugin, installed separately).

## Notes

----
url: https://docs.openclaw.ai/tools/slash-commands
----

# Slash Commands - OpenClaw

Commands are handled by the Gateway. Most commands must be sent as a **standalone** message that starts with `/`. The host-only bash chat command uses `! <cmd>` (with `/bash <cmd>` as an alias). There are two related systems:

There are also a few **inline shortcuts** (allowlisted/authorized senders only): `/help`, `/commands`, `/status`, `/whoami` (`/id`). They run immediately, are stripped before the model sees the message, and the remaining text continues through the normal flow.

## Config

```
{
  commands: {
    native: "auto",
    nativeSkills: "auto",
    text: true,
    bash: false,
    bashForegroundMs: 2000,
    config: false,
    mcp: false,
    plugins: false,
    debug: false,
    restart: false,
    allowFrom: {
      "*": ["user1"],
      discord: ["user:123"],
    },
    useAccessGroups: true,
  },
}
```

## Command list

Text + native (when enabled):

* `/help`
* `/commands`
* `/skill <name> [input]` (run a skill by name)
* `/status` (show current status; includes provider usage/quota for the current model provider when available)
* `/allowlist` (list/add/remove allowlist entries)
* `/approve <id> allow-once|allow-always|deny` (resolve exec approval prompts)
* `/context [list|detail|json]` (explain “context”; `detail` shows per-file + per-tool + per-skill + system prompt size)
* `/btw <question>` (ask an ephemeral side question about the current session without changing future session context; see [/tools/btw](https://docs.openclaw.ai/tools/btw))
* `/export-session [path]` (alias: `/export`) (export current session to HTML with full system prompt)
* `/whoami` (show your sender id; alias: `/id`)
* `/session idle <duration|off>` (manage inactivity auto-unfocus for focused thread bindings)
* `/session max-age <duration|off>` (manage hard max-age auto-unfocus for focused thread bindings)
* `/subagents list|kill|log|info|send|steer|spawn` (inspect, control, or spawn sub-agent runs for the current session)
* `/acp spawn|cancel|steer|close|status|set-mode|set|cwd|permissions|timeout|model|reset-options|doctor|install|sessions` (inspect and control ACP runtime sessions)
* `/agents` (list thread-bound agents for this session)
* `/focus <target>` (Discord: bind this thread, or a new thread, to a session/subagent target)
* `/unfocus` (Discord: remove the current thread binding)
* `/kill <id|#|all>` (immediately abort one or all running sub-agents for this session; no confirmation message)
* `/steer <id|#> <message>` (steer a running sub-agent immediately: in-run when possible, otherwise abort current work and restart on the steer message)
* `/tell <id|#> <message>` (alias for `/steer`)
* `/config show|get|set|unset` (persist config to disk, owner-only; requires `commands.config: true`)
* `/mcp show|get|set|unset` (manage OpenClaw MCP server config, owner-only; requires `commands.mcp: true`)
* `/plugins list|show|get|install|enable|disable` (inspect discovered plugins, install new ones, and toggle enablement; owner-only for writes; requires `commands.plugins: true`)
* `/debug show|set|unset|reset` (runtime overrides, owner-only; requires `commands.debug: true`)
* `/usage off|tokens|full|cost` (per-response usage footer or local cost summary)
* `/tts off|always|inbound|tagged|status|provider|limit|summary|audio` (control TTS; see [/tts](https://docs.openclaw.ai/tools/tts))
* `/stop`
* `/restart`
* `/dock-telegram` (alias: `/dock_telegram`) (switch replies to Telegram)
* `/dock-discord` (alias: `/dock_discord`) (switch replies to Discord)
* `/dock-slack` (alias: `/dock_slack`) (switch replies to Slack)
* `/activation mention|always` (groups only)
* `/send on|off|inherit` (owner-only)
* `/reset` or `/new [model]` (optional model hint; remainder is passed through)
* `/think <off|minimal|low|medium|high|xhigh>` (dynamic choices by model/provider; aliases: `/thinking`, `/t`)
* `/fast status|on|off` (omitting the arg shows the current effective fast-mode state)
* `/verbose on|full|off` (alias: `/v`)
* `/reasoning on|off|stream` (alias: `/reason`; when on, sends a separate message prefixed `Reasoning:`; `stream` = Telegram draft only)
* `/elevated on|off|ask|full` (alias: `/elev`; `full` skips exec approvals)
* `/exec host=<sandbox|gateway|node> security=<deny|allowlist|full> ask=<off|on-miss|always> node=<id>` (send `/exec` to show current)
* `/model <name>` (alias: `/models`; or `/<alias>` from `agents.defaults.models.*.alias`)
* `/queue <mode>` (plus options like `debounce:2s cap:25 drop:summarize`; send `/queue` to see current settings)
* `/bash <command>` (host-only; alias for `! <command>`; requires `commands.bash: true` + `tools.elevated` allowlists)

Text-only:

Notes:

* Commands accept an optional `:` between the command and args (e.g. `/think: high`, `/send: on`, `/help:`).
* `/new <model>` accepts a model alias, `provider/model`, or a provider name (fuzzy match); if no match, the text is treated as the message body.
* For full provider usage breakdown, use `openclaw status --usage`.
* `/allowlist add|remove` requires `commands.config=true` and honors channel `configWrites`.
* In multi-account channels, config-targeted `/allowlist --account <id>` and `/config set channels.<provider>.accounts.<id>...` also honor the target account’s `configWrites`.
* `/usage` controls the per-response usage footer; `/usage cost` prints a local cost summary from OpenClaw session logs.
* `/restart` is enabled by default; set `commands.restart: false` to disable it.
* Discord-only native command: `/vc join|leave|status` controls voice channels (requires `channels.discord.voice` and native commands; not available as text).
* Discord thread-binding commands (`/focus`, `/unfocus`, `/agents`, `/session idle`, `/session max-age`) require effective thread bindings to be enabled (`session.threadBindings.enabled` and/or `channels.discord.threadBindings.enabled`).
* ACP command reference and runtime behavior: [ACP Agents](https://docs.openclaw.ai/tools/acp-agents).
* `/verbose` is meant for debugging and extra visibility; keep it **off** in normal use.
* `/fast on|off` persists a session override. Use the Sessions UI `inherit` option to clear it and fall back to config defaults.
* Tool failure summaries are still shown when relevant, but detailed failure text is only included when `/verbose` is `on` or `full`.
* `/reasoning` (and `/verbose`) are risky in group settings: they may reveal internal reasoning or tool output you did not intend to expose. Prefer leaving them off, especially in group chats.
* **Fast path:** command-only messages from allowlisted senders are handled immediately (bypass queue + model).
* **Group mention gating:** command-only messages from allowlisted senders bypass mention requirements.
* **Inline shortcuts (allowlisted senders only):** certain commands also work when embedded in a normal message and are stripped before the model sees the remaining text.
* Currently: `/help`, `/commands`, `/status`, `/whoami` (`/id`).
* Unauthorized command-only messages are silently ignored, and inline `/...` tokens are treated as plain text.
* **Skill commands:** `user-invocable` skills are exposed as slash commands. Names are sanitized to `a-z0-9_` (max 32 chars); collisions get numeric suffixes (e.g. `_2`).
* **Native command arguments:** Discord uses autocomplete for dynamic options (and button menus when you omit required args). Telegram and Slack show a button menu when a command supports choices and you omit the arg.

## Usage surfaces (what shows where)

## Model selection (`/model`)

`/model` is implemented as a directive. Examples:

Notes:

## Debug overrides

`/debug` lets you set **runtime-only** config overrides (memory, not disk). Owner-only. Disabled by default; enable with `commands.debug: true`. Examples:

Notes:

## Config updates

`/config` writes to your on-disk config (`openclaw.json`). Owner-only. Disabled by default; enable with `commands.config: true`. Examples:

Notes:

## MCP updates

`/mcp` writes OpenClaw-managed MCP server definitions under `mcp.servers`. Owner-only. Disabled by default; enable with `commands.mcp: true`. Examples:

Notes:

## Plugin updates

`/plugins` lets operators inspect discovered plugins and toggle enablement in config. Read-only flows can use `/plugin` as an alias. Disabled by default; enable with `commands.plugins: true`. Examples:

Notes:

## Surface notes

## BTW side questions

`/btw` is a quick **side question** about the current session. Unlike normal chat:

That makes `/btw` useful when you want a temporary clarification while the main task keeps going. Example:

See [BTW Side Questions](https://docs.openclaw.ai/tools/btw) for the full behavior and client UX details.

----
url: https://docs.openclaw.ai/cli/approvals
----

# approvals - OpenClaw

## [​](#openclaw-approvals)`openclaw approvals`

Manage exec approvals for the **local host**, **gateway host**, or a **node host**. By default, commands target the local approvals file on disk. Use `--gateway` to target the gateway, or `--node` to target a specific node. Related:

* Exec approvals: [Exec approvals](https://docs.openclaw.ai/tools/exec-approvals)
* Nodes: [Nodes](https://docs.openclaw.ai/nodes)

## [​](#common-commands)Common commands

```
openclaw approvals get
openclaw approvals get --node <id|name|ip>
openclaw approvals get --gateway
```

## [​](#replace-approvals-from-a-file)Replace approvals from a file

```
openclaw approvals set --file ./exec-approvals.json
openclaw approvals set --node <id|name|ip> --file ./exec-approvals.json
openclaw approvals set --gateway --file ./exec-approvals.json
```

## [​](#allowlist-helpers)Allowlist helpers

```
openclaw approvals allowlist add "~/Projects/**/bin/rg"
openclaw approvals allowlist add --agent main --node <id|name|ip> "/usr/bin/uptime"
openclaw approvals allowlist add --agent "*" "/usr/bin/uname"

openclaw approvals allowlist remove "~/Projects/**/bin/rg"
```

## [​](#notes)Notes

* `--node` uses the same resolver as `openclaw nodes` (id, name, ip, or id prefix).
* `--agent` defaults to `"*"`, which applies to all agents.
* The node host must advertise `system.execApprovals.get/set` (macOS app or headless node host).
* Approvals files are stored per host at `~/.openclaw/exec-approvals.json`.

----
url: https://docs.openclaw.ai/reference/api-usage-costs
----

# API Usage and Costs - OpenClaw

## [​](#api-usage-&-costs)API usage & costs

This doc lists **features that can invoke API keys** and where their costs show up. It focuses on OpenClaw features that can generate provider usage or paid API calls.

## [​](#where-costs-show-up-chat-+-cli)Where costs show up (chat + CLI)

**Per-session cost snapshot**

* `/status` shows the current session model, context usage, and last response tokens.
* If the model uses **API-key auth**, `/status` also shows **estimated cost** for the last reply.

**Per-message cost footer**

* `/usage full` appends a usage footer to every reply, including **estimated cost** (API-key only).
* `/usage tokens` shows tokens only; OAuth flows hide dollar cost.

**CLI usage windows (provider quotas)**

* `openclaw status --usage` and `openclaw channels list` show provider **usage windows** (quota snapshots, not per-message costs).

See [Token use & costs](https://docs.openclaw.ai/reference/token-use) for details and examples.

## [​](#how-keys-are-discovered)How keys are discovered

OpenClaw can pick up credentials from:

* **Auth profiles** (per-agent, stored in `auth-profiles.json`).
* **Environment variables** (e.g. `OPENAI_API_KEY`, `BRAVE_API_KEY`, `FIRECRAWL_API_KEY`).
* **Config** (`models.providers.*.apiKey`, `tools.web.search.*`, `tools.web.fetch.firecrawl.*`, `memorySearch.*`, `talk.apiKey`).
* **Skills** (`skills.entries.<name>.apiKey`) which may export keys to the skill process env.

## [​](#features-that-can-spend-keys)Features that can spend keys

### [​](#1-core-model-responses-chat-+-tools)1) Core model responses (chat + tools)

Every reply or tool call uses the **current model provider** (OpenAI, Anthropic, etc). This is the primary source of usage and cost. See [Models](https://docs.openclaw.ai/providers/models) for pricing config and [Token use & costs](https://docs.openclaw.ai/reference/token-use) for display.

### [​](#2-media-understanding-audio/image/video)2) Media understanding (audio/image/video)

Inbound media can be summarized/transcribed before the reply runs. This uses model/provider APIs.

* Audio: OpenAI / Groq / Deepgram (now **auto-enabled** when keys exist).
* Image: OpenAI / Anthropic / Google.
* Video: Google.

See [Media understanding](https://docs.openclaw.ai/nodes/media-understanding).

### [​](#3-memory-embeddings-+-semantic-search)3) Memory embeddings + semantic search

Semantic memory search uses **embedding APIs** when configured for remote providers:

* `memorySearch.provider = "openai"` → OpenAI embeddings
* `memorySearch.provider = "gemini"` → Gemini embeddings
* `memorySearch.provider = "voyage"` → Voyage embeddings
* `memorySearch.provider = "mistral"` → Mistral embeddings
* `memorySearch.provider = "ollama"` → Ollama embeddings (local/self-hosted; typically no hosted API billing)
* Optional fallback to a remote provider if local embeddings fail

You can keep it local with `memorySearch.provider = "local"` (no API usage). See [Memory](https://docs.openclaw.ai/concepts/memory).

### [​](#4-web-search-tool)4) Web search tool

`web_search` uses API keys and may incur usage charges depending on your provider:

* **Brave Search API**: `BRAVE_API_KEY` or `plugins.entries.brave.config.webSearch.apiKey`
* **Gemini (Google Search)**: `GEMINI_API_KEY` or `plugins.entries.google.config.webSearch.apiKey`
* **Grok (xAI)**: `XAI_API_KEY` or `plugins.entries.xai.config.webSearch.apiKey`
* **Kimi (Moonshot)**: `KIMI_API_KEY`, `MOONSHOT_API_KEY`, or `plugins.entries.moonshot.config.webSearch.apiKey`
* **Perplexity Search API**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `plugins.entries.perplexity.config.webSearch.apiKey`

Legacy `tools.web.search.*` provider paths still load through the temporary compatibility shim, but they are no longer the recommended config surface. **Brave Search free credit:** Each Brave plan includes $5/month in renewing free credit. The Search plan costs $5 per 1,000 requests, so the credit covers 1,000 requests/month at no charge. Set your usage limit in the Brave dashboard to avoid unexpected charges. See [Web tools](https://docs.openclaw.ai/tools/web).

### [​](#5-web-fetch-tool-firecrawl)5) Web fetch tool (Firecrawl)

`web_fetch` can call **Firecrawl** when an API key is present:

* `FIRECRAWL_API_KEY` or `tools.web.fetch.firecrawl.apiKey`

If Firecrawl isn’t configured, the tool falls back to direct fetch + readability (no paid API). See [Web tools](https://docs.openclaw.ai/tools/web).

### [​](#6-provider-usage-snapshots-status/health)6) Provider usage snapshots (status/health)

Some status commands call **provider usage endpoints** to display quota windows or auth health. These are typically low-volume calls but still hit provider APIs:

* `openclaw status --usage`
* `openclaw models status --json`

See [Models CLI](https://docs.openclaw.ai/cli/models).

### [​](#7-compaction-safeguard-summarization)7) Compaction safeguard summarization

The compaction safeguard can summarize session history using the **current model**, which invokes provider APIs when it runs. See [Session management + compaction](https://docs.openclaw.ai/reference/session-management-compaction).

### [​](#8-model-scan-/-probe)8) Model scan / probe

`openclaw models scan` can probe OpenRouter models and uses `OPENROUTER_API_KEY` when probing is enabled. See [Models CLI](https://docs.openclaw.ai/cli/models).

### [​](#9-talk-speech)9) Talk (speech)

Talk mode can invoke **ElevenLabs** when configured:

* `ELEVENLABS_API_KEY` or `talk.apiKey`

See [Talk mode](https://docs.openclaw.ai/nodes/talk).

### [​](#10-skills-third-party-apis)10) Skills (third-party APIs)

Skills can store `apiKey` in `skills.entries.<name>.apiKey`. If a skill uses that key for external APIs, it can incur costs according to the skill’s provider. See [Skills](https://docs.openclaw.ai/tools/skills).

----
url: https://docs.openclaw.ai/cli/message
----

# message - OpenClaw

## [​](#openclaw-message)`openclaw message`

Single outbound command for sending messages and channel actions (Discord/Google Chat/Slack/Mattermost (plugin)/Telegram/WhatsApp/Signal/iMessage/Microsoft Teams).

## [​](#usage)Usage

```
openclaw message <subcommand> [flags]
```

Channel selection:

* `--channel` required if more than one channel is configured.
* If exactly one channel is configured, it becomes the default.
* Values: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams` (Mattermost requires plugin)

Target formats (`--target`):

* WhatsApp: E.164 or group JID
* Telegram: chat id or `@username`
* Discord: `channel:<id>` or `user:<id>` (or `<@id>` mention; raw numeric ids are treated as channels)
* Google Chat: `spaces/<spaceId>` or `users/<userId>`
* Slack: `channel:<id>` or `user:<id>` (raw channel id is accepted)
* Mattermost (plugin): `channel:<id>`, `user:<id>`, or `@username` (bare ids are treated as channels)
* Signal: `+E.164`, `group:<id>`, `signal:+E.164`, `signal:group:<id>`, or `username:<name>`/`u:<name>`
* iMessage: handle, `chat_id:<id>`, `chat_guid:<guid>`, or `chat_identifier:<id>`
* Microsoft Teams: conversation id (`19:...@thread.tacv2`) or `conversation:<id>` or `user:<aad-object-id>`

Name lookup:

* For supported providers (Discord/Slack/etc), channel names like `Help` or `#help` are resolved via the directory cache.
* On cache miss, OpenClaw will attempt a live directory lookup when the provider supports it.

## [​](#common-flags)Common flags

* `--channel <name>`
* `--account <id>`
* `--target <dest>` (target channel or user for send/poll/read/etc)
* `--targets <name>` (repeat; broadcast only)
* `--json`
* `--dry-run`
* `--verbose`

## [​](#secretref-behavior)SecretRef behavior

* `openclaw message` resolves supported channel SecretRefs before running the selected action.

* Resolution is scoped to the active action target when possible:

  * channel-scoped when `--channel` is set (or inferred from prefixed targets like `discord:...`)
  * account-scoped when `--account` is set (channel globals + selected account surfaces)
  * when `--account` is omitted, OpenClaw does not force a `default` account SecretRef scope

* Unresolved SecretRefs on unrelated channels do not block a targeted message action.

* If the selected channel/account SecretRef is unresolved, the command fails closed for that action.

## [​](#actions)Actions

### [​](#core)Core

* `send`

  * Channels: WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/Microsoft Teams
  * Required: `--target`, plus `--message` or `--media`
  * Optional: `--media`, `--reply-to`, `--thread-id`, `--gif-playback`
  * Telegram only: `--buttons` (requires `channels.telegram.capabilities.inlineButtons` to allow it)
  * Telegram only: `--force-document` (send images and GIFs as documents to avoid Telegram compression)
  * Telegram only: `--thread-id` (forum topic id)
  * Slack only: `--thread-id` (thread timestamp; `--reply-to` uses the same field)
  * WhatsApp only: `--gif-playback`

* `poll`

  * Channels: WhatsApp/Telegram/Discord/Matrix/Microsoft Teams
  * Required: `--target`, `--poll-question`, `--poll-option` (repeat)
  * Optional: `--poll-multi`
  * Discord only: `--poll-duration-hours`, `--silent`, `--message`
  * Telegram only: `--poll-duration-seconds` (5-600), `--silent`, `--poll-anonymous` / `--poll-public`, `--thread-id`

* `react`

  * Channels: Discord/Google Chat/Slack/Telegram/WhatsApp/Signal
  * Required: `--message-id`, `--target`
  * Optional: `--emoji`, `--remove`, `--participant`, `--from-me`, `--target-author`, `--target-author-uuid`
  * Note: `--remove` requires `--emoji` (omit `--emoji` to clear own reactions where supported; see /tools/reactions)
  * WhatsApp only: `--participant`, `--from-me`
  * Signal group reactions: `--target-author` or `--target-author-uuid` required

* `reactions`

  * Channels: Discord/Google Chat/Slack
  * Required: `--message-id`, `--target`
  * Optional: `--limit`

* `read`

  * Channels: Discord/Slack
  * Required: `--target`
  * Optional: `--limit`, `--before`, `--after`
  * Discord only: `--around`

* `edit`

  * Channels: Discord/Slack
  * Required: `--message-id`, `--message`, `--target`

* `delete`

  * Channels: Discord/Slack/Telegram
  * Required: `--message-id`, `--target`

* `pin` / `unpin`

  * Channels: Discord/Slack
  * Required: `--message-id`, `--target`

* `pins` (list)

  * Channels: Discord/Slack
  * Required: `--target`

* `permissions`

  * Channels: Discord
  * Required: `--target`

* `search`

  * Channels: Discord
  * Required: `--guild-id`, `--query`
  * Optional: `--channel-id`, `--channel-ids` (repeat), `--author-id`, `--author-ids` (repeat), `--limit`

### [​](#threads)Threads

* `thread create`

  * Channels: Discord
  * Required: `--thread-name`, `--target` (channel id)
  * Optional: `--message-id`, `--message`, `--auto-archive-min`

* `thread list`

  * Channels: Discord
  * Required: `--guild-id`
  * Optional: `--channel-id`, `--include-archived`, `--before`, `--limit`

* `thread reply`

  * Channels: Discord
  * Required: `--target` (thread id), `--message`
  * Optional: `--media`, `--reply-to`

### [​](#emojis)Emojis

* `emoji list`

  * Discord: `--guild-id`
  * Slack: no extra flags

* `emoji upload`

  * Channels: Discord
  * Required: `--guild-id`, `--emoji-name`, `--media`
  * Optional: `--role-ids` (repeat)

### [​](#stickers)Stickers

* `sticker send`

  * Channels: Discord
  * Required: `--target`, `--sticker-id` (repeat)
  * Optional: `--message`

* `sticker upload`

  * Channels: Discord
  * Required: `--guild-id`, `--sticker-name`, `--sticker-desc`, `--sticker-tags`, `--media`

### [​](#roles-/-channels-/-members-/-voice)Roles / Channels / Members / Voice

* `role info` (Discord): `--guild-id`
* `role add` / `role remove` (Discord): `--guild-id`, `--user-id`, `--role-id`
* `channel info` (Discord): `--target`
* `channel list` (Discord): `--guild-id`
* `member info` (Discord/Slack): `--user-id` (+ `--guild-id` for Discord)
* `voice status` (Discord): `--guild-id`, `--user-id`

### [​](#events)Events

* `event list` (Discord): `--guild-id`
* `event create` (Discord): `--guild-id`, `--event-name`, `--start-time`
  * Optional: `--end-time`, `--desc`, `--channel-id`, `--location`, `--event-type`

### [​](#moderation-discord)Moderation (Discord)

* `timeout`: `--guild-id`, `--user-id` (optional `--duration-min` or `--until`; omit both to clear timeout)
* `kick`: `--guild-id`, `--user-id` (+ `--reason`)
* `ban`: `--guild-id`, `--user-id` (+ `--delete-days`, `--reason`)
  * `timeout` also supports `--reason`

### [​](#broadcast)Broadcast

* `broadcast`

  * Channels: any configured channel; use `--channel all` to target all providers
  * Required: `--targets` (repeat)
  * Optional: `--message`, `--media`, `--dry-run`

## [​](#examples)Examples

Send a Discord reply:

```
openclaw message send --channel discord \
  --target channel:123 --message "hi" --reply-to 456
```

Send a Discord message with components:

```
openclaw message send --channel discord \
  --target channel:123 --message "Choose:" \
  --components '{"text":"Choose a path","blocks":[{"type":"actions","buttons":[{"label":"Approve","style":"success"},{"label":"Decline","style":"danger"}]}]}'
```

See [Discord components](https://docs.openclaw.ai/channels/discord#interactive-components) for the full schema. Create a Discord poll:

```
openclaw message poll --channel discord \
  --target channel:123 \
  --poll-question "Snack?" \
  --poll-option Pizza --poll-option Sushi \
  --poll-multi --poll-duration-hours 48
```

Create a Telegram poll (auto-close in 2 minutes):

```
openclaw message poll --channel telegram \
  --target @mychat \
  --poll-question "Lunch?" \
  --poll-option Pizza --poll-option Sushi \
  --poll-duration-seconds 120 --silent
```

Send a Teams proactive message:

```
openclaw message send --channel msteams \
  --target conversation:19:abc@thread.tacv2 --message "hi"
```

Create a Teams poll:

```
openclaw message poll --channel msteams \
  --target conversation:19:abc@thread.tacv2 \
  --poll-question "Lunch?" \
  --poll-option Pizza --poll-option Sushi
```

React in Slack:

```
openclaw message react --channel slack \
  --target C123 --message-id 456 --emoji "✅"
```

React in a Signal group:

```
openclaw message react --channel signal \
  --target signal:group:abc123 --message-id 1737630212345 \
  --emoji "✅" --target-author-uuid 123e4567-e89b-12d3-a456-426614174000
```

Send Telegram inline buttons:

```
openclaw message send --channel telegram --target @mychat --message "Choose:" \
  --buttons '[ [{"text":"Yes","callback_data":"cmd:yes"}], [{"text":"No","callback_data":"cmd:no"}] ]'
```

Send a Telegram image as a document to avoid compression:

```
openclaw message send --channel telegram --target @mychat \
  --media ./diagram.png --force-document
```

----
url: https://docs.openclaw.ai/cli/docs
----

# docs - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw docs](#openclaw-docs)

## [​](#openclaw-docs)`openclaw docs`

Search the live docs index.

```
openclaw docs browser existing-session
openclaw docs sandbox allowHostControl
```

[dns](https://docs.openclaw.ai/cli/dns)[RPC Adapters](https://docs.openclaw.ai/reference/rpc)

----
url: https://docs.openclaw.ai/gateway/remote-gateway-readme
----

# Remote Gateway Setup - OpenClaw

## Running OpenClaw\.app with a Remote Gateway

OpenClaw\.app uses SSH tunneling to connect to a remote gateway. This guide shows you how to set it up.

## Overview

## Quick Setup

### Step 1: Add SSH Config

Edit `~/.ssh/config` and add:

Replace `<REMOTE_IP>` and `<REMOTE_USER>` with your values.

### Step 2: Copy SSH Key

Copy your public key to the remote machine (enter password once):

### Step 3: Set Gateway Token

### Step 4: Start SSH Tunnel

### Step 5: Restart OpenClaw\.app

The app will now connect to the remote gateway through the SSH tunnel.

***

## Auto-Start Tunnel on Login

To have the SSH tunnel start automatically when you log in, create a Launch Agent.

### Create the PLIST file

Save this as `~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist`:

### Load the Launch Agent

The tunnel will now:

Legacy note: remove any leftover `com.openclaw.ssh-tunnel` LaunchAgent if present.

***

## Troubleshooting

**Check if tunnel is running:**

**Restart the tunnel:**

**Stop the tunnel:**

***

## How It Works

| Component                            | What It Does                                                 |
| ------------------------------------ | ------------------------------------------------------------ |
| `LocalForward 18789 127.0.0.1:18789` | Forwards local port 18789 to remote port 18789               |
| `ssh -N`                             | SSH without executing remote commands (just port forwarding) |
| `KeepAlive`                          | Automatically restarts tunnel if it crashes                  |
| `RunAtLoad`                          | Starts tunnel when the agent loads                           |

OpenClaw\.app connects to `ws://127.0.0.1:18789` on your client machine. The SSH tunnel forwards that connection to port 18789 on the remote machine where the Gateway is running.

----
url: https://docs.openclaw.ai/providers/modelstudio
----

# Model Studio - OpenClaw

## Model Studio (Alibaba Cloud)

The Model Studio provider gives access to Alibaba Cloud Coding Plan models, including Qwen and third-party models hosted on the platform.

## Quick start

1. Set the API key:

2) Set a default model:

## Region endpoints

Model Studio has two endpoints based on region:

| Region     | Endpoint                             |
| ---------- | ------------------------------------ |
| China (CN) | `coding.dashscope.aliyuncs.com`      |
| Global     | `coding-intl.dashscope.aliyuncs.com` |

The provider auto-selects based on the auth choice (`modelstudio-api-key` for global, `modelstudio-api-key-cn` for China). You can override with a custom `baseUrl` in config.

## Available models

Most models support image input. Context windows range from 200K to 1M tokens.

## Environment note

If the Gateway runs as a daemon (launchd/systemd), make sure `MODELSTUDIO_API_KEY` is available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).

----
url: https://docs.openclaw.ai/channels/discord
----

# Discord - OpenClaw

## Discord (Bot API)

Status: ready for DMs and guild channels via the official Discord gateway.

## Quick setup

You will need to create a new application with a bot, add the bot to your server, and pair it to OpenClaw. We recommend adding your bot to your own private server. If you don’t have one yet, [create one first](https://support.discord.com/hc/en-us/articles/204849977-How-do-I-create-a-server) (choose **Create My Own > For me and my friends**).

## Recommended: Set up a guild workspace

Once DMs are working, you can set up your Discord server as a full workspace where each channel gets its own agent session with its own context. This is recommended for private servers where it’s just you and your bot.

Now create some channels on your Discord server and start chatting. Your agent can see the channel name, and each channel gets its own isolated session — so you can set up `#coding`, `#home`, `#research`, or whatever fits your workflow.

## Runtime model

## Forum channels

Discord forum and media channels only accept thread posts. OpenClaw supports two ways to create them:

Example: send to forum parent to create a thread

Example: create a forum thread explicitly

Forum parents do not accept Discord components. If you need components, send to the thread itself (`channel:<threadId>`).

## Interactive components

OpenClaw supports Discord components v2 containers for agent messages. Use the message tool with a `components` payload. Interaction results are routed back to the agent as normal inbound messages and follow the existing Discord `replyToMode` settings. Supported blocks:

By default, components are single use. Set `components.reusable=true` to allow buttons, selects, and forms to be used multiple times until they expire. To restrict who can click a button, set `allowedUsers` on that button (Discord user IDs, tags, or `*`). When configured, unmatched users receive an ephemeral denial. The `/model` and `/models` slash commands open an interactive model picker with provider and model dropdowns plus a Submit step. The picker reply is ephemeral and only the invoking user can use it. File attachments:

Modal forms:

Example:

```
{
  channel: "discord",
  action: "send",
  to: "channel:123456789012345678",
  message: "Optional fallback text",
  components: {
    reusable: true,
    text: "Choose a path",
    blocks: [
      {
        type: "actions",
        buttons: [
          {
            label: "Approve",
            style: "success",
            allowedUsers: ["123456789012345678"],
          },
          { label: "Decline", style: "danger" },
        ],
      },
      {
        type: "actions",
        select: {
          type: "string",
          placeholder: "Pick an option",
          options: [
            { label: "Option A", value: "a" },
            { label: "Option B", value: "b" },
          ],
        },
      },
    ],
    modal: {
      title: "Details",
      triggerLabel: "Open form",
      fields: [
        { type: "text", label: "Requester" },
        {
          type: "select",
          label: "Priority",
          options: [
            { label: "Low", value: "low" },
            { label: "High", value: "high" },
          ],
        },
      ],
    },
  },
}
```

## Access control and routing

Guild handling is controlled by `channels.discord.groupPolicy`:

Secure baseline when `channels.discord` exists is `allowlist`.`allowlist` behavior:

Example:

```
{
  channels: {
    discord: {
      groupPolicy: "allowlist",
      guilds: {
        "123456789012345678": {
          requireMention: true,
          ignoreOtherMentions: true,
          users: ["987654321098765432"],
          roles: ["123456789012345678"],
          channels: {
            general: { allow: true },
            help: { allow: true, requireMention: true },
          },
        },
      },
    },
  },
}
```

If you only set `DISCORD_BOT_TOKEN` and do not create a `channels.discord` block, runtime fallback is `groupPolicy="allowlist"` (with a warning in logs), even if `channels.defaults.groupPolicy` is `open`.

### Role-based agent routing

Use `bindings[].match.roles` to route Discord guild members to different agents by role ID. Role-based bindings accept role IDs only and are evaluated after peer or parent-peer bindings and before guild-only bindings. If a binding also sets other match fields (for example `peer` + `guildId` + `roles`), all configured fields must match.

```
{
  bindings: [
    {
      agentId: "opus",
      match: {
        channel: "discord",
        guildId: "123456789012345678",
        roles: ["111111111111111111"],
      },
    },
    {
      agentId: "sonnet",
      match: {
        channel: "discord",
        guildId: "123456789012345678",
      },
    },
  ],
}
```

## Developer Portal setup

## Native commands and command auth

See [Slash commands](https://docs.openclaw.ai/tools/slash-commands) for command catalog and behavior. Default slash command settings:

## Feature details

## Tools and action gates

Discord message actions include messaging, channel admin, moderation, presence, and metadata actions. Core examples:

Action gates live under `channels.discord.actions.*`. Default gate behavior:

| Action group                                                                                                                                                             | Default  |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| reactions, messages, threads, pins, polls, search, memberInfo, roleInfo, channelInfo, channels, voiceStatus, events, stickers, emojiUploads, stickerUploads, permissions | enabled  |
| roles                                                                                                                                                                    | disabled |
| moderation                                                                                                                                                               | disabled |
| presence                                                                                                                                                                 | disabled |

## Components v2 UI

OpenClaw uses Discord components v2 for exec approvals and cross-context markers. Discord message actions can also accept `components` for custom UI (advanced; requires Carbon component instances), while legacy `embeds` remain available but are not recommended.

Example:

## Voice channels

OpenClaw can join Discord voice channels for realtime, continuous conversations. This is separate from voice message attachments. Requirements:

Use the Discord-only native command `/vc join|leave|status` to control sessions. The command uses the account default agent and follows the same allowlist and group policy rules as other Discord commands. Auto-join example:

```
{
  channels: {
    discord: {
      voice: {
        enabled: true,
        autoJoin: [
          {
            guildId: "123456789012345678",
            channelId: "234567890123456789",
          },
        ],
        daveEncryption: true,
        decryptionFailureTolerance: 24,
        tts: {
          provider: "openai",
          openai: { voice: "alloy" },
        },
      },
    },
  },
}
```

Notes:

## Voice messages

Discord voice messages show a waveform preview and require OGG/Opus audio plus metadata. OpenClaw generates the waveform automatically, but it needs `ffmpeg` and `ffprobe` available on the gateway host to inspect and convert audio files. Requirements and constraints:

Example:

## Troubleshooting

## Configuration reference pointers

Primary reference:

High-signal Discord fields:

* startup/auth: `enabled`, `token`, `accounts.*`, `allowBots`
* policy: `groupPolicy`, `dm.*`, `guilds.*`, `guilds.*.channels.*`
* command: `commands.native`, `commands.useAccessGroups`, `configWrites`, `slashCommand.*`
* event queue: `eventQueue.listenerTimeout` (listener budget), `eventQueue.maxQueueSize`, `eventQueue.maxConcurrency`
* inbound worker: `inboundWorker.runTimeoutMs`
* reply/history: `replyToMode`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`
* delivery: `textChunkLimit`, `chunkMode`, `maxLinesPerMessage`
* streaming: `streaming` (legacy alias: `streamMode`), `draftChunk`, `blockStreaming`, `blockStreamingCoalesce`
* media/retry: `mediaMaxMb`, `retry`
* actions: `actions.*`
* presence: `activity`, `status`, `activityType`, `activityUrl`
* UI: `ui.components.accentColor`
* features: `threadBindings`, top-level `bindings[]` (`type: "acp"`), `pluralkit`, `execApprovals`, `intents`, `agentComponents`, `heartbeat`, `responsePrefix`

## Safety and operations

----
url: https://docs.openclaw.ai/providers/together
----

# Together AI - OpenClaw

## [​](#together-ai)Together AI

The [Together AI](https://together.ai/) provides access to leading open-source models including Llama, DeepSeek, Kimi, and more through a unified API.

* Provider: `together`
* Auth: `TOGETHER_API_KEY`
* API: OpenAI-compatible

## [​](#quick-start)Quick start

1. Set the API key (recommended: store it for the Gateway):

```
openclaw onboard --auth-choice together-api-key
```

2. Set a default model:

```
{
  agents: {
    defaults: {
      model: { primary: "together/moonshotai/Kimi-K2.5" },
    },
  },
}
```

## [​](#non-interactive-example)Non-interactive example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice together-api-key \
  --together-api-key "$TOGETHER_API_KEY"
```

This will set `together/moonshotai/Kimi-K2.5` as the default model.

## [​](#environment-note)Environment note

If the Gateway runs as a daemon (launchd/systemd), make sure `TOGETHER_API_KEY` is available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).

## [​](#available-models)Available models

Together AI provides access to many popular open-source models:

* **GLM 4.7 Fp8** - Default model with 200K context window
* **Llama 3.3 70B Instruct Turbo** - Fast, efficient instruction following
* **Llama 4 Scout** - Vision model with image understanding
* **Llama 4 Maverick** - Advanced vision and reasoning
* **DeepSeek V3.1** - Powerful coding and reasoning model
* **DeepSeek R1** - Advanced reasoning model
* **Kimi K2 Instruct** - High-performance model with 262K context window

All models support standard chat completions and are OpenAI API compatible.

----
url: https://docs.openclaw.ai/providers/sglang
----

# SGLang - OpenClaw

SGLang can serve open-source models via an **OpenAI-compatible** HTTP API. OpenClaw can connect to SGLang using the `openai-completions` API. OpenClaw can also **auto-discover** available models from SGLang when you opt in with `SGLANG_API_KEY` (any value works if your server does not enforce auth) and you do not define an explicit `models.providers.sglang` entry.

## Quick start

1. Start SGLang with an OpenAI-compatible server.

Your base URL should expose `/v1` endpoints (for example `/v1/models`, `/v1/chat/completions`). SGLang commonly runs on:

2. Opt in (any value works if no auth is configured):

3) Run onboarding and choose `SGLang`, or set a model directly:

## Model discovery (implicit provider)

When `SGLANG_API_KEY` is set (or an auth profile exists) and you **do not** define `models.providers.sglang`, OpenClaw will query:

and convert the returned IDs into model entries. If you set `models.providers.sglang` explicitly, auto-discovery is skipped and you must define models manually.

## Explicit configuration (manual models)

Use explicit config when:

```
{
  models: {
    providers: {
      sglang: {
        baseUrl: "http://127.0.0.1:30000/v1",
        apiKey: "${SGLANG_API_KEY}",
        api: "openai-completions",
        models: [
          {
            id: "your-model-id",
            name: "Local SGLang Model",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 128000,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

## Troubleshooting

----
url: https://docs.openclaw.ai/providers/volcengine
----

# Volcengine (Doubao) - OpenClaw

The Volcengine provider gives access to Doubao models and third-party models hosted on Volcano Engine, with separate endpoints for general and coding workloads.

## Quick start

1. Set the API key:

2) Set a default model:

## Non-interactive example

## Providers and endpoints

| Provider          | Endpoint                                  | Use case       |
| ----------------- | ----------------------------------------- | -------------- |
| `volcengine`      | `ark.cn-beijing.volces.com/api/v3`        | General models |
| `volcengine-plan` | `ark.cn-beijing.volces.com/api/coding/v3` | Coding models  |

Both providers are configured from a single API key. Setup registers both automatically.

## Available models

Most models support text + image input. Context windows range from 128K to 256K tokens.

## Environment note

If the Gateway runs as a daemon (launchd/systemd), make sure `VOLCANO_ENGINE_API_KEY` is available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).

----
url: https://docs.openclaw.ai/tools/tts
----

# Text-to-Speech - OpenClaw

OpenClaw can convert outbound replies into audio using ElevenLabs, Microsoft, or OpenAI. It works anywhere OpenClaw can send audio; Telegram gets a round voice-note bubble.

## Supported services

### Microsoft speech notes

The bundled Microsoft speech provider currently uses Microsoft Edge’s online neural TTS service via the `node-edge-tts` library. It’s a hosted service (not local), uses Microsoft endpoints, and does not require an API key. `node-edge-tts` exposes speech configuration options and output formats, but not all options are supported by the service. Legacy config and directive input using `edge` still works and is normalized to `microsoft`. Because this path is a public web service without a published SLA or quota, treat it as best-effort. If you need guaranteed limits and support, use OpenAI or ElevenLabs.

## Optional keys

If you want OpenAI or ElevenLabs:

Microsoft speech does **not** require an API key. If no API keys are found, OpenClaw defaults to Microsoft (unless disabled via `messages.tts.microsoft.enabled=false` or `messages.tts.edge.enabled=false`). If multiple providers are configured, the selected provider is used first and the others are fallback options. Auto-summary uses the configured `summaryModel` (or `agents.defaults.model.primary`), so that provider must also be authenticated if you enable summaries.

## Service links

## Is it enabled by default?

No. Auto‑TTS is **off** by default. Enable it in config with `messages.tts.auto` or per session with `/tts always` (alias: `/tts on`). Microsoft speech **is** enabled by default once TTS is on, and is used automatically when no OpenAI or ElevenLabs API keys are available.

## Config

TTS config lives under `messages.tts` in `openclaw.json`. Full schema is in [Gateway configuration](https://docs.openclaw.ai/gateway/configuration).

### Minimal config (enable + provider)

### OpenAI primary with ElevenLabs fallback

```
{
  messages: {
    tts: {
      auto: "always",
      provider: "openai",
      summaryModel: "openai/gpt-4.1-mini",
      modelOverrides: {
        enabled: true,
      },
      openai: {
        apiKey: "openai_api_key",
        baseUrl: "https://api.openai.com/v1",
        model: "gpt-4o-mini-tts",
        voice: "alloy",
      },
      elevenlabs: {
        apiKey: "elevenlabs_api_key",
        baseUrl: "https://api.elevenlabs.io",
        voiceId: "voice_id",
        modelId: "eleven_multilingual_v2",
        seed: 42,
        applyTextNormalization: "auto",
        languageCode: "en",
        voiceSettings: {
          stability: 0.5,
          similarityBoost: 0.75,
          style: 0.0,
          useSpeakerBoost: true,
          speed: 1.0,
        },
      },
    },
  },
}
```

### Microsoft primary (no API key)

```
{
  messages: {
    tts: {
      auto: "always",
      provider: "microsoft",
      microsoft: {
        enabled: true,
        voice: "en-US-MichelleNeural",
        lang: "en-US",
        outputFormat: "audio-24khz-48kbitrate-mono-mp3",
        rate: "+10%",
        pitch: "-5%",
      },
    },
  },
}
```

### Disable Microsoft speech

### Custom limits + prefs path

### Only reply with audio after an inbound voice note

Then run:

### Notes on fields

* `auto`: auto‑TTS mode (`off`, `always`, `inbound`, `tagged`).
* `enabled`: legacy toggle (doctor migrates this to `auto`).
* `mode`: `"final"` (default) or `"all"` (includes tool/block replies).
* `provider`: speech provider id such as `"elevenlabs"`, `"microsoft"`, or `"openai"` (fallback is automatic).
* If `provider` is **unset**, OpenClaw prefers `openai` (if key), then `elevenlabs` (if key), otherwise `microsoft`.
* Legacy `provider: "edge"` still works and is normalized to `microsoft`.
* `summaryModel`: optional cheap model for auto-summary; defaults to `agents.defaults.model.primary`.
* `modelOverrides`: allow the model to emit TTS directives (on by default).
* `maxTextLength`: hard cap for TTS input (chars). `/tts audio` fails if exceeded.
* `timeoutMs`: request timeout (ms).
* `prefsPath`: override the local prefs JSON path (provider/limit/summary).
* `apiKey` values fall back to env vars (`ELEVENLABS_API_KEY`/`XI_API_KEY`, `OPENAI_API_KEY`).
* `elevenlabs.baseUrl`: override ElevenLabs API base URL.
* `openai.baseUrl`: override the OpenAI TTS endpoint.
* `elevenlabs.voiceSettings`:
* `elevenlabs.applyTextNormalization`: `auto|on|off`
* `elevenlabs.languageCode`: 2-letter ISO 639-1 (e.g. `en`, `de`)
* `elevenlabs.seed`: integer `0..4294967295` (best-effort determinism)
* `microsoft.enabled`: allow Microsoft speech usage (default `true`; no API key).
* `microsoft.voice`: Microsoft neural voice name (e.g. `en-US-MichelleNeural`).
* `microsoft.lang`: language code (e.g. `en-US`).
* `microsoft.outputFormat`: Microsoft output format (e.g. `audio-24khz-48kbitrate-mono-mp3`).
* `microsoft.rate` / `microsoft.pitch` / `microsoft.volume`: percent strings (e.g. `+10%`, `-5%`).
* `microsoft.saveSubtitles`: write JSON subtitles alongside the audio file.
* `microsoft.proxy`: proxy URL for Microsoft speech requests.
* `microsoft.timeoutMs`: request timeout override (ms).
* `edge.*`: legacy alias for the same Microsoft settings.

## Model-driven overrides (default on)

By default, the model **can** emit TTS directives for a single reply. When `messages.tts.auto` is `tagged`, these directives are required to trigger audio. When enabled, the model can emit `[[tts:...]]` directives to override the voice for a single reply, plus an optional `[[tts:text]]...[[/tts:text]]` block to provide expressive tags (laughter, singing cues, etc) that should only appear in the audio. `provider=...` directives are ignored unless `modelOverrides.allowProvider: true`. Example reply payload:

Available directive keys (when enabled):

Disable all model overrides:

Optional allowlist (enable provider switching while keeping other knobs configurable):

## Per-user preferences

Slash commands write local overrides to `prefsPath` (default: `~/.openclaw/settings/tts.json`, override with `OPENCLAW_TTS_PREFS` or `messages.tts.prefsPath`). Stored fields:

These override `messages.tts.*` for that host.

## Output formats (fixed)

OpenAI/ElevenLabs formats are fixed; Telegram expects Opus for voice-note UX.

## Auto-TTS behavior

When enabled, OpenClaw:

If the reply exceeds `maxLength` and summary is off (or no API key for the summary model), audio is skipped and the normal text reply is sent.

## Flow diagram

## Slash command usage

There is a single command: `/tts`. See [Slash commands](https://docs.openclaw.ai/tools/slash-commands) for enablement details. Discord note: `/tts` is a built-in Discord command, so OpenClaw registers `/voice` as the native command there. Text `/tts ...` still works.

Notes:

## Agent tool

The `tts` tool converts text to speech and returns an audio attachment for reply delivery. When the result is Telegram-compatible, OpenClaw marks it for voice-bubble delivery.

## Gateway RPC

Gateway methods:

----
url: https://docs.openclaw.ai/nodes/voicewake
----

# Voice Wake - OpenClaw

* [Voice Wake (Global Wake Words)](#voice-wake-global-wake-words)
* [Storage (Gateway host)](#storage-gateway-host)
* [Protocol](#protocol)
* [Methods](#methods)
* [Events](#events)
* [Client behavior](#client-behavior)
* [macOS app](#macos-app)
* [iOS node](#ios-node)
* [Android node](#android-node)

## [​](#voice-wake-global-wake-words)Voice Wake (Global Wake Words)

OpenClaw treats **wake words as a single global list** owned by the **Gateway**.

* There are **no per-node custom wake words**.
* **Any node/app UI may edit** the list; changes are persisted by the Gateway and broadcast to everyone.
* macOS and iOS keep local **Voice Wake enabled/disabled** toggles (local UX + permissions differ).
* Android currently keeps Voice Wake off and uses a manual mic flow in the Voice tab.

## [​](#storage-gateway-host)Storage (Gateway host)

Wake words are stored on the gateway machine at:

* `~/.openclaw/settings/voicewake.json`

Shape:

```
{ "triggers": ["openclaw", "claude", "computer"], "updatedAtMs": 1730000000000 }
```

## [​](#protocol)Protocol

### [​](#methods)Methods

* `voicewake.get` → `{ triggers: string[] }`
* `voicewake.set` with params `{ triggers: string[] }` → `{ triggers: string[] }`

Notes:

* Triggers are normalized (trimmed, empties dropped). Empty lists fall back to defaults.
* Limits are enforced for safety (count/length caps).

### [​](#events)Events

* `voicewake.changed` payload `{ triggers: string[] }`

Who receives it:

* All WebSocket clients (macOS app, WebChat, etc.)
* All connected nodes (iOS/Android), and also on node connect as an initial “current state” push.

## [​](#client-behavior)Client behavior

### [​](#macos-app)macOS app

* Uses the global list to gate `VoiceWakeRuntime` triggers.
* Editing “Trigger words” in Voice Wake settings calls `voicewake.set` and then relies on the broadcast to keep other clients in sync.

### [​](#ios-node)iOS node

* Uses the global list for `VoiceWakeManager` trigger detection.
* Editing Wake Words in Settings calls `voicewake.set` (over the Gateway WS) and also keeps local wake-word detection responsive.

### [​](#android-node)Android node

* Voice Wake is currently disabled in Android runtime/Settings.
* Android voice uses manual mic capture in the Voice tab instead of wake-word triggers.

[Talk Mode](https://docs.openclaw.ai/nodes/talk)[Location Command](https://docs.openclaw.ai/nodes/location-command)

----
url: https://docs.openclaw.ai/install/installer
----

# Installer Internals - OpenClaw

OpenClaw ships three installer scripts, served from `openclaw.ai`.

| Script                             | Platform             | What it does                                                                                 |
| ---------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| [`install.sh`](#installsh)         | macOS / Linux / WSL  | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
| [`install-cli.sh`](#install-clish) | macOS / Linux / WSL  | Installs Node + OpenClaw into a local prefix (`~/.openclaw`). No root required.              |
| [`install.ps1`](#installps1)       | Windows (PowerShell) | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |

## Quick commands

***

## install.sh

### Flow (install.sh)

### Source checkout detection

If run inside an OpenClaw checkout (`package.json` + `pnpm-workspace.yaml`), the script offers:

If no TTY is available and no install method is set, it defaults to `npm` and warns. The script exits with code `2` for invalid method selection or invalid `--install-method` values.

### Examples (install.sh)

Flags reference

| Flag                                  | Description                                                |
| ------------------------------------- | ---------------------------------------------------------- |
| `--install-method npm\|git`           | Choose install method (default: `npm`). Alias: `--method`  |
| `--npm`                               | Shortcut for npm method                                    |
| `--git`                               | Shortcut for git method. Alias: `--github`                 |
| `--version <version\|dist-tag\|spec>` | npm version, dist-tag, or package spec (default: `latest`) |
| `--beta`                              | Use beta dist-tag if available, else fallback to `latest`  |
| `--git-dir <path>`                    | Checkout directory (default: `~/openclaw`). Alias: `--dir` |
| `--no-git-update`                     | Skip `git pull` for existing checkout                      |
| `--no-prompt`                         | Disable prompts                                            |
| `--no-onboard`                        | Skip onboarding                                            |
| `--onboard`                           | Enable onboarding                                          |
| `--dry-run`                           | Print actions without applying changes                     |
| `--verbose`                           | Enable debug output (`set -x`, npm notice-level logs)      |
| `--help`                              | Show usage (`-h`)                                          |

Environment variables reference

| Variable                                                | Description                                   |
| ------------------------------------------------------- | --------------------------------------------- |
| `OPENCLAW_INSTALL_METHOD=git\|npm`                      | Install method                                |
| `OPENCLAW_VERSION=latest\|next\|main\|<semver>\|<spec>` | npm version, dist-tag, or package spec        |
| `OPENCLAW_BETA=0\|1`                                    | Use beta if available                         |
| `OPENCLAW_GIT_DIR=<path>`                               | Checkout directory                            |
| `OPENCLAW_GIT_UPDATE=0\|1`                              | Toggle git updates                            |
| `OPENCLAW_NO_PROMPT=1`                                  | Disable prompts                               |
| `OPENCLAW_NO_ONBOARD=1`                                 | Skip onboarding                               |
| `OPENCLAW_DRY_RUN=1`                                    | Dry run mode                                  |
| `OPENCLAW_VERBOSE=1`                                    | Debug mode                                    |
| `OPENCLAW_NPM_LOGLEVEL=error\|warn\|notice`             | npm log level                                 |
| `SHARP_IGNORE_GLOBAL_LIBVIPS=0\|1`                      | Control sharp/libvips behavior (default: `1`) |

***

## install-cli.sh

### Flow (install-cli.sh)

### Examples (install-cli.sh)

Flags reference

| Flag                   | Description                                                                     |
| ---------------------- | ------------------------------------------------------------------------------- |
| `--prefix <path>`      | Install prefix (default: `~/.openclaw`)                                         |
| `--version <ver>`      | OpenClaw version or dist-tag (default: `latest`)                                |
| `--node-version <ver>` | Node version (default: `22.22.0`)                                               |
| `--json`               | Emit NDJSON events                                                              |
| `--onboard`            | Run `openclaw onboard` after install                                            |
| `--no-onboard`         | Skip onboarding (default)                                                       |
| `--set-npm-prefix`     | On Linux, force npm prefix to `~/.npm-global` if current prefix is not writable |
| `--help`               | Show usage (`-h`)                                                               |

Environment variables reference

| Variable                                    | Description                                                                       |
| ------------------------------------------- | --------------------------------------------------------------------------------- |
| `OPENCLAW_PREFIX=<path>`                    | Install prefix                                                                    |
| `OPENCLAW_VERSION=<ver>`                    | OpenClaw version or dist-tag                                                      |
| `OPENCLAW_NODE_VERSION=<ver>`               | Node version                                                                      |
| `OPENCLAW_NO_ONBOARD=1`                     | Skip onboarding                                                                   |
| `OPENCLAW_NPM_LOGLEVEL=error\|warn\|notice` | npm log level                                                                     |
| `OPENCLAW_GIT_DIR=<path>`                   | Legacy cleanup lookup path (used when removing old `Peekaboo` submodule checkout) |
| `SHARP_IGNORE_GLOBAL_LIBVIPS=0\|1`          | Control sharp/libvips behavior (default: `1`)                                     |

***

## install.ps1

### Flow (install.ps1)

### Examples (install.ps1)

Flags reference

| Flag                        | Description                                                |
| --------------------------- | ---------------------------------------------------------- |
| `-InstallMethod npm\|git`   | Install method (default: `npm`)                            |
| `-Tag <tag\|version\|spec>` | npm dist-tag, version, or package spec (default: `latest`) |
| `-GitDir <path>`            | Checkout directory (default: `%USERPROFILE%\openclaw`)     |
| `-NoOnboard`                | Skip onboarding                                            |
| `-NoGitUpdate`              | Skip `git pull`                                            |
| `-DryRun`                   | Print actions only                                         |

Environment variables reference

| Variable                           | Description        |
| ---------------------------------- | ------------------ |
| `OPENCLAW_INSTALL_METHOD=git\|npm` | Install method     |
| `OPENCLAW_GIT_DIR=<path>`          | Checkout directory |
| `OPENCLAW_NO_ONBOARD=1`            | Skip onboarding    |
| `OPENCLAW_GIT_UPDATE=0`            | Disable git pull   |
| `OPENCLAW_DRY_RUN=1`               | Dry run mode       |

***

## CI and automation

Use non-interactive flags/env vars for predictable runs.

***

## Troubleshooting

----
url: https://docs.openclaw.ai/install/migrating
----

# Migration Guide - OpenClaw

## Migrating OpenClaw to a New Machine

This guide moves an OpenClaw gateway to a new machine without redoing onboarding.

## What Gets Migrated

When you copy the **state directory** (`~/.openclaw/` by default) and your **workspace**, you preserve:

## Migration Steps

## Common Pitfalls

Profile or state-dir mismatch

If the old gateway used `--profile` or `OPENCLAW_STATE_DIR` and the new one does not, channels will appear logged out and sessions will be empty. Launch the gateway with the **same** profile or state-dir you migrated, then rerun `openclaw doctor`.

Copying only openclaw\.json

The config file alone is not enough. Credentials live under `credentials/`, and agent state lives under `agents/`. Always migrate the **entire** state directory.

Permissions and ownership

If you copied as root or switched users, the gateway may fail to read credentials. Ensure the state directory and workspace are owned by the user running the gateway.

Remote mode

If your UI points at a **remote** gateway, the remote host owns sessions and workspace. Migrate the gateway host itself, not your local laptop. See [FAQ](https://docs.openclaw.ai/help/faq#where-does-openclaw-store-its-data).

Secrets in backups

The state directory contains API keys, OAuth tokens, and channel credentials. Store backups encrypted, avoid insecure transfer channels, and rotate keys if you suspect exposure.

## Verification Checklist

On the new machine, confirm:

----
url: https://docs.openclaw.ai/providers/bedrock
----

# Amazon Bedrock - OpenClaw

OpenClaw can use **Amazon Bedrock** models via pi‑ai’s **Bedrock Converse** streaming provider. Bedrock auth uses the **AWS SDK default credential chain**, not an API key.

## What pi-ai supports

## Automatic model discovery

If AWS credentials are detected, OpenClaw can automatically discover Bedrock models that support **streaming** and **text output**. Discovery uses `bedrock:ListFoundationModels` and is cached (default: 1 hour). Config options live under `models.bedrockDiscovery`:

Notes:

## Onboarding

1. Ensure AWS credentials are available on the **gateway host**:

2) Add a Bedrock provider and model to your config (no `apiKey` required):

```
{
  models: {
    providers: {
      "amazon-bedrock": {
        baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
        api: "bedrock-converse-stream",
        auth: "aws-sdk",
        models: [
          {
            id: "us.anthropic.claude-opus-4-6-v1:0",
            name: "Claude Opus 4.6 (Bedrock)",
            reasoning: true,
            input: ["text", "image"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 200000,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
  agents: {
    defaults: {
      model: { primary: "amazon-bedrock/us.anthropic.claude-opus-4-6-v1:0" },
    },
  },
}
```

## EC2 Instance Roles

When running OpenClaw on an EC2 instance with an IAM role attached, the AWS SDK will automatically use the instance metadata service (IMDS) for authentication. However, OpenClaw’s credential detection currently only checks for environment variables, not IMDS credentials. **Workaround:** Set `AWS_PROFILE=default` to signal that AWS credentials are available. The actual authentication still uses the instance role via IMDS.

**Required IAM permissions** for the EC2 instance role:

Or attach the managed policy `AmazonBedrockFullAccess`.

## Quick setup (AWS path)

## Notes

----
url: https://docs.openclaw.ai/date-time
----

# Date and Time - OpenClaw

## [​](#date-&-time)Date & Time

OpenClaw defaults to **host-local time for transport timestamps** and **user timezone only in the system prompt**. Provider timestamps are preserved so tools keep their native semantics (current time is available via `session_status`).

## [​](#message-envelopes-local-by-default)Message envelopes (local by default)

Inbound messages are wrapped with a timestamp (minute precision):

```
[Provider ... 2026-01-05 16:26 PST] message text
```

This envelope timestamp is **host-local by default**, regardless of the provider timezone. You can override this behavior:

```
{
  agents: {
    defaults: {
      envelopeTimezone: "local", // "utc" | "local" | "user" | IANA timezone
      envelopeTimestamp: "on", // "on" | "off"
      envelopeElapsed: "on", // "on" | "off"
    },
  },
}
```

* `envelopeTimezone: "utc"` uses UTC.
* `envelopeTimezone: "local"` uses the host timezone.
* `envelopeTimezone: "user"` uses `agents.defaults.userTimezone` (falls back to host timezone).
* Use an explicit IANA timezone (e.g., `"America/Chicago"`) for a fixed zone.
* `envelopeTimestamp: "off"` removes absolute timestamps from envelope headers.
* `envelopeElapsed: "off"` removes elapsed time suffixes (the `+2m` style).

### [​](#examples)Examples

**Local (default):**

```
[WhatsApp +1555 2026-01-18 00:19 PST] hello
```

**User timezone:**

```
[WhatsApp +1555 2026-01-18 00:19 CST] hello
```

**Elapsed time enabled:**

```
[WhatsApp +1555 +30s 2026-01-18T05:19Z] follow-up
```

## [​](#system-prompt-current-date-&-time)System prompt: Current Date & Time

If the user timezone is known, the system prompt includes a dedicated **Current Date & Time** section with the **time zone only** (no clock/time format) to keep prompt caching stable:

```
Time zone: America/Chicago
```

When the agent needs the current time, use the `session_status` tool; the status card includes a timestamp line.

## [​](#system-event-lines-local-by-default)System event lines (local by default)

Queued system events inserted into agent context are prefixed with a timestamp using the same timezone selection as message envelopes (default: host-local).

```
System: [2026-01-12 12:19:17 PST] Model switched.
```

### [​](#configure-user-timezone-+-format)Configure user timezone + format

```
{
  agents: {
    defaults: {
      userTimezone: "America/Chicago",
      timeFormat: "auto", // auto | 12 | 24
    },
  },
}
```

* `userTimezone` sets the **user-local timezone** for prompt context.
* `timeFormat` controls **12h/24h display** in the prompt. `auto` follows OS prefs.

## [​](#time-format-detection-auto)Time format detection (auto)

When `timeFormat: "auto"`, OpenClaw inspects the OS preference (macOS/Windows) and falls back to locale formatting. The detected value is **cached per process** to avoid repeated system calls.

## [​](#tool-payloads-+-connectors-raw-provider-time-+-normalized-fields)Tool payloads + connectors (raw provider time + normalized fields)

Channel tools return **provider-native timestamps** and add normalized fields for consistency:

* `timestampMs`: epoch milliseconds (UTC)
* `timestampUtc`: ISO 8601 UTC string

Raw provider fields are preserved so nothing is lost.

* Slack: epoch-like strings from the API
* Discord: UTC ISO timestamps
* Telegram/WhatsApp: provider-specific numeric/ISO timestamps

If you need local time, convert it downstream using the known timezone.

## [​](#related-docs)Related docs

* [System Prompt](https://docs.openclaw.ai/concepts/system-prompt)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)
* [Messages](https://docs.openclaw.ai/concepts/messages)

----
url: https://docs.openclaw.ai/platforms/android
----

# Android App - OpenClaw

## [​](#android-app-node)Android App (Node)

> **Note:** The Android app has not been publicly released yet. The source code is available in the [OpenClaw repository](https://github.com/openclaw/openclaw) under `apps/android`. You can build it yourself using Java 17 and the Android SDK (`./gradlew :app:assemblePlayDebug`). See [apps/android/README.md](https://github.com/openclaw/openclaw/blob/main/apps/android/README.md) for build instructions.

## [​](#support-snapshot)Support snapshot

* Role: companion node app (Android does not host the Gateway).
* Gateway required: yes (run it on macOS, Linux, or Windows via WSL2).
* Install: [Getting Started](https://docs.openclaw.ai/start/getting-started) + [Pairing](https://docs.openclaw.ai/channels/pairing).
* Gateway: [Runbook](https://docs.openclaw.ai/gateway) + [Configuration](https://docs.openclaw.ai/gateway/configuration).
  * Protocols: [Gateway protocol](https://docs.openclaw.ai/gateway/protocol) (nodes + control plane).

## [​](#system-control)System control

System control (launchd/systemd) lives on the Gateway host. See [Gateway](https://docs.openclaw.ai/gateway).

## [​](#connection-runbook)Connection Runbook

Android node app ⇄ (mDNS/NSD + WebSocket) ⇄ **Gateway** Android connects directly to the Gateway WebSocket (default `ws://<host>:18789`) and uses device pairing (`role: node`).

### [​](#prerequisites)Prerequisites

* You can run the Gateway on the “master” machine.

* Android device/emulator can reach the gateway WebSocket:

  * Same LAN with mDNS/NSD, **or**
  * Same Tailscale tailnet using Wide-Area Bonjour / unicast DNS-SD (see below), **or**
  * Manual gateway host/port (fallback)

* You can run the CLI (`openclaw`) on the gateway machine (or via SSH).

### [​](#1-start-the-gateway)1) Start the Gateway

```
openclaw gateway --port 18789 --verbose
```

Confirm in logs you see something like:

* `listening on ws://0.0.0.0:18789`

For tailnet-only setups (recommended for Vienna ⇄ London), bind the gateway to the tailnet IP:

* Set `gateway.bind: "tailnet"` in `~/.openclaw/openclaw.json` on the gateway host.
* Restart the Gateway / macOS menubar app.

### [​](#2-verify-discovery-optional)2) Verify discovery (optional)

From the gateway machine:

```
dns-sd -B _openclaw-gw._tcp local.
```

More debugging notes: [Bonjour](https://docs.openclaw.ai/gateway/bonjour).

#### [​](#tailnet-vienna-⇄-london-discovery-via-unicast-dns-sd)Tailnet (Vienna ⇄ London) discovery via unicast DNS-SD

Android NSD/mDNS discovery won’t cross networks. If your Android node and the gateway are on different networks but connected via Tailscale, use Wide-Area Bonjour / unicast DNS-SD instead:

1. Set up a DNS-SD zone (example `openclaw.internal.`) on the gateway host and publish `_openclaw-gw._tcp` records.
2. Configure Tailscale split DNS for your chosen domain pointing at that DNS server.

Details and example CoreDNS config: [Bonjour](https://docs.openclaw.ai/gateway/bonjour).

### [​](#3-connect-from-android)3) Connect from Android

In the Android app:

* The app keeps its gateway connection alive via a **foreground service** (persistent notification).
* Open the **Connect** tab.
* Use **Setup Code** or **Manual** mode.
* If discovery is blocked, use manual host/port (and TLS/token/password when required) in **Advanced controls**.

After the first successful pairing, Android auto-reconnects on launch:

* Manual endpoint (if enabled), otherwise
* The last discovered gateway (best-effort).

### [​](#4-approve-pairing-cli)4) Approve pairing (CLI)

On the gateway machine:

```
openclaw devices list
openclaw devices approve <requestId>
openclaw devices reject <requestId>
```

Pairing details: [Pairing](https://docs.openclaw.ai/channels/pairing).

### [​](#5-verify-the-node-is-connected)5) Verify the node is connected

* Via nodes status:
  ```
  openclaw nodes status
  ```
* Via Gateway:
  ```
  openclaw gateway call node.list --params "{}"
  ```

### [​](#6-chat-+-history)6) Chat + history

The Android Chat tab supports session selection (default `main`, plus other existing sessions):

* History: `chat.history`
* Send: `chat.send`
* Push updates (best-effort): `chat.subscribe` → `event:"chat"`

### [​](#7-canvas-+-camera)7) Canvas + camera

#### [​](#gateway-canvas-host-recommended-for-web-content)Gateway Canvas Host (recommended for web content)

If you want the node to show real HTML/CSS/JS that the agent can edit on disk, point the node at the Gateway canvas host. Note: nodes load canvas from the Gateway HTTP server (same port as `gateway.port`, default `18789`).

1. Create `~/.openclaw/workspace/canvas/index.html` on the gateway host.
2. Navigate the node to it (LAN):

```
openclaw nodes invoke --node "<Android Node>" --command canvas.navigate --params '{"url":"http://<gateway-hostname>.local:18789/__openclaw__/canvas/"}'
```

Tailnet (optional): if both devices are on Tailscale, use a MagicDNS name or tailnet IP instead of `.local`, e.g. `http://<gateway-magicdns>:18789/__openclaw__/canvas/`. This server injects a live-reload client into HTML and reloads on file changes. The A2UI host lives at `http://<gateway-host>:18789/__openclaw__/a2ui/`. Canvas commands (foreground only):

* `canvas.eval`, `canvas.snapshot`, `canvas.navigate` (use `{"url":""}` or `{"url":"/"}` to return to the default scaffold). `canvas.snapshot` returns `{ format, base64 }` (default `format="jpeg"`).
* A2UI: `canvas.a2ui.push`, `canvas.a2ui.reset` (`canvas.a2ui.pushJSONL` legacy alias)

Camera commands (foreground only; permission-gated):

* `camera.snap` (jpg)
* `camera.clip` (mp4)

See [Camera node](https://docs.openclaw.ai/nodes/camera) for parameters and CLI helpers.

### [​](#8-voice-+-expanded-android-command-surface)8) Voice + expanded Android command surface

* Voice: Android uses a single mic on/off flow in the Voice tab with transcript capture and TTS playback (ElevenLabs when configured, system TTS fallback). Voice stops when the app leaves the foreground.

* Voice wake/talk-mode toggles are currently removed from Android UX/runtime.

* Additional Android command families (availability depends on device + permissions):

  * `device.status`, `device.info`, `device.permissions`, `device.health`
  * `notifications.list`, `notifications.actions`
  * `photos.latest`
  * `contacts.search`, `contacts.add`
  * `calendar.events`, `calendar.add`
  * `callLog.search`
  * `sms.search`
  * `motion.activity`, `motion.pedometer`

----
url: https://docs.openclaw.ai/nodes
----

# Nodes - OpenClaw

A **node** is a companion device (macOS/iOS/Android/headless) that connects to the Gateway **WebSocket** (same port as operators) with `role: "node"` and exposes a command surface (e.g. `canvas.*`, `camera.*`, `device.*`, `notifications.*`, `system.*`) via `node.invoke`. Protocol details: [Gateway protocol](https://docs.openclaw.ai/gateway/protocol). Legacy transport: [Bridge protocol](https://docs.openclaw.ai/gateway/bridge-protocol) (TCP JSONL; deprecated/removed for current nodes). macOS can also run in **node mode**: the menubar app connects to the Gateway’s WS server and exposes its local canvas/camera commands as a node (so `openclaw nodes …` works against this Mac). Notes:

## Pairing + status

**WS nodes use device pairing.** Nodes present a device identity during `connect`; the Gateway creates a device pairing request for `role: node`. Approve via the devices CLI (or UI). Quick CLI:

If a node retries with changed auth details (role/scopes/public key), the prior pending request is superseded and a new `requestId` is created. Re-run `openclaw devices list` before approving. Notes:

## Remote node host (system.run)

Use a **node host** when your Gateway runs on one machine and you want commands to execute on another. The model still talks to the **gateway**; the gateway forwards `exec` calls to the **node host** when `host=node` is selected.

### What runs where

Approval note:

### Start a node host (foreground)

On the node machine:

### Remote gateway via SSH tunnel (loopback bind)

If the Gateway binds to loopback (`gateway.bind=loopback`, default in local mode), remote node hosts cannot connect directly. Create an SSH tunnel and point the node host at the local end of the tunnel. Example (node host -> gateway host):

Notes:

### Start a node host (service)

### Pair + name

On the gateway host:

If the node retries with changed auth details, re-run `openclaw devices list` and approve the current `requestId`. Naming options:

### Allowlist the commands

Exec approvals are **per node host**. Add allowlist entries from the gateway:

Approvals live on the node host at `~/.openclaw/exec-approvals.json`.

### Point exec at the node

Configure defaults (gateway config):

Or per session:

Once set, any `exec` call with `host=node` runs on the node host (subject to the node allowlist/approvals). Related:

## Invoking commands

Low-level (raw RPC):

Higher-level helpers exist for the common “give the agent a MEDIA attachment” workflows.

## Screenshots (canvas snapshots)

If the node is showing the Canvas (WebView), `canvas.snapshot` returns `{ format, base64 }`. CLI helper (writes to a temp file and prints `MEDIA:<path>`):

### Canvas controls

Notes:

### A2UI (Canvas)

Notes:

## Photos + videos (node camera)

Photos (`jpg`):

Video clips (`mp4`):

Notes:

## Screen recordings (nodes)

Supported nodes expose `screen.record` (mp4). Example:

Notes:

## Location (nodes)

Nodes expose `location.get` when Location is enabled in settings. CLI helper:

Notes:

## SMS (Android nodes)

Android nodes can expose `sms.send` when the user grants **SMS** permission and the device supports telephony. Low-level invoke:

Notes:

## Android device + personal data commands

Android nodes can advertise additional command families when the corresponding capabilities are enabled. Available families:

Example invokes:

Notes:

## System commands (node host / mac node)

The macOS node exposes `system.run`, `system.notify`, and `system.execApprovals.get/set`. The headless node host exposes `system.run`, `system.which`, and `system.execApprovals.get/set`. Examples:

Notes:

* `system.run` returns stdout/stderr/exit code in the payload.
* `system.notify` respects notification permission state on the macOS app.
* Unrecognized node `platform` / `deviceFamily` metadata uses a conservative default allowlist that excludes `system.run` and `system.which`. If you intentionally need those commands for an unknown platform, add them explicitly via `gateway.nodes.allowCommands`.
* `system.run` supports `--cwd`, `--env KEY=VAL`, `--command-timeout`, and `--needs-screen-recording`.
* For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped `--env` values are reduced to an explicit allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`).
* For allow-always decisions in allowlist mode, known dispatch wrappers (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist inner executable paths instead of wrapper paths. If unwrapping is not safe, no allowlist entry is persisted automatically.
* On Windows node hosts in allowlist mode, shell-wrapper runs via `cmd.exe /c` require approval (allowlist entry alone does not auto-allow the wrapper form).
* `system.notify` supports `--priority <passive|active|timeSensitive>` and `--delivery <system|overlay|auto>`.
* Node hosts ignore `PATH` overrides and strip dangerous startup/shell keys (`DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`, `SHELLOPTS`, `PS4`). If you need extra PATH entries, configure the node host service environment (or install tools in standard locations) instead of passing `PATH` via `--env`.
* On macOS node mode, `system.run` is gated by exec approvals in the macOS app (Settings → Exec approvals). Ask/allowlist/full behave the same as the headless node host; denied prompts return `SYSTEM_RUN_DENIED`.
* On headless node host, `system.run` is gated by exec approvals (`~/.openclaw/exec-approvals.json`).

## Exec node binding

When multiple nodes are available, you can bind exec to a specific node. This sets the default node for `exec host=node` (and can be overridden per agent). Global default:

Per-agent override:

Unset to allow any node:

## Permissions map

Nodes may include a `permissions` map in `node.list` / `node.describe`, keyed by permission name (e.g. `screenRecording`, `accessibility`) with boolean values (`true` = granted).

## Headless node host (cross-platform)

OpenClaw can run a **headless node host** (no UI) that connects to the Gateway WebSocket and exposes `system.run` / `system.which`. This is useful on Linux/Windows or for running a minimal node alongside a server. Start it:

Notes:

## Mac node mode

----
url: https://docs.openclaw.ai/start/setup
----

# Setup - OpenClaw

## TL;DR

## Prereqs (from source)

## Tailoring strategy (so updates do not hurt)

If you want “100% tailored to me” *and* easy updates, keep your customization in:

Bootstrap once:

From inside this repo, use the local CLI entry:

If you don’t have a global install yet, run it via `pnpm openclaw setup`.

## Run the Gateway from this repo

After `pnpm build`, you can run the packaged CLI directly:

## Stable workflow (macOS app first)

1. Install + launch **OpenClaw\.app** (menu bar).
2. Complete the onboarding/permissions checklist (TCC prompts).
3. Ensure Gateway is **Local** and running (the app manages it).
4. Link surfaces (example: WhatsApp):

5) Sanity check:

If onboarding is not available in your build:

## Bleeding edge workflow (Gateway in a terminal)

Goal: work on the TypeScript Gateway, get hot reload, keep the macOS app UI attached.

### 0) (Optional) Run the macOS app from source too

If you also want the macOS app on the bleeding edge:

### 1) Start the dev Gateway

`gateway:watch` runs the gateway in watch mode and reloads on relevant source, config, and bundled-plugin metadata changes.

### 2) Point the macOS app at your running Gateway

In **OpenClaw\.app**:

### 3) Verify

### Common footguns

## Credential storage map

Use this when debugging auth or deciding what to back up:

## Updating (without wrecking your setup)

## Linux (systemd user service)

Linux installs use a systemd **user** service. By default, systemd stops user services on logout/idle, which kills the Gateway. Onboarding attempts to enable lingering for you (may prompt for sudo). If it’s still off, run:

For always-on or multi-user servers, consider a **system** service instead of a user service (no lingering needed). See [Gateway runbook](https://docs.openclaw.ai/gateway) for the systemd notes.

----
url: https://docs.openclaw.ai/platforms/mac/health
----

# Health Checks (macOS) - OpenClaw

## [​](#health-checks-on-macos)Health Checks on macOS

How to see whether the linked channel is healthy from the menu bar app.

## [​](#menu-bar)Menu bar

* Status dot now reflects Baileys health:

  * Green: linked + socket opened recently.
  * Orange: connecting/retrying.
  * Red: logged out or probe failed.

* Secondary line reads “linked · auth 12m” or shows the failure reason.

* “Run Health Check” menu item triggers an on-demand probe.

## [​](#settings)Settings

* General tab gains a Health card showing: linked auth age, session-store path/count, last check time, last error/status code, and buttons for Run Health Check / Reveal Logs.
* Uses a cached snapshot so the UI loads instantly and falls back gracefully when offline.
* **Channels tab** surfaces channel status + controls for WhatsApp/Telegram (login QR, logout, probe, last disconnect/error).

## [​](#how-the-probe-works)How the probe works

* App runs `openclaw health --json` via `ShellExecutor` every \~60s and on demand. The probe loads creds and reports status without sending messages.
* Cache the last good snapshot and the last error separately to avoid flicker; show the timestamp of each.

## [​](#when-in-doubt)When in doubt

* You can still use the CLI flow in [Gateway health](https://docs.openclaw.ai/gateway/health) (`openclaw status`, `openclaw status --deep`, `openclaw health --json`) and tail `/tmp/openclaw/openclaw-*.log` for `web-heartbeat` / `web-reconnect`.

[Gateway Lifecycle](https://docs.openclaw.ai/platforms/mac/child-process)[Menu Bar Icon](https://docs.openclaw.ai/platforms/mac/icon)

----
url: https://docs.openclaw.ai/concepts/timezone
----

# Timezones - OpenClaw

## [​](#timezones)Timezones

OpenClaw standardizes timestamps so the model sees a **single reference time**.

## [​](#message-envelopes-local-by-default)Message envelopes (local by default)

Inbound messages are wrapped in an envelope like:

```
[Provider ... 2026-01-05 16:26 PST] message text
```

The timestamp in the envelope is **host-local by default**, with minutes precision. You can override this with:

```
{
  agents: {
    defaults: {
      envelopeTimezone: "local", // "utc" | "local" | "user" | IANA timezone
      envelopeTimestamp: "on", // "on" | "off"
      envelopeElapsed: "on", // "on" | "off"
    },
  },
}
```

* `envelopeTimezone: "utc"` uses UTC.
* `envelopeTimezone: "user"` uses `agents.defaults.userTimezone` (falls back to host timezone).
* Use an explicit IANA timezone (e.g., `"Europe/Vienna"`) for a fixed offset.
* `envelopeTimestamp: "off"` removes absolute timestamps from envelope headers.
* `envelopeElapsed: "off"` removes elapsed time suffixes (the `+2m` style).

### [​](#examples)Examples

**Local (default):**

```
[Signal Alice +1555 2026-01-18 00:19 PST] hello
```

**Fixed timezone:**

```
[Signal Alice +1555 2026-01-18 06:19 GMT+1] hello
```

**Elapsed time:**

```
[Signal Alice +1555 +2m 2026-01-18T05:19Z] follow-up
```

## [​](#tool-payloads-raw-provider-data-+-normalized-fields)Tool payloads (raw provider data + normalized fields)

Tool calls (`channels.discord.readMessages`, `channels.slack.readMessages`, etc.) return **raw provider timestamps**. We also attach normalized fields for consistency:

* `timestampMs` (UTC epoch milliseconds)
* `timestampUtc` (ISO 8601 UTC string)

Raw provider fields are preserved.

## [​](#user-timezone-for-the-system-prompt)User timezone for the system prompt

Set `agents.defaults.userTimezone` to tell the model the user’s local time zone. If it is unset, OpenClaw resolves the **host timezone at runtime** (no config write).

```
{
  agents: { defaults: { userTimezone: "America/Chicago" } },
}
```

The system prompt includes:

* `Current Date & Time` section with local time and timezone
* `Time format: 12-hour` or `24-hour`

You can control the prompt format with `agents.defaults.timeFormat` (`auto` | `12` | `24`). See [Date & Time](https://docs.openclaw.ai/date-time) for the full behavior and examples.

----
url: https://docs.openclaw.ai/concepts/retry
----

# Retry Policy - OpenClaw

## [​](#retry-policy)Retry policy

## [​](#goals)Goals

* Retry per HTTP request, not per multi-step flow.
* Preserve ordering by retrying only the current step.
* Avoid duplicating non-idempotent operations.

## [​](#defaults)Defaults

* Attempts: 3

* Max delay cap: 30000 ms

* Jitter: 0.1 (10 percent)

* Provider defaults:

  * Telegram min delay: 400 ms
  * Discord min delay: 500 ms

## [​](#behavior)Behavior

### [​](#discord)Discord

* Retries only on rate-limit errors (HTTP 429).
* Uses Discord `retry_after` when available, otherwise exponential backoff.

### [​](#telegram)Telegram

* Retries on transient errors (429, timeout, connect/reset/closed, temporarily unavailable).
* Uses `retry_after` when available, otherwise exponential backoff.
* Markdown parse errors are not retried; they fall back to plain text.

## [​](#configuration)Configuration

Set retry policy per provider in `~/.openclaw/openclaw.json`:

```
{
  channels: {
    telegram: {
      retry: {
        attempts: 3,
        minDelayMs: 400,
        maxDelayMs: 30000,
        jitter: 0.1,
      },
    },
    discord: {
      retry: {
        attempts: 3,
        minDelayMs: 500,
        maxDelayMs: 30000,
        jitter: 0.1,
      },
    },
  },
}
```

## [​](#notes)Notes

* Retries apply per request (message send, media upload, reaction, poll, sticker).
* Composite flows do not retry completed steps.

----
url: https://docs.openclaw.ai/cli/tui
----

# tui - OpenClaw

## [​](#openclaw-tui)`openclaw tui`

Open the terminal UI connected to the Gateway. Related:

* TUI guide: [TUI](https://docs.openclaw.ai/web/tui)

Notes:

* `tui` resolves configured gateway auth SecretRefs for token/password auth when possible (`env`/`file`/`exec` providers).
* When launched from inside a configured agent workspace directory, TUI auto-selects that agent for the session key default (unless `--session` is explicitly `agent:<id>:...`).

## [​](#examples)Examples

```
openclaw tui
openclaw tui --url ws://127.0.0.1:18789 --token <token>
openclaw tui --session main --deliver
# when run inside an agent workspace, infers that agent automatically
openclaw tui --session bugfix
```

----
url: https://docs.openclaw.ai/cli/gateway
----

# gateway - OpenClaw

## Gateway CLI

The Gateway is OpenClaw’s WebSocket server (channels, nodes, sessions, hooks). Subcommands in this page live under `openclaw gateway …`. Related docs:

## Run the Gateway

Run a local Gateway process:

Foreground alias:

Notes:

### Options

## Query a running Gateway

All query commands use WebSocket RPC. Output modes:

Shared options (where supported):

Note: when you set `--url`, the CLI does not fall back to config or environment credentials. Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.

### `gateway health`

### `gateway status`

`gateway status` shows the Gateway service (launchd/systemd/schtasks) plus an optional RPC probe.

Options:

Notes:

### `gateway probe`

`gateway probe` is the “debug everything” command. It always probes:

If multiple gateways are reachable, it prints all of them. Multiple gateways are supported when you use isolated profiles/ports (e.g., a rescue bot), but most installs still run a single gateway.

Interpretation:

JSON notes (`--json`):

#### Remote over SSH (Mac app parity)

The macOS app “Remote over SSH” mode uses a local port-forward so the remote gateway (which may be bound to loopback only) becomes reachable at `ws://127.0.0.1:<port>`. CLI equivalent:

Options:

Config (optional, used as defaults):

### `gateway call <method>`

Low-level RPC helper.

## Manage the Gateway service

Notes:

* `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
* When token auth requires a token and `gateway.auth.token` is SecretRef-managed, `gateway install` validates that the SecretRef is resolvable but does not persist the resolved token into service environment metadata.
* If token auth requires a token and the configured token SecretRef is unresolved, install fails closed instead of persisting fallback plaintext.
* For password auth on `gateway run`, prefer `OPENCLAW_GATEWAY_PASSWORD`, `--password-file`, or a SecretRef-backed `gateway.auth.password` over inline `--password`.
* In inferred auth mode, shell-only `OPENCLAW_GATEWAY_PASSWORD` does not relax install token requirements; use durable config (`gateway.auth.password` or config `env`) when installing a managed service.
* If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, install is blocked until mode is set explicitly.
* Lifecycle commands accept `--json` for scripting.

## Discover gateways (Bonjour)

`gateway discover` scans for Gateway beacons (`_openclaw-gw._tcp`).

Only gateways with Bonjour discovery enabled (default) advertise the beacon. Wide-Area discovery records include (TXT):

### `gateway discover`

Options:

Examples:

----
url: https://docs.openclaw.ai/cli/agent
----

# agent - OpenClaw

## [​](#openclaw-agent)`openclaw agent`

Run an agent turn via the Gateway (use `--local` for embedded). Use `--agent <id>` to target a configured agent directly. Related:

* Agent send tool: [Agent send](https://docs.openclaw.ai/tools/agent-send)

## [​](#examples)Examples

```
openclaw agent --to +15555550123 --message "status update" --deliver
openclaw agent --agent ops --message "Summarize logs"
openclaw agent --session-id 1234 --message "Summarize inbox" --thinking medium
openclaw agent --agent ops --message "Generate report" --deliver --reply-channel slack --reply-to "#reports"
```

## [​](#notes)Notes

* When this command triggers `models.json` regeneration, SecretRef-managed provider credentials are persisted as non-secret markers (for example env var names, `secretref-env:ENV_VAR_NAME`, or `secretref-managed`), not resolved secret plaintext.
* Marker writes are source-authoritative: OpenClaw persists markers from the active source config snapshot, not from resolved runtime secret values.

----
url: https://docs.openclaw.ai/cli/cron
----

# cron - OpenClaw

## [​](#openclaw-cron)`openclaw cron`

Manage cron jobs for the Gateway scheduler. Related:

* Cron jobs: [Cron jobs](https://docs.openclaw.ai/automation/cron-jobs)

Tip: run `openclaw cron --help` for the full command surface. Note: isolated `cron add` jobs default to `--announce` delivery. Use `--no-deliver` to keep output internal. `--deliver` remains as a deprecated alias for `--announce`. Note: one-shot (`--at`) jobs delete after success by default. Use `--keep-after-run` to keep them. Note: recurring jobs now use exponential retry backoff after consecutive errors (30s → 1m → 5m → 15m → 60m), then return to normal schedule after the next successful run. Note: `openclaw cron run` now returns as soon as the manual run is queued for execution. Successful responses include `{ ok: true, enqueued: true, runId }`; use `openclaw cron runs --id <job-id>` to follow the eventual outcome. Note: retention/pruning is controlled in config:

* `cron.sessionRetention` (default `24h`) prunes completed isolated run sessions.
* `cron.runLog.maxBytes` + `cron.runLog.keepLines` prune `~/.openclaw/cron/runs/<jobId>.jsonl`.

Upgrade note: if you have older cron jobs from before the current delivery/store format, run `openclaw doctor --fix`. Doctor now normalizes legacy cron fields (`jobId`, `schedule.cron`, top-level delivery fields, payload `provider` delivery aliases) and migrates simple `notify: true` webhook fallback jobs to explicit webhook delivery when `cron.webhook` is configured.

## [​](#common-edits)Common edits

Update delivery settings without changing the message:

```
openclaw cron edit <job-id> --announce --channel telegram --to "123456789"
```

Disable delivery for an isolated job:

```
openclaw cron edit <job-id> --no-deliver
```

Enable lightweight bootstrap context for an isolated job:

```
openclaw cron edit <job-id> --light-context
```

Announce to a specific channel:

```
openclaw cron edit <job-id> --announce --channel slack --to "channel:C1234567890"
```

Create an isolated job with lightweight bootstrap context:

```
openclaw cron add \
  --name "Lightweight morning brief" \
  --cron "0 7 * * *" \
  --session isolated \
  --message "Summarize overnight updates." \
  --light-context \
  --no-deliver
```

`--light-context` applies to isolated agent-turn jobs only. For cron runs, lightweight mode keeps bootstrap context empty instead of injecting the full workspace bootstrap set.

----
url: https://docs.openclaw.ai/plugins/sdk-migration
----

# Plugin SDK Migration - OpenClaw

OpenClaw has moved from a broad backwards-compatibility layer to a modern plugin architecture with focused, documented imports. If your plugin was built before the new architecture, this guide helps you migrate.

## What is changing

The old plugin system provided two wide-open surfaces that let plugins import anything they needed from a single entry point:

Both surfaces are now **deprecated**. They still work at runtime, but new plugins must not use them, and existing plugins should migrate before the next major release removes them.

## Why this changed

The old approach caused problems:

The modern plugin SDK fixes this: each import path (`openclaw/plugin-sdk/\<subpath\>`) is a small, self-contained module with a clear purpose and documented contract.

## How to migrate

Replace with focused imports

Each export from the old surface maps to a specific modern import path:

For host-side helpers, use the injected plugin runtime instead of importing directly:

The same pattern applies to other legacy bridge helpers:

| Old import                 | Modern equivalent                            |
| -------------------------- | -------------------------------------------- |
| `resolveAgentDir`          | `api.runtime.agent.resolveAgentDir`          |
| `resolveAgentWorkspaceDir` | `api.runtime.agent.resolveAgentWorkspaceDir` |
| `resolveAgentIdentity`     | `api.runtime.agent.resolveAgentIdentity`     |
| `resolveThinkingDefault`   | `api.runtime.agent.resolveThinkingDefault`   |
| `resolveAgentTimeoutMs`    | `api.runtime.agent.resolveAgentTimeoutMs`    |
| `ensureAgentWorkspace`     | `api.runtime.agent.ensureAgentWorkspace`     |
| session store helpers      | `api.runtime.agent.session.*`                |

## Import path reference

Full import path table

| Import path                         | Purpose                                                 | Key exports                                           |
| ----------------------------------- | ------------------------------------------------------- | ----------------------------------------------------- |
| `plugin-sdk/plugin-entry`           | Canonical plugin entry helper                           | `definePluginEntry`                                   |
| `plugin-sdk/core`                   | Channel entry definitions, channel builders, base types | `defineChannelPluginEntry`, `createChatChannelPlugin` |
| `plugin-sdk/channel-setup`          | Setup wizard adapters                                   | `createOptionalChannelSetupSurface`                   |
| `plugin-sdk/channel-pairing`        | DM pairing primitives                                   | `createChannelPairingController`                      |
| `plugin-sdk/channel-reply-pipeline` | Reply prefix + typing wiring                            | `createChannelReplyPipeline`                          |
| `plugin-sdk/channel-config-helpers` | Config adapter factories                                | `createHybridChannelConfigAdapter`                    |
| `plugin-sdk/channel-config-schema`  | Config schema builders                                  | Channel config schema types                           |
| `plugin-sdk/channel-policy`         | Group/DM policy resolution                              | `resolveChannelGroupRequireMention`                   |
| `plugin-sdk/channel-lifecycle`      | Account status tracking                                 | `createAccountStatusSink`                             |
| `plugin-sdk/channel-runtime`        | Runtime wiring helpers                                  | Channel runtime utilities                             |
| `plugin-sdk/channel-send-result`    | Send result types                                       | Reply result types                                    |
| `plugin-sdk/runtime-store`          | Persistent plugin storage                               | `createPluginRuntimeStore`                            |
| `plugin-sdk/allow-from`             | Allowlist formatting                                    | `formatAllowFromLowercase`                            |
| `plugin-sdk/allowlist-resolution`   | Allowlist input mapping                                 | `mapAllowlistResolutionInputs`                        |
| `plugin-sdk/command-auth`           | Command gating                                          | `resolveControlCommandGate`                           |
| `plugin-sdk/secret-input`           | Secret input parsing                                    | Secret input helpers                                  |
| `plugin-sdk/webhook-ingress`        | Webhook request helpers                                 | Webhook target utilities                              |
| `plugin-sdk/reply-payload`          | Message reply types                                     | Reply payload types                                   |
| `plugin-sdk/provider-onboard`       | Provider onboarding patches                             | Onboarding config helpers                             |
| `plugin-sdk/keyed-async-queue`      | Ordered async queue                                     | `KeyedAsyncQueue`                                     |
| `plugin-sdk/testing`                | Test utilities                                          | Test helpers and mocks                                |

Use the narrowest import that matches the job. If you cannot find an export, check the source at `src/plugin-sdk/` or ask in Discord.

## Removal timeline

| When                   | What happens                                                            |
| ---------------------- | ----------------------------------------------------------------------- |
| **Now**                | Deprecated surfaces emit runtime warnings                               |
| **Next major release** | Deprecated surfaces will be removed; plugins still using them will fail |

All core plugins have already been migrated. External plugins should migrate before the next major release.

## Suppressing the warnings temporarily

Set these environment variables while you work on migrating:

This is a temporary escape hatch, not a permanent solution.

----
url: https://docs.openclaw.ai/start/showcase
----

# Showcase - OpenClaw

## Showcase

Real projects from the community. See what people are building with OpenClaw.

## 🎥 OpenClaw in Action

Full setup walkthrough (28m) by VelvetShark.

[Watch on YouTube](https://www.youtube.com/watch?v=SaWSPZoPX34)

[Watch on YouTube](https://www.youtube.com/watch?v=mMSKQvlmFuQ)

[Watch on YouTube](https://www.youtube.com/watch?v=5kkIJNUGFho)

## 🆕 Fresh from Discord

## PR Review → Telegram Feedback

**@bangnokia** • `review` `github` `telegram`OpenCode finishes the change → opens a PR → OpenClaw reviews the diff and replies in Telegram with “minor suggestions” plus a clear merge verdict (including critical fixes to apply first).

## Wine Cellar Skill in Minutes

**@prades\_maxime** • `skills` `local` `csv`Asked “Robby” (@openclaw) for a local wine cellar skill. It requests a sample CSV export + where to store it, then builds/tests the skill fast (962 bottles in the example).

## Tesco Shop Autopilot

**@marchattonhere** • `automation` `browser` `shopping`Weekly meal plan → regulars → book delivery slot → confirm order. No APIs, just browser control.

## SNAG Screenshot-to-Markdown

**@am-will** • `devtools` `screenshots` `markdown`Hotkey a screen region → Gemini vision → instant Markdown in your clipboard.

## Agents UI

**@kitze** • `ui` `skills` `sync`Desktop app to manage skills/commands across Agents, Claude, Codex, and OpenClaw.

## Telegram Voice Notes (papla.media)

**Community** • `voice` `tts` `telegram`Wraps papla.media TTS and sends results as Telegram voice notes (no annoying autoplay).

## CodexMonitor

**@odrobnik** • `devtools` `codex` `brew`Homebrew-installed helper to list/inspect/watch local OpenAI Codex sessions (CLI + VS Code).

## Bambu 3D Printer Control

**@tobiasbischoff** • `hardware` `3d-printing` `skill`Control and troubleshoot BambuLab printers: status, jobs, camera, AMS, calibration, and more.

## Vienna Transport (Wiener Linien)

**@hjanuschka** • `travel` `transport` `skill`Real-time departures, disruptions, elevator status, and routing for Vienna’s public transport.

## iOS App via Telegram

**@coard** • `ios` `xcode` `testflight`Built a complete iOS app with maps and voice recording, deployed to TestFlight entirely via Telegram chat.

## Oura Ring Health Assistant

**@AS** • `health` `oura` `calendar`Personal AI health assistant integrating Oura ring data with calendar, appointments, and gym schedule.

## 🤖 Automation & Workflows

## Winix Air Purifier Control

**@antonplex** • `automation` `hardware` `air-quality`Claude Code discovered and confirmed the purifier controls, then OpenClaw takes over to manage room air quality.

## Pretty Sky Camera Shots

**@signalgaining** • `automation` `camera` `skill` `images`Triggered by a roof camera: ask OpenClaw to snap a sky photo whenever it looks pretty — it designed a skill and took the shot.

## Padel Court Booking

**@joshp123** • `automation` `booking` `cli`Playtomic availability checker + booking CLI. Never miss an open court again.

## 🧠 Knowledge & Memory

## xuezh Chinese Learning

**@joshp123** • `learning` `voice` `skill`Chinese learning engine with pronunciation feedback and study flows via OpenClaw.

## 🎙️ Voice & Phone

## 🏗️ Infrastructure & Deployment

## 🏠 Home & Hardware

## GoHome Automation

**@joshp123** • `home` `nix` `grafana`Nix-native home automation with OpenClaw as the interface, plus beautiful Grafana dashboards.

## Roborock Vacuum

**@joshp123** • `vacuum` `iot` `plugin`Control your Roborock robot vacuum through natural conversation.

## 🌟 Community Projects

***

## Submit Your Project

Have something to share? We’d love to feature it!

----
url: https://docs.openclaw.ai/concepts/compaction
----

# Compaction - OpenClaw

## Context Window & Compaction

Every model has a **context window** (max tokens it can see). Long-running chats accumulate messages and tool results; once the window is tight, OpenClaw **compacts** older history to stay within limits.

## What compaction is

Compaction **summarizes older conversation** into a compact summary entry and keeps recent messages intact. The summary is stored in the session history, so future requests use:

Compaction **persists** in the session’s JSONL history.

## Configuration

Use the `agents.defaults.compaction` setting in your `openclaw.json` to configure compaction behavior (mode, target tokens, etc.). Compaction summarization preserves opaque identifiers by default (`identifierPolicy: "strict"`). You can override this with `identifierPolicy: "off"` or provide custom text with `identifierPolicy: "custom"` and `identifierInstructions`. You can optionally specify a different model for compaction summarization via `agents.defaults.compaction.model`. This is useful when your primary model is a local or small model and you want compaction summaries produced by a more capable model. The override accepts any `provider/model-id` string:

This also works with local models, for example a second Ollama model dedicated to summarization or a fine-tuned compaction specialist:

When unset, compaction uses the agent’s primary model.

## Auto-compaction (default on)

When a session nears or exceeds the model’s context window, OpenClaw triggers auto-compaction and may retry the original request using the compacted context. You’ll see:

Before compaction, OpenClaw can run a **silent memory flush** turn to store durable notes to disk. See [Memory](https://docs.openclaw.ai/concepts/memory) for details and config.

## Manual compaction

Use `/compact` (optionally with instructions) to force a compaction pass:

## Context window source

Context window is model-specific. OpenClaw uses the model definition from the configured provider catalog to determine limits.

## Compaction vs pruning

See [/concepts/session-pruning](https://docs.openclaw.ai/concepts/session-pruning) for pruning details.

## OpenAI server-side compaction

OpenClaw also supports OpenAI Responses server-side compaction hints for compatible direct OpenAI models. This is separate from local OpenClaw compaction and can run alongside it.

See [OpenAI provider](https://docs.openclaw.ai/providers/openai) for model params and overrides.

## Custom context engines

Compaction behavior is owned by the active [context engine](https://docs.openclaw.ai/concepts/context-engine). The legacy engine uses the built-in summarization described above. Plugin engines (selected via `plugins.slots.contextEngine`) can implement any compaction strategy — DAG summaries, vector retrieval, incremental condensation, etc. When a plugin engine sets `ownsCompaction: true`, OpenClaw delegates all compaction decisions to the engine and does not run built-in auto-compaction. When `ownsCompaction` is `false` or unset, OpenClaw may still use Pi’s built-in in-attempt auto-compaction, but the active engine’s `compact()` method still handles `/compact` and overflow recovery. There is no automatic fallback to the legacy engine’s compaction path. If you are building a non-owning context engine, implement `compact()` by calling `delegateCompactionToRuntime(...)` from `openclaw/plugin-sdk/core`.

## Tips

----
url: https://docs.openclaw.ai/platforms/linux
----

# Linux App - OpenClaw

## [​](#linux-app)Linux App

The Gateway is fully supported on Linux. **Node is the recommended runtime**. Bun is not recommended for the Gateway (WhatsApp/Telegram bugs). Native Linux companion apps are planned. Contributions are welcome if you want to help build one.

## [​](#beginner-quick-path-vps)Beginner quick path (VPS)

1. Install Node 24 (recommended; Node 22 LTS, currently `22.16+`, still works for compatibility)
2. `npm i -g openclaw@latest`
3. `openclaw onboard --install-daemon`
4. From your laptop: `ssh -N -L 18789:127.0.0.1:18789 <user>@<host>`
5. Open `http://127.0.0.1:18789/` and paste your token

Full Linux server guide: [Linux Server](https://docs.openclaw.ai/vps). Step-by-step VPS example: [exe.dev](https://docs.openclaw.ai/install/exe-dev)

## [​](#install)Install

* [Getting Started](https://docs.openclaw.ai/start/getting-started)
* [Install & updates](https://docs.openclaw.ai/install/updating)
* Optional flows: [Bun (experimental)](https://docs.openclaw.ai/install/bun), [Nix](https://docs.openclaw.ai/install/nix), [Docker](https://docs.openclaw.ai/install/docker)

## [​](#gateway)Gateway

* [Gateway runbook](https://docs.openclaw.ai/gateway)
* [Configuration](https://docs.openclaw.ai/gateway/configuration)

## [​](#gateway-service-install-cli)Gateway service install (CLI)

Use one of these:

```
openclaw onboard --install-daemon
```

Or:

```
openclaw gateway install
```

Or:

```
openclaw configure
```

Select **Gateway service** when prompted. Repair/migrate:

```
openclaw doctor
```

## [​](#system-control-systemd-user-unit)System control (systemd user unit)

OpenClaw installs a systemd **user** service by default. Use a **system** service for shared or always-on servers. The full unit example and guidance live in the [Gateway runbook](https://docs.openclaw.ai/gateway). Minimal setup: Create `~/.config/systemd/user/openclaw-gateway[-<profile>].service`:

```
[Unit]
Description=OpenClaw Gateway (profile: <profile>, v<version>)
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/openclaw gateway --port 18789
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
```

Enable it:

```
systemctl --user enable --now openclaw-gateway[-<profile>].service
```

----
url: https://docs.openclaw.ai/platforms/mac/signing
----

# macOS Signing - OpenClaw

## [​](#mac-signing-debug-builds)mac signing (debug builds)

This app is usually built from [`scripts/package-mac-app.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-app.sh), which now:

* sets a stable debug bundle identifier: `ai.openclaw.mac.debug`
* writes the Info.plist with that bundle id (override via `BUNDLE_ID=...`)
* calls [`scripts/codesign-mac-app.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/codesign-mac-app.sh) to sign the main binary and app bundle so macOS treats each rebuild as the same signed bundle and keeps TCC permissions (notifications, accessibility, screen recording, mic, speech). For stable permissions, use a real signing identity; ad-hoc is opt-in and fragile (see [macOS permissions](https://docs.openclaw.ai/platforms/mac/permissions)).
* uses `CODESIGN_TIMESTAMP=auto` by default; it enables trusted timestamps for Developer ID signatures. Set `CODESIGN_TIMESTAMP=off` to skip timestamping (offline debug builds).
* inject build metadata into Info.plist: `OpenClawBuildTimestamp` (UTC) and `OpenClawGitCommit` (short hash) so the About pane can show build, git, and debug/release channel.
* **Packaging defaults to Node 24**: the script runs TS builds and the Control UI build. Node 22 LTS, currently `22.16+`, remains supported for compatibility.
* reads `SIGN_IDENTITY` from the environment. Add `export SIGN_IDENTITY="Apple Development: Your Name (TEAMID)"` (or your Developer ID Application cert) to your shell rc to always sign with your cert. Ad-hoc signing requires explicit opt-in via `ALLOW_ADHOC_SIGNING=1` or `SIGN_IDENTITY="-"` (not recommended for permission testing).
* runs a Team ID audit after signing and fails if any Mach-O inside the app bundle is signed by a different Team ID. Set `SKIP_TEAM_ID_CHECK=1` to bypass.

## [​](#usage)Usage

```
# from repo root
scripts/package-mac-app.sh               # auto-selects identity; errors if none found
SIGN_IDENTITY="Developer ID Application: Your Name" scripts/package-mac-app.sh   # real cert
ALLOW_ADHOC_SIGNING=1 scripts/package-mac-app.sh    # ad-hoc (permissions will not stick)
SIGN_IDENTITY="-" scripts/package-mac-app.sh        # explicit ad-hoc (same caveat)
DISABLE_LIBRARY_VALIDATION=1 scripts/package-mac-app.sh   # dev-only Sparkle Team ID mismatch workaround
```

### [​](#ad-hoc-signing-note)Ad-hoc Signing Note

When signing with `SIGN_IDENTITY="-"` (ad-hoc), the script automatically disables the **Hardened Runtime** (`--options runtime`). This is necessary to prevent crashes when the app attempts to load embedded frameworks (like Sparkle) that do not share the same Team ID. Ad-hoc signatures also break TCC permission persistence; see [macOS permissions](https://docs.openclaw.ai/platforms/mac/permissions) for recovery steps.

## [​](#build-metadata-for-about)Build metadata for About

`package-mac-app.sh` stamps the bundle with:

* `OpenClawBuildTimestamp`: ISO8601 UTC at package time
* `OpenClawGitCommit`: short git hash (or `unknown` if unavailable)

The About tab reads these keys to show version, build date, git commit, and whether it’s a debug build (via `#if DEBUG`). Run the packager to refresh these values after code changes.

## [​](#why)Why

TCC permissions are tied to the bundle identifier *and* code signature. Unsigned debug builds with changing UUIDs were causing macOS to forget grants after each rebuild. Signing the binaries (ad‑hoc by default) and keeping a fixed bundle id/path (`dist/OpenClaw.app`) preserves the grants between builds, matching the VibeTunnel approach.

----
url: https://docs.openclaw.ai/platforms/mac/icon
----

# Menu Bar Icon - OpenClaw

## [​](#menu-bar-icon-states)Menu Bar Icon States

Author: steipete · Updated: 2025-12-06 · Scope: macOS app (`apps/macos`)

* **Idle:** Normal icon animation (blink, occasional wiggle).
* **Paused:** Status item uses `appearsDisabled`; no motion.
* **Voice trigger (big ears):** Voice wake detector calls `AppState.triggerVoiceEars(ttl: nil)` when the wake word is heard, keeping `earBoostActive=true` while the utterance is captured. Ears scale up (1.9x), get circular ear holes for readability, then drop via `stopVoiceEars()` after 1s of silence. Only fired from the in-app voice pipeline.
* **Working (agent running):** `AppState.isWorking=true` drives a “tail/leg scurry” micro-motion: faster leg wiggle and slight offset while work is in-flight. Currently toggled around WebChat agent runs; add the same toggle around other long tasks when you wire them.

Wiring points

* Voice wake: runtime/tester call `AppState.triggerVoiceEars(ttl: nil)` on trigger and `stopVoiceEars()` after 1s of silence to match the capture window.
* Agent activity: set `AppStateStore.shared.setWorking(true/false)` around work spans (already done in WebChat agent call). Keep spans short and reset in `defer` blocks to avoid stuck animations.

Shapes & sizes

* Base icon drawn in `CritterIconRenderer.makeIcon(blink:legWiggle:earWiggle:earScale:earHoles:)`.
* Ear scale defaults to `1.0`; voice boost sets `earScale=1.9` and toggles `earHoles=true` without changing overall frame (18×18 pt template image rendered into a 36×36 px Retina backing store).
* Scurry uses leg wiggle up to \~1.0 with a small horizontal jiggle; it’s additive to any existing idle wiggle.

Behavioral notes

* No external CLI/broker toggle for ears/working; keep it internal to the app’s own signals to avoid accidental flapping.
* Keep TTLs short (<10s) so the icon returns to baseline quickly if a job hangs.

[Health Checks (macOS)](https://docs.openclaw.ai/platforms/mac/health)[macOS Logging](https://docs.openclaw.ai/platforms/mac/logging)

----
url: https://docs.openclaw.ai/tools/elevated
----

# Elevated Mode - OpenClaw

When an agent runs inside a sandbox, its `exec` commands are confined to the sandbox environment. **Elevated mode** lets the agent break out and run commands on the gateway host instead, with configurable approval gates.

## Directives

Control elevated mode per-session with slash commands:

| Directive        | What it does                                        |
| ---------------- | --------------------------------------------------- |
| `/elevated on`   | Run on the gateway host, keep exec approvals        |
| `/elevated ask`  | Same as `on` (alias)                                |
| `/elevated full` | Run on the gateway host **and** skip exec approvals |
| `/elevated off`  | Return to sandbox-confined execution                |

Also available as `/elev on|off|ask|full`. Send `/elevated` with no argument to see the current level.

## How it works

## Resolution order

1. **Inline directive** on the message (applies only to that message)
2. **Session override** (set by sending a directive-only message)
3. **Global default** (`agents.defaults.elevatedDefault` in config)

## Availability and allowlists

Allowlist entry formats:

| Prefix                  | Matches                         |
| ----------------------- | ------------------------------- |
| (none)                  | Sender ID, E.164, or From field |
| `name:`                 | Sender display name             |
| `username:`             | Sender username                 |
| `tag:`                  | Sender tag                      |
| `id:`, `from:`, `e164:` | Explicit identity targeting     |

## What elevated does not control

----
url: https://docs.openclaw.ai/cli/doctor
----

# doctor - OpenClaw

## [​](#openclaw-doctor)`openclaw doctor`

Health checks + quick fixes for the gateway and channels. Related:

* Troubleshooting: [Troubleshooting](https://docs.openclaw.ai/gateway/troubleshooting)
* Security audit: [Security](https://docs.openclaw.ai/gateway/security)

## [​](#examples)Examples

```
openclaw doctor
openclaw doctor --repair
openclaw doctor --deep
```

Notes:

* Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts.
* `--fix` (alias for `--repair`) writes a backup to `~/.openclaw/openclaw.json.bak` and drops unknown config keys, listing each removal.
* State integrity checks now detect orphan transcript files in the sessions directory and can archive them as `.deleted.<timestamp>` to reclaim space safely.
* Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime.
* Doctor includes a memory-search readiness check and can recommend `openclaw configure --section model` when embedding credentials are missing.
* If sandbox mode is enabled but Docker is unavailable, doctor reports a high-signal warning with remediation (`install Docker` or `openclaw config set agents.defaults.sandbox.mode off`).
* If `gateway.auth.token`/`gateway.auth.password` are SecretRef-managed and unavailable in the current command path, doctor reports a read-only warning and does not write plaintext fallback credentials.
* If channel SecretRef inspection fails in a fix path, doctor continues and reports a warning instead of exiting early.
* Telegram `allowFrom` username auto-resolution (`doctor --fix`) requires a resolvable Telegram token in the current command path. If token inspection is unavailable, doctor reports a warning and skips auto-resolution for that pass.

## [​](#macos-launchctl-env-overrides)macOS: `launchctl` env overrides

If you previously ran `launchctl setenv OPENCLAW_GATEWAY_TOKEN ...` (or `...PASSWORD`), that value overrides your config file and can cause persistent “unauthorized” errors.

```
launchctl getenv OPENCLAW_GATEWAY_TOKEN
launchctl getenv OPENCLAW_GATEWAY_PASSWORD

launchctl unsetenv OPENCLAW_GATEWAY_TOKEN
launchctl unsetenv OPENCLAW_GATEWAY_PASSWORD
```

----
url: https://docs.openclaw.ai/cli/agents
----

# agents - OpenClaw

## [​](#openclaw-agents)`openclaw agents`

Manage isolated agents (workspaces + auth + routing). Related:

* Multi-agent routing: [Multi-Agent Routing](https://docs.openclaw.ai/concepts/multi-agent)
* Agent workspace: [Agent workspace](https://docs.openclaw.ai/concepts/agent-workspace)

## [​](#examples)Examples

```
openclaw agents list
openclaw agents add work --workspace ~/.openclaw/workspace-work
openclaw agents bindings
openclaw agents bind --agent work --bind telegram:ops
openclaw agents unbind --agent work --bind telegram:ops
openclaw agents set-identity --workspace ~/.openclaw/workspace --from-identity
openclaw agents set-identity --agent main --avatar avatars/openclaw.png
openclaw agents delete work
```

## [​](#routing-bindings)Routing bindings

Use routing bindings to pin inbound channel traffic to a specific agent. List bindings:

```
openclaw agents bindings
openclaw agents bindings --agent work
openclaw agents bindings --json
```

Add bindings:

```
openclaw agents bind --agent work --bind telegram:ops --bind discord:guild-a
```

If you omit `accountId` (`--bind <channel>`), OpenClaw resolves it from channel defaults and plugin setup hooks when available.

### [​](#binding-scope-behavior)Binding scope behavior

* A binding without `accountId` matches the channel default account only.
* `accountId: "*"` is the channel-wide fallback (all accounts) and is less specific than an explicit account binding.
* If the same agent already has a matching channel binding without `accountId`, and you later bind with an explicit or resolved `accountId`, OpenClaw upgrades that existing binding in place instead of adding a duplicate.

Example:

```
# initial channel-only binding
openclaw agents bind --agent work --bind telegram

# later upgrade to account-scoped binding
openclaw agents bind --agent work --bind telegram:ops
```

After the upgrade, routing for that binding is scoped to `telegram:ops`. If you also want default-account routing, add it explicitly (for example `--bind telegram:default`). Remove bindings:

```
openclaw agents unbind --agent work --bind telegram:ops
openclaw agents unbind --agent work --all
```

## [​](#identity-files)Identity files

Each agent workspace can include an `IDENTITY.md` at the workspace root:

* Example path: `~/.openclaw/workspace/IDENTITY.md`
* `set-identity --from-identity` reads from the workspace root (or an explicit `--identity-file`)

Avatar paths resolve relative to the workspace root.

## [​](#set-identity)Set identity

`set-identity` writes fields into `agents.list[].identity`:

* `name`
* `theme`
* `emoji`
* `avatar` (workspace-relative path, http(s) URL, or data URI)

Load from `IDENTITY.md`:

```
openclaw agents set-identity --workspace ~/.openclaw/workspace --from-identity
```

Override fields explicitly:

```
openclaw agents set-identity --agent main --name "OpenClaw" --emoji "🦞" --avatar avatars/openclaw.png
```

Config sample:

```
{
  agents: {
    list: [
      {
        id: "main",
        identity: {
          name: "OpenClaw",
          theme: "space lobster",
          emoji: "🦞",
          avatar: "avatars/openclaw.png",
        },
      },
    ],
  },
}
```

----
url: https://docs.openclaw.ai/install/node
----

# Node.js - OpenClaw

OpenClaw requires **Node 22.16 or newer**. **Node 24 is the default and recommended runtime** for installs, CI, and release workflows. Node 22 remains supported via the active LTS line. The [installer script](https://docs.openclaw.ai/install#alternative-install-methods) will detect and install Node automatically — this page is for when you want to set up Node yourself and make sure everything is wired up correctly (versions, PATH, global installs).

## Check your version

If this prints `v24.x.x` or higher, you’re on the recommended default. If it prints `v22.16.x` or higher, you’re on the supported Node 22 LTS path, but we still recommend upgrading to Node 24 when convenient. If Node isn’t installed or the version is too old, pick an install method below.

## Install Node

Using a version manager (nvm, fnm, mise, asdf)

## Troubleshooting

### `openclaw: command not found`

This almost always means npm’s global bin directory isn’t on your PATH.

### Permission errors on `npm install -g` (Linux)

If you see `EACCES` errors, switch npm’s global prefix to a user-writable directory:

Add the `export PATH=...` line to your `~/.bashrc` or `~/.zshrc` to make it permanent.

----
url: https://docs.openclaw.ai/plugins/bundles
----

# Plugin Bundles - OpenClaw

OpenClaw can install plugins from three external ecosystems: **Codex**, **Claude**, and **Cursor**. These are called **bundles** — content and metadata packs that OpenClaw maps into native features like skills, hooks, and MCP tools.

## Why bundles exist

Many useful plugins are published in Codex, Claude, or Cursor format. Instead of requiring authors to rewrite them as native OpenClaw plugins, OpenClaw detects these formats and maps their supported content into the native feature set. This means you can install a Claude command pack or a Codex skill bundle and use it immediately.

## Install a bundle

## What OpenClaw maps from bundles

Not every bundle feature runs in OpenClaw today. Here is what works and what is detected but not yet wired.

### Supported now

| Feature       | How it maps                                                                                          | Applies to     |
| ------------- | ---------------------------------------------------------------------------------------------------- | -------------- |
| Skill content | Bundle skill roots load as normal OpenClaw skills                                                    | All formats    |
| Commands      | `commands/` and `.cursor/commands/` treated as skill roots                                           | Claude, Cursor |
| Hook packs    | OpenClaw-style `HOOK.md` + `handler.ts` layouts                                                      | Codex          |
| MCP tools     | Bundle MCP config merged into embedded Pi settings; supported stdio servers launched as subprocesses | All formats    |
| Settings      | Claude `settings.json` imported as embedded Pi defaults                                              | Claude         |

### Detected but not executed

These are recognized and shown in diagnostics, but OpenClaw does not run them:

## Bundle formats

## Detection precedence

OpenClaw checks for native plugin format first:

1. `openclaw.plugin.json` or valid `package.json` with `openclaw.extensions` — treated as **native plugin**
2. Bundle markers (`.codex-plugin/`, `.claude-plugin/`, or default Claude/Cursor layout) — treated as **bundle**

If a directory contains both, OpenClaw uses the native path. This prevents dual-format packages from being partially installed as bundles.

## Security

Bundles have a narrower trust boundary than native plugins:

This makes bundles safer by default, but you should still treat third-party bundles as trusted content for the features they do expose.

## Troubleshooting

----
url: https://docs.openclaw.ai/providers/google
----

# Google (Gemini) - OpenClaw

The Google plugin provides access to Gemini models through Google AI Studio, plus image generation, media understanding (image/audio/video), and web search via Gemini Grounding.

## Quick start

1. Set the API key:

2) Set a default model:

## Non-interactive example

## OAuth (Gemini CLI)

An alternative provider `google-gemini-cli` uses PKCE OAuth instead of an API key. This is an unofficial integration; some users report account restrictions. Use at your own risk. Environment variables:

(Or the `GEMINI_CLI_*` variants.)

## Capabilities

| Capability             | Supported         |
| ---------------------- | ----------------- |
| Chat completions       | Yes               |
| Image generation       | Yes               |
| Image understanding    | Yes               |
| Audio transcription    | Yes               |
| Video understanding    | Yes               |
| Web search (Grounding) | Yes               |
| Thinking/reasoning     | Yes (Gemini 3.1+) |

## Environment note

If the Gateway runs as a daemon (launchd/systemd), make sure `GEMINI_API_KEY` is available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).

----
url: https://docs.openclaw.ai/concepts/context-engine
----

# Context Engine - OpenClaw

A **context engine** controls how OpenClaw builds model context for each run. It decides which messages to include, how to summarize older history, and how to manage context across subagent boundaries. OpenClaw ships with a built-in `legacy` engine. Plugins can register alternative engines that replace the active context-engine lifecycle.

## Quick start

Check which engine is active:

### Installing a context engine plugin

Context engine plugins are installed like any other OpenClaw plugin. Install first, then select the engine in the slot:

Then enable the plugin and select it as the active engine in your config:

Restart the gateway after installing and configuring. To switch back to the built-in engine, set `contextEngine` to `"legacy"` (or remove the key entirely — `"legacy"` is the default).

## How it works

Every time OpenClaw runs a model prompt, the context engine participates at four lifecycle points:

1. **Ingest** — called when a new message is added to the session. The engine can store or index the message in its own data store.
2. **Assemble** — called before each model run. The engine returns an ordered set of messages (and an optional `systemPromptAddition`) that fit within the token budget.
3. **Compact** — called when the context window is full, or when the user runs `/compact`. The engine summarizes older history to free space.
4. **After turn** — called after a run completes. The engine can persist state, trigger background compaction, or update indexes.

### Subagent lifecycle (optional)

OpenClaw currently calls one subagent lifecycle hook:

The `prepareSubagentSpawn` hook is part of the interface for future use, but the runtime does not invoke it yet.

### System prompt addition

The `assemble` method can return a `systemPromptAddition` string. OpenClaw prepends this to the system prompt for the run. This lets engines inject dynamic recall guidance, retrieval instructions, or context-aware hints without requiring static workspace files.

## The legacy engine

The built-in `legacy` engine preserves OpenClaw’s original behavior:

The legacy engine does not register tools or provide a `systemPromptAddition`. When no `plugins.slots.contextEngine` is set (or it’s set to `"legacy"`), this engine is used automatically.

## Plugin engines

A plugin can register a context engine using the plugin API:

```
export default function register(api) {
  api.registerContextEngine("my-engine", () => ({
    info: {
      id: "my-engine",
      name: "My Context Engine",
      ownsCompaction: true,
    },

    async ingest({ sessionId, message, isHeartbeat }) {
      // Store the message in your data store
      return { ingested: true };
    },

    async assemble({ sessionId, messages, tokenBudget }) {
      // Return messages that fit the budget
      return {
        messages: buildContext(messages, tokenBudget),
        estimatedTokens: countTokens(messages),
        systemPromptAddition: "Use lcm_grep to search history...",
      };
    },

    async compact({ sessionId, force }) {
      // Summarize older context
      return { ok: true, compacted: true };
    },
  }));
}
```

Then enable it in config:

### The ContextEngine interface

Required members:

| Member             | Kind     | Purpose                                                  |
| ------------------ | -------- | -------------------------------------------------------- |
| `info`             | Property | Engine id, name, version, and whether it owns compaction |
| `ingest(params)`   | Method   | Store a single message                                   |
| `assemble(params)` | Method   | Build context for a model run (returns `AssembleResult`) |
| `compact(params)`  | Method   | Summarize/reduce context                                 |

`assemble` returns an `AssembleResult` with:

Optional members:

| Member                         | Kind   | Purpose                                                                                                         |
| ------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------- |
| `bootstrap(params)`            | Method | Initialize engine state for a session. Called once when the engine first sees a session (e.g., import history). |
| `ingestBatch(params)`          | Method | Ingest a completed turn as a batch. Called after a run completes, with all messages from that turn at once.     |
| `afterTurn(params)`            | Method | Post-run lifecycle work (persist state, trigger background compaction).                                         |
| `prepareSubagentSpawn(params)` | Method | Set up shared state for a child session.                                                                        |
| `onSubagentEnded(params)`      | Method | Clean up after a subagent ends.                                                                                 |
| `dispose()`                    | Method | Release resources. Called during gateway shutdown or plugin reload — not per-session.                           |

### ownsCompaction

`ownsCompaction` controls whether Pi’s built-in in-attempt auto-compaction stays enabled for the run:

`ownsCompaction: false` does **not** mean OpenClaw automatically falls back to the legacy engine’s compaction path. That means there are two valid plugin patterns:

A no-op `compact()` is unsafe for an active non-owning engine because it disables the normal `/compact` and overflow-recovery compaction path for that engine slot.

## Configuration reference

The slot is exclusive at run time — only one registered context engine is resolved for a given run or compaction operation. Other enabled `kind: "context-engine"` plugins can still load and run their registration code; `plugins.slots.contextEngine` only selects which registered engine id OpenClaw resolves when it needs a context engine.

## Relationship to compaction and memory

## Tips

See also: [Compaction](https://docs.openclaw.ai/concepts/compaction), [Context](https://docs.openclaw.ai/concepts/context), [Plugins](https://docs.openclaw.ai/tools/plugin), [Plugin manifest](https://docs.openclaw.ai/plugins/manifest).

----
url: https://docs.openclaw.ai/diagnostics/flags
----

# Diagnostics Flags - OpenClaw

## [​](#diagnostics-flags)Diagnostics Flags

Diagnostics flags let you enable targeted debug logs without turning on verbose logging everywhere. Flags are opt-in and have no effect unless a subsystem checks them.

## [​](#how-it-works)How it works

* Flags are strings (case-insensitive).

* You can enable flags in config or via an env override.

* Wildcards are supported:

  * `telegram.*` matches `telegram.http`
  * `*` enables all flags

## [​](#enable-via-config)Enable via config

```
{
  "diagnostics": {
    "flags": ["telegram.http"]
  }
}
```

Multiple flags:

```
{
  "diagnostics": {
    "flags": ["telegram.http", "gateway.*"]
  }
}
```

Restart the gateway after changing flags.

## [​](#env-override-one-off)Env override (one-off)

```
OPENCLAW_DIAGNOSTICS=telegram.http,telegram.payload
```

Disable all flags:

```
OPENCLAW_DIAGNOSTICS=0
```

## [​](#where-logs-go)Where logs go

Flags emit logs into the standard diagnostics log file. By default:

```
/tmp/openclaw/openclaw-YYYY-MM-DD.log
```

If you set `logging.file`, use that path instead. Logs are JSONL (one JSON object per line). Redaction still applies based on `logging.redactSensitive`.

## [​](#extract-logs)Extract logs

Pick the latest log file:

```
ls -t /tmp/openclaw/openclaw-*.log | head -n 1
```

Filter for Telegram HTTP diagnostics:

```
rg "telegram http error" /tmp/openclaw/openclaw-*.log
```

Or tail while reproducing:

```
tail -f /tmp/openclaw/openclaw-$(date +%F).log | rg "telegram http error"
```

For remote gateways, you can also use `openclaw logs --follow` (see [/cli/logs](https://docs.openclaw.ai/cli/logs)).

## [​](#notes)Notes

* If `logging.level` is set higher than `warn`, these logs may be suppressed. Default `info` is fine.
* Flags are safe to leave enabled; they only affect log volume for the specific subsystem.
* Use [/logging](https://docs.openclaw.ai/logging) to change log destinations, levels, and redaction.

----
url: https://docs.openclaw.ai/tools/tavily
----

# Tavily - OpenClaw

OpenClaw can use **Tavily** in two ways:

Tavily is a search API designed for AI applications, returning structured results optimized for LLM consumption. It supports configurable search depth, topic filtering, domain filters, AI-generated answer summaries, and content extraction from URLs (including JavaScript-rendered pages).

## Get an API key

1. Create a Tavily account at [tavily.com](https://tavily.com/).
2. Generate an API key in the dashboard.
3. Store it in config or set `TAVILY_API_KEY` in the gateway environment.

## Configure Tavily search

```
{
  plugins: {
    entries: {
      tavily: {
        enabled: true,
        config: {
          webSearch: {
            apiKey: "tvly-...", // optional if TAVILY_API_KEY is set
            baseUrl: "https://api.tavily.com",
          },
        },
      },
    },
  },
  tools: {
    web: {
      search: {
        provider: "tavily",
      },
    },
  },
}
```

Notes:

## Tavily plugin tools

### `tavily_search`

Use this when you want Tavily-specific search controls instead of generic `web_search`.

| Parameter         | Description                                                           |
| ----------------- | --------------------------------------------------------------------- |
| `query`           | Search query string (keep under 400 characters)                       |
| `search_depth`    | `basic` (default, balanced) or `advanced` (highest relevance, slower) |
| `topic`           | `general` (default), `news` (real-time updates), or `finance`         |
| `max_results`     | Number of results, 1-20 (default: 5)                                  |
| `include_answer`  | Include an AI-generated answer summary (default: false)               |
| `time_range`      | Filter by recency: `day`, `week`, `month`, or `year`                  |
| `include_domains` | Array of domains to restrict results to                               |
| `exclude_domains` | Array of domains to exclude from results                              |

**Search depth:**

| Depth      | Speed  | Relevance | Best for                            |
| ---------- | ------ | --------- | ----------------------------------- |
| `basic`    | Faster | High      | General-purpose queries (default)   |
| `advanced` | Slower | Highest   | Precision, specific facts, research |

Use this to extract clean content from one or more URLs. Handles JavaScript-rendered pages and supports query-focused chunking for targeted extraction.

| Parameter           | Description                                                |
| ------------------- | ---------------------------------------------------------- |
| `urls`              | Array of URLs to extract (1-20 per request)                |
| `query`             | Rerank extracted chunks by relevance to this query         |
| `extract_depth`     | `basic` (default, fast) or `advanced` (for JS-heavy pages) |
| `chunks_per_source` | Chunks per URL, 1-5 (requires `query`)                     |
| `include_images`    | Include image URLs in results (default: false)             |

**Extract depth:**

| Depth      | When to use                               |
| ---------- | ----------------------------------------- |
| `basic`    | Simple pages - try this first             |
| `advanced` | JS-rendered SPAs, dynamic content, tables |

Tips:

## Choosing the right tool

| Need                                 | Tool             |
| ------------------------------------ | ---------------- |
| Quick web search, no special options | `web_search`     |
| Search with depth, topic, AI answers | `tavily_search`  |
| Extract content from specific URLs   | `tavily_extract` |

----
url: https://docs.openclaw.ai/plugins/manifest
----

# Plugin Manifest - OpenClaw

This page is for the **native OpenClaw plugin manifest** only. For compatible bundle layouts, see [Plugin bundles](https://docs.openclaw.ai/plugins/bundles). Compatible bundle formats use different manifest files:

OpenClaw auto-detects those bundle layouts too, but they are not validated against the `openclaw.plugin.json` schema described here. For compatible bundles, OpenClaw currently reads bundle metadata plus declared skill roots, Claude command roots, Claude bundle `settings.json` defaults, and supported hook packs when the layout matches OpenClaw runtime expectations. Every native OpenClaw plugin **must** ship a `openclaw.plugin.json` file in the **plugin root**. OpenClaw uses this manifest to validate configuration **without executing plugin code**. Missing or invalid manifests are treated as plugin errors and block config validation. See the full plugin system guide: [Plugins](https://docs.openclaw.ai/tools/plugin). For the native capability model and current external-compatibility guidance: [Capability model](https://docs.openclaw.ai/plugins/architecture#public-capability-model).

## What this file does

`openclaw.plugin.json` is the metadata OpenClaw reads before it loads your plugin code. Use it for:

Do not use it for:

Those belong in your plugin code and `package.json`.

## Minimal example

## Rich example

```
{
  "id": "openrouter",
  "name": "OpenRouter",
  "description": "OpenRouter provider plugin",
  "version": "1.0.0",
  "providers": ["openrouter"],
  "providerAuthEnvVars": {
    "openrouter": ["OPENROUTER_API_KEY"]
  },
  "providerAuthChoices": [
    {
      "provider": "openrouter",
      "method": "api-key",
      "choiceId": "openrouter-api-key",
      "choiceLabel": "OpenRouter API key",
      "groupId": "openrouter",
      "groupLabel": "OpenRouter",
      "optionKey": "openrouterApiKey",
      "cliFlag": "--openrouter-api-key",
      "cliOption": "--openrouter-api-key <key>",
      "cliDescription": "OpenRouter API key",
      "onboardingScopes": ["text-inference"]
    }
  ],
  "uiHints": {
    "apiKey": {
      "label": "API key",
      "placeholder": "sk-or-v1-...",
      "sensitive": true
    }
  },
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "apiKey": {
        "type": "string"
      }
    }
  }
}
```

## Top-level field reference

| Field                 | Required | Type                             | What it means                                                                                                                |
| --------------------- | -------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `id`                  | Yes      | `string`                         | Canonical plugin id. This is the id used in `plugins.entries.<id>`.                                                          |
| `configSchema`        | Yes      | `object`                         | Inline JSON Schema for this plugin’s config.                                                                                 |
| `enabledByDefault`    | No       | `true`                           | Marks a bundled plugin as enabled by default. Omit it, or set any non-`true` value, to leave the plugin disabled by default. |
| `kind`                | No       | `"memory"` \| `"context-engine"` | Declares an exclusive plugin kind used by `plugins.slots.*`.                                                                 |
| `channels`            | No       | `string[]`                       | Channel ids owned by this plugin. Used for discovery and config validation.                                                  |
| `providers`           | No       | `string[]`                       | Provider ids owned by this plugin.                                                                                           |
| `providerAuthEnvVars` | No       | `Record<string, string[]>`       | Cheap provider-auth env metadata that OpenClaw can inspect without loading plugin code.                                      |
| `providerAuthChoices` | No       | `object[]`                       | Cheap auth-choice metadata for onboarding pickers, preferred-provider resolution, and simple CLI flag wiring.                |
| `skills`              | No       | `string[]`                       | Skill directories to load, relative to the plugin root.                                                                      |
| `name`                | No       | `string`                         | Human-readable plugin name.                                                                                                  |
| `description`         | No       | `string`                         | Short summary shown in plugin surfaces.                                                                                      |
| `version`             | No       | `string`                         | Informational plugin version.                                                                                                |
| `uiHints`             | No       | `Record<string, object>`         | UI labels, placeholders, and sensitivity hints for config fields.                                                            |

## providerAuthChoices reference

Each `providerAuthChoices` entry describes one onboarding or auth choice. OpenClaw reads this before provider runtime loads.

| Field              | Required | Type                                            | What it means                                                                                            |
| ------------------ | -------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `provider`         | Yes      | `string`                                        | Provider id this choice belongs to.                                                                      |
| `method`           | Yes      | `string`                                        | Auth method id to dispatch to.                                                                           |
| `choiceId`         | Yes      | `string`                                        | Stable auth-choice id used by onboarding and CLI flows.                                                  |
| `choiceLabel`      | No       | `string`                                        | User-facing label. If omitted, OpenClaw falls back to `choiceId`.                                        |
| `choiceHint`       | No       | `string`                                        | Short helper text for the picker.                                                                        |
| `groupId`          | No       | `string`                                        | Optional group id for grouping related choices.                                                          |
| `groupLabel`       | No       | `string`                                        | User-facing label for that group.                                                                        |
| `groupHint`        | No       | `string`                                        | Short helper text for the group.                                                                         |
| `optionKey`        | No       | `string`                                        | Internal option key for simple one-flag auth flows.                                                      |
| `cliFlag`          | No       | `string`                                        | CLI flag name, such as `--openrouter-api-key`.                                                           |
| `cliOption`        | No       | `string`                                        | Full CLI option shape, such as `--openrouter-api-key <key>`.                                             |
| `cliDescription`   | No       | `string`                                        | Description used in CLI help.                                                                            |
| `onboardingScopes` | No       | `Array<"text-inference" \| "image-generation">` | Which onboarding surfaces this choice should appear in. If omitted, it defaults to `["text-inference"]`. |

## uiHints reference

`uiHints` is a map from config field names to small rendering hints.

Each field hint can include:

| Field         | Type       | What it means                           |
| ------------- | ---------- | --------------------------------------- |
| `label`       | `string`   | User-facing field label.                |
| `help`        | `string`   | Short helper text.                      |
| `tags`        | `string[]` | Optional UI tags.                       |
| `advanced`    | `boolean`  | Marks the field as advanced.            |
| `sensitive`   | `boolean`  | Marks the field as secret or sensitive. |
| `placeholder` | `string`   | Placeholder text for form inputs.       |

## Manifest versus package.json

The two files serve different jobs:

| File                   | Use it for                                                                                                         |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `openclaw.plugin.json` | Discovery, config validation, auth-choice metadata, and UI hints that must exist before plugin code runs           |
| `package.json`         | npm metadata, dependency installation, and the `openclaw` block used for entrypoints and setup or catalog metadata |

If you are unsure where a piece of metadata belongs, use this rule:

## JSON Schema requirements

## Validation behavior

See [Configuration reference](https://docs.openclaw.ai/gateway/configuration) for the full `plugins.*` schema.

## Notes

* The manifest is **required for native OpenClaw plugins**, including local filesystem loads.
* Runtime still loads the plugin module separately; the manifest is only for discovery + validation.
* Only documented manifest fields are read by the manifest loader. Avoid adding custom top-level keys here.
* `providerAuthEnvVars` is the cheap metadata path for auth probes, env-marker validation, and similar provider-auth surfaces that should not boot plugin runtime just to inspect env names.
* `providerAuthChoices` is the cheap metadata path for auth-choice pickers, `--auth-choice` resolution, preferred-provider mapping, and simple onboarding CLI flag registration before provider runtime loads. For runtime wizard metadata that requires provider code, see [Provider runtime hooks](https://docs.openclaw.ai/plugins/architecture#provider-runtime-hooks).
* Exclusive plugin kinds are selected through `plugins.slots.*`.
* `channels`, `providers`, and `skills` can be omitted when a plugin does not need them.
* If your plugin depends on native modules, document the build steps and any package-manager allowlist requirements (for example, pnpm `allow-build-scripts`

----
url: https://docs.openclaw.ai/plugins/sdk-testing
----

# Plugin Testing - OpenClaw

Reference for test utilities, patterns, and lint enforcement for OpenClaw plugins.

## Test utilities

**Import:** `openclaw/plugin-sdk/testing` The testing subpath exports a narrow set of helpers for plugin authors:

### Available exports

| Export                                 | Purpose                                                |
| -------------------------------------- | ------------------------------------------------------ |
| `installCommonResolveTargetErrorCases` | Shared test cases for target resolution error handling |
| `shouldAckReaction`                    | Check whether a channel should add an ack reaction     |
| `removeAckReactionAfterReply`          | Remove ack reaction after reply delivery               |

### Types

The testing subpath also re-exports types useful in test files:

## Testing target resolution

Use `installCommonResolveTargetErrorCases` to add standard error cases for channel target resolution:

## Testing patterns

### Unit testing a channel plugin

```
import { describe, it, expect, vi } from "vitest";

describe("my-channel plugin", () => {
  it("should resolve account from config", () => {
    const cfg = {
      channels: {
        "my-channel": {
          token: "test-token",
          allowFrom: ["user1"],
        },
      },
    };

    const account = myPlugin.setup.resolveAccount(cfg, undefined);
    expect(account.token).toBe("test-token");
  });

  it("should inspect account without materializing secrets", () => {
    const cfg = {
      channels: {
        "my-channel": { token: "test-token" },
      },
    };

    const inspection = myPlugin.setup.inspectAccount(cfg, undefined);
    expect(inspection.configured).toBe(true);
    expect(inspection.tokenStatus).toBe("available");
    // No token value exposed
    expect(inspection).not.toHaveProperty("token");
  });
});
```

### Unit testing a provider plugin

### Mocking the plugin runtime

For code that uses `createPluginRuntimeStore`, mock the runtime in tests:

### Testing with per-instance stubs

Prefer per-instance stubs over prototype mutation:

## Contract tests (in-repo plugins)

Bundled plugins have contract tests that verify registration ownership:

These tests assert:

### Running scoped tests

For a specific plugin:

For contract tests only:

## Lint enforcement (in-repo plugins)

Three rules are enforced by `pnpm check` for in-repo plugins:

1. **No monolithic root imports** — `openclaw/plugin-sdk` root barrel is rejected
2. **No direct `src/` imports** — plugins cannot import `../../src/` directly
3. **No self-imports** — plugins cannot import their own `plugin-sdk/<name>` subpath

External plugins are not subject to these lint rules, but following the same patterns is recommended.

## Test configuration

OpenClaw uses Vitest with V8 coverage thresholds. For plugin tests:

If local runs cause memory pressure:

----
url: https://docs.openclaw.ai/install/docker-vm-runtime
----

# Docker VM Runtime - OpenClaw

Shared runtime steps for VM-based Docker installs such as GCP, Hetzner, and similar VPS providers.

## Bake required binaries into the image

Installing binaries inside a running container is a trap. Anything installed at runtime will be lost on restart. All external binaries required by skills must be installed at image build time. The examples below show three common binaries only:

These are examples, not a complete list. You may install as many binaries as needed using the same pattern. If you add new skills later that depend on additional binaries, you must:

1. Update the Dockerfile
2. Rebuild the image
3. Restart the containers

**Example Dockerfile**

## Build and launch

If build fails with `Killed` or `exit code 137` during `pnpm install --frozen-lockfile`, the VM is out of memory. Use a larger machine class before retrying. Verify binaries:

Expected output:

Verify Gateway:

Expected output:

## What persists where

OpenClaw runs in Docker, but Docker is not the source of truth. All long-lived state must survive restarts, rebuilds, and reboots.

| Component           | Location                          | Persistence mechanism  | Notes                            |
| ------------------- | --------------------------------- | ---------------------- | -------------------------------- |
| Gateway config      | `/home/node/.openclaw/`           | Host volume mount      | Includes `openclaw.json`, tokens |
| Model auth profiles | `/home/node/.openclaw/`           | Host volume mount      | OAuth tokens, API keys           |
| Skill configs       | `/home/node/.openclaw/skills/`    | Host volume mount      | Skill-level state                |
| Agent workspace     | `/home/node/.openclaw/workspace/` | Host volume mount      | Code and agent artifacts         |
| WhatsApp session    | `/home/node/.openclaw/`           | Host volume mount      | Preserves QR login               |
| Gmail keyring       | `/home/node/.openclaw/`           | Host volume + password | Requires `GOG_KEYRING_PASSWORD`  |
| External binaries   | `/usr/local/bin/`                 | Docker image           | Must be baked at build time      |
| Node runtime        | Container filesystem              | Docker image           | Rebuilt every image build        |
| OS packages         | Container filesystem              | Docker image           | Do not install at runtime        |
| Docker container    | Ephemeral                         | Restartable            | Safe to destroy                  |

## Updates

To update OpenClaw on the VM:

----
url: https://docs.openclaw.ai/platforms/mac/voice-overlay
----

# Voice Overlay - OpenClaw

## [​](#voice-overlay-lifecycle-macos)Voice Overlay Lifecycle (macOS)

Audience: macOS app contributors. Goal: keep the voice overlay predictable when wake-word and push-to-talk overlap.

## [​](#current-intent)Current intent

* If the overlay is already visible from wake-word and the user presses the hotkey, the hotkey session *adopts* the existing text instead of resetting it. The overlay stays up while the hotkey is held. When the user releases: send if there is trimmed text, otherwise dismiss.
* Wake-word alone still auto-sends on silence; push-to-talk sends immediately on release.

## [​](#implemented-dec-9-2025)Implemented (Dec 9, 2025)

* Overlay sessions now carry a token per capture (wake-word or push-to-talk). Partial/final/send/dismiss/level updates are dropped when the token doesn’t match, avoiding stale callbacks.
* Push-to-talk adopts any visible overlay text as a prefix (so pressing the hotkey while the wake overlay is up keeps the text and appends new speech). It waits up to 1.5s for a final transcript before falling back to the current text.
* Chime/overlay logging is emitted at `info` in categories `voicewake.overlay`, `voicewake.ptt`, and `voicewake.chime` (session start, partial, final, send, dismiss, chime reason).

## [​](#next-steps)Next steps

1. **VoiceSessionCoordinator (actor)**

   * Owns exactly one `VoiceSession` at a time.
   * API (token-based): `beginWakeCapture`, `beginPushToTalk`, `updatePartial`, `endCapture`, `cancel`, `applyCooldown`.
   * Drops callbacks that carry stale tokens (prevents old recognizers from reopening the overlay).

2. **VoiceSession (model)**
   * Fields: `token`, `source` (wakeWord|pushToTalk), committed/volatile text, chime flags, timers (auto-send, idle), `overlayMode` (display|editing|sending), cooldown deadline.

3. **Overlay binding**

   * `VoiceSessionPublisher` (`ObservableObject`) mirrors the active session into SwiftUI.
   * `VoiceWakeOverlayView` renders only via the publisher; it never mutates global singletons directly.
   * Overlay user actions (`sendNow`, `dismiss`, `edit`) call back into the coordinator with the session token.

4. **Unified send path**

   * On `endCapture`: if trimmed text is empty → dismiss; else `performSend(session:)` (plays send chime once, forwards, dismisses).
   * Push-to-talk: no delay; wake-word: optional delay for auto-send.
   * Apply a short cooldown to the wake runtime after push-to-talk finishes so wake-word doesn’t immediately retrigger.

5. **Logging**

   * Coordinator emits `.info` logs in subsystem `ai.openclaw`, categories `voicewake.overlay` and `voicewake.chime`.
   * Key events: `session_started`, `adopted_by_push_to_talk`, `partial`, `finalized`, `send`, `dismiss`, `cancel`, `cooldown`.

## [​](#debugging-checklist)Debugging checklist

* Stream logs while reproducing a sticky overlay:
  ```
  sudo log stream --predicate 'subsystem == "ai.openclaw" AND category CONTAINS "voicewake"' --level info --style compact
  ```
* Verify only one active session token; stale callbacks should be dropped by the coordinator.
* Ensure push-to-talk release always calls `endCapture` with the active token; if text is empty, expect `dismiss` without chime or send.

## [​](#migration-steps-suggested)Migration steps (suggested)

1. Add `VoiceSessionCoordinator`, `VoiceSession`, and `VoiceSessionPublisher`.
2. Refactor `VoiceWakeRuntime` to create/update/end sessions instead of touching `VoiceWakeOverlayController` directly.
3. Refactor `VoicePushToTalk` to adopt existing sessions and call `endCapture` on release; apply runtime cooldown.
4. Wire `VoiceWakeOverlayController` to the publisher; remove direct calls from runtime/PTT.
5. Add integration tests for session adoption, cooldown, and empty-text dismissal.

----
url: https://docs.openclaw.ai/providers/zai
----

# Z.AI - OpenClaw

## [​](#z-ai)Z.AI

Z.AI is the API platform for **GLM** models. It provides REST APIs for GLM and uses API keys for authentication. Create your API key in the Z.AI console. OpenClaw uses the `zai` provider with a Z.AI API key.

## [​](#cli-setup)CLI setup

```
# Coding Plan Global, recommended for Coding Plan users
openclaw onboard --auth-choice zai-coding-global

# Coding Plan CN (China region), recommended for Coding Plan users
openclaw onboard --auth-choice zai-coding-cn

# General API
openclaw onboard --auth-choice zai-global

# General API CN (China region)
openclaw onboard --auth-choice zai-cn
```

## [​](#config-snippet)Config snippet

```
{
  env: { ZAI_API_KEY: "sk-..." },
  agents: { defaults: { model: { primary: "zai/glm-5" } } },
}
```

## [​](#notes)Notes

* GLM models are available as `zai/<model>` (example: `zai/glm-5`).
* `tool_stream` is enabled by default for Z.AI tool-call streaming. Set `agents.defaults.models["zai/<model>"].params.tool_stream` to `false` to disable it.
* See [/providers/glm](https://docs.openclaw.ai/providers/glm) for the model family overview.
* Z.AI uses Bearer auth with your API key.

----
url: https://docs.openclaw.ai/reference/memory-config
----

# Memory configuration reference - OpenClaw

This page covers the full configuration surface for OpenClaw memory search. For the conceptual overview (file layout, memory tools, when to write memory, and the automatic flush), see [Memory](https://docs.openclaw.ai/concepts/memory).

## Memory search defaults

Remote embeddings **require** an API key for the embedding provider. OpenClaw resolves keys from auth profiles, `models.providers.*.apiKey`, or environment variables. Codex OAuth only covers chat/completions and does **not** satisfy embeddings for memory search. For Gemini, use `GEMINI_API_KEY` or `models.providers.google.apiKey`. For Voyage, use `VOYAGE_API_KEY` or `models.providers.voyage.apiKey`. For Mistral, use `MISTRAL_API_KEY` or `models.providers.mistral.apiKey`. Ollama typically does not require a real API key (a placeholder like `OLLAMA_API_KEY=ollama-local` is enough when needed by local policy). When using a custom OpenAI-compatible endpoint, set `memorySearch.remote.apiKey` (and optional `memorySearch.remote.headers`).

## QMD backend (experimental)

Set `memory.backend = "qmd"` to swap the built-in SQLite indexer for [QMD](https://github.com/tobi/qmd): a local-first search sidecar that combines BM25 + vectors + reranking. Markdown stays the source of truth; OpenClaw shells out to QMD for retrieval. Key points:

### Prerequisites

### How the sidecar runs

### Config surface (`memory.qmd.*`)

* `command` (default `qmd`): override the executable path.
* `searchMode` (default `search`): pick which QMD command backs `memory_search` (`search`, `vsearch`, `query`).
* `includeDefaultMemory` (default `true`): auto-index `MEMORY.md` + `memory/**/*.md`.
* `paths[]`: add extra directories/files (`path`, optional `pattern`, optional stable `name`).
* `sessions`: opt into session JSONL indexing (`enabled`, `retentionDays`, `exportDir`).
* `update`: controls refresh cadence and maintenance execution: (`interval`, `debounceMs`, `onBoot`, `waitForBootSync`, `embedInterval`, `commandTimeoutMs`, `updateTimeoutMs`, `embedTimeoutMs`).
* `limits`: clamp recall payload (`maxResults`, `maxSnippetChars`, `maxInjectedChars`, `timeoutMs`).
* `scope`: same schema as [`session.sendPolicy`](https://docs.openclaw.ai/gateway/configuration-reference#session). Default is DM-only (`deny` all, `allow` direct chats); loosen it to surface QMD hits in groups/channels.
* When `scope` denies a search, OpenClaw logs a warning with the derived `channel`/`chatType` so empty results are easier to debug.
* Snippets sourced outside the workspace show up as `qmd/<collection>/<relative-path>` in `memory_search` results; `memory_get` understands that prefix and reads from the configured QMD collection root.
* When `memory.qmd.sessions.enabled = true`, OpenClaw exports sanitized session transcripts (User/Assistant turns) into a dedicated QMD collection under `~/.openclaw/agents/<id>/qmd/sessions/`, so `memory_search` can recall recent conversations without touching the builtin SQLite index.
* `memory_search` snippets now include a `Source: <path#line>` footer when `memory.citations` is `auto`/`on`; set `memory.citations = "off"` to keep the path metadata internal (the agent still receives the path for `memory_get`, but the snippet text omits the footer and the system prompt warns the agent not to cite it).

### QMD example

```
memory: {
  backend: "qmd",
  citations: "auto",
  qmd: {
    includeDefaultMemory: true,
    update: { interval: "5m", debounceMs: 15000 },
    limits: { maxResults: 6, timeoutMs: 4000 },
    scope: {
      default: "deny",
      rules: [
        { action: "allow", match: { chatType: "direct" } },
        // Normalized session-key prefix (strips `agent:<id>:`).
        { action: "deny", match: { keyPrefix: "discord:channel:" } },
        // Raw session-key prefix (includes `agent:<id>:`).
        { action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } },
      ]
    },
    paths: [
      { name: "docs", path: "~/notes", pattern: "**/*.md" }
    ]
  }
}
```

### Citations and fallback

## Additional memory paths

If you want to index Markdown files outside the default workspace layout, add explicit paths:

Notes:

## Multimodal memory files (Gemini image + audio)

OpenClaw can index image and audio files from `memorySearch.extraPaths` when using Gemini embedding 2:

Notes:

* Multimodal memory is currently supported only for `gemini-embedding-2-preview`.
* Multimodal indexing applies only to files discovered through `memorySearch.extraPaths`.
* Supported modalities in this phase: image and audio.
* `memorySearch.fallback` must stay `"none"` while multimodal memory is enabled.
* Matching image/audio file bytes are uploaded to the configured Gemini embedding endpoint during indexing.
* Supported image extensions: `.jpg`, `.jpeg`, `.png`, `.webp`, `.gif`, `.heic`, `.heif`.
* Supported audio extensions: `.mp3`, `.wav`, `.ogg`, `.opus`, `.m4a`, `.aac`, `.flac`.
* Search queries remain text, but Gemini can compare those text queries against indexed image/audio embeddings.
* `memory_get` still reads Markdown only; binary files are searchable but not returned as raw file contents.

## Gemini embeddings (native)

Set the provider to `gemini` to use the Gemini embeddings API directly:

Notes:

### Gemini Embedding 2 (preview)

> **Re-index required:** Switching from `gemini-embedding-001` (768 dimensions) to `gemini-embedding-2-preview` (3072 dimensions) changes the vector size. The same is true if you change `outputDimensionality` between 768, 1536, and 3072. OpenClaw will automatically reindex when it detects a model or dimension change.

## Custom OpenAI-compatible endpoint

If you want to use a custom OpenAI-compatible endpoint (OpenRouter, vLLM, or a proxy), you can use the `remote` configuration with the OpenAI provider:

If you don’t want to set an API key, use `memorySearch.provider = "local"` or set `memorySearch.fallback = "none"`.

### Fallbacks

### Batch indexing (OpenAI + Gemini + Voyage)

Why OpenAI batch is fast and cheap:

Config example:

## How the memory tools work

## What gets indexed (and when)

## Hybrid search (BM25 + vector)

When enabled, OpenClaw combines:

If full-text search is unavailable on your platform, OpenClaw falls back to vector-only search.

### Why hybrid

Vector search is great at “this means the same thing”:

But it can be weak at exact, high-signal tokens:

BM25 (full-text) is the opposite: strong at exact tokens, weaker at paraphrases. Hybrid search is the pragmatic middle ground: **use both retrieval signals** so you get good results for both “natural language” queries and “needle in a haystack” queries.

### How we merge results (the current design)

Implementation sketch:

1. Retrieve a candidate pool from both sides:

2) Convert BM25 rank into a 0..1-ish score:

3. Union candidates by chunk id and compute a weighted score:

Notes:

This isn’t “IR-theory perfect”, but it’s simple, fast, and tends to improve recall/precision on real notes. If we want to get fancier later, common next steps are Reciprocal Rank Fusion (RRF) or score normalization (min/max or z-score) before mixing.

### Post-processing pipeline

After merging vector and keyword scores, two optional post-processing stages refine the result list before it reaches the agent:

Both stages are **off by default** and can be enabled independently.

### MMR re-ranking (diversity)

When hybrid search returns results, multiple chunks may contain similar or overlapping content. For example, searching for “home network setup” might return five nearly identical snippets from different daily notes that all mention the same router configuration. **MMR (Maximal Marginal Relevance)** re-ranks the results to balance relevance with diversity, ensuring the top results cover different aspects of the query instead of repeating the same information. How it works:

1. Results are scored by their original relevance (vector + BM25 weighted score).
2. MMR iteratively selects results that maximize: `lambda x relevance - (1-lambda) x max_similarity_to_selected`.
3. Similarity between results is measured using Jaccard text similarity on tokenized content.

The `lambda` parameter controls the trade-off:

**Example — query: “home network setup”** Given these memory files:

Without MMR — top 3 results:

With MMR (lambda=0.7) — top 3 results:

The near-duplicate from Feb 8 drops out, and the agent gets three distinct pieces of information. **When to enable:** If you notice `memory_search` returning redundant or near-duplicate snippets, especially with daily notes that often repeat similar information across days.

### Temporal decay (recency boost)

Agents with daily notes accumulate hundreds of dated files over time. Without decay, a well-worded note from six months ago can outrank yesterday’s update on the same topic. **Temporal decay** applies an exponential multiplier to scores based on the age of each result, so recent memories naturally rank higher while old ones fade:

where `lambda = ln(2) / halfLifeDays`. With the default half-life of 30 days:

**Evergreen files are never decayed:**

**Dated daily files** (`memory/YYYY-MM-DD.md`) use the date extracted from the filename. Other sources (e.g., session transcripts) fall back to file modification time (`mtime`). **Example — query: “what’s Rod’s work schedule?”** Given these memory files (today is Feb 10):

Without decay:

With decay (halfLife=30):

The stale September note drops to the bottom despite having the best raw semantic match. **When to enable:** If your agent has months of daily notes and you find that old, stale information outranks recent context. A half-life of 30 days works well for daily-note-heavy workflows; increase it (e.g., 90 days) if you reference older notes frequently.

### Hybrid search configuration

Both features are configured under `memorySearch.query.hybrid`:

You can enable either feature independently:

## Embedding cache

OpenClaw can cache **chunk embeddings** in SQLite so reindexing and frequent updates (especially session transcripts) don’t re-embed unchanged text. Config:

## Session memory search (experimental)

You can optionally index **session transcripts** and surface them via `memory_search`. This is gated behind an experimental flag.

Notes:

Delta thresholds (defaults shown):

## SQLite vector acceleration (sqlite-vec)

When the sqlite-vec extension is available, OpenClaw stores embeddings in a SQLite virtual table (`vec0`) and performs vector distance queries in the database. This keeps search fast without loading every embedding into JS. Configuration (optional):

Notes:

## Local embedding auto-download

## Custom OpenAI-compatible endpoint example

Notes:

----
url: https://docs.openclaw.ai/cli/system
----

# system - OpenClaw

## [​](#openclaw-system)`openclaw system`

System-level helpers for the Gateway: enqueue system events, control heartbeats, and view presence.

## [​](#common-commands)Common commands

```
openclaw system event --text "Check for urgent follow-ups" --mode now
openclaw system heartbeat enable
openclaw system heartbeat last
openclaw system presence
```

## [​](#system-event)`system event`

Enqueue a system event on the **main** session. The next heartbeat will inject it as a `System:` line in the prompt. Use `--mode now` to trigger the heartbeat immediately; `next-heartbeat` waits for the next scheduled tick. Flags:

* `--text <text>`: required system event text.
* `--mode <mode>`: `now` or `next-heartbeat` (default).
* `--json`: machine-readable output.

## [​](#system-heartbeat-last|enable|disable)`system heartbeat last|enable|disable`

Heartbeat controls:

* `last`: show the last heartbeat event.
* `enable`: turn heartbeats back on (use this if they were disabled).
* `disable`: pause heartbeats.

Flags:

* `--json`: machine-readable output.

## [​](#system-presence)`system presence`

List the current system presence entries the Gateway knows about (nodes, instances, and similar status lines). Flags:

* `--json`: machine-readable output.

## [​](#notes)Notes

* Requires a running Gateway reachable by your current config (local or remote).
* System events are ephemeral and not persisted across restarts.

----
url: https://docs.openclaw.ai/cli/channels
----

# channels - OpenClaw

Manage chat channel accounts and their runtime status on the Gateway. Related docs:

## Common commands

## Add / remove accounts

Tip: `openclaw channels add --help` shows per-channel flags (token, private key, app token, signal-cli paths, etc). When you run `openclaw channels add` without flags, the interactive wizard can prompt:

If you confirm bind now, the wizard asks which agent should own each configured channel account and writes account-scoped routing bindings. You can also manage the same routing rules later with `openclaw agents bindings`, `openclaw agents bind`, and `openclaw agents unbind` (see [agents](https://docs.openclaw.ai/cli/agents)). When you add a non-default account to a channel that is still using single-account top-level settings (no `channels.<channel>.accounts` entries yet), OpenClaw moves account-scoped single-account top-level values into `channels.<channel>.accounts.default`, then writes the new account. This preserves the original account behavior while moving to the multi-account shape. Routing behavior stays consistent:

If your config was already in a mixed state (named accounts present, missing `default`, and top-level single-account values still set), run `openclaw doctor --fix` to move account-scoped values into `accounts.default`.

## Login / logout (interactive)

## Troubleshooting

## Capabilities probe

Fetch provider capability hints (intents/scopes where available) plus static feature support:

Notes:

## Resolve names to IDs

Resolve channel/user names to IDs using the provider directory:

Notes:

----
url: https://docs.openclaw.ai/gateway/openresponses-http-api
----

# OpenResponses API - OpenClaw

OpenClaw’s Gateway can serve an OpenResponses-compatible `POST /v1/responses` endpoint. This endpoint is **disabled by default**. Enable it in config first.

Under the hood, requests are executed as a normal Gateway agent run (same codepath as `openclaw agent`), so routing/permissions/config match your Gateway.

## Authentication, security, and routing

Operational behavior matches [OpenAI Chat Completions](https://docs.openclaw.ai/gateway/openai-http-api):

Enable or disable this endpoint with `gateway.http.endpoints.responses.enabled`.

## Session behavior

By default the endpoint is **stateless per request** (a new session key is generated each call). If the request includes an OpenResponses `user` string, the Gateway derives a stable session key from it, so repeated calls can share an agent session.

## Request shape (supported)

The request follows the OpenResponses API with item-based input. Current support:

Accepted but **currently ignored**:

## Items (input)

### `message`

Roles: `system`, `developer`, `user`, `assistant`.

### `function_call_output` (turn-based tools)

Send tool results back to the model:

### `reasoning` and `item_reference`

Accepted for schema compatibility but ignored when building the prompt.

## Tools (client-side function tools)

Provide tools with `tools: [{ type: "function", function: { name, description?, parameters? } }]`. If the agent decides to call a tool, the response returns a `function_call` output item. You then send a follow-up request with `function_call_output` to continue the turn.

## Images (`input_image`)

Supports base64 or URL sources:

Allowed MIME types (current): `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/heic`, `image/heif`. Max size (current): 10MB.

## Files (`input_file`)

Supports base64 or URL sources:

Allowed MIME types (current): `text/plain`, `text/markdown`, `text/html`, `text/csv`, `application/json`, `application/pdf`. Max size (current): 5MB. Current behavior:

PDF parsing uses the Node-friendly `pdfjs-dist` legacy build (no worker). The modern PDF.js build expects browser workers/DOM globals, so it is not used in the Gateway. URL fetch defaults:

## File + image limits (config)

Defaults can be tuned under `gateway.http.endpoints.responses`:

```
{
  gateway: {
    http: {
      endpoints: {
        responses: {
          enabled: true,
          maxBodyBytes: 20000000,
          maxUrlParts: 8,
          files: {
            allowUrl: true,
            urlAllowlist: ["cdn.example.com", "*.assets.example.com"],
            allowedMimes: [
              "text/plain",
              "text/markdown",
              "text/html",
              "text/csv",
              "application/json",
              "application/pdf",
            ],
            maxBytes: 5242880,
            maxChars: 200000,
            maxRedirects: 3,
            timeoutMs: 10000,
            pdf: {
              maxPages: 4,
              maxPixels: 4000000,
              minTextChars: 200,
            },
          },
          images: {
            allowUrl: true,
            urlAllowlist: ["images.example.com"],
            allowedMimes: [
              "image/jpeg",
              "image/png",
              "image/gif",
              "image/webp",
              "image/heic",
              "image/heif",
            ],
            maxBytes: 10485760,
            maxRedirects: 3,
            timeoutMs: 10000,
          },
        },
      },
    },
  },
}
```

Defaults when omitted:

Security note:

## Streaming (SSE)

Set `stream: true` to receive Server-Sent Events (SSE):

Event types currently emitted:

## Usage

`usage` is populated when the underlying provider reports token counts.

## Errors

Errors use a JSON object like:

Common cases:

## Examples

Non-streaming:

Streaming:

----
url: https://docs.openclaw.ai/plugins/architecture
----

# Plugin Internals - OpenClaw

This page covers the internal architecture of the OpenClaw plugin system.

## Public capability model

Capabilities are the public **native plugin** model inside OpenClaw. Every native OpenClaw plugin registers against one or more capability types:

| Capability          | Registration method                           | Example plugins           |
| ------------------- | --------------------------------------------- | ------------------------- |
| Text inference      | `api.registerProvider(...)`                   | `openai`, `anthropic`     |
| Speech              | `api.registerSpeechProvider(...)`             | `elevenlabs`, `microsoft` |
| Media understanding | `api.registerMediaUnderstandingProvider(...)` | `openai`, `google`        |
| Image generation    | `api.registerImageGenerationProvider(...)`    | `openai`, `google`        |
| Web search          | `api.registerWebSearchProvider(...)`          | `google`                  |
| Channel / messaging | `api.registerChannel(...)`                    | `msteams`, `matrix`       |

A plugin that registers zero capabilities but provides hooks, tools, or services is a **legacy hook-only** plugin. That pattern is still fully supported.

### External compatibility stance

The capability model is landed in core and used by bundled/native plugins today, but external plugin compatibility still needs a tighter bar than “it is exported, therefore it is frozen.” Current guidance:

Practical rule:

### Plugin shapes

OpenClaw classifies every loaded plugin into a shape based on its actual registration behavior (not just static metadata):

* **plain-capability** — registers exactly one capability type (for example a provider-only plugin like `mistral`)
* **hybrid-capability** — registers multiple capability types (for example `openai` owns text inference, speech, media understanding, and image generation)
* **hook-only** — registers only hooks (typed or custom), no capabilities, tools, commands, or services
* **non-capability** — registers tools, commands, services, or routes but no capabilities

Use `openclaw plugins inspect <id>` to see a plugin’s shape and capability breakdown. See [CLI reference](https://docs.openclaw.ai/cli/plugins#inspect) for details.

### Legacy hooks

The `before_agent_start` hook remains supported as a compatibility path for hook-only plugins. Legacy real-world plugins still depend on it. Direction:

### Compatibility signals

When you run `openclaw doctor` or `openclaw plugins inspect <id>`, you may see one of these labels:

| Signal                     | Meaning                                                      |
| -------------------------- | ------------------------------------------------------------ |
| **config valid**           | Config parses fine and plugins resolve                       |
| **compatibility advisory** | Plugin uses a supported-but-older pattern (e.g. `hook-only`) |
| **legacy warning**         | Plugin uses `before_agent_start`, which is deprecated        |
| **hard error**             | Config is invalid or plugin failed to load                   |

Neither `hook-only` nor `before_agent_start` will break your plugin today — `hook-only` is advisory, and `before_agent_start` only triggers a warning. These signals also appear in `openclaw status --all` and `openclaw plugins doctor`.

## Architecture overview

OpenClaw’s plugin system has four layers:

1. **Manifest + discovery** OpenClaw finds candidate plugins from configured paths, workspace roots, global extension roots, and bundled extensions. Discovery reads native `openclaw.plugin.json` manifests plus supported bundle manifests first.
2. **Enablement + validation** Core decides whether a discovered plugin is enabled, disabled, blocked, or selected for an exclusive slot such as memory.
3. **Runtime loading** Native OpenClaw plugins are loaded in-process via jiti and register capabilities into a central registry. Compatible bundles are normalized into registry records without importing runtime code.
4. **Surface consumption** The rest of OpenClaw reads the registry to expose tools, channels, provider setup, hooks, HTTP routes, CLI commands, and services.

The important design boundary:

That split lets OpenClaw validate config, explain missing/disabled plugins, and build UI/schema hints before the full runtime is active.

### Channel plugins and the shared message tool

Channel plugins do not need to register a separate send/edit/react tool for normal chat actions. OpenClaw keeps one shared `message` tool in core, and channel plugins own the channel-specific discovery and execution behind it. The current boundary is:

For channel plugins, the SDK surface is `ChannelMessageActionAdapter.describeMessageTool(...)`. That unified discovery call lets a plugin return its visible actions, capabilities, and schema contributions together so those pieces do not drift apart. Core passes runtime scope into that discovery step. Important fields include:

That matters for context-sensitive plugins. A channel can hide or expose message actions based on the active account, current room/thread/message, or trusted requester identity without hardcoding channel-specific branches in the core `message` tool. This is why embedded-runner routing changes are still plugin work: the runner is responsible for forwarding the current chat/session identity into the plugin discovery boundary so the shared `message` tool exposes the right channel-owned surface for the current turn. For channel-owned execution helpers, bundled plugins should keep the execution runtime inside their own extension modules. Core no longer owns the Discord, Slack, Telegram, or WhatsApp message-action runtimes under `src/agents/tools`. We do not publish separate `plugin-sdk/*-action-runtime` subpaths, and bundled plugins should import their own local runtime code directly from their extension-owned modules. For polls specifically, there are two execution paths:

Core now defers shared poll parsing until after plugin poll dispatch declines the action, so plugin-owned poll handlers can accept channel-specific poll fields without being blocked by the generic poll parser first. See [Load pipeline](#load-pipeline) for the full startup sequence.

## Capability ownership model

OpenClaw treats a native plugin as the ownership boundary for a **company** or a **feature**, not as a grab bag of unrelated integrations. That means:

Examples:

The intended end state is:

This is the key distinction:

So if OpenClaw adds a new domain such as video, the first question is not “which provider should hardcode video handling?” The first question is “what is the core video capability contract?” Once that contract exists, vendor plugins can register against it and channel/feature plugins can consume it. If the capability does not exist yet, the right move is usually:

1. define the missing capability in core
2. expose it through the plugin API/runtime in a typed way
3. wire channels/features against that capability
4. let vendor plugins register implementations

This keeps ownership explicit while avoiding core behavior that depends on a single vendor or a one-off plugin-specific code path.

### Capability layering

Use this mental model when deciding where code belongs:

* **core capability layer**: shared orchestration, policy, fallback, config merge rules, delivery semantics, and typed contracts
* **vendor plugin layer**: vendor-specific APIs, auth, model catalogs, speech synthesis, image generation, future video backends, usage endpoints
* **channel/feature plugin layer**: Slack/Discord/voice-call/etc. integration that consumes core capabilities and presents them on a surface

For example, TTS follows this shape:

That same pattern should be preferred for future capabilities.

### Multi-capability company plugin example

A company plugin should feel cohesive from the outside. If OpenClaw has shared contracts for models, speech, media understanding, and web search, a vendor can own all of its surfaces in one place:

```
import type { OpenClawPluginDefinition } from "openclaw/plugin-sdk";
import {
  buildOpenAISpeechProvider,
  createPluginBackedWebSearchProvider,
  describeImageWithModel,
  transcribeOpenAiCompatibleAudio,
} from "openclaw/plugin-sdk";

const plugin: OpenClawPluginDefinition = {
  id: "exampleai",
  name: "ExampleAI",
  register(api) {
    api.registerProvider({
      id: "exampleai",
      // auth/model catalog/runtime hooks
    });

    api.registerSpeechProvider(
      buildOpenAISpeechProvider({
        id: "exampleai",
        // vendor speech config
      }),
    );

    api.registerMediaUnderstandingProvider({
      id: "exampleai",
      capabilities: ["image", "audio", "video"],
      async describeImage(req) {
        return describeImageWithModel({
          provider: "exampleai",
          model: req.model,
          input: req.input,
        });
      },
      async transcribeAudio(req) {
        return transcribeOpenAiCompatibleAudio({
          provider: "exampleai",
          model: req.model,
          input: req.input,
        });
      },
    });

    api.registerWebSearchProvider(
      createPluginBackedWebSearchProvider({
        id: "exampleai-search",
        // credential + fetch logic
      }),
    );
  },
};

export default plugin;
```

What matters is not the exact helper names. The shape matters:

### Capability example: video understanding

OpenClaw already treats image/audio/video understanding as one shared capability. The same ownership model applies there:

1. core defines the media-understanding contract
2. vendor plugins register `describeImage`, `transcribeAudio`, and `describeVideo` as applicable
3. channels and feature plugins consume the shared core behavior instead of wiring directly to vendor code

That avoids baking one provider’s video assumptions into core. The plugin owns the vendor surface; core owns the capability contract and fallback behavior. If OpenClaw adds a new domain later, such as video generation, use the same sequence again: define the core capability first, then let vendor plugins register implementations against it. Need a concrete rollout checklist? See [Capability Cookbook](https://docs.openclaw.ai/tools/capability-cookbook).

## Contracts and enforcement

The plugin API surface is intentionally typed and centralized in `OpenClawPluginApi`. That contract defines the supported registration points and the runtime helpers a plugin may rely on. Why this matters:

There are two layers of enforcement:

1. **runtime registration enforcement** The plugin registry validates registrations as plugins load. Examples: duplicate provider ids, duplicate speech provider ids, and malformed registrations produce plugin diagnostics instead of undefined behavior.
2. **contract tests** Bundled plugins are captured in contract registries during test runs so OpenClaw can assert ownership explicitly. Today this is used for model providers, speech providers, web search providers, and bundled registration ownership.

The practical effect is that OpenClaw knows, up front, which plugin owns which surface. That lets core and channels compose seamlessly because ownership is declared, typed, and testable rather than implicit.

### What belongs in a contract

Good plugin contracts are:

Bad plugin contracts are:

When in doubt, raise the abstraction level: define the capability first, then let plugins plug into it.

## Execution model

Native OpenClaw plugins run **in-process** with the Gateway. They are not sandboxed. A loaded native plugin has the same process-level trust boundary as core code. Implications:

Compatible bundles are safer by default because OpenClaw currently treats them as metadata/content packs. In current releases, that mostly means bundled skills. Use allowlists and explicit install/load paths for non-bundled plugins. Treat workspace plugins as development-time code, not production defaults. For bundled workspace package names, keep the plugin id anchored in the npm name: `@openclaw/<id>` by default, or an approved typed suffix such as `-provider`, `-plugin`, `-speech`, `-sandbox`, or `-media-understanding` when the package intentionally exposes a narrower plugin role. Important trust note:

## Export boundary

OpenClaw exports capabilities, not implementation convenience. Keep capability registration public. Trim non-contract helper exports:

## Load pipeline

At startup, OpenClaw does roughly this:

1. discover candidate plugin roots
2. read native or compatible bundle manifests and package metadata
3. reject unsafe candidates
4. normalize plugin config (`plugins.enabled`, `allow`, `deny`, `entries`, `slots`, `load.paths`)
5. decide enablement for each candidate
6. load enabled native modules via jiti
7. call native `register(api)` hooks and collect registrations into the plugin registry
8. expose the registry to commands/runtime surfaces

The safety gates happen **before** runtime execution. Candidates are blocked when the entry escapes the plugin root, the path is world-writable, or path ownership looks suspicious for non-bundled plugins.

### Manifest-first behavior

The manifest is the control-plane source of truth. OpenClaw uses it to:

For native plugins, the runtime module is the data-plane part. It registers actual behavior such as hooks, tools, commands, or provider flows.

### What the loader caches

OpenClaw keeps short in-process caches for:

These caches reduce bursty startup and repeated command overhead. They are safe to think of as short-lived performance caches, not persistence. Performance note:

## Registry model

Loaded plugins do not directly mutate random core globals. They register into a central plugin registry. The registry tracks:

Core features then read from that registry instead of talking to plugin modules directly. This keeps loading one-way:

That separation matters for maintainability. It means most core surfaces only need one integration point: “read the registry”, not “special-case every plugin module”.

## Conversation binding callbacks

Plugins that bind a conversation can react when an approval is resolved. Use `api.onConversationBindingResolved(...)` to receive a callback after a bind request is approved or denied:

Callback payload fields:

This callback is notification-only. It does not change who is allowed to bind a conversation, and it runs after core approval handling finishes.

## Provider runtime hooks

Provider plugins now have two layers:

* manifest metadata: `providerAuthEnvVars` for cheap env-auth lookup before runtime load, plus `providerAuthChoices` for cheap onboarding/auth-choice labels and CLI flag metadata before runtime load
* config-time hooks: `catalog` / legacy `discovery`
* runtime hooks: `resolveDynamicModel`, `prepareDynamicModel`, `normalizeResolvedModel`, `capabilities`, `prepareExtraParams`, `wrapStreamFn`, `formatApiKey`, `refreshOAuth`, `buildAuthDoctorHint`, `isCacheTtlEligible`, `buildMissingAuthMessage`, `suppressBuiltInModel`, `augmentModelCatalog`, `isBinaryThinking`, `supportsXHighThinking`, `resolveDefaultThinkingLevel`, `isModernModelRef`, `prepareRuntimeAuth`, `resolveUsageAuth`, `fetchUsageSnapshot`

OpenClaw still owns the generic agent loop, failover, transcript handling, and tool policy. These hooks are the extension surface for provider-specific behavior without needing a whole custom inference transport. Use manifest `providerAuthEnvVars` when the provider has env-based credentials that generic auth/status/model-picker paths should see without loading plugin runtime. Use manifest `providerAuthChoices` when onboarding/auth-choice CLI surfaces should know the provider’s choice id, group labels, and simple one-flag auth wiring without loading provider runtime. Keep provider runtime `envVars` for operator-facing hints such as onboarding labels or OAuth client-id/client-secret setup vars.

### Hook order and usage

For model/provider plugins, OpenClaw calls hooks in this rough order. The “When to use” column is the quick decision guide.

| #  | Hook                          | What it does                                                                             | When to use                                                                          |
| -- | ----------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| 1  | `catalog`                     | Publish provider config into `models.providers` during `models.json` generation          | Provider owns a catalog or base URL defaults                                         |
| —  | *(built-in model lookup)*     | OpenClaw tries the normal registry/catalog path first                                    | *(not a plugin hook)*                                                                |
| 2  | `resolveDynamicModel`         | Sync fallback for provider-owned model ids not in the local registry yet                 | Provider accepts arbitrary upstream model ids                                        |
| 3  | `prepareDynamicModel`         | Async warm-up, then `resolveDynamicModel` runs again                                     | Provider needs network metadata before resolving unknown ids                         |
| 4  | `normalizeResolvedModel`      | Final rewrite before the embedded runner uses the resolved model                         | Provider needs transport rewrites but still uses a core transport                    |
| 5  | `capabilities`                | Provider-owned transcript/tooling metadata used by shared core logic                     | Provider needs transcript/provider-family quirks                                     |
| 6  | `prepareExtraParams`          | Request-param normalization before generic stream option wrappers                        | Provider needs default request params or per-provider param cleanup                  |
| 7  | `wrapStreamFn`                | Stream wrapper after generic wrappers are applied                                        | Provider needs request headers/body/model compat wrappers without a custom transport |
| 8  | `formatApiKey`                | Auth-profile formatter: stored profile becomes the runtime `apiKey` string               | Provider stores extra auth metadata and needs a custom runtime token shape           |
| 9  | `refreshOAuth`                | OAuth refresh override for custom refresh endpoints or refresh-failure policy            | Provider does not fit the shared `pi-ai` refreshers                                  |
| 10 | `buildAuthDoctorHint`         | Repair hint appended when OAuth refresh fails                                            | Provider needs provider-owned auth repair guidance after refresh failure             |
| 11 | `isCacheTtlEligible`          | Prompt-cache policy for proxy/backhaul providers                                         | Provider needs proxy-specific cache TTL gating                                       |
| 12 | `buildMissingAuthMessage`     | Replacement for the generic missing-auth recovery message                                | Provider needs a provider-specific missing-auth recovery hint                        |
| 13 | `suppressBuiltInModel`        | Stale upstream model suppression plus optional user-facing error hint                    | Provider needs to hide stale upstream rows or replace them with a vendor hint        |
| 14 | `augmentModelCatalog`         | Synthetic/final catalog rows appended after discovery                                    | Provider needs synthetic forward-compat rows in `models list` and pickers            |
| 15 | `isBinaryThinking`            | On/off reasoning toggle for binary-thinking providers                                    | Provider exposes only binary thinking on/off                                         |
| 16 | `supportsXHighThinking`       | `xhigh` reasoning support for selected models                                            | Provider wants `xhigh` on only a subset of models                                    |
| 17 | `resolveDefaultThinkingLevel` | Default `/think` level for a specific model family                                       | Provider owns default `/think` policy for a model family                             |
| 18 | `isModernModelRef`            | Modern-model matcher for live profile filters and smoke selection                        | Provider owns live/smoke preferred-model matching                                    |
| 19 | `prepareRuntimeAuth`          | Exchange a configured credential into the actual runtime token/key just before inference | Provider needs a token exchange or short-lived request credential                    |
| 20 | `resolveUsageAuth`            | Resolve usage/billing credentials for `/usage` and related status surfaces               | Provider needs custom usage/quota token parsing or a different usage credential      |
| 21 | `fetchUsageSnapshot`          | Fetch and normalize provider-specific usage/quota snapshots after auth is resolved       | Provider needs a provider-specific usage endpoint or payload parser                  |

If the provider needs a fully custom wire protocol or custom request executor, that is a different class of extension. These hooks are for provider behavior that still runs on OpenClaw’s normal inference loop.

### Provider example

```
api.registerProvider({
  id: "example-proxy",
  label: "Example Proxy",
  auth: [],
  catalog: {
    order: "simple",
    run: async (ctx) => {
      const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey;
      if (!apiKey) {
        return null;
      }
      return {
        provider: {
          baseUrl: "https://proxy.example.com/v1",
          apiKey,
          api: "openai-completions",
          models: [{ id: "auto", name: "Auto" }],
        },
      };
    },
  },
  resolveDynamicModel: (ctx) => ({
    id: ctx.modelId,
    name: ctx.modelId,
    provider: "example-proxy",
    api: "openai-completions",
    baseUrl: "https://proxy.example.com/v1",
    reasoning: false,
    input: ["text"],
    cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
    contextWindow: 128000,
    maxTokens: 8192,
  }),
  prepareRuntimeAuth: async (ctx) => {
    const exchanged = await exchangeToken(ctx.apiKey);
    return {
      apiKey: exchanged.token,
      baseUrl: exchanged.baseUrl,
      expiresAt: exchanged.expiresAt,
    };
  },
  resolveUsageAuth: async (ctx) => {
    const auth = await ctx.resolveOAuthToken();
    return auth ? { token: auth.token } : null;
  },
  fetchUsageSnapshot: async (ctx) => {
    return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn);
  },
});
```

### Built-in examples

* Anthropic uses `resolveDynamicModel`, `capabilities`, `buildAuthDoctorHint`, `resolveUsageAuth`, `fetchUsageSnapshot`, `isCacheTtlEligible`, `resolveDefaultThinkingLevel`, and `isModernModelRef` because it owns Claude 4.6 forward-compat, provider-family hints, auth repair guidance, usage endpoint integration, prompt-cache eligibility, and Claude default/adaptive thinking policy.
* OpenAI uses `resolveDynamicModel`, `normalizeResolvedModel`, and `capabilities` plus `buildMissingAuthMessage`, `suppressBuiltInModel`, `augmentModelCatalog`, `supportsXHighThinking`, and `isModernModelRef` because it owns GPT-5.4 forward-compat, the direct OpenAI `openai-completions` -> `openai-responses` normalization, Codex-aware auth hints, Spark suppression, synthetic OpenAI list rows, and GPT-5 thinking / live-model policy.
* OpenRouter uses `catalog` plus `resolveDynamicModel` and `prepareDynamicModel` because the provider is pass-through and may expose new model ids before OpenClaw’s static catalog updates; it also uses `capabilities`, `wrapStreamFn`, and `isCacheTtlEligible` to keep provider-specific request headers, routing metadata, reasoning patches, and prompt-cache policy out of core.
* GitHub Copilot uses `catalog`, `auth`, `resolveDynamicModel`, and `capabilities` plus `prepareRuntimeAuth` and `fetchUsageSnapshot` because it needs provider-owned device login, model fallback behavior, Claude transcript quirks, a GitHub token -> Copilot token exchange, and a provider-owned usage endpoint.
* OpenAI Codex uses `catalog`, `resolveDynamicModel`, `normalizeResolvedModel`, `refreshOAuth`, and `augmentModelCatalog` plus `prepareExtraParams`, `resolveUsageAuth`, and `fetchUsageSnapshot` because it still runs on core OpenAI transports but owns its transport/base URL normalization, OAuth refresh fallback policy, default transport choice, synthetic Codex catalog rows, and ChatGPT usage endpoint integration.
* Google AI Studio and Gemini CLI OAuth use `resolveDynamicModel` and `isModernModelRef` because they own Gemini 3.1 forward-compat fallback and modern-model matching; Gemini CLI OAuth also uses `formatApiKey`, `resolveUsageAuth`, and `fetchUsageSnapshot` for token formatting, token parsing, and quota endpoint wiring.
* Moonshot uses `catalog` plus `wrapStreamFn` because it still uses the shared OpenAI transport but needs provider-owned thinking payload normalization.
* Kilocode uses `catalog`, `capabilities`, `wrapStreamFn`, and `isCacheTtlEligible` because it needs provider-owned request headers, reasoning payload normalization, Gemini transcript hints, and Anthropic cache-TTL gating.
* Z.AI uses `resolveDynamicModel`, `prepareExtraParams`, `wrapStreamFn`, `isCacheTtlEligible`, `isBinaryThinking`, `isModernModelRef`, `resolveUsageAuth`, and `fetchUsageSnapshot` because it owns GLM-5 fallback, `tool_stream` defaults, binary thinking UX, modern-model matching, and both usage auth + quota fetching.
* Mistral, OpenCode Zen, and OpenCode Go use `capabilities` only to keep transcript/tooling quirks out of core.
* Catalog-only bundled providers such as `byteplus`, `cloudflare-ai-gateway`, `huggingface`, `kimi-coding`, `modelstudio`, `nvidia`, `qianfan`, `synthetic`, `together`, `venice`, `vercel-ai-gateway`, and `volcengine` use `catalog` only.
* Qwen portal uses `catalog`, `auth`, and `refreshOAuth`.
* MiniMax and Xiaomi use `catalog` plus usage hooks because their `/usage` behavior is plugin-owned even though inference still runs through the shared transports.

## Runtime helpers

Plugins can access selected core helpers via `api.runtime`. For TTS:

Notes:

Plugins can also register speech providers via `api.registerSpeechProvider(...)`.

Notes:

For image/audio/video understanding, plugins register one typed media-understanding provider instead of a generic key/value bag:

Notes:

For media-understanding runtime helpers, plugins can call:

For audio transcription, plugins can use either the media-understanding runtime or the older STT alias:

Notes:

Plugins can also launch background subagent runs through `api.runtime.subagent`:

Notes:

For web search, plugins can consume the shared runtime helper instead of reaching into the agent tool wiring:

Plugins can also register web-search providers via `api.registerWebSearchProvider(...)`. Notes:

## Gateway HTTP routes

Plugins can expose HTTP endpoints with `api.registerHttpRoute(...)`.

Route fields:

Notes:

## Plugin SDK import paths

Use SDK subpaths instead of the monolithic `openclaw/plugin-sdk` import when authoring plugins:

* `openclaw/plugin-sdk/plugin-entry` for plugin registration primitives.
* `openclaw/plugin-sdk/core` for the generic shared plugin-facing contract.
* Stable channel primitives such as `openclaw/plugin-sdk/channel-setup`, `openclaw/plugin-sdk/channel-pairing`, `openclaw/plugin-sdk/channel-contract`, `openclaw/plugin-sdk/channel-feedback`, `openclaw/plugin-sdk/channel-inbound`, `openclaw/plugin-sdk/channel-lifecycle`, `openclaw/plugin-sdk/channel-reply-pipeline`, `openclaw/plugin-sdk/command-auth`, `openclaw/plugin-sdk/secret-input`, and `openclaw/plugin-sdk/webhook-ingress` for shared setup/auth/reply/webhook wiring. `channel-inbound` is the shared home for debounce, mention matching, envelope formatting, and inbound envelope context helpers.
* Domain subpaths such as `openclaw/plugin-sdk/channel-config-helpers`, `openclaw/plugin-sdk/allow-from`, `openclaw/plugin-sdk/channel-config-schema`, `openclaw/plugin-sdk/channel-policy`, `openclaw/plugin-sdk/config-runtime`, `openclaw/plugin-sdk/infra-runtime`, `openclaw/plugin-sdk/agent-runtime`, `openclaw/plugin-sdk/lazy-runtime`, `openclaw/plugin-sdk/reply-history`, `openclaw/plugin-sdk/routing`, `openclaw/plugin-sdk/status-helpers`, `openclaw/plugin-sdk/runtime-store`, and `openclaw/plugin-sdk/directory-runtime` for shared runtime/config helpers.
* `openclaw/plugin-sdk/channel-runtime` remains only as a compatibility shim. New code should import the narrower primitives instead.
* Bundled extension internals remain private. External plugins should use only `openclaw/plugin-sdk/*` subpaths. OpenClaw core/test code may use the repo public entry points under `extensions/<id>/index.js`, `api.js`, `runtime-api.js`, `setup-entry.js`, and narrowly scoped files such as `login-qr-api.js`. Never import `extensions/<id>/src/*` from core or from another extension.
* Repo entry point split: `extensions/<id>/api.js` is the helper/types barrel, `extensions/<id>/runtime-api.js` is the runtime-only barrel, `extensions/<id>/index.js` is the bundled plugin entry, and `extensions/<id>/setup-entry.js` is the setup plugin entry.
* No bundled channel-branded public subpaths remain. Channel-specific helper and runtime seams live under `extensions/<id>/api.js` and `extensions/<id>/runtime-api.js`; the public SDK contract is the generic shared primitives instead.

Compatibility note:

## Message tool schemas

Plugins should own channel-specific `describeMessageTool(...)` schema contributions. Keep provider-specific fields in the plugin, not in shared core. For shared portable schema fragments, reuse the generic helpers exported through `openclaw/plugin-sdk/channel-actions`:

If a schema shape only makes sense for one provider, define it in that plugin’s own source instead of promoting it into the shared SDK.

## Channel target resolution

Channel plugins should own channel-specific target semantics. Keep the shared outbound host generic and use the messaging adapter surface for provider rules:

Recommended split:

## Config-backed directories

Plugins that derive directory entries from config should keep that logic in the plugin and reuse the shared helpers from `openclaw/plugin-sdk/directory-runtime`. Use this when a channel needs config-backed peers/groups such as:

The shared helpers in `directory-runtime` only handle generic operations:

Channel-specific account inspection and id normalization should stay in the plugin implementation.

## Provider catalogs

Provider plugins can define model catalogs for inference with `registerProvider({ catalog: { run(...) { ... } } })`. `catalog.run(...)` returns the same shape OpenClaw writes into `models.providers`:

Use `catalog` when the plugin owns provider-specific model ids, base URL defaults, or auth-gated model metadata. `catalog.order` controls when a plugin’s catalog merges relative to OpenClaw’s built-in implicit providers:

Later providers win on key collision, so plugins can intentionally override a built-in provider entry with the same provider id. Compatibility:

## Read-only channel inspection

If your plugin registers a channel, prefer implementing `plugin.config.inspectAccount(cfg, accountId)` alongside `resolveAccount(...)`. Why:

Recommended `inspectAccount(...)` behavior:

This lets read-only commands report “configured but unavailable in this command path” instead of crashing or misreporting the account as not configured.

## Package packs

A plugin directory may include a `package.json` with `openclaw.extensions`:

Each entry becomes a plugin. If the pack lists multiple extensions, the plugin id becomes `name/<fileBase>`. If your plugin imports npm deps, install them in that directory so `node_modules` is available (`npm install` / `pnpm install`). Security guardrail: every `openclaw.extensions` entry must stay inside the plugin directory after symlink resolution. Entries that escape the package directory are rejected. Security note: `openclaw plugins install` installs plugin dependencies with `npm install --ignore-scripts` (no lifecycle scripts). Keep plugin dependency trees “pure JS/TS” and avoid packages that require `postinstall` builds. Optional: `openclaw.setupEntry` can point at a lightweight setup-only module. When OpenClaw needs setup surfaces for a disabled channel plugin, or when a channel plugin is enabled but still unconfigured, it loads `setupEntry` instead of the full plugin entry. This keeps startup and setup lighter when your main plugin entry also wires tools, hooks, or other runtime-only code. Optional: `openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen` can opt a channel plugin into the same `setupEntry` path during the gateway’s pre-listen startup phase, even when the channel is already configured. Use this only when `setupEntry` fully covers the startup surface that must exist before the gateway starts listening. In practice, that means the setup entry must register every channel-owned capability that startup depends on, such as:

If your full entry still owns any required startup capability, do not enable this flag. Keep the plugin on the default behavior and let OpenClaw load the full entry during startup. Example:

### Channel catalog metadata

Channel plugins can advertise setup/discovery metadata via `openclaw.channel` and install hints via `openclaw.install`. This keeps the core catalog data-free. Example:

```
{
  "name": "@openclaw/nextcloud-talk",
  "openclaw": {
    "extensions": ["./index.ts"],
    "channel": {
      "id": "nextcloud-talk",
      "label": "Nextcloud Talk",
      "selectionLabel": "Nextcloud Talk (self-hosted)",
      "docsPath": "/channels/nextcloud-talk",
      "docsLabel": "nextcloud-talk",
      "blurb": "Self-hosted chat via Nextcloud Talk webhook bots.",
      "order": 65,
      "aliases": ["nc-talk", "nc"]
    },
    "install": {
      "npmSpec": "@openclaw/nextcloud-talk",
      "localPath": "extensions/nextcloud-talk",
      "defaultChoice": "npm"
    }
  }
}
```

OpenClaw can also merge **external channel catalogs** (for example, an MPM registry export). Drop a JSON file at one of:

Or point `OPENCLAW_PLUGIN_CATALOG_PATHS` (or `OPENCLAW_MPM_CATALOG_PATHS`) at one or more JSON files (comma/semicolon/`PATH`-delimited). Each file should contain `{ "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }`.

## Context engine plugins

Context engine plugins own session context orchestration for ingest, assembly, and compaction. Register them from your plugin with `api.registerContextEngine(id, factory)`, then select the active engine with `plugins.slots.contextEngine`. Use this when your plugin needs to replace or extend the default context pipeline rather than just add memory search or hooks.

If your engine does **not** own the compaction algorithm, keep `compact()` implemented and delegate it explicitly:

## Adding a new capability

When a plugin needs behavior that does not fit the current API, do not bypass the plugin system with a private reach-in. Add the missing capability. Recommended sequence:

1. define the core contract Decide what shared behavior core should own: policy, fallback, config merge, lifecycle, channel-facing semantics, and runtime helper shape.
2. add typed plugin registration/runtime surfaces Extend `OpenClawPluginApi` and/or `api.runtime` with the smallest useful typed capability surface.
3. wire core + channel/feature consumers Channels and feature plugins should consume the new capability through core, not by importing a vendor implementation directly.
4. register vendor implementations Vendor plugins then register their backends against the capability.
5. add contract coverage Add tests so ownership and registration shape stay explicit over time.

This is how OpenClaw stays opinionated without becoming hardcoded to one provider’s worldview. See the [Capability Cookbook](https://docs.openclaw.ai/tools/capability-cookbook) for a concrete file checklist and worked example.

### Capability checklist

When you add a new capability, the implementation should usually touch these surfaces together:

If one of those surfaces is missing, that is usually a sign the capability is not fully integrated yet.

### Capability template

Minimal pattern:

Contract test pattern:

That keeps the rule simple:

----
url: https://docs.openclaw.ai/gateway
----

# Gateway Runbook - OpenClaw

Use this page for day-1 startup and day-2 operations of the Gateway service.

## 5-minute local startup

## Runtime model

### Port and bind precedence

| Setting      | Resolution order                                              |
| ------------ | ------------------------------------------------------------- |
| Gateway port | `--port` → `OPENCLAW_GATEWAY_PORT` → `gateway.port` → `18789` |
| Bind mode    | CLI/override → `gateway.bind` → `loopback`                    |

### Hot reload modes

| `gateway.reload.mode` | Behavior                                   |
| --------------------- | ------------------------------------------ |
| `off`                 | No config reload                           |
| `hot`                 | Apply only hot-safe changes                |
| `restart`             | Restart on reload-required changes         |
| `hybrid` (default)    | Hot-apply when safe, restart when required |

## Operator command set

## Remote access

Preferred: Tailscale/VPN. Fallback: SSH tunnel.

Then connect clients to `ws://127.0.0.1:18789` locally.

See: [Remote Gateway](https://docs.openclaw.ai/gateway/remote), [Authentication](https://docs.openclaw.ai/gateway/authentication), [Tailscale](https://docs.openclaw.ai/gateway/tailscale).

## Supervision and service lifecycle

Use supervised runs for production-like reliability.

## Multiple gateways on one host

Most setups should run **one** Gateway. Use multiple only for strict isolation/redundancy (for example a rescue profile). Checklist per instance:

Example:

See: [Multiple gateways](https://docs.openclaw.ai/gateway/multiple-gateways).

### Dev profile quick path

Defaults include isolated state/config and base gateway port `19001`.

## Protocol quick reference (operator view)

* First client frame must be `connect`.
* Gateway returns `hello-ok` snapshot (`presence`, `health`, `stateVersion`, `uptimeMs`, limits/policy).
* Requests: `req(method, params)` → `res(ok/payload|error)`.
* Common events: `connect.challenge`, `agent`, `chat`, `presence`, `tick`, `health`, `heartbeat`, `shutdown`.

Agent runs are two-stage:

1. Immediate accepted ack (`status:"accepted"`)
2. Final completion response (`status:"ok"|"error"`), with streamed `agent` events in between.

See full protocol docs: [Gateway Protocol](https://docs.openclaw.ai/gateway/protocol).

## Operational checks

### Liveness

### Readiness

### Gap recovery

Events are not replayed. On sequence gaps, refresh state (`health`, `system-presence`) before continuing.

## Common failure signatures

| Signature                                                      | Likely issue                             |
| -------------------------------------------------------------- | ---------------------------------------- |
| `refusing to bind gateway ... without auth`                    | Non-loopback bind without token/password |
| `another gateway instance is already listening` / `EADDRINUSE` | Port conflict                            |
| `Gateway start blocked: set gateway.mode=local`                | Config set to remote mode                |
| `unauthorized` during connect                                  | Auth mismatch between client and gateway |

For full diagnosis ladders, use [Gateway Troubleshooting](https://docs.openclaw.ai/gateway/troubleshooting).

## Safety guarantees

***

Related:

----
url: https://docs.openclaw.ai/platforms
----

# Platforms - OpenClaw

## [​](#platforms)Platforms

OpenClaw core is written in TypeScript. **Node is the recommended runtime**. Bun is not recommended for the Gateway (WhatsApp/Telegram bugs). Companion apps exist for macOS (menu bar app) and mobile nodes (iOS/Android). Windows and Linux companion apps are planned, but the Gateway is fully supported today. Native companion apps for Windows are also planned; the Gateway is recommended via WSL2.

## [​](#choose-your-os)Choose your OS

* macOS: [macOS](https://docs.openclaw.ai/platforms/macos)
* iOS: [iOS](https://docs.openclaw.ai/platforms/ios)
* Android: [Android](https://docs.openclaw.ai/platforms/android)
* Windows: [Windows](https://docs.openclaw.ai/platforms/windows)
* Linux: [Linux](https://docs.openclaw.ai/platforms/linux)

## [​](#vps-&-hosting)VPS & hosting

* VPS hub: [VPS hosting](https://docs.openclaw.ai/vps)
* Fly.io: [Fly.io](https://docs.openclaw.ai/install/fly)
* Hetzner (Docker): [Hetzner](https://docs.openclaw.ai/install/hetzner)
* GCP (Compute Engine): [GCP](https://docs.openclaw.ai/install/gcp)
* Azure (Linux VM): [Azure](https://docs.openclaw.ai/install/azure)
* exe.dev (VM + HTTPS proxy): [exe.dev](https://docs.openclaw.ai/install/exe-dev)

## [​](#common-links)Common links

* Install guide: [Getting Started](https://docs.openclaw.ai/start/getting-started)
* Gateway runbook: [Gateway](https://docs.openclaw.ai/gateway)
* Gateway configuration: [Configuration](https://docs.openclaw.ai/gateway/configuration)
* Service status: `openclaw gateway status`

## [​](#gateway-service-install-cli)Gateway service install (CLI)

Use one of these (all supported):

* Wizard (recommended): `openclaw onboard --install-daemon`
* Direct: `openclaw gateway install`
* Configure flow: `openclaw configure` → select **Gateway service**
* Repair/migrate: `openclaw doctor` (offers to install or fix the service)

The service target depends on OS:

* macOS: LaunchAgent (`ai.openclaw.gateway` or `ai.openclaw.<profile>`; legacy `com.openclaw.*`)
* Linux/WSL2: systemd user service (`openclaw-gateway[-<profile>].service`)

----
url: https://docs.openclaw.ai/help/faq
----

# FAQ - OpenClaw

Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, multi-agent, OAuth/API keys, model failover). For runtime diagnostics, see [Troubleshooting](https://docs.openclaw.ai/gateway/troubleshooting). For the full config reference, see [Configuration](https://docs.openclaw.ai/gateway/configuration).

## First 60 seconds if something is broken

1. **Quick status (first check)** Fast local summary: OS + update, gateway/service reachability, agents/sessions, provider config + runtime issues (when gateway is reachable).
2. **Pasteable report (safe to share)** Read-only diagnosis with log tail (tokens redacted).
3. **Daemon + port state** Shows supervisor runtime vs RPC reachability, the probe target URL, and which config the service likely used.
4. **Deep probes** Runs gateway health checks + provider probes (requires a reachable gateway). See [Health](https://docs.openclaw.ai/gateway/health).
5. **Tail the latest log** If RPC is down, fall back to: File logs are separate from service logs; see [Logging](https://docs.openclaw.ai/logging) and [Troubleshooting](https://docs.openclaw.ai/gateway/troubleshooting).
6. **Run the doctor (repairs)** Repairs/migrates config/state + runs health checks. See [Doctor](https://docs.openclaw.ai/gateway/doctor).
7. **Gateway snapshot** Asks the running gateway for a full snapshot (WS-only). See [Health](https://docs.openclaw.ai/gateway/health).

## Quick start and first-run setup

## What is OpenClaw?

## Skills and automation

## Sandboxing and memory

## Where things live on disk

Is all data used with OpenClaw saved locally?

Where does OpenClaw store its data?

Everything lives under `$OPENCLAW_STATE_DIR` (default: `~/.openclaw`):

| Path                                                            | Purpose                                                            |
| --------------------------------------------------------------- | ------------------------------------------------------------------ |
| `$OPENCLAW_STATE_DIR/openclaw.json`                             | Main config (JSON5)                                                |
| `$OPENCLAW_STATE_DIR/credentials/oauth.json`                    | Legacy OAuth import (copied into auth profiles on first use)       |
| `$OPENCLAW_STATE_DIR/agents/<agentId>/agent/auth-profiles.json` | Auth profiles (OAuth, API keys, and optional `keyRef`/`tokenRef`)  |
| `$OPENCLAW_STATE_DIR/secrets.json`                              | Optional file-backed secret payload for `file` SecretRef providers |
| `$OPENCLAW_STATE_DIR/agents/<agentId>/agent/auth.json`          | Legacy compatibility file (static `api_key` entries scrubbed)      |
| `$OPENCLAW_STATE_DIR/credentials/`                              | Provider state (e.g. `whatsapp/<accountId>/creds.json`)            |
| `$OPENCLAW_STATE_DIR/agents/`                                   | Per-agent state (agentDir + sessions)                              |
| `$OPENCLAW_STATE_DIR/agents/<agentId>/sessions/`                | Conversation history & state (per agent)                           |
| `$OPENCLAW_STATE_DIR/agents/<agentId>/sessions/sessions.json`   | Session metadata (per agent)                                       |

Legacy single-agent path: `~/.openclaw/agent/*` (migrated by `openclaw doctor`).Your **workspace** (AGENTS.md, memory files, skills, etc.) is separate and configured via `agents.defaults.workspace` (default: `~/.openclaw/workspace`).

Where should AGENTS.md / SOUL.md / USER.md / MEMORY.md live?

These files live in the **agent workspace**, not `~/.openclaw`.

* **Workspace (per agent)**: `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, `MEMORY.md` (or legacy fallback `memory.md` when `MEMORY.md` is absent), `memory/YYYY-MM-DD.md`, optional `HEARTBEAT.md`.
* **State dir (`~/.openclaw`)**: config, credentials, auth profiles, sessions, logs, and shared skills (`~/.openclaw/skills`).

Default workspace is `~/.openclaw/workspace`, configurable via:

If the bot “forgets” after a restart, confirm the Gateway is using the same workspace on every launch (and remember: remote mode uses the **gateway host’s** workspace, not your local laptop).Tip: if you want a durable behavior or preference, ask the bot to **write it into AGENTS.md or MEMORY.md** rather than relying on chat history.See [Agent workspace](https://docs.openclaw.ai/concepts/agent-workspace) and [Memory](https://docs.openclaw.ai/concepts/memory).

Recommended backup strategy

Put your **agent workspace** in a **private** git repo and back it up somewhere private (for example GitHub private). This captures memory + AGENTS/SOUL/USER files, and lets you restore the assistant’s “mind” later.Do **not** commit anything under `~/.openclaw` (credentials, sessions, tokens, or encrypted secrets payloads). If you need a full restore, back up both the workspace and the state directory separately (see the migration question above).Docs: [Agent workspace](https://docs.openclaw.ai/concepts/agent-workspace).

How do I completely uninstall OpenClaw?

Can agents work outside the workspace?

I'm in remote mode - where is the session store?

Session state is owned by the **gateway host**. If you’re in remote mode, the session store you care about is on the remote machine, not your local laptop. See [Session management](https://docs.openclaw.ai/concepts/session).

## Config basics

## Remote gateways and nodes

## Env vars and .env loading

## Sessions and multiple chats

## Models: defaults, selection, aliases, switching

## Model failover and “All models failed”

## Auth profiles: what they are and how to manage them

Related: [/concepts/oauth](https://docs.openclaw.ai/concepts/oauth) (OAuth flows, token storage, multi-account patterns)

## Gateway: ports, “already running”, and remote mode

## Logging and debugging

## Security and access control

## Chat commands, aborting tasks, and “it will not stop”

## Miscellaneous

***

Still stuck? Ask in [Discord](https://discord.com/invite/clawd) or open a [GitHub discussion](https://github.com/openclaw/openclaw/discussions).

----
url: https://docs.openclaw.ai/gateway/configuration
----

# Configuration - OpenClaw

OpenClaw reads an optional config from `~/.openclaw/openclaw.json`. If the file is missing, OpenClaw uses safe defaults. Common reasons to add a config:

See the [full reference](https://docs.openclaw.ai/gateway/configuration-reference) for every available field.

## Minimal config

## Editing config

## Strict validation

When validation fails:

## Common tasks

## Config hot reload

The Gateway watches `~/.openclaw/openclaw.json` and applies changes automatically — no manual restart needed for most settings.

### Reload modes

| Mode                   | Behavior                                                                                |
| ---------------------- | --------------------------------------------------------------------------------------- |
| **`hybrid`** (default) | Hot-applies safe changes instantly. Automatically restarts for critical ones.           |
| **`hot`**              | Hot-applies safe changes only. Logs a warning when a restart is needed — you handle it. |
| **`restart`**          | Restarts the Gateway on any config change, safe or not.                                 |
| **`off`**              | Disables file watching. Changes take effect on the next manual restart.                 |

### What hot-applies vs what needs a restart

Most fields hot-apply without downtime. In `hybrid` mode, restart-required changes are handled automatically.

| Category            | Fields                                                               | Restart needed? |
| ------------------- | -------------------------------------------------------------------- | --------------- |
| Channels            | `channels.*`, `web` (WhatsApp) — all built-in and extension channels | No              |
| Agent & models      | `agent`, `agents`, `models`, `routing`                               | No              |
| Automation          | `hooks`, `cron`, `agent.heartbeat`                                   | No              |
| Sessions & messages | `session`, `messages`                                                | No              |
| Tools & media       | `tools`, `browser`, `skills`, `audio`, `talk`                        | No              |
| UI & misc           | `ui`, `logging`, `identity`, `bindings`                              | No              |
| Gateway server      | `gateway.*` (port, bind, auth, tailscale, TLS, HTTP)                 | **Yes**         |
| Infrastructure      | `discovery`, `canvasHost`, `plugins`                                 | **Yes**         |

## Config RPC (programmatic updates)

## Environment variables

OpenClaw reads env vars from the parent process plus:

Neither file overrides existing env vars. You can also set inline env vars in config:

Shell env import (optional)

Env var substitution in config values

Secret refs (env, file, exec)

For fields that support SecretRef objects, you can use:

```
{
  models: {
    providers: {
      openai: { apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" } },
    },
  },
  skills: {
    entries: {
      "image-lab": {
        apiKey: {
          source: "file",
          provider: "filemain",
          id: "/skills/entries/image-lab/apiKey",
        },
      },
    },
  },
  channels: {
    googlechat: {
      serviceAccountRef: {
        source: "exec",
        provider: "vault",
        id: "channels/googlechat/serviceAccount",
      },
    },
  },
}
```

SecretRef details (including `secrets.providers` for `env`/`file`/`exec`) are in [Secrets Management](https://docs.openclaw.ai/gateway/secrets). Supported credential paths are listed in [SecretRef Credential Surface](https://docs.openclaw.ai/reference/secretref-credential-surface).

See [Environment](https://docs.openclaw.ai/help/environment) for full precedence and sources.

## Full reference

For the complete field-by-field reference, see **[Configuration Reference](https://docs.openclaw.ai/gateway/configuration-reference)**.

***

*Related: [Configuration Examples](https://docs.openclaw.ai/gateway/configuration-examples) · [Configuration Reference](https://docs.openclaw.ai/gateway/configuration-reference) · [Doctor](https://docs.openclaw.ai/gateway/doctor)*

----
url: https://docs.openclaw.ai/automation/webhook
----

# Webhooks - OpenClaw

Gateway can expose a small HTTP webhook endpoint for external triggers.

## Enable

Notes:

## Auth

Every request must include the hook token. Prefer headers:

## Endpoints

### `POST /hooks/wake`

Payload:

Effect:

### `POST /hooks/agent`

Payload:

```
{
  "message": "Run this",
  "name": "Email",
  "agentId": "hooks",
  "sessionKey": "hook:email:msg-123",
  "wakeMode": "now",
  "deliver": true,
  "channel": "last",
  "to": "+15551234567",
  "model": "openai/gpt-5.2-mini",
  "thinking": "low",
  "timeoutSeconds": 120
}
```

* `message` **required** (string): The prompt or message for the agent to process.
* `name` optional (string): Human-readable name for the hook (e.g., “GitHub”), used as a prefix in session summaries.
* `agentId` optional (string): Route this hook to a specific agent. Unknown IDs fall back to the default agent. When set, the hook runs using the resolved agent’s workspace and configuration.
* `sessionKey` optional (string): The key used to identify the agent’s session. By default this field is rejected unless `hooks.allowRequestSessionKey=true`.
* `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
* `deliver` optional (boolean): If `true`, the agent’s response will be sent to the messaging channel. Defaults to `true`. Responses that are only heartbeat acknowledgments are automatically skipped.
* `channel` optional (string): The messaging channel for delivery. Core channels: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `irc`, `googlechat`, `line`. Extension channels (plugins): `msteams`, `mattermost`, and others. Defaults to `last`.
* `to` optional (string): The recipient identifier for the channel (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack/Mattermost (plugin), conversation ID for Microsoft Teams). Defaults to the last recipient in the main session.
* `model` optional (string): Model override (e.g., `anthropic/claude-3-5-sonnet` or an alias). Must be in the allowed model list if restricted.
* `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`).
* `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.

Effect:

## Session key policy (breaking change)

`/hooks/agent` payload `sessionKey` overrides are disabled by default.

Recommended config:

Compatibility config (legacy behavior):

### `POST /hooks/<name>` (mapped)

Custom hook names are resolved via `hooks.mappings` (see configuration). A mapping can turn arbitrary payloads into `wake` or `agent` actions, with optional templates or code transforms. Mapping options (summary):

## Responses

## Examples

### Use a different model

Add `model` to the agent payload (or mapping) to override the model for that run:

If you enforce `agents.defaults.models`, make sure the override model is included there.

## Security

----
url: https://docs.openclaw.ai/cli/security
----

# security - OpenClaw

Security tools (audit + optional fixes). Related:

## Audit

The audit warns when multiple DM senders share the main session and recommends **secure DM mode**: `session.dmScope="per-channel-peer"` (or `per-account-channel-peer` for multi-account channels) for shared inboxes. This is for cooperative/shared inbox hardening. A single Gateway shared by mutually untrusted/adversarial operators is not a recommended setup; split trust boundaries with separate gateways (or separate OS users/hosts). It also emits `security.trust_model.multi_user_heuristic` when config suggests likely shared-user ingress (for example open DM/group policy, configured group targets, or wildcard sender rules), and reminds you that OpenClaw is a personal-assistant trust model by default. For intentional shared-user setups, the audit guidance is to sandbox all sessions, keep filesystem access workspace-scoped, and keep personal/private identities or credentials off that runtime. It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled. For webhook ingress, it warns when `hooks.token` reuses the Gateway token, when `hooks.defaultSessionKey` is unset, when `hooks.allowedAgentIds` is unrestricted, when request `sessionKey` overrides are enabled, and when overrides are enabled without `hooks.allowedSessionKeyPrefixes`. It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries (exact node command-name matching only, not shell-text filtering), when `gateway.nodes.allowCommands` explicitly enables dangerous node commands, when global `tools.profile="minimal"` is overridden by agent tool profiles, when open groups expose runtime/filesystem tools without sandbox/workspace guards, and when installed extension plugin tools may be reachable under permissive tool policy. It also flags `gateway.allowRealIpFallback=true` (header-spoofing risk if proxies are misconfigured) and `discovery.mdns.mode="full"` (metadata leakage via mDNS TXT records). It also warns when sandbox browser uses Docker `bridge` network without `sandbox.browser.cdpSourceRange`. It also flags dangerous sandbox Docker network modes (including `host` and `container:*` namespace joins). It also warns when existing sandbox browser Docker containers have missing/stale hash labels (for example pre-migration containers missing `openclaw.browserConfigEpoch`) and recommends `openclaw sandbox recreate --browser --all`. It also warns when npm-based plugin/hook install records are unpinned, missing integrity metadata, or drift from currently installed package versions. It warns when channel allowlists rely on mutable names/emails/tags instead of stable IDs (Discord, Slack, Google Chat, Microsoft Teams, Mattermost, IRC scopes where applicable). It warns when `gateway.auth.mode="none"` leaves Gateway HTTP APIs reachable without a shared secret (`/tools/invoke` plus any enabled `/v1/*` endpoint). Settings prefixed with `dangerous`/`dangerously` are explicit break-glass operator overrides; enabling one is not, by itself, a security vulnerability report. For the complete dangerous-parameter inventory, see the “Insecure or dangerous flags summary” section in [Security](https://docs.openclaw.ai/gateway/security). SecretRef behavior:

## JSON output

Use `--json` for CI/policy checks:

If `--fix` and `--json` are combined, output includes both fix actions and final report:

## What `--fix` changes

`--fix` applies safe, deterministic remediations:

`--fix` does **not**:

----
url: https://docs.openclaw.ai/pi-dev
----

# Pi Development Workflow - OpenClaw

## [​](#pi-development-workflow)Pi Development Workflow

This guide summarizes a sane workflow for working on the pi integration in OpenClaw.

## [​](#type-checking-and-linting)Type Checking and Linting

* Type check and build: `pnpm build`
* Lint: `pnpm lint`
* Format check: `pnpm format`
* Full gate before pushing: `pnpm lint && pnpm build && pnpm test`

## [​](#running-pi-tests)Running Pi Tests

Run the Pi-focused test set directly with Vitest:

```
pnpm test -- \
  "src/agents/pi-*.test.ts" \
  "src/agents/pi-embedded-*.test.ts" \
  "src/agents/pi-tools*.test.ts" \
  "src/agents/pi-settings.test.ts" \
  "src/agents/pi-tool-definition-adapter*.test.ts" \
  "src/agents/pi-extensions/**/*.test.ts"
```

To include the live provider exercise:

```
OPENCLAW_LIVE_TEST=1 pnpm test -- src/agents/pi-embedded-runner-extraparams.live.test.ts
```

This covers the main Pi unit suites:

* `src/agents/pi-*.test.ts`
* `src/agents/pi-embedded-*.test.ts`
* `src/agents/pi-tools*.test.ts`
* `src/agents/pi-settings.test.ts`
* `src/agents/pi-tool-definition-adapter.test.ts`
* `src/agents/pi-extensions/*.test.ts`

## [​](#manual-testing)Manual Testing

Recommended flow:

* Run the gateway in dev mode:
  * `pnpm gateway:dev`
* Trigger the agent directly:
  * `pnpm openclaw agent --message "Hello" --thinking low`
* Use the TUI for interactive debugging:
  * `pnpm tui`

For tool call behavior, prompt for a `read` or `exec` action so you can see tool streaming and payload handling.

## [​](#clean-slate-reset)Clean Slate Reset

State lives under the OpenClaw state directory. Default is `~/.openclaw`. If `OPENCLAW_STATE_DIR` is set, use that directory instead. To reset everything:

* `openclaw.json` for config
* `credentials/` for auth profiles and tokens
* `agents/<agentId>/sessions/` for agent session history
* `agents/<agentId>/sessions.json` for the session index
* `sessions/` if legacy paths exist
* `workspace/` if you want a blank workspace

If you only want to reset sessions, delete `agents/<agentId>/sessions/` and `agents/<agentId>/sessions.json` for that agent. Keep `credentials/` if you do not want to reauthenticate.

## [​](#references)References

* [Testing](https://docs.openclaw.ai/help/testing)
* [Getting Started](https://docs.openclaw.ai/start/getting-started)

----
url: https://docs.openclaw.ai/plugins/sdk-setup
----

# Plugin Setup and Config - OpenClaw

Reference for plugin packaging (`package.json` metadata), manifests (`openclaw.plugin.json`), setup entries, and config schemas.

Your `package.json` needs an `openclaw` field that tells the plugin system what your plugin provides: **Channel plugin:**

**Provider plugin:**

### `openclaw` fields

| Field        | Type       | Description                                                                                |
| ------------ | ---------- | ------------------------------------------------------------------------------------------ |
| `extensions` | `string[]` | Entry point files (relative to package root)                                               |
| `setupEntry` | `string`   | Lightweight setup-only entry (optional)                                                    |
| `channel`    | `object`   | Channel metadata: `id`, `label`, `blurb`, `selectionLabel`, `docsPath`, `order`, `aliases` |
| `providers`  | `string[]` | Provider ids registered by this plugin                                                     |
| `install`    | `object`   | Install hints: `npmSpec`, `localPath`, `defaultChoice`                                     |
| `startup`    | `object`   | Startup behavior flags                                                                     |

### Deferred full load

Channel plugins can opt into deferred loading with:

When enabled, OpenClaw loads only `setupEntry` during the pre-listen startup phase, even for already-configured channels. The full entry loads after the gateway starts listening.

## Plugin manifest

Every native plugin must ship an `openclaw.plugin.json` in the package root. OpenClaw uses this to validate config without executing plugin code.

For channel plugins, add `kind` and `channels`:

Even plugins with no config must ship a schema. An empty schema is valid:

See [Plugin Manifest](https://docs.openclaw.ai/plugins/manifest) for the full schema reference.

## Setup entry

The `setup-entry.ts` file is a lightweight alternative to `index.ts` that OpenClaw loads when it only needs setup surfaces (onboarding, config repair, disabled channel inspection).

This avoids loading heavy runtime code (crypto libraries, CLI registrations, background services) during setup flows. **When OpenClaw uses `setupEntry` instead of the full entry:**

**What `setupEntry` must register:**

**What `setupEntry` should NOT include:**

## Config schema

Plugin config is validated against the JSON Schema in your manifest. Users configure plugins via:

Your plugin receives this config as `api.pluginConfig` during registration. For channel-specific config, use the channel config section instead:

### Building channel config schemas

Use `buildChannelConfigSchema` from `openclaw/plugin-sdk/core` to convert a Zod schema into the `ChannelConfigSchema` wrapper that OpenClaw validates:

## Setup wizards

Channel plugins can provide interactive setup wizards for `openclaw onboard`. The wizard is a `ChannelSetupWizard` object on the `ChannelPlugin`:

```
import type { ChannelSetupWizard } from "openclaw/plugin-sdk/channel-setup";

const setupWizard: ChannelSetupWizard = {
  channel: "my-channel",
  status: {
    configuredLabel: "Connected",
    unconfiguredLabel: "Not configured",
    resolveConfigured: ({ cfg }) => Boolean((cfg.channels as any)?.["my-channel"]?.token),
  },
  credentials: [
    {
      inputKey: "token",
      providerHint: "my-channel",
      credentialLabel: "Bot token",
      preferredEnvVar: "MY_CHANNEL_BOT_TOKEN",
      envPrompt: "Use MY_CHANNEL_BOT_TOKEN from environment?",
      keepPrompt: "Keep current token?",
      inputPrompt: "Enter your bot token:",
      inspect: ({ cfg, accountId }) => {
        const token = (cfg.channels as any)?.["my-channel"]?.token;
        return {
          accountConfigured: Boolean(token),
          hasConfiguredValue: Boolean(token),
        };
      },
    },
  ],
};
```

The `ChannelSetupWizard` type supports `credentials`, `textInputs`, `dmPolicy`, `allowFrom`, `groupAccess`, `prepare`, `finalize`, and more. See bundled plugins (e.g. `extensions/discord/src/channel.setup.ts`) for full examples. For DM allowlist prompts that only need the standard `note -> prompt -> parse -> merge -> patch` flow, prefer the shared setup helpers from `openclaw/plugin-sdk/setup`: `createPromptParsedAllowFromForAccount(...)`, `createTopLevelChannelParsedAllowFromPrompt(...)`, and `createNestedChannelParsedAllowFromPrompt(...)`. For channel setup status blocks that only vary by labels, scores, and optional extra lines, prefer `createStandardChannelSetupStatus(...)` from `openclaw/plugin-sdk/setup` instead of hand-rolling the same `status` object in each plugin. For optional setup surfaces that should only appear in certain contexts, use `createOptionalChannelSetupSurface` from `openclaw/plugin-sdk/channel-setup`:

## Publishing and installing

**External plugins:** publish to [ClawHub](https://docs.openclaw.ai/tools/clawhub) or npm, then install:

OpenClaw tries ClawHub first and falls back to npm automatically. You can also force a specific source:

**In-repo plugins:** place under `extensions/` and they are automatically discovered during build. **Users can browse and install:**

----
url: https://docs.openclaw.ai/start/openclaw
----

# Personal Assistant Setup - OpenClaw

## Building a personal assistant with OpenClaw

OpenClaw is a self-hosted gateway that connects WhatsApp, Telegram, Discord, iMessage, and more to AI agents. This guide covers the “personal assistant” setup: a dedicated WhatsApp number that behaves like your always-on AI assistant.

## ⚠️ Safety first

You’re putting an agent in a position to:

Start conservative:

## Prerequisites

## The two-phone setup (recommended)

You want this:

If you link your personal WhatsApp to OpenClaw, every message to you becomes “agent input”. That’s rarely what you want.

## 5-minute quick start

1. Pair WhatsApp Web (shows QR; scan with the assistant phone):

2) Start the Gateway (leave it running):

3. Put a minimal config in `~/.openclaw/openclaw.json`:

Now message the assistant number from your allowlisted phone. When onboarding finishes, we auto-open the dashboard and print a clean (non-tokenized) link. If it prompts for auth, paste the token from `gateway.auth.token` into Control UI settings. To reopen later: `openclaw dashboard`.

## Give the agent a workspace (AGENTS)

OpenClaw reads operating instructions and “memory” from its workspace directory. By default, OpenClaw uses `~/.openclaw/workspace` as the agent workspace, and will create it (plus starter `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`) automatically on setup/first agent run. `BOOTSTRAP.md` is only created when the workspace is brand new (it should not come back after you delete it). `MEMORY.md` is optional (not auto-created); when present, it is loaded for normal sessions. Subagent sessions only inject `AGENTS.md` and `TOOLS.md`. Tip: treat this folder like OpenClaw’s “memory” and make it a git repo (ideally private) so your `AGENTS.md` + memory files are backed up. If git is installed, brand-new workspaces are auto-initialized.

Full workspace layout + backup guide: [Agent workspace](https://docs.openclaw.ai/concepts/agent-workspace) Memory workflow: [Memory](https://docs.openclaw.ai/concepts/memory) Optional: choose a different workspace with `agents.defaults.workspace` (supports `~`).

If you already ship your own workspace files from a repo, you can disable bootstrap file creation entirely:

## The config that turns it into “an assistant”

OpenClaw defaults to a good assistant setup, but you’ll usually want to tune:

Example:

```
{
  logging: { level: "info" },
  agent: {
    model: "anthropic/claude-opus-4-6",
    workspace: "~/.openclaw/workspace",
    thinkingDefault: "high",
    timeoutSeconds: 1800,
    // Start with 0; enable later.
    heartbeat: { every: "0m" },
  },
  channels: {
    whatsapp: {
      allowFrom: ["+15555550123"],
      groups: {
        "*": { requireMention: true },
      },
    },
  },
  routing: {
    groupChat: {
      mentionPatterns: ["@openclaw", "openclaw"],
    },
  },
  session: {
    scope: "per-sender",
    resetTriggers: ["/new", "/reset"],
    reset: {
      mode: "daily",
      atHour: 4,
      idleMinutes: 10080,
    },
  },
}
```

## Sessions and memory

## Heartbeats (proactive mode)

By default, OpenClaw runs a heartbeat every 30 minutes with the prompt: `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.` Set `agents.defaults.heartbeat.every: "0m"` to disable.

Inbound attachments (images/audio/docs) can be surfaced to your command via templates:

Outbound attachments from the agent: include `MEDIA:<path-or-url>` on its own line (no spaces). Example:

OpenClaw extracts these and sends them as media alongside the text. For local paths, the default allowlist is intentionally narrow: the OpenClaw temp root, the media cache, agent workspace paths, and sandbox-generated files. If you need broader local-file attachment roots, configure an explicit channel/plugin allowlist instead of relying on arbitrary host paths.

## Operations checklist

Logs live under `/tmp/openclaw/` (default: `openclaw-YYYY-MM-DD.log`).

## Next steps

----
url: https://docs.openclaw.ai/providers/openrouter
----

# OpenRouter - OpenClaw

## [​](#openrouter)OpenRouter

OpenRouter provides a **unified API** that routes requests to many models behind a single endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switching the base URL.

## [​](#cli-setup)CLI setup

```
openclaw onboard --auth-choice apiKey --token-provider openrouter --token "$OPENROUTER_API_KEY"
```

## [​](#config-snippet)Config snippet

```
{
  env: { OPENROUTER_API_KEY: "sk-or-..." },
  agents: {
    defaults: {
      model: { primary: "openrouter/anthropic/claude-sonnet-4-6" },
    },
  },
}
```

## [​](#notes)Notes

* Model refs are `openrouter/<provider>/<model>`.
* For more model/provider options, see [/concepts/model-providers](https://docs.openclaw.ai/concepts/model-providers).
* OpenRouter uses a Bearer token with your API key under the hood.

----
url: https://docs.openclaw.ai/help/troubleshooting
----

# General Troubleshooting - OpenClaw

## [​](#troubleshooting)Troubleshooting

If you only have 2 minutes, use this page as a triage front door.

## [​](#first-60-seconds)First 60 seconds

Run this exact ladder in order:

```
openclaw status
openclaw status --all
openclaw gateway probe
openclaw gateway status
openclaw doctor
openclaw channels status --probe
openclaw logs --follow
```

Good output in one line:

* `openclaw status` → shows configured channels and no obvious auth errors.
* `openclaw status --all` → full report is present and shareable.
* `openclaw gateway probe` → expected gateway target is reachable (`Reachable: yes`). `RPC: limited - missing scope: operator.read` is degraded diagnostics, not a connect failure.
* `openclaw gateway status` → `Runtime: running` and `RPC probe: ok`.
* `openclaw doctor` → no blocking config/service errors.
* `openclaw channels status --probe` → channels report `connected` or `ready`.
* `openclaw logs --follow` → steady activity, no repeating fatal errors.

## [​](#anthropic-long-context-429)Anthropic long context 429

If you see: `HTTP 429: rate_limit_error: Extra usage is required for long context requests`, go to [/gateway/troubleshooting#anthropic-429-extra-usage-required-for-long-context](https://docs.openclaw.ai/gateway/troubleshooting#anthropic-429-extra-usage-required-for-long-context).

## [​](#plugin-install-fails-with-missing-openclaw-extensions)Plugin install fails with missing openclaw extensions

If install fails with `package.json missing openclaw.extensions`, the plugin package is using an old shape that OpenClaw no longer accepts. Fix in the plugin package:

1. Add `openclaw.extensions` to `package.json`.
2. Point entries at built runtime files (usually `./dist/index.js`).
3. Republish the plugin and run `openclaw plugins install <package>` again.

Example:

```
{
  "name": "@openclaw/my-plugin",
  "version": "1.2.3",
  "openclaw": {
    "extensions": ["./dist/index.js"]
  }
}
```

Reference: [Plugin architecture](https://docs.openclaw.ai/plugins/architecture)

## [​](#decision-tree)Decision tree

No replies

```
openclaw status
openclaw gateway status
openclaw channels status --probe
openclaw pairing list --channel <channel> [--account <id>]
openclaw logs --follow
```

Good output looks like:

* `Runtime: running`
* `RPC probe: ok`
* Your channel shows connected/ready in `channels status --probe`
* Sender appears approved (or DM policy is open/allowlist)

Common log signatures:

* `drop guild message (mention required` → mention gating blocked the message in Discord.
* `pairing request` → sender is unapproved and waiting for DM pairing approval.
* `blocked` / `allowlist` in channel logs → sender, room, or group is filtered.

Deep pages:

* [/gateway/troubleshooting#no-replies](https://docs.openclaw.ai/gateway/troubleshooting#no-replies)
* [/channels/troubleshooting](https://docs.openclaw.ai/channels/troubleshooting)
* [/channels/pairing](https://docs.openclaw.ai/channels/pairing)

Dashboard or Control UI will not connect

```
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```

Good output looks like:

* `Dashboard: http://...` is shown in `openclaw gateway status`
* `RPC probe: ok`
* No auth loop in logs

Common log signatures:

* `device identity required` → HTTP/non-secure context cannot complete device auth.
* `AUTH_TOKEN_MISMATCH` with retry hints (`canRetryWithDeviceToken=true`) → one trusted device-token retry may occur automatically.
* repeated `unauthorized` after that retry → wrong token/password, auth mode mismatch, or stale paired device token.
* `gateway connect failed:` → UI is targeting the wrong URL/port or unreachable gateway.

Deep pages:

* [/gateway/troubleshooting#dashboard-control-ui-connectivity](https://docs.openclaw.ai/gateway/troubleshooting#dashboard-control-ui-connectivity)
* [/web/control-ui](https://docs.openclaw.ai/web/control-ui)
* [/gateway/authentication](https://docs.openclaw.ai/gateway/authentication)

Gateway will not start or service installed but not running

```
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```

Good output looks like:

* `Service: ... (loaded)`
* `Runtime: running`
* `RPC probe: ok`

Common log signatures:

* `Gateway start blocked: set gateway.mode=local` → gateway mode is unset/remote.
* `refusing to bind gateway ... without auth` → non-loopback bind without token/password.
* `another gateway instance is already listening` or `EADDRINUSE` → port already taken.

Deep pages:

* [/gateway/troubleshooting#gateway-service-not-running](https://docs.openclaw.ai/gateway/troubleshooting#gateway-service-not-running)
* [/gateway/background-process](https://docs.openclaw.ai/gateway/background-process)
* [/gateway/configuration](https://docs.openclaw.ai/gateway/configuration)

Channel connects but messages do not flow

```
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```

Good output looks like:

* Channel transport is connected.
* Pairing/allowlist checks pass.
* Mentions are detected where required.

Common log signatures:

* `mention required` → group mention gating blocked processing.
* `pairing` / `pending` → DM sender is not approved yet.
* `not_in_channel`, `missing_scope`, `Forbidden`, `401/403` → channel permission token issue.

Deep pages:

* [/gateway/troubleshooting#channel-connected-messages-not-flowing](https://docs.openclaw.ai/gateway/troubleshooting#channel-connected-messages-not-flowing)
* [/channels/troubleshooting](https://docs.openclaw.ai/channels/troubleshooting)

Cron or heartbeat did not fire or did not deliver

```
openclaw status
openclaw gateway status
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw logs --follow
```

Good output looks like:

* `cron.status` shows enabled with a next wake.
* `cron runs` shows recent `ok` entries.
* Heartbeat is enabled and not outside active hours.

Common log signatures:

* `cron: scheduler disabled; jobs will not run automatically` → cron is disabled.
* `heartbeat skipped` with `reason=quiet-hours` → outside configured active hours.
* `requests-in-flight` → main lane busy; heartbeat wake was deferred.
* `unknown accountId` → heartbeat delivery target account does not exist.

Deep pages:

* [/gateway/troubleshooting#cron-and-heartbeat-delivery](https://docs.openclaw.ai/gateway/troubleshooting#cron-and-heartbeat-delivery)
* [/automation/troubleshooting](https://docs.openclaw.ai/automation/troubleshooting)
* [/gateway/heartbeat](https://docs.openclaw.ai/gateway/heartbeat)

Node is paired but tool fails camera canvas screen exec

```
openclaw status
openclaw gateway status
openclaw nodes status
openclaw nodes describe --node <idOrNameOrIp>
openclaw logs --follow
```

Good output looks like:

* Node is listed as connected and paired for role `node`.
* Capability exists for the command you are invoking.
* Permission state is granted for the tool.

Common log signatures:

* `NODE_BACKGROUND_UNAVAILABLE` → bring node app to foreground.
* `*_PERMISSION_REQUIRED` → OS permission was denied/missing.
* `SYSTEM_RUN_DENIED: approval required` → exec approval is pending.
* `SYSTEM_RUN_DENIED: allowlist miss` → command not on exec allowlist.

Deep pages:

* [/gateway/troubleshooting#node-paired-tool-fails](https://docs.openclaw.ai/gateway/troubleshooting#node-paired-tool-fails)
* [/nodes/troubleshooting](https://docs.openclaw.ai/nodes/troubleshooting)
* [/tools/exec-approvals](https://docs.openclaw.ai/tools/exec-approvals)

Browser tool fails

```
openclaw status
openclaw gateway status
openclaw browser status
openclaw logs --follow
openclaw doctor
```

Good output looks like:

* Browser status shows `running: true` and a chosen browser/profile.
* `openclaw` starts, or `user` can see local Chrome tabs.

Common log signatures:

* `Failed to start Chrome CDP on port` → local browser launch failed.
* `browser.executablePath not found` → configured binary path is wrong.
* `No Chrome tabs found for profile="user"` → the Chrome MCP attach profile has no open local Chrome tabs.
* `Browser attachOnly is enabled ... not reachable` → attach-only profile has no live CDP target.

Deep pages:

* [/gateway/troubleshooting#browser-tool-fails](https://docs.openclaw.ai/gateway/troubleshooting#browser-tool-fails)
* [/tools/browser-linux-troubleshooting](https://docs.openclaw.ai/tools/browser-linux-troubleshooting)
* [/tools/browser-wsl2-windows-remote-cdp-troubleshooting](https://docs.openclaw.ai/tools/browser-wsl2-windows-remote-cdp-troubleshooting)

----
url: https://docs.openclaw.ai/logging
----

# Logging Overview - OpenClaw

OpenClaw logs in two places:

This page explains where logs live, how to read them, and how to configure log levels and formats.

## Where logs live

By default, the Gateway writes a rolling log file under: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` The date uses the gateway host’s local timezone. You can override this in `~/.openclaw/openclaw.json`:

## How to read logs

### CLI: live tail (recommended)

Use the CLI to tail the gateway log file via RPC:

Output modes:

In JSON mode, the CLI emits `type`-tagged objects:

If the Gateway is unreachable, the CLI prints a short hint to run:

### Control UI (web)

The Control UI’s **Logs** tab tails the same file using `logs.tail`. See [/web/control-ui](https://docs.openclaw.ai/web/control-ui) for how to open it.

### Channel-only logs

To filter channel activity (WhatsApp/Telegram/etc), use:

## Log formats

### File logs (JSONL)

Each line in the log file is a JSON object. The CLI and Control UI parse these entries to render structured output (time, level, subsystem, message).

### Console output

Console logs are **TTY-aware** and formatted for readability:

Console formatting is controlled by `logging.consoleStyle`.

## Configuring logging

All logging configuration lives under `logging` in `~/.openclaw/openclaw.json`.

### Log levels

You can override both via the **`OPENCLAW_LOG_LEVEL`** environment variable (e.g. `OPENCLAW_LOG_LEVEL=debug`). The env var takes precedence over the config file, so you can raise verbosity for a single run without editing `openclaw.json`. You can also pass the global CLI option **`--log-level <level>`** (for example, `openclaw --log-level debug gateway run`), which overrides the environment variable for that command. `--verbose` only affects console output; it does not change file log levels.

### Console styles

`logging.consoleStyle`:

### Redaction

Tool summaries can redact sensitive tokens before they hit the console:

Redaction affects **console output only** and does not alter file logs.

## Diagnostics + OpenTelemetry

Diagnostics are structured, machine-readable events for model runs **and** message-flow telemetry (webhooks, queueing, session state). They do **not** replace logs; they exist to feed metrics, traces, and other exporters. Diagnostics events are emitted in-process, but exporters only attach when diagnostics + the exporter plugin are enabled.

### OpenTelemetry vs OTLP

### Signals exported

### Diagnostic event catalog

Model usage:

Message flow:

Queue + session:

### Enable diagnostics (no exporter)

Use this if you want diagnostics events available to plugins or custom sinks:

### Diagnostics flags (targeted logs)

Use flags to turn on extra, targeted debug logs without raising `logging.level`. Flags are case-insensitive and support wildcards (e.g. `telegram.*` or `*`).

Env override (one-off):

Notes:

### Export to OpenTelemetry

Diagnostics can be exported via the `diagnostics-otel` plugin (OTLP/HTTP). This works with any OpenTelemetry collector/backend that accepts OTLP/HTTP.

```
{
  "plugins": {
    "allow": ["diagnostics-otel"],
    "entries": {
      "diagnostics-otel": {
        "enabled": true
      }
    }
  },
  "diagnostics": {
    "enabled": true,
    "otel": {
      "enabled": true,
      "endpoint": "http://otel-collector:4318",
      "protocol": "http/protobuf",
      "serviceName": "openclaw-gateway",
      "traces": true,
      "metrics": true,
      "logs": true,
      "sampleRate": 0.2,
      "flushIntervalMs": 60000
    }
  }
}
```

Notes:

### Exported metrics (names + types)

Model usage:

* `openclaw.tokens` (counter, attrs: `openclaw.token`, `openclaw.channel`, `openclaw.provider`, `openclaw.model`)
* `openclaw.cost.usd` (counter, attrs: `openclaw.channel`, `openclaw.provider`, `openclaw.model`)
* `openclaw.run.duration_ms` (histogram, attrs: `openclaw.channel`, `openclaw.provider`, `openclaw.model`)
* `openclaw.context.tokens` (histogram, attrs: `openclaw.context`, `openclaw.channel`, `openclaw.provider`, `openclaw.model`)

Message flow:

* `openclaw.webhook.received` (counter, attrs: `openclaw.channel`, `openclaw.webhook`)
* `openclaw.webhook.error` (counter, attrs: `openclaw.channel`, `openclaw.webhook`)
* `openclaw.webhook.duration_ms` (histogram, attrs: `openclaw.channel`, `openclaw.webhook`)
* `openclaw.message.queued` (counter, attrs: `openclaw.channel`, `openclaw.source`)
* `openclaw.message.processed` (counter, attrs: `openclaw.channel`, `openclaw.outcome`)
* `openclaw.message.duration_ms` (histogram, attrs: `openclaw.channel`, `openclaw.outcome`)

Queues + sessions:

### Exported spans (names + key attributes)

### Sampling + flushing

### Protocol notes

### Log export behavior

## Troubleshooting tips

----
url: https://docs.openclaw.ai/install/updating
----

# Updating - OpenClaw

Keep OpenClaw up to date.

## Recommended: `openclaw update`

The fastest way to update. It detects your install type (npm or git), fetches the latest version, runs `openclaw doctor`, and restarts the gateway.

To switch channels or target a specific version:

See [Development channels](https://docs.openclaw.ai/install/development-channels) for channel semantics.

## Alternative: re-run the installer

Add `--no-onboard` to skip onboarding. For source installs, pass `--install-method git --no-onboard`.

## Alternative: manual npm or pnpm

## Auto-updater

The auto-updater is off by default. Enable it in `~/.openclaw/openclaw.json`:

| Channel  | Behavior                                                                                                      |
| -------- | ------------------------------------------------------------------------------------------------------------- |
| `stable` | Waits `stableDelayHours`, then applies with deterministic jitter across `stableJitterHours` (spread rollout). |
| `beta`   | Checks every `betaCheckIntervalHours` (default: hourly) and applies immediately.                              |
| `dev`    | No automatic apply. Use `openclaw update` manually.                                                           |

The gateway also logs an update hint on startup (disable with `update.checkOnStart: false`).

## After updating

## Rollback

### Pin a version (npm)

Tip: `npm view openclaw version` shows the current published version.

### Pin a commit (source)

To return to latest: `git checkout main && git pull`.

## If you are stuck

----
url: https://docs.openclaw.ai/platforms/mac/logging
----

# macOS Logging - OpenClaw

## [​](#logging-macos)Logging (macOS)

## [​](#rolling-diagnostics-file-log-debug-pane)Rolling diagnostics file log (Debug pane)

OpenClaw routes macOS app logs through swift-log (unified logging by default) and can write a local, rotating file log to disk when you need a durable capture.

* Verbosity: **Debug pane → Logs → App logging → Verbosity**
* Enable: **Debug pane → Logs → App logging → “Write rolling diagnostics log (JSONL)”**
* Location: `~/Library/Logs/OpenClaw/diagnostics.jsonl` (rotates automatically; old files are suffixed with `.1`, `.2`, …)
* Clear: **Debug pane → Logs → App logging → “Clear”**

Notes:

* This is **off by default**. Enable only while actively debugging.
* Treat the file as sensitive; don’t share it without review.

## [​](#unified-logging-private-data-on-macos)Unified logging private data on macOS

Unified logging redacts most payloads unless a subsystem opts into `privacy -off`. Per Peter’s write-up on macOS [logging privacy shenanigans](https://steipete.me/posts/2025/logging-privacy-shenanigans) (2025) this is controlled by a plist in `/Library/Preferences/Logging/Subsystems/` keyed by the subsystem name. Only new log entries pick up the flag, so enable it before reproducing an issue.

## [​](#enable-for-openclaw-ai-openclaw)Enable for OpenClaw (`ai.openclaw`)

* Write the plist to a temp file first, then install it atomically as root:

```
cat <<'EOF' >/tmp/ai.openclaw.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>DEFAULT-OPTIONS</key>
    <dict>
        <key>Enable-Private-Data</key>
        <true/>
    </dict>
</dict>
</plist>
EOF
sudo install -m 644 -o root -g wheel /tmp/ai.openclaw.plist /Library/Preferences/Logging/Subsystems/ai.openclaw.plist
```

* No reboot is required; logd notices the file quickly, but only new log lines will include private payloads.
* View the richer output with the existing helper, e.g. `./scripts/clawlog.sh --category WebChat --last 5m`.

## [​](#disable-after-debugging)Disable after debugging

* Remove the override: `sudo rm /Library/Preferences/Logging/Subsystems/ai.openclaw.plist`.
* Optionally run `sudo log config --reload` to force logd to drop the override immediately.
* Remember this surface can include phone numbers and message bodies; keep the plist in place only while you actively need the extra detail.

----
url: https://docs.openclaw.ai/automation/cron-jobs
----

# Cron Jobs - OpenClaw

## Cron jobs (Gateway scheduler)

> **Cron vs Heartbeat?** See [Cron vs Heartbeat](https://docs.openclaw.ai/automation/cron-vs-heartbeat) for guidance on when to use each.

Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at the right time, and can optionally deliver output back to a chat. If you want *“run this every morning”* or *“poke the agent in 20 minutes”*, cron is the mechanism. Troubleshooting: [/automation/troubleshooting](https://docs.openclaw.ai/automation/troubleshooting)

## TL;DR

## Quick start (actionable)

Create a one-shot reminder, verify it exists, and run it immediately:

Schedule a recurring isolated job with delivery:

## Tool-call equivalents (Gateway cron tool)

For the canonical JSON shapes and examples, see [JSON schema for tool calls](https://docs.openclaw.ai/automation/cron-jobs#json-schema-for-tool-calls).

## Where cron jobs are stored

Cron jobs are persisted on the Gateway host at `~/.openclaw/cron/jobs.json` by default. The Gateway loads the file into memory and writes it back on changes, so manual edits are only safe when the Gateway is stopped. Prefer `openclaw cron add/edit` or the cron tool call API for changes.

## Beginner-friendly overview

Think of a cron job as: **when** to run + **what** to do.

1. **Choose a schedule**
2. **Choose where it runs** Default behavior (unchanged): To use current session binding, explicitly set `sessionTarget: "current"`.
3. **Choose the payload**

Optional: one-shot jobs (`schedule.kind = "at"`) delete after success by default. Set `deleteAfterRun: false` to keep them (they will disable after success).

## Concepts

### Jobs

A cron job is a stored record with:

Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs). In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility. One-shot jobs auto-delete after success by default; set `deleteAfterRun: false` to keep them.

### Schedules

Cron supports three schedule kinds:

Cron expressions use `croner`. If a timezone is omitted, the Gateway host’s local timezone is used. To reduce top-of-hour load spikes across many gateways, OpenClaw applies a deterministic per-job stagger window of up to 5 minutes for recurring top-of-hour expressions (for example `0 * * * *`, `0 */2 * * *`). Fixed-hour expressions such as `0 7 * * *` remain exact. For any cron schedule, you can set an explicit stagger window with `schedule.staggerMs` (`0` keeps exact timing). CLI shortcuts:

### Main vs isolated execution

#### Main session jobs (system events)

Main jobs enqueue a system event and optionally wake the heartbeat runner. They must use `payload.kind = "systemEvent"`.

This is the best fit when you want the normal heartbeat prompt + main-session context. See [Heartbeat](https://docs.openclaw.ai/gateway/heartbeat).

#### Isolated jobs (dedicated cron sessions)

Isolated jobs run a dedicated agent turn in session `cron:<jobId>` or a custom session. Key behaviors:

Use isolated jobs for noisy, frequent, or “background chores” that shouldn’t spam your main chat history.

### Payload shapes (what runs)

Two payload kinds are supported:

Common `agentTurn` fields:

Delivery config:

Announce delivery suppresses messaging tool sends for the run; use `delivery.channel`/`delivery.to` to target the chat instead. When `delivery.mode = "none"`, no summary is posted to the main session. If `delivery` is omitted for isolated jobs, OpenClaw defaults to `announce`.

#### Announce delivery flow

When `delivery.mode = "announce"`, cron delivers directly via the outbound channel adapters. The main agent is not spun up to craft or forward the message. Behavior details:

#### Webhook delivery flow

When `delivery.mode = "webhook"`, cron posts the finished event payload to `delivery.to` when the finished event includes a summary. Behavior details:

### Model and thinking overrides

Isolated jobs (`agentTurn`) can override the model and thinking level:

Note: You can set `model` on main-session jobs too, but it changes the shared main session model. We recommend model overrides only for isolated jobs to avoid unexpected context shifts. Resolution priority:

1. Job payload override (highest)
2. Hook-specific defaults (e.g., `hooks.gmail.model`)
3. Agent config default

### Lightweight bootstrap context

Isolated jobs (`agentTurn`) can set `lightContext: true` to run with lightweight bootstrap context.

### Delivery (channel + target)

Isolated jobs can deliver output to a channel via the top-level `delivery` config:

`announce` delivery is only valid for isolated jobs (`sessionTarget: "isolated"`). `webhook` delivery is valid for both main and isolated jobs. If `delivery.channel` or `delivery.to` is omitted, cron can fall back to the main session’s “last route” (the last place the agent replied). Target format reminders:

#### Telegram delivery targets (topics / forum threads)

Telegram supports forum topics via `message_thread_id`. For cron delivery, you can encode the topic/thread into the `to` field:

Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:

## JSON schema for tool calls

Use these shapes when calling Gateway `cron.*` tools directly (agent tool calls or RPC). CLI flags accept human durations like `20m`, but tool calls should use an ISO 8601 string for `schedule.at` and milliseconds for `schedule.everyMs`.

### cron.add params

One-shot, main session job (system event):

Recurring, isolated job with delivery:

```
{
  "name": "Morning brief",
  "schedule": { "kind": "cron", "expr": "0 7 * * *", "tz": "America/Los_Angeles" },
  "sessionTarget": "isolated",
  "wakeMode": "next-heartbeat",
  "payload": {
    "kind": "agentTurn",
    "message": "Summarize overnight updates.",
    "lightContext": true
  },
  "delivery": {
    "mode": "announce",
    "channel": "slack",
    "to": "channel:C1234567890",
    "bestEffort": true
  }
}
```

Recurring job bound to current session (auto-resolved at creation):

Recurring job in a custom persistent session:

Notes:

* `schedule.kind`: `at` (`at`), `every` (`everyMs`), or `cron` (`expr`, optional `tz`).
* `schedule.at` accepts ISO 8601 (timezone optional; treated as UTC when omitted).
* `everyMs` is milliseconds.
* `sessionTarget`: `"main"`, `"isolated"`, `"current"`, or `"session:<custom-id>"`.
* `"current"` is resolved to `"session:<sessionKey>"` at creation time.
* Custom sessions (`session:xxx`) maintain persistent context across runs.
* Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun` (defaults to true for `at`), `delivery`.
* `wakeMode` defaults to `"now"` when omitted.

### cron.update params

Notes:

### cron.run and cron.remove params

## Storage & history

## Retry policy

When a job fails, OpenClaw classifies errors as **transient** (retryable) or **permanent** (disable immediately).

### Transient errors (retried)

### Permanent errors (no retry)

### Default behavior (no config)

**One-shot jobs (`schedule.kind: "at"`):**

**Recurring jobs (`cron` / `every`):**

Configure `cron.retry` to override these defaults (see [Configuration](https://docs.openclaw.ai/automation/cron-jobs#configuration)).

## Configuration

```
{
  cron: {
    enabled: true, // default true
    store: "~/.openclaw/cron/jobs.json",
    maxConcurrentRuns: 1, // default 1
    // Optional: override retry policy for one-shot jobs
    retry: {
      maxAttempts: 3,
      backoffMs: [60000, 120000, 300000],
      retryOn: ["rate_limit", "overloaded", "network", "server_error"],
    },
    webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs
    webhookToken: "replace-with-dedicated-webhook-token", // optional bearer token for webhook mode
    sessionRetention: "24h", // duration string or false
    runLog: {
      maxBytes: "2mb", // default 2_000_000 bytes
      keepLines: 2000, // default 2000
    },
  },
}
```

Run-log pruning behavior:

Webhook behavior:

Disable cron entirely:

## Maintenance

Cron has two built-in maintenance paths: isolated run-session retention and run-log pruning.

### Defaults

### How it works

### Performance caveat for high volume schedulers

High-frequency cron setups can generate large run-session and run-log footprints. Maintenance is built in, but loose limits can still create avoidable IO and cleanup work. What to watch:

What to do:

### Customize examples

Keep run sessions for a week and allow bigger run logs:

Disable isolated run-session pruning but keep run-log pruning:

Tune for high-volume cron usage (example):

## CLI quickstart

One-shot reminder (UTC ISO, auto-delete after success):

One-shot reminder (main session, wake immediately):

Recurring isolated job (announce to WhatsApp):

Recurring cron job with explicit 30-second stagger:

Recurring isolated job (deliver to a Telegram topic):

Isolated job with model and thinking override:

Agent selection (multi-agent setups):

Manual run (force is the default, use `--due` to only run when due):

`cron.run` now acknowledges once the manual run is queued, not after the job finishes. Successful queue responses look like `{ ok: true, enqueued: true, runId }`. If the job is already running or `--due` finds nothing due, the response stays `{ ok: true, ran: false, reason }`. Use `openclaw cron runs --id <jobId>` or the `cron.runs` gateway method to inspect the eventual finished entry. Edit an existing job (patch fields):

Force an existing cron job to run exactly on schedule (no stagger):

Run history:

Immediate system event without creating a job:

## Gateway API surface

## Troubleshooting

### ”Nothing runs”

### A recurring job keeps delaying after failures

### Telegram delivers to the wrong place

### Subagent announce delivery retries

----
url: https://docs.openclaw.ai/platforms/mac/remote
----

# Remote Control - OpenClaw

## [​](#remote-openclaw-macos-⇄-remote-host)Remote OpenClaw (macOS ⇄ remote host)

This flow lets the macOS app act as a full remote control for an OpenClaw gateway running on another host (desktop/server). It’s the app’s **Remote over SSH** (remote run) feature. All features—health checks, Voice Wake forwarding, and Web Chat—reuse the same remote SSH configuration from *Settings → General*.

## [​](#modes)Modes

* **Local (this Mac)**: Everything runs on the laptop. No SSH involved.
* **Remote over SSH (default)**: OpenClaw commands are executed on the remote host. The mac app opens an SSH connection with `-o BatchMode` plus your chosen identity/key and a local port-forward.
* **Remote direct (ws/wss)**: No SSH tunnel. The mac app connects to the gateway URL directly (for example, via Tailscale Serve or a public HTTPS reverse proxy).

## [​](#remote-transports)Remote transports

Remote mode supports two transports:

* **SSH tunnel** (default): Uses `ssh -N -L ...` to forward the gateway port to localhost. The gateway will see the node’s IP as `127.0.0.1` because the tunnel is loopback.
* **Direct (ws/wss)**: Connects straight to the gateway URL. The gateway sees the real client IP.

## [​](#prereqs-on-the-remote-host)Prereqs on the remote host

1. Install Node + pnpm and build/install the OpenClaw CLI (`pnpm install && pnpm build && pnpm link --global`).
2. Ensure `openclaw` is on PATH for non-interactive shells (symlink into `/usr/local/bin` or `/opt/homebrew/bin` if needed).
3. Open SSH with key auth. We recommend **Tailscale** IPs for stable reachability off-LAN.

## [​](#macos-app-setup)macOS app setup

1. Open *Settings → General*.

2. Under **OpenClaw runs**, pick **Remote over SSH** and set:

   * **Transport**: **SSH tunnel** or **Direct (ws/wss)**.
   * **SSH target**: `user@host` (optional `:port`).
     * If the gateway is on the same LAN and advertises Bonjour, pick it from the discovered list to auto-fill this field.
   * **Gateway URL** (Direct only): `wss://gateway.example.ts.net` (or `ws://...` for local/LAN).
   * **Identity file** (advanced): path to your key.
   * **Project root** (advanced): remote checkout path used for commands.
   * **CLI path** (advanced): optional path to a runnable `openclaw` entrypoint/binary (auto-filled when advertised).

3. Hit **Test remote**. Success indicates the remote `openclaw status --json` runs correctly. Failures usually mean PATH/CLI issues; exit 127 means the CLI isn’t found remotely.

4. Health checks and Web Chat will now run through this SSH tunnel automatically.

## [​](#web-chat)Web Chat

* **SSH tunnel**: Web Chat connects to the gateway over the forwarded WebSocket control port (default 18789).
* **Direct (ws/wss)**: Web Chat connects straight to the configured gateway URL.
* There is no separate WebChat HTTP server anymore.

## [​](#permissions)Permissions

* The remote host needs the same TCC approvals as local (Automation, Accessibility, Screen Recording, Microphone, Speech Recognition, Notifications). Run onboarding on that machine to grant them once.
* Nodes advertise their permission state via `node.list` / `node.describe` so agents know what’s available.

## [​](#security-notes)Security notes

* Prefer loopback binds on the remote host and connect via SSH or Tailscale.
* SSH tunneling uses strict host-key checking; trust the host key first so it exists in `~/.ssh/known_hosts`.
* If you bind the Gateway to a non-loopback interface, require token/password auth.
* See [Security](https://docs.openclaw.ai/gateway/security) and [Tailscale](https://docs.openclaw.ai/gateway/tailscale).

## [​](#whatsapp-login-flow-remote)WhatsApp login flow (remote)

* Run `openclaw channels login --verbose` **on the remote host**. Scan the QR with WhatsApp on your phone.
* Re-run login on that host if auth expires. Health check will surface link problems.

## [​](#troubleshooting)Troubleshooting

* **exit 127 / not found**: `openclaw` isn’t on PATH for non-login shells. Add it to `/etc/paths`, your shell rc, or symlink into `/usr/local/bin`/`/opt/homebrew/bin`.
* **Health probe failed**: check SSH reachability, PATH, and that Baileys is logged in (`openclaw status --json`).
* **Web Chat stuck**: confirm the gateway is running on the remote host and the forwarded port matches the gateway WS port; the UI requires a healthy WS connection.
* **Node IP shows 127.0.0.1**: expected with the SSH tunnel. Switch **Transport** to **Direct (ws/wss)** if you want the gateway to see the real client IP.
* **Voice Wake**: trigger phrases are forwarded automatically in remote mode; no separate forwarder is needed.

## [​](#notification-sounds)Notification sounds

Pick sounds per notification from scripts with `openclaw` and `node.invoke`, e.g.:

```
openclaw nodes notify --node <id> --title "Ping" --body "Remote gateway ready" --sound Glass
```

There is no global “default sound” toggle in the app anymore; callers choose a sound (or none) per request.

----
url: https://docs.openclaw.ai/automation/standing-orders
----

# Standing Orders - OpenClaw

Standing orders grant your agent **permanent operating authority** for defined programs. Instead of giving individual task instructions each time, you define programs with clear scope, triggers, and escalation rules — and the agent executes autonomously within those boundaries. This is the difference between telling your assistant “send the weekly report” every Friday vs. granting standing authority: “You own the weekly report. Compile it every Friday, send it, and only escalate if something looks wrong.”

## Why Standing Orders?

**Without standing orders:**

**With standing orders:**

## How They Work

Standing orders are defined in your [agent workspace](https://docs.openclaw.ai/concepts/agent-workspace) files. The recommended approach is to include them directly in `AGENTS.md` (which is auto-injected every session) so the agent always has them in context. For larger configurations, you can also place them in a dedicated file like `standing-orders.md` and reference it from `AGENTS.md`. Each program specifies:

1. **Scope** — what the agent is authorized to do
2. **Triggers** — when to execute (schedule, event, or condition)
3. **Approval gates** — what requires human sign-off before acting
4. **Escalation rules** — when to stop and ask for help

The agent loads these instructions every session via the workspace bootstrap files (see [Agent Workspace](https://docs.openclaw.ai/concepts/agent-workspace) for the full list of auto-injected files) and executes against them, combined with [cron jobs](https://docs.openclaw.ai/automation/cron-jobs) for time-based enforcement.

## Anatomy of a Standing Order

## Standing Orders + Cron Jobs

Standing orders define **what** the agent is authorized to do. [Cron jobs](https://docs.openclaw.ai/automation/cron-jobs) define **when** it happens. They work together:

The cron job prompt should reference the standing order rather than duplicating it:

## Examples

### Example 1: Content & Social Media (Weekly Cycle)

### Example 2: Finance Operations (Event-Triggered)

### Example 3: Monitoring & Alerts (Continuous)

## The Execute-Verify-Report Pattern

Standing orders work best when combined with strict execution discipline. Every task in a standing order should follow this loop:

1. **Execute** — Do the actual work (don’t just acknowledge the instruction)
2. **Verify** — Confirm the result is correct (file exists, message delivered, data parsed)
3. **Report** — Tell the owner what was done and what was verified

This pattern prevents the most common agent failure mode: acknowledging a task without completing it.

## Multi-Program Architecture

For agents managing multiple concerns, organize standing orders as separate programs with clear boundaries:

Each program should have:

## Best Practices

### Do

### Avoid

----
url: https://docs.openclaw.ai/tools/web-fetch
----

# Web Fetch - OpenClaw

The `web_fetch` tool does a plain HTTP GET and extracts readable content (HTML to markdown or text). It does **not** execute JavaScript. For JS-heavy sites or login-protected pages, use the [Web Browser](https://docs.openclaw.ai/tools/browser) instead.

## Quick start

`web_fetch` is **enabled by default** — no configuration needed. The agent can call it immediately:

## Tool parameters

| Parameter     | Type     | Description                              |
| ------------- | -------- | ---------------------------------------- |
| `url`         | `string` | URL to fetch (required, http/https only) |
| `extractMode` | `string` | `"markdown"` (default) or `"text"`       |
| `maxChars`    | `number` | Truncate output to this many chars       |

## How it works

## Config

```
{
  tools: {
    web: {
      fetch: {
        enabled: true, // default: true
        maxChars: 50000, // max output chars
        maxCharsCap: 50000, // hard cap for maxChars param
        maxResponseBytes: 2000000, // max download size before truncation
        timeoutSeconds: 30,
        cacheTtlMinutes: 15,
        maxRedirects: 3,
        readability: true, // use Readability extraction
        userAgent: "Mozilla/5.0 ...", // override User-Agent
      },
    },
  },
}
```

## Firecrawl fallback

If Readability extraction fails, `web_fetch` can fall back to [Firecrawl](https://docs.openclaw.ai/tools/firecrawl) for bot-circumvention and better extraction:

```
{
  tools: {
    web: {
      fetch: {
        firecrawl: {
          enabled: true,
          apiKey: "fc-...", // optional if FIRECRAWL_API_KEY is set
          baseUrl: "https://api.firecrawl.dev",
          onlyMainContent: true,
          maxAgeMs: 86400000, // cache duration (1 day)
          timeoutSeconds: 60,
        },
      },
    },
  },
}
```

`tools.web.fetch.firecrawl.apiKey` supports SecretRef objects.

## Limits and safety

## Tool profiles

If you use tool profiles or allowlists, add `web_fetch` or `group:web`:

----
url: https://docs.openclaw.ai/concepts/session-pruning
----

# Session Pruning - OpenClaw

## [​](#session-pruning)Session Pruning

Session pruning trims **old tool results** from the in-memory context right before each LLM call. It does **not** rewrite the on-disk session history (`*.jsonl`).

## [​](#when-it-runs)When it runs

* When `mode: "cache-ttl"` is enabled and the last Anthropic call for the session is older than `ttl`.
* Only affects the messages sent to the model for that request.
* Only active for Anthropic API calls (and OpenRouter Anthropic models).
* For best results, match `ttl` to your model `cacheRetention` policy (`short` = 5m, `long` = 1h).
* After a prune, the TTL window resets so subsequent requests keep cache until `ttl` expires again.

## [​](#smart-defaults-anthropic)Smart defaults (Anthropic)

* **OAuth or setup-token** profiles: enable `cache-ttl` pruning and set heartbeat to `1h`.
* **API key** profiles: enable `cache-ttl` pruning, set heartbeat to `30m`, and default `cacheRetention: "short"` on Anthropic models.
* If you set any of these values explicitly, OpenClaw does **not** override them.

## [​](#what-this-improves-cost-+-cache-behavior)What this improves (cost + cache behavior)

* **Why prune:** Anthropic prompt caching only applies within the TTL. If a session goes idle past the TTL, the next request re-caches the full prompt unless you trim it first.
* **What gets cheaper:** pruning reduces the **cacheWrite** size for that first request after the TTL expires.
* **Why the TTL reset matters:** once pruning runs, the cache window resets, so follow‑up requests can reuse the freshly cached prompt instead of re-caching the full history again.
* **What it does not do:** pruning doesn’t add tokens or “double” costs; it only changes what gets cached on that first post‑TTL request.

## [​](#what-can-be-pruned)What can be pruned

* Only `toolResult` messages.
* User + assistant messages are **never** modified.
* The last `keepLastAssistants` assistant messages are protected; tool results after that cutoff are not pruned.
* If there aren’t enough assistant messages to establish the cutoff, pruning is skipped.
* Tool results containing **image blocks** are skipped (never trimmed/cleared).

## [​](#context-window-estimation)Context window estimation

Pruning uses an estimated context window (chars ≈ tokens × 4). The base window is resolved in this order:

1. `models.providers.*.models[].contextWindow` override.
2. Model definition `contextWindow` (from the model registry).
3. Default `200000` tokens.

If `agents.defaults.contextTokens` is set, it is treated as a cap (min) on the resolved window.

## [​](#mode)Mode

### [​](#cache-ttl)cache-ttl

* Pruning only runs if the last Anthropic call is older than `ttl` (default `5m`).
* When it runs: same soft-trim + hard-clear behavior as before.

## [​](#soft-vs-hard-pruning)Soft vs hard pruning

* **Soft-trim**: only for oversized tool results.

  * Keeps head + tail, inserts `...`, and appends a note with the original size.
  * Skips results with image blocks.

* **Hard-clear**: replaces the entire tool result with `hardClear.placeholder`.

## [​](#tool-selection)Tool selection

* `tools.allow` / `tools.deny` support `*` wildcards.
* Deny wins.
* Matching is case-insensitive.
* Empty allow list => all tools allowed.

## [​](#interaction-with-other-limits)Interaction with other limits

* Built-in tools already truncate their own output; session pruning is an extra layer that prevents long-running chats from accumulating too much tool output in the model context.
* Compaction is separate: compaction summarizes and persists, pruning is transient per request. See [/concepts/compaction](https://docs.openclaw.ai/concepts/compaction).

## [​](#defaults-when-enabled)Defaults (when enabled)

* `ttl`: `"5m"`
* `keepLastAssistants`: `3`
* `softTrimRatio`: `0.3`
* `hardClearRatio`: `0.5`
* `minPrunableToolChars`: `50000`
* `softTrim`: `{ maxChars: 4000, headChars: 1500, tailChars: 1500 }`
* `hardClear`: `{ enabled: true, placeholder: "[Old tool result content cleared]" }`

## [​](#examples)Examples

Default (off):

```
{
  agents: { defaults: { contextPruning: { mode: "off" } } },
}
```

Enable TTL-aware pruning:

```
{
  agents: { defaults: { contextPruning: { mode: "cache-ttl", ttl: "5m" } } },
}
```

Restrict pruning to specific tools:

```
{
  agents: {
    defaults: {
      contextPruning: {
        mode: "cache-ttl",
        tools: { allow: ["exec", "read"], deny: ["*image*"] },
      },
    },
  },
}
```

See config reference: [Gateway Configuration](https://docs.openclaw.ai/gateway/configuration)

----
url: https://docs.openclaw.ai/plugins/sdk-entrypoints
----

# Plugin Entry Points - OpenClaw

Every plugin exports a default entry object. The SDK provides three helpers for creating them.

## `definePluginEntry`

**Import:** `openclaw/plugin-sdk/plugin-entry` For provider plugins, tool plugins, hook plugins, and anything that is **not** a messaging channel.

| Field          | Type                                                             | Required | Default             |
| -------------- | ---------------------------------------------------------------- | -------- | ------------------- |
| `id`           | `string`                                                         | Yes      | —                   |
| `name`         | `string`                                                         | Yes      | —                   |
| `description`  | `string`                                                         | Yes      | —                   |
| `kind`         | `string`                                                         | No       | —                   |
| `configSchema` | `OpenClawPluginConfigSchema \| () => OpenClawPluginConfigSchema` | No       | Empty object schema |
| `register`     | `(api: OpenClawPluginApi) => void`                               | Yes      | —                   |

## `defineChannelPluginEntry`

**Import:** `openclaw/plugin-sdk/core` Wraps `definePluginEntry` with channel-specific wiring. Automatically calls `api.registerChannel({ plugin })` and gates `registerFull` on registration mode.

| Field          | Type                                                             | Required | Default             |
| -------------- | ---------------------------------------------------------------- | -------- | ------------------- |
| `id`           | `string`                                                         | Yes      | —                   |
| `name`         | `string`                                                         | Yes      | —                   |
| `description`  | `string`                                                         | Yes      | —                   |
| `plugin`       | `ChannelPlugin`                                                  | Yes      | —                   |
| `configSchema` | `OpenClawPluginConfigSchema \| () => OpenClawPluginConfigSchema` | No       | Empty object schema |
| `setRuntime`   | `(runtime: PluginRuntime) => void`                               | No       | —                   |
| `registerFull` | `(api: OpenClawPluginApi) => void`                               | No       | —                   |

## `defineSetupPluginEntry`

**Import:** `openclaw/plugin-sdk/core` For the lightweight `setup-entry.ts` file. Returns just `{ plugin }` with no runtime or CLI wiring.

OpenClaw loads this instead of the full entry when a channel is disabled, unconfigured, or when deferred loading is enabled. See [Setup and Config](https://docs.openclaw.ai/plugins/sdk-setup#setup-entry) for when this matters.

## Registration mode

`api.registrationMode` tells your plugin how it was loaded:

| Mode              | When                              | What to register              |
| ----------------- | --------------------------------- | ----------------------------- |
| `"full"`          | Normal gateway startup            | Everything                    |
| `"setup-only"`    | Disabled/unconfigured channel     | Channel registration only     |
| `"setup-runtime"` | Setup flow with runtime available | Channel + lightweight runtime |

`defineChannelPluginEntry` handles this split automatically. If you use `definePluginEntry` directly for a channel, check mode yourself:

## Plugin shapes

OpenClaw classifies loaded plugins by their registration behavior:

| Shape                 | Description                                        |
| --------------------- | -------------------------------------------------- |
| **plain-capability**  | One capability type (e.g. provider-only)           |
| **hybrid-capability** | Multiple capability types (e.g. provider + speech) |
| **hook-only**         | Only hooks, no capabilities                        |
| **non-capability**    | Tools/commands/services but no capabilities        |

Use `openclaw plugins inspect <id>` to see a plugin’s shape.

----
url: https://docs.openclaw.ai/channels/group-messages
----

# Group Messages - OpenClaw

## Group messages (WhatsApp web channel)

Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session. Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).

## Current implementation (2025-12-03)

## Config example (WhatsApp)

Add a `groupChat` block to `~/.openclaw/openclaw.json` so display-name pings work even when WhatsApp strips the visual `@` in the text body:

```
{
  channels: {
    whatsapp: {
      groups: {
        "*": { requireMention: true },
      },
    },
  },
  agents: {
    list: [
      {
        id: "main",
        groupChat: {
          historyLimit: 50,
          mentionPatterns: ["@?openclaw", "\\+?15555550123"],
        },
      },
    ],
  },
}
```

Notes:

### Activation command (owner-only)

Use the group chat command:

Only the owner number (from `channels.whatsapp.allowFrom`, or the bot’s own E.164 when unset) can change this. Send `/status` as a standalone message in the group to see the current activation mode.

## How to use

1. Add your WhatsApp account (the one running OpenClaw) to the group.
2. Say `@openclaw …` (or include the number). Only allowlisted senders can trigger it unless you set `groupPolicy: "open"`.
3. The agent prompt will include recent group context plus the trailing `[from: …]` marker so it can address the right person.
4. Session-level directives (`/verbose on`, `/think high`, `/new` or `/reset`, `/compact`) apply only to that group’s session; send them as standalone messages so they register. Your personal DM session remains independent.

## Testing / verification

## Known considerations

----
url: https://docs.openclaw.ai/install/migrating-matrix
----

# Matrix migration - OpenClaw

This page covers upgrades from the previous public `matrix` plugin to the current implementation. For most users, the upgrade is in place:

You do not need to rename config keys or reinstall the plugin under a new name.

## What the migration does automatically

When the gateway starts, and when you run [`openclaw doctor --fix`](https://docs.openclaw.ai/gateway/doctor), OpenClaw tries to repair old Matrix state automatically. Before any actionable Matrix migration step mutates on-disk state, OpenClaw creates or reuses a focused recovery snapshot. When you use `openclaw update`, the exact trigger depends on how OpenClaw is installed:

Automatic migration covers:

Snapshot details:

About multi-account upgrades:

## What the migration cannot do automatically

The previous public Matrix plugin did **not** automatically create Matrix room-key backups. It persisted local crypto state and requested device verification, but it did not guarantee that your room keys were backed up to the homeserver. That means some encrypted installs can only be migrated partially. OpenClaw cannot automatically recover:

Current warning scope:

If your old installation had local-only encrypted history that was never backed up, some older encrypted messages may remain unreadable after the upgrade.

## Recommended upgrade flow

1. Update OpenClaw and the Matrix plugin normally. Prefer plain `openclaw update` without `--no-restart` so startup can finish the Matrix migration immediately.
2. Run: If Matrix has actionable migration work, doctor will create or reuse the pre-migration snapshot first and print the archive path.
3. Start or restart the gateway.
4. Check current verification and backup state:
5. If OpenClaw tells you a recovery key is needed, run:
6. If this device is still unverified, run:
7. If you are intentionally abandoning unrecoverable old history and want a fresh backup baseline for future messages, run:
8. If no server-side key backup exists yet, create one for future recoveries:

## How encrypted migration works

Encrypted migration is a two-stage process:

1. Startup or `openclaw doctor --fix` creates or reuses the pre-migration snapshot if encrypted migration is actionable.
2. Startup or `openclaw doctor --fix` inspects the old Matrix crypto store through the active Matrix plugin install.
3. If a backup decryption key is found, OpenClaw writes it into the new recovery-key flow and marks room-key restore as pending.
4. On the next Matrix startup, OpenClaw restores backed-up room keys into the new crypto store automatically.

If the old store reports room keys that were never backed up, OpenClaw warns instead of pretending recovery succeeded.

## Common messages and what they mean

### Upgrade and detection messages

`Matrix plugin upgraded in place.`

`Matrix migration snapshot created before applying Matrix upgrades.`

`Matrix migration snapshot reused before applying Matrix upgrades.`

`Legacy Matrix state detected at ... but channels.matrix is not configured yet.`

`Legacy Matrix state detected at ... but the new account-scoped target could not be resolved yet (need homeserver, userId, and access token for channels.matrix...).`

`Legacy Matrix state detected at ... but multiple Matrix accounts are configured and channels.matrix.defaultAccount is not set.`

`Matrix legacy sync store not migrated because the target already exists (...)`

`Failed migrating Matrix legacy sync store (...)` or `Failed migrating Matrix legacy crypto store (...)`

`Legacy Matrix encrypted state detected at ... but channels.matrix is not configured yet.`

`Legacy Matrix encrypted state detected at ... but the account-scoped target could not be resolved yet (need homeserver, userId, and access token for channels.matrix...).`

`Legacy Matrix encrypted state detected at ... but multiple Matrix accounts are configured and channels.matrix.defaultAccount is not set.`

`Matrix migration warnings are present, but no on-disk Matrix mutation is actionable yet. No pre-migration snapshot was needed.`

`Legacy Matrix encrypted state was detected, but the Matrix plugin helper is unavailable. Install or repair @openclaw/matrix so OpenClaw can inspect the old rust crypto store before upgrading.`

`Matrix plugin helper path is unsafe: ... Reinstall @openclaw/matrix and try again.`

`- Failed creating a Matrix migration snapshot before repair: ...` `- Skipping Matrix migration changes for now. Resolve the snapshot failure, then rerun "openclaw doctor --fix".`

`Failed migrating legacy Matrix client storage: ...`

`Matrix is installed from a custom path: ...`

### Encrypted-state recovery messages

`matrix: restored X/Y room key(s) from legacy encrypted-state backup`

`matrix: N legacy local-only room key(s) were never backed up and could not be restored automatically`

`Legacy Matrix encrypted state for account "..." has backed-up room keys, but no local backup decryption key was found. Ask the operator to run "openclaw matrix verify backup restore --recovery-key <key>" after upgrade if they have the recovery key.`

`Failed inspecting legacy Matrix encrypted state for account "..." (...): ...`

`Legacy Matrix backup key was found for account "...", but .../recovery-key.json already contains a different recovery key. Leaving the existing file unchanged.`

`Legacy Matrix encrypted state for account "..." cannot be fully converted automatically because the old rust crypto store does not expose all local room keys for export.`

`matrix: failed restoring room keys from legacy encrypted-state backup: ...`

### Manual recovery messages

`Backup key is not loaded on this device. Run 'openclaw matrix verify backup restore' to load it and restore old room keys.`

`Store a recovery key with 'openclaw matrix verify device <key>', then run 'openclaw matrix verify backup restore'.`

`Backup key mismatch on this device. Re-run 'openclaw matrix verify device <key>' with the matching recovery key.`

If you accept losing unrecoverable old encrypted history, you can instead reset the current backup baseline with `openclaw matrix verify backup reset --yes`. `Backup trust chain is not verified on this device. Re-run 'openclaw matrix verify device <key>'.`

`Matrix recovery key is required`

`Invalid Matrix recovery key: ...`

`Matrix device is still unverified after applying recovery key. Verify your recovery key and ensure cross-signing is available.`

`Matrix key backup is not active on this device after loading from secret storage.`

`Matrix crypto backend cannot load backup keys from secret storage. Verify this device with 'openclaw matrix verify device <key>' first.`

### Custom plugin install messages

`Matrix is installed from a custom path that no longer exists: ...`

## If encrypted history still does not come back

Run these checks in order:

If the backup restores successfully but some old rooms are still missing history, those missing keys were probably never backed up by the previous plugin.

## If you want to start fresh for future messages

If you accept losing unrecoverable old encrypted history and only want a clean backup baseline going forward, run these commands in order:

If the device is still unverified after that, finish verification from your Matrix client by comparing the SAS emoji or decimal codes and confirming that they match.

----
url: https://docs.openclaw.ai/network
----

# Network - OpenClaw

##### Gateway

##### Remote access

* [Remote Access](https://docs.openclaw.ai/gateway/remote)
* [Remote Gateway Setup](https://docs.openclaw.ai/gateway/remote-gateway-readme)
* [Tailscale](https://docs.openclaw.ai/gateway/tailscale)

##### Security

* [Formal Verification (Security Models)](https://docs.openclaw.ai/security/formal-verification)
* [Threat Model (MITRE ATLAS)](https://docs.openclaw.ai/security/THREAT-MODEL-ATLAS)
* [Contributing to the Threat Model](https://docs.openclaw.ai/security/CONTRIBUTING-THREAT-MODEL)

##### Nodes and devices

##### Web interfaces

* [Web](https://docs.openclaw.ai/web)
* [Control UI](https://docs.openclaw.ai/web/control-ui)
* [Dashboard](https://docs.openclaw.ai/web/dashboard)
* [WebChat](https://docs.openclaw.ai/web/webchat)
* [TUI](https://docs.openclaw.ai/web/tui)

- [Network hub](#network-hub)
- [Core model](#core-model)
- [Pairing + identity](#pairing-%2B-identity)
- [Discovery + transports](#discovery-%2B-transports)
- [Nodes + transports](#nodes-%2B-transports)
- [Security](#security)

## [​](#network-hub)Network hub

This hub links the core docs for how OpenClaw connects, pairs, and secures devices across localhost, LAN, and tailnet.

## [​](#core-model)Core model

* [Gateway architecture](https://docs.openclaw.ai/concepts/architecture)
* [Gateway protocol](https://docs.openclaw.ai/gateway/protocol)
* [Gateway runbook](https://docs.openclaw.ai/gateway)
* [Web surfaces + bind modes](https://docs.openclaw.ai/web)

## [​](#pairing-+-identity)Pairing + identity

* [Pairing overview (DM + nodes)](https://docs.openclaw.ai/channels/pairing)
* [Gateway-owned node pairing](https://docs.openclaw.ai/gateway/pairing)
* [Devices CLI (pairing + token rotation)](https://docs.openclaw.ai/cli/devices)
* [Pairing CLI (DM approvals)](https://docs.openclaw.ai/cli/pairing)

Local trust:

* Local connections (loopback or the gateway host’s own tailnet address) can be auto‑approved for pairing to keep same‑host UX smooth.
* Non‑local tailnet/LAN clients still require explicit pairing approval.

## [​](#discovery-+-transports)Discovery + transports

* [Discovery & transports](https://docs.openclaw.ai/gateway/discovery)
* [Bonjour / mDNS](https://docs.openclaw.ai/gateway/bonjour)
* [Remote access (SSH)](https://docs.openclaw.ai/gateway/remote)
* [Tailscale](https://docs.openclaw.ai/gateway/tailscale)

## [​](#nodes-+-transports)Nodes + transports

* [Nodes overview](https://docs.openclaw.ai/nodes)
* [Bridge protocol (legacy nodes)](https://docs.openclaw.ai/gateway/bridge-protocol)
* [Node runbook: iOS](https://docs.openclaw.ai/platforms/ios)
* [Node runbook: Android](https://docs.openclaw.ai/platforms/android)

## [​](#security)Security

* [Security overview](https://docs.openclaw.ai/gateway/security)
* [Gateway config reference](https://docs.openclaw.ai/gateway/configuration)
* [Troubleshooting](https://docs.openclaw.ai/gateway/troubleshooting)
* [Doctor](https://docs.openclaw.ai/gateway/doctor)

[Local Models](https://docs.openclaw.ai/gateway/local-models)[Network model](https://docs.openclaw.ai/gateway/network-model)

----
url: https://docs.openclaw.ai/concepts/presence
----

# Presence - OpenClaw

OpenClaw “presence” is a lightweight, best‑effort view of:

Presence is used primarily to render the macOS app’s **Instances** tab and to provide quick operator visibility.

## Presence fields (what shows up)

Presence entries are structured objects with fields like:

* `instanceId` (optional but strongly recommended): stable client identity (usually `connect.client.instanceId`)
* `host`: human‑friendly host name
* `ip`: best‑effort IP address
* `version`: client version string
* `deviceFamily` / `modelIdentifier`: hardware hints
* `mode`: `ui`, `webchat`, `cli`, `backend`, `probe`, `test`, `node`, …
* `lastInputSeconds`: “seconds since last user input” (if known)
* `reason`: `self`, `connect`, `node-connected`, `periodic`, …
* `ts`: last update timestamp (ms since epoch)

## Producers (where presence comes from)

Presence entries are produced by multiple sources and **merged**.

### 1) Gateway self entry

The Gateway always seeds a “self” entry at startup so UIs show the gateway host even before any clients connect.

### 2) WebSocket connect

Every WS client begins with a `connect` request. On successful handshake the Gateway upserts a presence entry for that connection.

#### Why one-off CLI commands do not show up

The CLI often connects for short, one‑off commands. To avoid spamming the Instances list, `client.mode === "cli"` is **not** turned into a presence entry.

### 3) `system-event` beacons

Clients can send richer periodic beacons via the `system-event` method. The mac app uses this to report host name, IP, and `lastInputSeconds`.

### 4) Node connects (role: node)

When a node connects over the Gateway WebSocket with `role: node`, the Gateway upserts a presence entry for that node (same flow as other WS clients).

## Merge + dedupe rules (why `instanceId` matters)

Presence entries are stored in a single in‑memory map:

If a client reconnects without a stable `instanceId`, it may show up as a **duplicate** row.

## TTL and bounded size

Presence is intentionally ephemeral:

This keeps the list fresh and avoids unbounded memory growth.

## Remote/tunnel caveat (loopback IPs)

When a client connects over an SSH tunnel / local port forward, the Gateway may see the remote address as `127.0.0.1`. To avoid overwriting a good client‑reported IP, loopback remote addresses are ignored.

## Consumers

### macOS Instances tab

The macOS app renders the output of `system-presence` and applies a small status indicator (Active/Idle/Stale) based on the age of the last update.

## Debugging tips

----
url: https://docs.openclaw.ai/gateway/authentication
----

# Authentication - OpenClaw

OpenClaw supports OAuth and API keys for model providers. For always-on gateway hosts, API keys are usually the most predictable option. Subscription/OAuth flows are also supported when they match your provider account model. See [/concepts/oauth](https://docs.openclaw.ai/concepts/oauth) for the full OAuth flow and storage layout. For SecretRef-based auth (`env`/`file`/`exec` providers), see [Secrets Management](https://docs.openclaw.ai/gateway/secrets). For credential eligibility/reason-code rules used by `models status --probe`, see [Auth Credential Semantics](https://docs.openclaw.ai/auth-credential-semantics).

## Recommended setup (API key, any provider)

If you’re running a long-lived gateway, start with an API key for your chosen provider. For Anthropic specifically, API key auth is the safe path and is recommended over subscription setup-token auth.

1. Create an API key in your provider console.
2. Put it on the **gateway host** (the machine running `openclaw gateway`).

3) If the Gateway runs under systemd/launchd, prefer putting the key in `~/.openclaw/.env` so the daemon can read it:

Then restart the daemon (or restart your Gateway process) and re-check:

If you’d rather not manage env vars yourself, onboarding can store API keys for daemon use: `openclaw onboard`. See [Help](https://docs.openclaw.ai/help) for details on env inheritance (`env.shellEnv`, `~/.openclaw/.env`, systemd/launchd).

## Anthropic: setup-token (subscription auth)

If you’re using a Claude subscription, the setup-token flow is supported. Run it on the **gateway host**:

Then paste it into OpenClaw:

If the token was created on another machine, paste it manually:

If you see an Anthropic error like:

…use an Anthropic API key instead.

Manual token entry (any provider; writes `auth-profiles.json` + updates config):

Auth profile refs are also supported for static credentials:

Automation-friendly check (exit `1` when expired/missing, `2` when expiring):

Optional ops scripts (systemd/Termux) are documented here: [/automation/auth-monitoring](https://docs.openclaw.ai/automation/auth-monitoring)

> `claude setup-token` requires an interactive TTY.

## Checking model auth status

## API key rotation behavior (gateway)

Some providers support retrying a request with alternative keys when an API call hits a provider rate limit.

## Controlling which credential is used

### Per-session (chat command)

Use `/model <alias-or-id>@<profileId>` to pin a specific provider credential for the current session (example profile ids: `anthropic:default`, `anthropic:work`). Use `/model` (or `/model list`) for a compact picker; use `/model status` for the full view (candidates + next auth profile, plus provider endpoint details when configured).

### Per-agent (CLI override)

Set an explicit auth profile order override for an agent (stored in that agent’s `auth-profiles.json`):

Use `--agent <id>` to target a specific agent; omit it to use the configured default agent.

## Troubleshooting

### ”No credentials found”

If the Anthropic token profile is missing, run `claude setup-token` on the **gateway host**, then re-check:

### Token expiring/expired

Run `openclaw models status` to confirm which profile is expiring. If the profile is missing, rerun `claude setup-token` and paste the token again.

## Requirements

----
url: https://docs.openclaw.ai/install/gcp
----

# GCP - OpenClaw

## OpenClaw on GCP Compute Engine (Docker, Production VPS Guide)

## Goal

Run a persistent OpenClaw Gateway on a GCP Compute Engine VM using Docker, with durable state, baked-in binaries, and safe restart behavior. If you want “OpenClaw 24/7 for \~$5-12/mo”, this is a reliable setup on Google Cloud. Pricing varies by machine type and region; pick the smallest VM that fits your workload and scale up if you hit OOMs.

## What are we doing (simple terms)?

The Gateway can be accessed via:

This guide uses Debian on GCP Compute Engine. Ubuntu also works; map packages accordingly. For the generic Docker flow, see [Docker](https://docs.openclaw.ai/install/docker).

***

## Quick path (experienced operators)

1. Create GCP project + enable Compute Engine API
2. Create Compute Engine VM (e2-small, Debian 12, 20GB)
3. SSH into the VM
4. Install Docker
5. Clone OpenClaw repository
6. Create persistent host directories
7. Configure `.env` and `docker-compose.yml`
8. Bake required binaries, build, and launch

***

## What you need

***

Create the VM

**Machine types:**

| Type      | Specs                    | Cost               | Notes                                        |
| --------- | ------------------------ | ------------------ | -------------------------------------------- |
| e2-medium | 2 vCPU, 4GB RAM          | \~$25/mo           | Most reliable for local Docker builds        |
| e2-small  | 2 vCPU, 2GB RAM          | \~$12/mo           | Minimum recommended for Docker build         |
| e2-micro  | 2 vCPU (shared), 1GB RAM | Free tier eligible | Often fails with Docker build OOM (exit 137) |

**CLI:**

**Console:**

1. Go to Compute Engine > VM instances > Create instance
2. Name: `openclaw-gateway`
3. Region: `us-central1`, Zone: `us-central1-a`
4. Machine type: `e2-small`
5. Boot disk: Debian 12, 20GB
6. Create

***

## Troubleshooting

**SSH connection refused** SSH key propagation can take 1-2 minutes after VM creation. Wait and retry. **OS Login issues** Check your OS Login profile:

Ensure your account has the required IAM permissions (Compute OS Login or Compute OS Admin Login). **Out of memory (OOM)** If Docker build fails with `Killed` and `exit code 137`, the VM was OOM-killed. Upgrade to e2-small (minimum) or e2-medium (recommended for reliable local builds):

***

## Service accounts (security best practice)

For personal use, your default user account works fine. For automation or CI/CD pipelines, create a dedicated service account with minimal permissions:

1. Create a service account:
2. Grant Compute Instance Admin role (or narrower custom role):

Avoid using the Owner role for automation. Use the principle of least privilege. See <https://cloud.google.com/iam/docs/understanding-roles> for IAM role details.

***

## Next steps

----
url: https://docs.openclaw.ai/platforms/mac/menu-bar
----

# Menu Bar - OpenClaw

## [​](#menu-bar-status-logic)Menu Bar Status Logic

## [​](#what-is-shown)What is shown

* We surface the current agent work state in the menu bar icon and in the first status row of the menu.
* Health status is hidden while work is active; it returns when all sessions are idle.
* The “Nodes” block in the menu lists **devices** only (paired nodes via `node.list`), not client/presence entries.
* A “Usage” section appears under Context when provider usage snapshots are available.

## [​](#state-model)State model

* Sessions: events arrive with `runId` (per-run) plus `sessionKey` in the payload. The “main” session is the key `main`; if absent, we fall back to the most recently updated session.

* Priority: main always wins. If main is active, its state is shown immediately. If main is idle, the most recently active non‑main session is shown. We do not flip‑flop mid‑activity; we only switch when the current session goes idle or main becomes active.

* Activity kinds:

  * `job`: high‑level command execution (`state: started|streaming|done|error`).
  * `tool`: `phase: start|result` with `toolName` and `meta/args`.

## [​](#iconstate-enum-swift)IconState enum (Swift)

* `idle`
* `workingMain(ActivityKind)`
* `workingOther(ActivityKind)`
* `overridden(ActivityKind)` (debug override)

### [​](#activitykind-→-glyph)ActivityKind → glyph

* `exec` → 💻
* `read` → 📄
* `write` → ✍️
* `edit` → 📝
* `attach` → 📎
* default → 🛠️

### [​](#visual-mapping)Visual mapping

* `idle`: normal critter.
* `workingMain`: badge with glyph, full tint, leg “working” animation.
* `workingOther`: badge with glyph, muted tint, no scurry.
* `overridden`: uses the chosen glyph/tint regardless of activity.

## [​](#status-row-text-menu)Status row text (menu)

* While work is active: `<Session role> · <activity label>`
  * Examples: `Main · exec: pnpm test`, `Other · read: apps/macos/Sources/OpenClaw/AppState.swift`.
* When idle: falls back to the health summary.

## [​](#event-ingestion)Event ingestion

* Source: control‑channel `agent` events (`ControlChannel.handleAgentEvent`).

* Parsed fields:

  * `stream: "job"` with `data.state` for start/stop.
  * `stream: "tool"` with `data.phase`, `name`, optional `meta`/`args`.

* Labels:

  * `exec`: first line of `args.command`.
  * `read`/`write`: shortened path.
  * `edit`: path plus inferred change kind from `meta`/diff counts.
  * fallback: tool name.

## [​](#debug-override)Debug override

* Settings ▸ Debug ▸ “Icon override” picker:

  * `System (auto)` (default)
  * `Working: main` (per tool kind)
  * `Working: other` (per tool kind)
  * `Idle`

* Stored via `@AppStorage("iconOverride")`; mapped to `IconState.overridden`.

## [​](#testing-checklist)Testing checklist

* Trigger main session job: verify icon switches immediately and status row shows main label.
* Trigger non‑main session job while main idle: icon/status shows non‑main; stays stable until it finishes.
* Start main while other active: icon flips to main instantly.
* Rapid tool bursts: ensure badge does not flicker (TTL grace on tool results).
* Health row reappears once all sessions idle.

[macOS Dev Setup](https://docs.openclaw.ai/platforms/mac/dev-setup)[Voice Wake (macOS)](https://docs.openclaw.ai/platforms/mac/voicewake)

----
url: https://docs.openclaw.ai/channels/zalouser
----

# Zalo Personal - OpenClaw

## [​](#zalo-personal-unofficial)Zalo Personal (unofficial)

Status: experimental. This integration automates a **personal Zalo account** via native `zca-js` inside OpenClaw.

> **Warning:** This is an unofficial integration and may result in account suspension/ban. Use at your own risk.

## [​](#plugin-required)Plugin required

Zalo Personal ships as a plugin and is not bundled with the core install.

* Install via CLI: `openclaw plugins install @openclaw/zalouser`
* Or from a source checkout: `openclaw plugins install ./extensions/zalouser`
* Details: [Plugins](https://docs.openclaw.ai/tools/plugin)

No external `zca`/`openzca` CLI binary is required.

## [​](#quick-setup-beginner)Quick setup (beginner)

1. Install the plugin (see above).

2. Login (QR, on the Gateway machine):

   * `openclaw channels login --channel zalouser`
   * Scan the QR code with the Zalo mobile app.

3. Enable the channel:

```
{
  channels: {
    zalouser: {
      enabled: true,
      dmPolicy: "pairing",
    },
  },
}
```

4. Restart the Gateway (or finish setup).
5. DM access defaults to pairing; approve the pairing code on first contact.

## [​](#what-it-is)What it is

* Runs entirely in-process via `zca-js`.
* Uses native event listeners to receive inbound messages.
* Sends replies directly through the JS API (text/media/link).
* Designed for “personal account” use cases where Zalo Bot API is not available.

## [​](#naming)Naming

Channel id is `zalouser` to make it explicit this automates a **personal Zalo user account** (unofficial). We keep `zalo` reserved for a potential future official Zalo API integration.

## [​](#finding-ids-directory)Finding IDs (directory)

Use the directory CLI to discover peers/groups and their IDs:

```
openclaw directory self --channel zalouser
openclaw directory peers list --channel zalouser --query "name"
openclaw directory groups list --channel zalouser --query "work"
```

## [​](#limits)Limits

* Outbound text is chunked to \~2000 characters (Zalo client limits).
* Streaming is blocked by default.

## [​](#access-control-dms)Access control (DMs)

`channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`). `channels.zalouser.allowFrom` accepts user IDs or names. During setup, names are resolved to IDs using the plugin’s in-process contact lookup. Approve via:

* `openclaw pairing list zalouser`
* `openclaw pairing approve zalouser <code>`

## [​](#group-access-optional)Group access (optional)

* Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset.

* Restrict to an allowlist with:

  * `channels.zalouser.groupPolicy = "allowlist"`
  * `channels.zalouser.groups` (keys should be stable group IDs; names are resolved to IDs on startup when possible)
  * `channels.zalouser.groupAllowFrom` (controls which senders in allowed groups can trigger the bot)

* Block all groups: `channels.zalouser.groupPolicy = "disabled"`.

* The configure wizard can prompt for group allowlists.

* On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping.

* Group allowlist matching is ID-only by default. Unresolved names are ignored for auth unless `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled.

* `channels.zalouser.dangerouslyAllowNameMatching: true` is a break-glass compatibility mode that re-enables mutable group-name matching.

* If `groupAllowFrom` is unset, runtime falls back to `allowFrom` for group sender checks.

* Sender checks apply to both normal group messages and control commands (for example `/new`, `/reset`).

Example:

```
{
  channels: {
    zalouser: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["1471383327500481391"],
      groups: {
        "123456789": { allow: true },
        "Work Chat": { allow: true },
      },
    },
  },
}
```

### [​](#group-mention-gating)Group mention gating

* `channels.zalouser.groups.<group>.requireMention` controls whether group replies require a mention.
* Resolution order: exact group id/name -> normalized group slug -> `*` -> default (`true`).
* This applies both to allowlisted groups and open group mode.
* Authorized control commands (for example `/new`) can bypass mention gating.
* When a group message is skipped because mention is required, OpenClaw stores it as pending group history and includes it on the next processed group message.
* Group history limit defaults to `messages.groupChat.historyLimit` (fallback `50`). You can override per account with `channels.zalouser.historyLimit`.

Example:

```
{
  channels: {
    zalouser: {
      groupPolicy: "allowlist",
      groups: {
        "*": { allow: true, requireMention: true },
        "Work Chat": { allow: true, requireMention: false },
      },
    },
  },
}
```

## [​](#multi-account)Multi-account

Accounts map to `zalouser` profiles in OpenClaw state. Example:

```
{
  channels: {
    zalouser: {
      enabled: true,
      defaultAccount: "default",
      accounts: {
        work: { enabled: true, profile: "work" },
      },
    },
  },
}
```

## [​](#typing-reactions-and-delivery-acknowledgements)Typing, reactions, and delivery acknowledgements

* OpenClaw sends a typing event before dispatching a reply (best-effort).

* Message reaction action `react` is supported for `zalouser` in channel actions.

  * Use `remove: true` to remove a specific reaction emoji from a message.
  * Reaction semantics: [Reactions](https://docs.openclaw.ai/tools/reactions)

* For inbound messages that include event metadata, OpenClaw sends delivered + seen acknowledgements (best-effort).

## [​](#troubleshooting)Troubleshooting

**Login doesn’t stick:**

* `openclaw channels status --probe`
* Re-login: `openclaw channels logout --channel zalouser && openclaw channels login --channel zalouser`

**Allowlist/group name didn’t resolve:**

* Use numeric IDs in `allowFrom`/`groupAllowFrom`/`groups`, or exact friend/group names.

**Upgraded from old CLI-based setup:**

* Remove any old external `zca` process assumptions.
* The channel now runs fully in OpenClaw without external CLI binaries.

----
url: https://docs.openclaw.ai/gateway/openshell
----

# OpenShell - OpenClaw

OpenShell is a managed sandbox backend for OpenClaw. Instead of running Docker containers locally, OpenClaw delegates sandbox lifecycle to the `openshell` CLI, which provisions remote environments with SSH-based command execution. The OpenShell plugin reuses the same core SSH transport and remote filesystem bridge as the generic [SSH backend](https://docs.openclaw.ai/gateway/sandboxing#ssh-backend). It adds OpenShell-specific lifecycle (`sandbox create/get/delete`, `sandbox ssh-config`) and an optional `mirror` workspace mode.

## Prerequisites

## Quick start

1. Enable the plugin and set the sandbox backend:

```
{
  agents: {
    defaults: {
      sandbox: {
        mode: "all",
        backend: "openshell",
        scope: "session",
        workspaceAccess: "rw",
      },
    },
  },
  plugins: {
    entries: {
      openshell: {
        enabled: true,
        config: {
          from: "openclaw",
          mode: "remote",
        },
      },
    },
  },
}
```

2. Restart the Gateway. On the next agent turn, OpenClaw creates an OpenShell sandbox and routes tool execution through it.
3. Verify:

## Workspace modes

This is the most important decision when using OpenShell.

### `mirror`

Use `plugins.entries.openshell.config.mode: "mirror"` when you want the **local workspace to stay canonical**. Behavior:

Best for:

Tradeoff: extra sync cost before and after each exec.

### `remote`

Use `plugins.entries.openshell.config.mode: "remote"` when you want the **OpenShell workspace to become canonical**. Behavior:

Best for:

Important: if you edit files on the host outside OpenClaw after the initial seed, the remote sandbox does **not** see those changes. Use `openclaw sandbox recreate` to re-seed.

### Choosing a mode

|                          | `mirror`                   | `remote`                  |
| ------------------------ | -------------------------- | ------------------------- |
| **Canonical workspace**  | Local host                 | Remote OpenShell          |
| **Sync direction**       | Bidirectional (each exec)  | One-time seed             |
| **Per-turn overhead**    | Higher (upload + download) | Lower (direct remote ops) |
| **Local edits visible?** | Yes, on next exec          | No, until recreate        |
| **Best for**             | Development workflows      | Long-running agents, CI   |

## Configuration reference

All OpenShell config lives under `plugins.entries.openshell.config`:

| Key                       | Type                     | Default       | Description                                           |
| ------------------------- | ------------------------ | ------------- | ----------------------------------------------------- |
| `mode`                    | `"mirror"` or `"remote"` | `"mirror"`    | Workspace sync mode                                   |
| `command`                 | `string`                 | `"openshell"` | Path or name of the `openshell` CLI                   |
| `from`                    | `string`                 | `"openclaw"`  | Sandbox source for first-time create                  |
| `gateway`                 | `string`                 | —             | OpenShell gateway name (`--gateway`)                  |
| `gatewayEndpoint`         | `string`                 | —             | OpenShell gateway endpoint URL (`--gateway-endpoint`) |
| `policy`                  | `string`                 | —             | OpenShell policy ID for sandbox creation              |
| `providers`               | `string[]`               | `[]`          | Provider names to attach when sandbox is created      |
| `gpu`                     | `boolean`                | `false`       | Request GPU resources                                 |
| `autoProviders`           | `boolean`                | `true`        | Pass `--auto-providers` during sandbox create         |
| `remoteWorkspaceDir`      | `string`                 | `"/sandbox"`  | Primary writable workspace inside the sandbox         |
| `remoteAgentWorkspaceDir` | `string`                 | `"/agent"`    | Agent workspace mount path (for read-only access)     |
| `timeoutSeconds`          | `number`                 | `120`         | Timeout for `openshell` CLI operations                |

Sandbox-level settings (`mode`, `scope`, `workspaceAccess`) are configured under `agents.defaults.sandbox` as with any backend. See [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing) for the full matrix.

## Examples

### Minimal remote setup

```
{
  agents: {
    defaults: {
      sandbox: {
        mode: "all",
        backend: "openshell",
      },
    },
  },
  plugins: {
    entries: {
      openshell: {
        enabled: true,
        config: {
          from: "openclaw",
          mode: "remote",
        },
      },
    },
  },
}
```

### Mirror mode with GPU

```
{
  agents: {
    defaults: {
      sandbox: {
        mode: "all",
        backend: "openshell",
        scope: "agent",
        workspaceAccess: "rw",
      },
    },
  },
  plugins: {
    entries: {
      openshell: {
        enabled: true,
        config: {
          from: "openclaw",
          mode: "mirror",
          gpu: true,
          providers: ["openai"],
          timeoutSeconds: 180,
        },
      },
    },
  },
}
```

### Per-agent OpenShell with custom gateway

```
{
  agents: {
    defaults: {
      sandbox: { mode: "off" },
    },
    list: [
      {
        id: "researcher",
        sandbox: {
          mode: "all",
          backend: "openshell",
          scope: "agent",
          workspaceAccess: "rw",
        },
      },
    ],
  },
  plugins: {
    entries: {
      openshell: {
        enabled: true,
        config: {
          from: "openclaw",
          mode: "remote",
          gateway: "lab",
          gatewayEndpoint: "https://lab.example",
          policy: "strict",
        },
      },
    },
  },
}
```

## Lifecycle management

OpenShell sandboxes are managed through the normal sandbox CLI:

For `remote` mode, **recreate is especially important**: it deletes the canonical remote workspace for that scope. The next use seeds a fresh remote workspace from the local workspace. For `mirror` mode, recreate mainly resets the remote execution environment because the local workspace remains canonical.

### When to recreate

Recreate after changing any of these:

## Current limitations

## How it works

1. OpenClaw calls `openshell sandbox create` (with `--from`, `--gateway`, `--policy`, `--providers`, `--gpu` flags as configured).
2. OpenClaw calls `openshell sandbox ssh-config <name>` to get SSH connection details for the sandbox.
3. Core writes the SSH config to a temp file and opens an SSH session using the same remote filesystem bridge as the generic SSH backend.
4. In `mirror` mode: sync local to remote before exec, run, sync back after exec.
5. In `remote` mode: seed once on create, then operate directly on the remote workspace.

## See also

----
url: https://docs.openclaw.ai/tools/browser-wsl2-windows-remote-cdp-troubleshooting
----

# WSL2 + Windows + remote Chrome CDP troubleshooting - OpenClaw

This guide covers the common split-host setup where:

It also covers the layered failure pattern from [issue #39369](https://github.com/openclaw/openclaw/issues/39369): several independent problems can show up at once, which makes the wrong layer look broken first.

## Choose the right browser mode first

You have two valid patterns:

### Option 1: Raw remote CDP from WSL2 to Windows

Use a remote browser profile that points from WSL2 to a Windows Chrome CDP endpoint. Choose this when:

### Option 2: Host-local Chrome MCP

Use `existing-session` / `user` only when the Gateway itself runs on the same host as Chrome. Choose this when:

For WSL2 Gateway + Windows Chrome, prefer raw remote CDP. Chrome MCP is host-local, not a WSL2-to-Windows bridge.

## Working architecture

Reference shape:

## Why this setup is confusing

Several failures can overlap:

Because of that, fixing one layer can still leave a different error visible.

## Critical rule for the Control UI

When the UI is opened from Windows, use Windows localhost unless you have a deliberate HTTPS setup. Use: `http://127.0.0.1:18789/` Do not default to a LAN IP for the Control UI. Plain HTTP on a LAN or tailnet address can trigger insecure-origin/device-auth behavior that is unrelated to CDP itself. See [Control UI](https://docs.openclaw.ai/web/control-ui).

## Validate in layers

Work top to bottom. Do not skip ahead.

### Layer 1: Verify Chrome is serving CDP on Windows

Start Chrome on Windows with remote debugging enabled:

From Windows, verify Chrome itself first:

If this fails on Windows, OpenClaw is not the problem yet.

### Layer 2: Verify WSL2 can reach that Windows endpoint

From WSL2, test the exact address you plan to use in `cdpUrl`:

Good result:

If this fails:

Fix that before touching OpenClaw config.

### Layer 3: Configure the correct browser profile

For raw remote CDP, point OpenClaw at the address that is reachable from WSL2:

Notes:

### Layer 4: Verify the Control UI layer separately

Open the UI from Windows: `http://127.0.0.1:18789/` Then verify:

Helpful page:

### Layer 5: Verify end-to-end browser control

From WSL2:

Good result:

## Common misleading errors

Treat each message as a layer-specific clue:

## Fast triage checklist

1. Windows: does `curl http://127.0.0.1:9222/json/version` work?
2. WSL2: does `curl http://WINDOWS_HOST_OR_IP:9222/json/version` work?
3. OpenClaw config: does `browser.profiles.<name>.cdpUrl` use that exact WSL2-reachable address?
4. Control UI: are you opening `http://127.0.0.1:18789/` instead of a LAN IP?
5. Are you trying to use `existing-session` across WSL2 and Windows instead of raw remote CDP?

## Practical takeaway

The setup is usually viable. The hard part is that browser transport, Control UI origin security, and token/pairing can each fail independently while looking similar from the user side. When in doubt:

----
url: https://docs.openclaw.ai/vps
----

# Linux Server - OpenClaw

## [​](#linux-server)Linux Server

Run the OpenClaw Gateway on any Linux server or cloud VPS. This page helps you pick a provider, explains how cloud deployments work, and covers generic Linux tuning that applies everywhere.

## [​](#pick-a-provider)Pick a provider

## Railway

One-click, browser setup

## Northflank

One-click, browser setup

## DigitalOcean

Simple paid VPS

## Oracle Cloud

Always Free ARM tier

## Fly.io

Fly Machines

## Hetzner

Docker on Hetzner VPS

## GCP

Compute Engine

## Azure

Linux VM

## exe.dev

VM with HTTPS proxy

## Raspberry Pi

ARM self-hosted

**AWS (EC2 / Lightsail / free tier)** also works well. A community video walkthrough is available at [x.com/techfrenAJ/status/2014934471095812547](https://x.com/techfrenAJ/status/2014934471095812547) (community resource — may become unavailable).

## [​](#how-cloud-setups-work)How cloud setups work

* The **Gateway runs on the VPS** and owns state + workspace.
* You connect from your laptop or phone via the **Control UI** or **Tailscale/SSH**.
* Treat the VPS as the source of truth and **back up** the state + workspace regularly.
* Secure default: keep the Gateway on loopback and access it via SSH tunnel or Tailscale Serve. If you bind to `lan` or `tailnet`, require `gateway.auth.token` or `gateway.auth.password`.

Related pages: [Gateway remote access](https://docs.openclaw.ai/gateway/remote), [Platforms hub](https://docs.openclaw.ai/platforms).

## [​](#shared-company-agent-on-a-vps)Shared company agent on a VPS

Running a single agent for a team is a valid setup when every user is in the same trust boundary and the agent is business-only.

* Keep it on a dedicated runtime (VPS/VM/container + dedicated OS user/accounts).
* Do not sign that runtime into personal Apple/Google accounts or personal browser/password-manager profiles.
* If users are adversarial to each other, split by gateway/host/OS user.

Security model details: [Security](https://docs.openclaw.ai/gateway/security).

## [​](#using-nodes-with-a-vps)Using nodes with a VPS

You can keep the Gateway in the cloud and pair **nodes** on your local devices (Mac/iOS/Android/headless). Nodes provide local screen/camera/canvas and `system.run` capabilities while the Gateway stays in the cloud. Docs: [Nodes](https://docs.openclaw.ai/nodes), [Nodes CLI](https://docs.openclaw.ai/cli/nodes).

## [​](#startup-tuning-for-small-vms-and-arm-hosts)Startup tuning for small VMs and ARM hosts

If CLI commands feel slow on low-power VMs (or ARM hosts), enable Node’s module compile cache:

```
grep -q 'NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache' ~/.bashrc || cat >> ~/.bashrc <<'EOF'
export NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache
mkdir -p /var/tmp/openclaw-compile-cache
export OPENCLAW_NO_RESPAWN=1
EOF
source ~/.bashrc
```

* `NODE_COMPILE_CACHE` improves repeated command startup times.
* `OPENCLAW_NO_RESPAWN=1` avoids extra startup overhead from a self-respawn path.
* First command run warms the cache; subsequent runs are faster.
* For Raspberry Pi specifics, see [Raspberry Pi](https://docs.openclaw.ai/install/raspberry-pi).

### [​](#systemd-tuning-checklist-optional)systemd tuning checklist (optional)

For VM hosts using `systemd`, consider:

* Add service env for a stable startup path:

  * `OPENCLAW_NO_RESPAWN=1`
  * `NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache`

* Keep restart behavior explicit:

  * `Restart=always`
  * `RestartSec=2`
  * `TimeoutStartSec=90`

* Prefer SSD-backed disks for state/cache paths to reduce random-I/O cold-start penalties.

Example:

```
sudo systemctl edit openclaw
```

```
[Service]
Environment=OPENCLAW_NO_RESPAWN=1
Environment=NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache
Restart=always
RestartSec=2
TimeoutStartSec=90
```

How `Restart=` policies help automated recovery: [systemd can automate service recovery](https://www.redhat.com/en/blog/systemd-automate-recovery).

----
url: https://docs.openclaw.ai/tools/duckduckgo-search
----

# DuckDuckGo Search - OpenClaw

OpenClaw supports DuckDuckGo as a **key-free** `web_search` provider. No API key or account is required.

## Setup

No API key needed — just set DuckDuckGo as your provider:

## Config

Optional plugin-level settings for region and SafeSearch:

## Tool parameters

| Parameter    | Description                                                |
| ------------ | ---------------------------------------------------------- |
| `query`      | Search query (required)                                    |
| `count`      | Results to return (1-10, default: 5)                       |
| `region`     | DuckDuckGo region code (e.g. `us-en`, `uk-en`, `de-de`)    |
| `safeSearch` | SafeSearch level: `strict`, `moderate` (default), or `off` |

Region and SafeSearch can also be set in plugin config (see above) — tool parameters override config values per-query.

## Notes

----
url: https://docs.openclaw.ai/gateway/openai-http-api
----

# OpenAI Chat Completions - OpenClaw

## [​](#openai-chat-completions-http)OpenAI Chat Completions (HTTP)

OpenClaw’s Gateway can serve a small OpenAI-compatible Chat Completions endpoint. This endpoint is **disabled by default**. Enable it in config first.

* `POST /v1/chat/completions`
* Same port as the Gateway (WS + HTTP multiplex): `http://<gateway-host>:<port>/v1/chat/completions`

Under the hood, requests are executed as a normal Gateway agent run (same codepath as `openclaw agent`), so routing/permissions/config match your Gateway.

## [​](#authentication)Authentication

Uses the Gateway auth configuration. Send a bearer token:

* `Authorization: Bearer <token>`

Notes:

* When `gateway.auth.mode="token"`, use `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`).
* When `gateway.auth.mode="password"`, use `gateway.auth.password` (or `OPENCLAW_GATEWAY_PASSWORD`).
* If `gateway.auth.rateLimit` is configured and too many auth failures occur, the endpoint returns `429` with `Retry-After`.

## [​](#security-boundary-important)Security boundary (important)

Treat this endpoint as a **full operator-access** surface for the gateway instance.

* HTTP bearer auth here is not a narrow per-user scope model.
* A valid Gateway token/password for this endpoint should be treated like an owner/operator credential.
* Requests run through the same control-plane agent path as trusted operator actions.
* There is no separate non-owner/per-user tool boundary on this endpoint; once a caller passes Gateway auth here, OpenClaw treats that caller as a trusted operator for this gateway.
* If the target agent policy allows sensitive tools, this endpoint can use them.
* Keep this endpoint on loopback/tailnet/private ingress only; do not expose it directly to the public internet.

See [Security](https://docs.openclaw.ai/gateway/security) and [Remote access](https://docs.openclaw.ai/gateway/remote).

## [​](#choosing-an-agent)Choosing an agent

No custom headers required: encode the agent id in the OpenAI `model` field:

* `model: "openclaw:<agentId>"` (example: `"openclaw:main"`, `"openclaw:beta"`)
* `model: "agent:<agentId>"` (alias)

Or target a specific OpenClaw agent by header:

* `x-openclaw-agent-id: <agentId>` (default: `main`)

Advanced:

* `x-openclaw-session-key: <sessionKey>` to fully control session routing.

## [​](#enabling-the-endpoint)Enabling the endpoint

Set `gateway.http.endpoints.chatCompletions.enabled` to `true`:

```
{
  gateway: {
    http: {
      endpoints: {
        chatCompletions: { enabled: true },
      },
    },
  },
}
```

## [​](#disabling-the-endpoint)Disabling the endpoint

Set `gateway.http.endpoints.chatCompletions.enabled` to `false`:

```
{
  gateway: {
    http: {
      endpoints: {
        chatCompletions: { enabled: false },
      },
    },
  },
}
```

## [​](#session-behavior)Session behavior

By default the endpoint is **stateless per request** (a new session key is generated each call). If the request includes an OpenAI `user` string, the Gateway derives a stable session key from it, so repeated calls can share an agent session.

## [​](#streaming-sse)Streaming (SSE)

Set `stream: true` to receive Server-Sent Events (SSE):

* `Content-Type: text/event-stream`
* Each event line is `data: <json>`
* Stream ends with `data: [DONE]`

## [​](#examples)Examples

Non-streaming:

```
curl -sS http://127.0.0.1:18789/v1/chat/completions \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'x-openclaw-agent-id: main' \
  -d '{
    "model": "openclaw",
    "messages": [{"role":"user","content":"hi"}]
  }'
```

Streaming:

```
curl -N http://127.0.0.1:18789/v1/chat/completions \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'x-openclaw-agent-id: main' \
  -d '{
    "model": "openclaw",
    "stream": true,
    "messages": [{"role":"user","content":"hi"}]
  }'
```

----
url: https://docs.openclaw.ai/providers/nvidia
----

# NVIDIA - OpenClaw

## [​](#nvidia)NVIDIA

NVIDIA provides an OpenAI-compatible API at `https://integrate.api.nvidia.com/v1` for Nemotron and NeMo models. Authenticate with an API key from [NVIDIA NGC](https://catalog.ngc.nvidia.com/).

## [​](#cli-setup)CLI setup

Export the key once, then run onboarding and set an NVIDIA model:

```
export NVIDIA_API_KEY="nvapi-..."
openclaw onboard --auth-choice skip
openclaw models set nvidia/nvidia/llama-3.1-nemotron-70b-instruct
```

If you still pass `--token`, remember it lands in shell history and `ps` output; prefer the env var when possible.

## [​](#config-snippet)Config snippet

```
{
  env: { NVIDIA_API_KEY: "nvapi-..." },
  models: {
    providers: {
      nvidia: {
        baseUrl: "https://integrate.api.nvidia.com/v1",
        api: "openai-completions",
      },
    },
  },
  agents: {
    defaults: {
      model: { primary: "nvidia/nvidia/llama-3.1-nemotron-70b-instruct" },
    },
  },
}
```

## [​](#model-ids)Model IDs

* `nvidia/llama-3.1-nemotron-70b-instruct` (default)
* `meta/llama-3.3-70b-instruct`
* `nvidia/mistral-nemo-minitron-8b-8k-instruct`

## [​](#notes)Notes

* OpenAI-compatible `/v1` endpoint; use an API key from NVIDIA NGC.
* Provider auto-enables when `NVIDIA_API_KEY` is set; uses static defaults (131,072-token context window, 4,096 max tokens).

----
url: https://docs.openclaw.ai/concepts/agent-loop
----

# Agent Loop - OpenClaw

## [​](#agent-loop-openclaw)Agent Loop (OpenClaw)

An agentic loop is the full “real” run of an agent: intake → context assembly → model inference → tool execution → streaming replies → persistence. It’s the authoritative path that turns a message into actions and a final reply, while keeping session state consistent. In OpenClaw, a loop is a single, serialized run per session that emits lifecycle and stream events as the model thinks, calls tools, and streams output. This doc explains how that authentic loop is wired end-to-end.

## [​](#entry-points)Entry points

* Gateway RPC: `agent` and `agent.wait`.
* CLI: `agent` command.

## [​](#how-it-works-high-level)How it works (high-level)

1. `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately.

2. `agentCommand` runs the agent:

   * resolves model + thinking/verbose defaults
   * loads skills snapshot
   * calls `runEmbeddedPiAgent` (pi-agent-core runtime)
   * emits **lifecycle end/error** if the embedded loop does not emit one

3. `runEmbeddedPiAgent`:

   * serializes runs via per-session + global queues
   * resolves model + auth profile and builds the pi session
   * subscribes to pi events and streams assistant/tool deltas
   * enforces timeout -> aborts run if exceeded
   * returns payloads + usage metadata

4. `subscribeEmbeddedPiSession` bridges pi-agent-core events to OpenClaw `agent` stream:

   * tool events => `stream: "tool"`
   * assistant deltas => `stream: "assistant"`
   * lifecycle events => `stream: "lifecycle"` (`phase: "start" | "end" | "error"`)

5. `agent.wait` uses `waitForAgentJob`:

   * waits for **lifecycle end/error** for `runId`
   * returns `{ status: ok|error|timeout, startedAt, endedAt, error? }`

## [​](#queueing-+-concurrency)Queueing + concurrency

* Runs are serialized per session key (session lane) and optionally through a global lane.
* This prevents tool/session races and keeps session history consistent.
* Messaging channels can choose queue modes (collect/steer/followup) that feed this lane system. See [Command Queue](https://docs.openclaw.ai/concepts/queue).

## [​](#session-+-workspace-preparation)Session + workspace preparation

* Workspace is resolved and created; sandboxed runs may redirect to a sandbox workspace root.
* Skills are loaded (or reused from a snapshot) and injected into env and prompt.
* Bootstrap/context files are resolved and injected into the system prompt report.
* A session write lock is acquired; `SessionManager` is opened and prepared before streaming.

## [​](#prompt-assembly-+-system-prompt)Prompt assembly + system prompt

* System prompt is built from OpenClaw’s base prompt, skills prompt, bootstrap context, and per-run overrides.
* Model-specific limits and compaction reserve tokens are enforced.
* See [System prompt](https://docs.openclaw.ai/concepts/system-prompt) for what the model sees.

## [​](#hook-points-where-you-can-intercept)Hook points (where you can intercept)

OpenClaw has two hook systems:

* **Internal hooks** (Gateway hooks): event-driven scripts for commands and lifecycle events.
* **Plugin hooks**: extension points inside the agent/tool lifecycle and gateway pipeline.

### [​](#internal-hooks-gateway-hooks)Internal hooks (Gateway hooks)

* **`agent:bootstrap`**: runs while building bootstrap files before the system prompt is finalized. Use this to add/remove bootstrap context files.
* **Command hooks**: `/new`, `/reset`, `/stop`, and other command events (see Hooks doc).

See [Hooks](https://docs.openclaw.ai/automation/hooks) for setup and examples.

### [​](#plugin-hooks-agent-+-gateway-lifecycle)Plugin hooks (agent + gateway lifecycle)

These run inside the agent loop or gateway pipeline:

* **`before_model_resolve`**: runs pre-session (no `messages`) to deterministically override provider/model before model resolution.
* **`before_prompt_build`**: runs after session load (with `messages`) to inject `prependContext`, `systemPrompt`, `prependSystemContext`, or `appendSystemContext` before prompt submission. Use `prependContext` for per-turn dynamic text and system-context fields for stable guidance that should sit in system prompt space.
* **`before_agent_start`**: legacy compatibility hook that may run in either phase; prefer the explicit hooks above.
* **`agent_end`**: inspect the final message list and run metadata after completion.
* **`before_compaction` / `after_compaction`**: observe or annotate compaction cycles.
* **`before_tool_call` / `after_tool_call`**: intercept tool params/results.
* **`tool_result_persist`**: synchronously transform tool results before they are written to the session transcript.
* **`message_received` / `message_sending` / `message_sent`**: inbound + outbound message hooks.
* **`session_start` / `session_end`**: session lifecycle boundaries.
* **`gateway_start` / `gateway_stop`**: gateway lifecycle events.

See [Plugin hooks](https://docs.openclaw.ai/plugins/architecture#provider-runtime-hooks) for the hook API and registration details.

## [​](#streaming-+-partial-replies)Streaming + partial replies

* Assistant deltas are streamed from pi-agent-core and emitted as `assistant` events.
* Block streaming can emit partial replies either on `text_end` or `message_end`.
* Reasoning streaming can be emitted as a separate stream or as block replies.
* See [Streaming](https://docs.openclaw.ai/concepts/streaming) for chunking and block reply behavior.

## [​](#tool-execution-+-messaging-tools)Tool execution + messaging tools

* Tool start/update/end events are emitted on the `tool` stream.
* Tool results are sanitized for size and image payloads before logging/emitting.
* Messaging tool sends are tracked to suppress duplicate assistant confirmations.

## [​](#reply-shaping-+-suppression)Reply shaping + suppression

* Final payloads are assembled from:

  * assistant text (and optional reasoning)
  * inline tool summaries (when verbose + allowed)
  * assistant error text when the model errors

* `NO_REPLY` is treated as a silent token and filtered from outgoing payloads.

* Messaging tool duplicates are removed from the final payload list.

* If no renderable payloads remain and a tool errored, a fallback tool error reply is emitted (unless a messaging tool already sent a user-visible reply).

## [​](#compaction-+-retries)Compaction + retries

* Auto-compaction emits `compaction` stream events and can trigger a retry.
* On retry, in-memory buffers and tool summaries are reset to avoid duplicate output.
* See [Compaction](https://docs.openclaw.ai/concepts/compaction) for the compaction pipeline.

## [​](#event-streams-today)Event streams (today)

* `lifecycle`: emitted by `subscribeEmbeddedPiSession` (and as a fallback by `agentCommand`)
* `assistant`: streamed deltas from pi-agent-core
* `tool`: streamed tool events from pi-agent-core

## [​](#chat-channel-handling)Chat channel handling

* Assistant deltas are buffered into chat `delta` messages.
* A chat `final` is emitted on **lifecycle end/error**.

## [​](#timeouts)Timeouts

* `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
* Agent runtime: `agents.defaults.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.

## [​](#where-things-can-end-early)Where things can end early

* Agent timeout (abort)
* AbortSignal (cancel)
* Gateway disconnect or RPC timeout
* `agent.wait` timeout (wait-only, does not stop agent)

----
url: https://docs.openclaw.ai/gateway/protocol
----

# Gateway Protocol - OpenClaw

## Gateway protocol (WebSocket)

The Gateway WS protocol is the **single control plane + node transport** for OpenClaw. All clients (CLI, web UI, macOS app, iOS/Android nodes, headless nodes) connect over WebSocket and declare their **role** + **scope** at handshake time.

## Transport

## Handshake (connect)

Gateway → Client (pre-connect challenge):

Client → Gateway:

```
{
  "type": "req",
  "id": "…",
  "method": "connect",
  "params": {
    "minProtocol": 3,
    "maxProtocol": 3,
    "client": {
      "id": "cli",
      "version": "1.2.3",
      "platform": "macos",
      "mode": "operator"
    },
    "role": "operator",
    "scopes": ["operator.read", "operator.write"],
    "caps": [],
    "commands": [],
    "permissions": {},
    "auth": { "token": "…" },
    "locale": "en-US",
    "userAgent": "openclaw-cli/1.2.3",
    "device": {
      "id": "device_fingerprint",
      "publicKey": "…",
      "signature": "…",
      "signedAt": 1737264000000,
      "nonce": "…"
    }
  }
}
```

Gateway → Client:

When a device token is issued, `hello-ok` also includes:

### Node example

```
{
  "type": "req",
  "id": "…",
  "method": "connect",
  "params": {
    "minProtocol": 3,
    "maxProtocol": 3,
    "client": {
      "id": "ios-node",
      "version": "1.2.3",
      "platform": "ios",
      "mode": "node"
    },
    "role": "node",
    "scopes": [],
    "caps": ["camera", "canvas", "screen", "location", "voice"],
    "commands": ["camera.snap", "canvas.navigate", "screen.record", "location.get"],
    "permissions": { "camera.capture": true, "screen.record": false },
    "auth": { "token": "…" },
    "locale": "en-US",
    "userAgent": "openclaw-ios/1.2.3",
    "device": {
      "id": "device_fingerprint",
      "publicKey": "…",
      "signature": "…",
      "signedAt": 1737264000000,
      "nonce": "…"
    }
  }
}
```

## Framing

* **Request**: `{type:"req", id, method, params}`
* **Response**: `{type:"res", id, ok, payload|error}`
* **Event**: `{type:"event", event, payload, seq?, stateVersion?}`

Side-effecting methods require **idempotency keys** (see schema).

## Roles + scopes

### Roles

### Scopes (operator)

Common scopes:

Method scope is only the first gate. Some slash commands reached through `chat.send` apply stricter command-level checks on top. For example, persistent `/config set` and `/config unset` writes require `operator.admin`.

### Caps/commands/permissions (node)

Nodes declare capability claims at connect time:

The Gateway treats these as **claims** and enforces server-side allowlists.

## Presence

### Node helper methods

### Operator helper methods

## Exec approvals

## Versioning

## Auth

## Device identity + pairing

### Device auth migration diagnostics

For legacy clients that still use pre-challenge signing behavior, `connect` now returns `DEVICE_AUTH_*` detail codes under `error.details.code` with a stable `error.details.reason`. Common migration failures:

| Message                     | details.code                     | details.reason           | Meaning                                            |
| --------------------------- | -------------------------------- | ------------------------ | -------------------------------------------------- |
| `device nonce required`     | `DEVICE_AUTH_NONCE_REQUIRED`     | `device-nonce-missing`   | Client omitted `device.nonce` (or sent blank).     |
| `device nonce mismatch`     | `DEVICE_AUTH_NONCE_MISMATCH`     | `device-nonce-mismatch`  | Client signed with a stale/wrong nonce.            |
| `device signature invalid`  | `DEVICE_AUTH_SIGNATURE_INVALID`  | `device-signature`       | Signature payload does not match v2 payload.       |
| `device signature expired`  | `DEVICE_AUTH_SIGNATURE_EXPIRED`  | `device-signature-stale` | Signed timestamp is outside allowed skew.          |
| `device identity mismatch`  | `DEVICE_AUTH_DEVICE_ID_MISMATCH` | `device-id-mismatch`     | `device.id` does not match public key fingerprint. |
| `device public key invalid` | `DEVICE_AUTH_PUBLIC_KEY_INVALID` | `device-public-key`      | Public key format/canonicalization failed.         |

Migration target:

## TLS + pinning

## Scope

This protocol exposes the **full gateway API** (status, channels, models, chat, agent, sessions, nodes, approvals, etc.). The exact surface is defined by the TypeBox schemas in `src/gateway/protocol/schema.ts`.

----
url: https://docs.openclaw.ai/web/webchat
----

# WebChat - OpenClaw

## [​](#webchat-gateway-websocket-ui)WebChat (Gateway WebSocket UI)

Status: the macOS/iOS SwiftUI chat UI talks directly to the Gateway WebSocket.

## [​](#what-it-is)What it is

* A native chat UI for the gateway (no embedded browser and no local static server).
* Uses the same sessions and routing rules as other channels.
* Deterministic routing: replies always go back to WebChat.

## [​](#quick-start)Quick start

1. Start the gateway.
2. Open the WebChat UI (macOS/iOS app) or the Control UI chat tab.
3. Ensure gateway auth is configured (required by default, even on loopback).

## [​](#how-it-works-behavior)How it works (behavior)

* The UI connects to the Gateway WebSocket and uses `chat.history`, `chat.send`, and `chat.inject`.
* `chat.history` is bounded for stability: Gateway may truncate long text fields, omit heavy metadata, and replace oversized entries with `[chat.history omitted: message too large]`.
* `chat.inject` appends an assistant note directly to the transcript and broadcasts it to the UI (no agent run).
* Aborted runs can keep partial assistant output visible in the UI.
* Gateway persists aborted partial assistant text into transcript history when buffered output exists, and marks those entries with abort metadata.
* History is always fetched from the gateway (no local file watching).
* If the gateway is unreachable, WebChat is read-only.

## [​](#control-ui-agents-tools-panel)Control UI agents tools panel

* The Control UI `/agents` Tools panel fetches a runtime catalog via `tools.catalog` and labels each tool as `core` or `plugin:<id>` (plus `optional` for optional plugin tools).
* If `tools.catalog` is unavailable, the panel falls back to a built-in static list.
* The panel edits profile and override config, but effective runtime access still follows policy precedence (`allow`/`deny`, per-agent and provider/channel overrides).

## [​](#remote-use)Remote use

* Remote mode tunnels the gateway WebSocket over SSH/Tailscale.
* You do not need to run a separate WebChat server.

## [​](#configuration-reference-webchat)Configuration reference (WebChat)

Full configuration: [Configuration](https://docs.openclaw.ai/gateway/configuration) Channel options:

* No dedicated `webchat.*` block. WebChat uses the gateway endpoint + auth settings below.

Related global options:

* `gateway.port`, `gateway.bind`: WebSocket host/port.
* `gateway.auth.mode`, `gateway.auth.token`, `gateway.auth.password`: WebSocket auth (token/password).
* `gateway.auth.mode: "trusted-proxy"`: reverse-proxy auth for browser clients (see [Trusted Proxy Auth](https://docs.openclaw.ai/gateway/trusted-proxy-auth)).
* `gateway.remote.url`, `gateway.remote.token`, `gateway.remote.password`: remote gateway target.
* `session.*`: session storage and main key defaults.

----
url: https://docs.openclaw.ai/reference/session-management-compaction
----

# Session Management Deep Dive - OpenClaw

## Session Management & Compaction (Deep Dive)

This document explains how OpenClaw manages sessions end-to-end:

If you want a higher-level overview first, start with:

***

## Source of truth: the Gateway

OpenClaw is designed around a single **Gateway process** that owns session state.

***

## Two persistence layers

OpenClaw persists sessions in two layers:

1. **Session store (`sessions.json`)**
2. **Transcript (`<sessionId>.jsonl`)**

***

## On-disk locations

Per agent, on the Gateway host:

OpenClaw resolves these via `src/config/sessions.ts`.

***

## Store maintenance and disk controls

Session persistence has automatic maintenance controls (`session.maintenance`) for `sessions.json` and transcript artifacts:

Enforcement order for disk budget cleanup (`mode: "enforce"`):

1. Remove oldest archived or orphan transcript artifacts first.
2. If still above the target, evict oldest session entries and their transcript files.
3. Keep going until usage is at or below `highWaterBytes`.

In `mode: "warn"`, OpenClaw reports potential evictions but does not mutate the store/files. Run maintenance on demand:

***

## Cron sessions and run logs

Isolated cron runs also create session entries/transcripts, and they have dedicated retention controls:

***

## Session keys (`sessionKey`)

A `sessionKey` identifies *which conversation bucket* you’re in (routing + isolation). Common patterns:

The canonical rules are documented at [/concepts/session](https://docs.openclaw.ai/concepts/session).

***

## Session ids (`sessionId`)

Each `sessionKey` points at a current `sessionId` (the transcript file that continues the conversation). Rules of thumb:

Implementation detail: the decision happens in `initSessionState()` in `src/auto-reply/reply/session.ts`.

***

## Session store schema (`sessions.json`)

The store’s value type is `SessionEntry` in `src/config/sessions.ts`. Key fields (not exhaustive):

The store is safe to edit, but the Gateway is the authority: it may rewrite or rehydrate entries as sessions run.

***

## Transcript structure (`*.jsonl`)

Transcripts are managed by `@mariozechner/pi-coding-agent`’s `SessionManager`. The file is JSONL:

Notable entry types:

OpenClaw intentionally does **not** “fix up” transcripts; the Gateway uses `SessionManager` to read/write them.

***

## Context windows vs tracked tokens

Two different concepts matter:

1. **Model context window**: hard cap per model (tokens visible to the model)
2. **Session store counters**: rolling stats written into `sessions.json` (used for /status and dashboards)

If you’re tuning limits:

For more, see [/token-use](https://docs.openclaw.ai/reference/token-use).

***

## Compaction: what it is

Compaction summarizes older conversation into a persisted `compaction` entry in the transcript and keeps recent messages intact. After compaction, future turns see:

Compaction is **persistent** (unlike session pruning). See [/concepts/session-pruning](https://docs.openclaw.ai/concepts/session-pruning).

***

## When auto-compaction happens (Pi runtime)

In the embedded Pi agent, auto-compaction triggers in two cases:

1. **Overflow recovery**: the model returns a context overflow error → compact → retry.
2. **Threshold maintenance**: after a successful turn, when:

`contextTokens > contextWindow - reserveTokens` Where:

These are Pi runtime semantics (OpenClaw consumes the events, but Pi decides when to compact).

***

## Compaction settings (`reserveTokens`, `keepRecentTokens`)

Pi’s compaction settings live in Pi settings:

OpenClaw also enforces a safety floor for embedded runs:

Why: leave enough headroom for multi-turn “housekeeping” (like memory writes) before compaction becomes unavoidable. Implementation: `ensurePiCompactionReserveTokens()` in `src/agents/pi-settings.ts` (called from `src/agents/pi-embedded-runner.ts`).

***

## User-visible surfaces

You can observe compaction and session state via:

***

## Silent housekeeping (`NO_REPLY`)

OpenClaw supports “silent” turns for background tasks where the user should not see intermediate output. Convention:

As of `2026.1.10`, OpenClaw also suppresses **draft/typing streaming** when a partial chunk begins with `NO_REPLY`, so silent operations don’t leak partial output mid-turn.

***

## Pre-compaction “memory flush” (implemented)

Goal: before auto-compaction happens, run a silent agentic turn that writes durable state to disk (e.g. `memory/YYYY-MM-DD.md` in the agent workspace) so compaction can’t erase critical context. OpenClaw uses the **pre-threshold flush** approach:

1. Monitor session context usage.
2. When it crosses a “soft threshold” (below Pi’s compaction threshold), run a silent “write memory now” directive to the agent.
3. Use `NO_REPLY` so the user sees nothing.

Config (`agents.defaults.compaction.memoryFlush`):

Notes:

Pi also exposes a `session_before_compact` hook in the extension API, but OpenClaw’s flush logic lives on the Gateway side today.

***

## Troubleshooting checklist

----
url: https://docs.openclaw.ai/start/hubs
----

[Skip to main content](#content-area)

[OpenClaw home page](/)

[Get started](/)[Install](/install)[Channels](/channels)[Agents](/concepts/architecture)[Tools & Plugins](/tools)[Models](/providers)[Platforms](/platforms)[Gateway & Ops](/gateway)[Reference](/cli)[Help](/help)

##### Help

* [Help](/help)
* [General Troubleshooting](/help/troubleshooting)
* [FAQ](/help/faq)

##### Community

* [OpenClaw Lore](/start/lore)

##### Environment and debugging

* [Environment Variables](/help/environment)
* [Debugging](/help/debugging)
* [Testing](/help/testing)
* [Scripts](/help/scripts)
* [Node + tsx Crash](/debug/node-issue)
* [Diagnostics Flags](/diagnostics/flags)

##### Compaction internals

* [Session Management Deep Dive](/reference/session-management-compaction)

##### Developer setup

* [Setup](/start/setup)
* [Pi Development Workflow](/pi-dev)

##### Contributing

* [CI Pipeline](/ci)

##### Docs meta

* [Docs Hubs](/start/hubs)
* [Docs directory](/start/docs-directory)

- [Docs hubs](#docs-hubs)
- [Start here](#start-here)
- [Installation + updates](#installation-%2B-updates)
- [Core concepts](#core-concepts)
- [Providers + ingress](#providers-%2B-ingress)
- [Gateway + operations](#gateway-%2B-operations)
- [Tools + automation](#tools-%2B-automation)
- [Nodes, media, voice](#nodes-media-voice)
- [Platforms](#platforms)
- [macOS companion app (advanced)](#macos-companion-app-advanced)
- [Extensions + plugins](#extensions-%2B-plugins)
- [Workspace + templates](#workspace-%2B-templates)
- [Project](#project)
- [Testing + release](#testing-%2B-release)

# [​](#docs-hubs)Docs hubs

If you are new to OpenClaw, start with [Getting Started](/start/getting-started).

Use these hubs to discover every page, including deep dives and reference docs that don’t appear in the left nav.

## [​](#start-here)Start here

* [Index](/)
* [Getting Started](/start/getting-started)
* [Onboarding](/start/onboarding)
* [Onboarding (CLI)](/start/wizard)
* [Setup](/start/setup)
* [Dashboard (local Gateway)](http://127.0.0.1:18789/)
* [Help](/help)
* [Docs directory](/start/docs-directory)
* [Configuration](/gateway/configuration)
* [Configuration examples](/gateway/configuration-examples)
* [OpenClaw assistant](/start/openclaw)
* [Showcase](/start/showcase)
* [Lore](/start/lore)

## [​](#installation-+-updates)Installation + updates

* [Docker](/install/docker)
* [Nix](/install/nix)
* [Updating / rollback](/install/updating)
* [Bun workflow (experimental)](/install/bun)

## [​](#core-concepts)Core concepts

* [Architecture](/concepts/architecture)
* [Features](/concepts/features)
* [Network hub](/network)
* [Agent runtime](/concepts/agent)
* [Agent workspace](/concepts/agent-workspace)
* [Memory](/concepts/memory)
* [Agent loop](/concepts/agent-loop)
* [Streaming + chunking](/concepts/streaming)
* [Multi-agent routing](/concepts/multi-agent)
* [Compaction](/concepts/compaction)
* [Sessions](/concepts/session)
* [Session pruning](/concepts/session-pruning)
* [Session tools](/concepts/session-tool)
* [Queue](/concepts/queue)
* [Slash commands](/tools/slash-commands)
* [RPC adapters](/reference/rpc)
* [TypeBox schemas](/concepts/typebox)
* [Timezone handling](/concepts/timezone)
* [Presence](/concepts/presence)
* [Discovery + transports](/gateway/discovery)
* [Bonjour](/gateway/bonjour)
* [Channel routing](/channels/channel-routing)
* [Groups](/channels/groups)
* [Group messages](/channels/group-messages)
* [Model failover](/concepts/model-failover)
* [OAuth](/concepts/oauth)

## [​](#providers-+-ingress)Providers + ingress

* [Chat channels hub](/channels)
* [Model providers hub](/providers/models)
* [WhatsApp](/channels/whatsapp)
* [Telegram](/channels/telegram)
* [Slack](/channels/slack)
* [Discord](/channels/discord)
* [Mattermost](/channels/mattermost) (plugin)
* [Signal](/channels/signal)
* [BlueBubbles (iMessage)](/channels/bluebubbles)
* [iMessage (legacy)](/channels/imessage)
* [Location parsing](/channels/location)
* [WebChat](/web/webchat)
* [Webhooks](/automation/webhook)
* [Gmail Pub/Sub](/automation/gmail-pubsub)

## [​](#gateway-+-operations)Gateway + operations

* [Gateway runbook](/gateway)
* [Network model](/gateway/network-model)
* [Gateway pairing](/gateway/pairing)
* [Gateway lock](/gateway/gateway-lock)
* [Background process](/gateway/background-process)
* [Health](/gateway/health)
* [Heartbeat](/gateway/heartbeat)
* [Doctor](/gateway/doctor)
* [Logging](/gateway/logging)
* [Sandboxing](/gateway/sandboxing)
* [Dashboard](/web/dashboard)
* [Control UI](/web/control-ui)
* [Remote access](/gateway/remote)
* [Remote gateway README](/gateway/remote-gateway-readme)
* [Tailscale](/gateway/tailscale)
* [Security](/gateway/security)
* [Troubleshooting](/gateway/troubleshooting)

## [​](#tools-+-automation)Tools + automation

* [Tools surface](/tools)
* [OpenProse](/prose)
* [CLI reference](/cli)
* [Exec tool](/tools/exec)
* [PDF tool](/tools/pdf)
* [Elevated mode](/tools/elevated)
* [Cron jobs](/automation/cron-jobs)
* [Cron vs Heartbeat](/automation/cron-vs-heartbeat)
* [Thinking + verbose](/tools/thinking)
* [Models](/concepts/models)
* [Sub-agents](/tools/subagents)
* [Agent send CLI](/tools/agent-send)
* [Terminal UI](/web/tui)
* [Browser control](/tools/browser)
* [Browser (Linux troubleshooting)](/tools/browser-linux-troubleshooting)
* [Polls](/automation/poll)

## [​](#nodes-media-voice)Nodes, media, voice

* [Nodes overview](/nodes)
* [Camera](/nodes/camera)
* [Images](/nodes/images)
* [Audio](/nodes/audio)
* [Location command](/nodes/location-command)
* [Voice wake](/nodes/voicewake)
* [Talk mode](/nodes/talk)

## [​](#platforms)Platforms

* [Platforms overview](/platforms)
* [macOS](/platforms/macos)
* [iOS](/platforms/ios)
* [Android](/platforms/android)
* [Windows (WSL2)](/platforms/windows)
* [Linux](/platforms/linux)
* [Web surfaces](/web)

## [​](#macos-companion-app-advanced)macOS companion app (advanced)

* [macOS dev setup](/platforms/mac/dev-setup)
* [macOS menu bar](/platforms/mac/menu-bar)
* [macOS voice wake](/platforms/mac/voicewake)
* [macOS voice overlay](/platforms/mac/voice-overlay)
* [macOS WebChat](/platforms/mac/webchat)
* [macOS Canvas](/platforms/mac/canvas)
* [macOS child process](/platforms/mac/child-process)
* [macOS health](/platforms/mac/health)
* [macOS icon](/platforms/mac/icon)
* [macOS logging](/platforms/mac/logging)
* [macOS permissions](/platforms/mac/permissions)
* [macOS remote](/platforms/mac/remote)
* [macOS signing](/platforms/mac/signing)
* [macOS gateway (launchd)](/platforms/mac/bundled-gateway)
* [macOS XPC](/platforms/mac/xpc)
* [macOS skills](/platforms/mac/skills)
* [macOS Peekaboo](/platforms/mac/peekaboo)

## [​](#extensions-+-plugins)Extensions + plugins

* [Plugins overview](/tools/plugin)
* [Building plugins](/plugins/building-plugins)
* [Plugin manifest](/plugins/manifest)
* [Agent tools](/plugins/building-plugins#registering-agent-tools)
* [Plugin bundles](/plugins/bundles)
* [Community plugins](/plugins/community)
* [Capability cookbook](/tools/capability-cookbook)
* [Voice call plugin](/plugins/voice-call)
* [Zalo user plugin](/plugins/zalouser)

## [​](#workspace-+-templates)Workspace + templates

* [Skills](/tools/skills)
* [ClawHub](/tools/clawhub)
* [Skills config](/tools/skills-config)
* [Default AGENTS](/reference/AGENTS.default)
* [Templates: AGENTS](/reference/templates/AGENTS)
* [Templates: BOOTSTRAP](/reference/templates/BOOTSTRAP)
* [Templates: HEARTBEAT](/reference/templates/HEARTBEAT)
* [Templates: IDENTITY](/reference/templates/IDENTITY)
* [Templates: SOUL](/reference/templates/SOUL)
* [Templates: TOOLS](/reference/templates/TOOLS)
* [Templates: USER](/reference/templates/USER)

## [​](#project)Project

* [Credits](/reference/credits)

## [​](#testing-+-release)Testing + release

* [Testing](/reference/test)
* [Release policy](/reference/RELEASING)
* [Device models](/reference/device-models)

[CI Pipeline](/ci)[Docs directory](/start/docs-directory)

⌘I

----
url: https://docs.openclaw.ai/platforms/mac/child-process
----

# Gateway Lifecycle - OpenClaw

## [​](#gateway-lifecycle-on-macos)Gateway lifecycle on macOS

The macOS app **manages the Gateway via launchd** by default and does not spawn the Gateway as a child process. It first tries to attach to an already‑running Gateway on the configured port; if none is reachable, it enables the launchd service via the external `openclaw` CLI (no embedded runtime). This gives you reliable auto‑start at login and restart on crashes. Child‑process mode (Gateway spawned directly by the app) is **not in use** today. If you need tighter coupling to the UI, run the Gateway manually in a terminal.

## [​](#default-behavior-launchd)Default behavior (launchd)

* The app installs a per‑user LaunchAgent labeled `ai.openclaw.gateway` (or `ai.openclaw.<profile>` when using `--profile`/`OPENCLAW_PROFILE`; legacy `com.openclaw.*` is supported).
* When Local mode is enabled, the app ensures the LaunchAgent is loaded and starts the Gateway if needed.
* Logs are written to the launchd gateway log path (visible in Debug Settings).

Common commands:

```
launchctl kickstart -k gui/$UID/ai.openclaw.gateway
launchctl bootout gui/$UID/ai.openclaw.gateway
```

Replace the label with `ai.openclaw.<profile>` when running a named profile.

## [​](#unsigned-dev-builds)Unsigned dev builds

`scripts/restart-mac.sh --no-sign` is for fast local builds when you don’t have signing keys. To prevent launchd from pointing at an unsigned relay binary, it:

* Writes `~/.openclaw/disable-launchagent`.

Signed runs of `scripts/restart-mac.sh` clear this override if the marker is present. To reset manually:

```
rm ~/.openclaw/disable-launchagent
```

## [​](#attach-only-mode)Attach-only mode

To force the macOS app to **never install or manage launchd**, launch it with `--attach-only` (or `--no-launchd`). This sets `~/.openclaw/disable-launchagent`, so the app only attaches to an already running Gateway. You can toggle the same behavior in Debug Settings.

## [​](#remote-mode)Remote mode

Remote mode never starts a local Gateway. The app uses an SSH tunnel to the remote host and connects over that tunnel.

## [​](#why-we-prefer-launchd)Why we prefer launchd

* Auto‑start at login.
* Built‑in restart/KeepAlive semantics.
* Predictable logs and supervision.

If a true child‑process mode is ever needed again, it should be documented as a separate, explicit dev‑only mode.

----
url: https://docs.openclaw.ai/tools/multi-agent-sandbox-tools
----

# Multi-Agent Sandbox & Tools - OpenClaw

## Multi-Agent Sandbox & Tools Configuration

Each agent in a multi-agent setup can override the global sandbox and tool policy. This page covers per-agent configuration, precedence rules, and examples.

Auth is per-agent: each agent reads from its own `agentDir` auth store at `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`. Credentials are **not** shared between agents. Never reuse `agentDir` across agents. If you want to share creds, copy `auth-profiles.json` into the other agent’s `agentDir`.

***

## Configuration Examples

### Example 1: Personal + Restricted Family Agent

```
{
  "agents": {
    "list": [
      {
        "id": "main",
        "default": true,
        "name": "Personal Assistant",
        "workspace": "~/.openclaw/workspace",
        "sandbox": { "mode": "off" }
      },
      {
        "id": "family",
        "name": "Family Bot",
        "workspace": "~/.openclaw/workspace-family",
        "sandbox": {
          "mode": "all",
          "scope": "agent"
        },
        "tools": {
          "allow": ["read"],
          "deny": ["exec", "write", "edit", "apply_patch", "process", "browser"]
        }
      }
    ]
  },
  "bindings": [
    {
      "agentId": "family",
      "match": {
        "provider": "whatsapp",
        "accountId": "*",
        "peer": {
          "kind": "group",
          "id": "120363424282127706@g.us"
        }
      }
    }
  ]
}
```

**Result:**

***

### Example 2: Work Agent with Shared Sandbox

```
{
  "agents": {
    "list": [
      {
        "id": "personal",
        "workspace": "~/.openclaw/workspace-personal",
        "sandbox": { "mode": "off" }
      },
      {
        "id": "work",
        "workspace": "~/.openclaw/workspace-work",
        "sandbox": {
          "mode": "all",
          "scope": "shared",
          "workspaceRoot": "/tmp/work-sandboxes"
        },
        "tools": {
          "allow": ["read", "write", "apply_patch", "exec"],
          "deny": ["browser", "gateway", "discord"]
        }
      }
    ]
  }
}
```

***

### Example 2b: Global coding profile + messaging-only agent

**Result:**

***

### Example 3: Different Sandbox Modes per Agent

```
{
  "agents": {
    "defaults": {
      "sandbox": {
        "mode": "non-main", // Global default
        "scope": "session"
      }
    },
    "list": [
      {
        "id": "main",
        "workspace": "~/.openclaw/workspace",
        "sandbox": {
          "mode": "off" // Override: main never sandboxed
        }
      },
      {
        "id": "public",
        "workspace": "~/.openclaw/workspace-public",
        "sandbox": {
          "mode": "all", // Override: public always sandboxed
          "scope": "agent"
        },
        "tools": {
          "allow": ["read"],
          "deny": ["exec", "write", "edit", "apply_patch"]
        }
      }
    ]
  }
}
```

***

## Configuration Precedence

When both global (`agents.defaults.*`) and agent-specific (`agents.list[].*`) configs exist:

### Sandbox Config

Agent-specific settings override global:

**Notes:**

### Tool Restrictions

The filtering order is:

1. **Tool profile** (`tools.profile` or `agents.list[].tools.profile`)
2. **Provider tool profile** (`tools.byProvider[provider].profile` or `agents.list[].tools.byProvider[provider].profile`)
3. **Global tool policy** (`tools.allow` / `tools.deny`)
4. **Provider tool policy** (`tools.byProvider[provider].allow/deny`)
5. **Agent-specific tool policy** (`agents.list[].tools.allow/deny`)
6. **Agent provider policy** (`agents.list[].tools.byProvider[provider].allow/deny`)
7. **Sandbox tool policy** (`tools.sandbox.tools` or `agents.list[].tools.sandbox.tools`)
8. **Subagent tool policy** (`tools.subagents.tools`, if applicable)

Each level can further restrict tools, but cannot grant back denied tools from earlier levels. If `agents.list[].tools.sandbox.tools` is set, it replaces `tools.sandbox.tools` for that agent. If `agents.list[].tools.profile` is set, it overrides `tools.profile` for that agent. Provider tool keys accept either `provider` (e.g. `google-antigravity`) or `provider/model` (e.g. `openai/gpt-5.2`). Tool policies support `group:*` shorthands that expand to multiple tools. See [Tool groups](https://docs.openclaw.ai/gateway/sandbox-vs-tool-policy-vs-elevated#tool-groups-shorthands) for the full list. Per-agent elevated overrides (`agents.list[].tools.elevated`) can further restrict elevated exec for specific agents. See [Elevated Mode](https://docs.openclaw.ai/tools/elevated) for details.

***

## Migration from Single Agent

**Before (single agent):**

**After (multi-agent with different profiles):**

Legacy `agent.*` configs are migrated by `openclaw doctor`; prefer `agents.defaults` + `agents.list` going forward.

***

## Tool Restriction Examples

### Read-only Agent

### Safe Execution Agent (no file modifications)

### Communication-only Agent

```
{
  "tools": {
    "sessions": { "visibility": "tree" },
    "allow": ["sessions_list", "sessions_send", "sessions_history", "session_status"],
    "deny": ["exec", "write", "edit", "apply_patch", "read", "browser"]
  }
}
```

***

## Common Pitfall: “non-main”

`agents.defaults.sandbox.mode: "non-main"` is based on `session.mainKey` (default `"main"`), not the agent id. Group/channel sessions always get their own keys, so they are treated as non-main and will be sandboxed. If you want an agent to never sandbox, set `agents.list[].sandbox.mode: "off"`.

***

## Testing

After configuring multi-agent sandbox and tools:

1. **Check agent resolution:**
2. **Verify sandbox containers:**
3. **Test tool restrictions:**
4. **Monitor logs:**

***

## Troubleshooting

### Agent not sandboxed despite `mode: "all"`

### Tools still available despite deny list

### Container not isolated per agent

***

## See also

----
url: https://docs.openclaw.ai/tools/browser-login
----

# Browser Login - OpenClaw

## [​](#browser-login-+-x/twitter-posting)Browser login + X/Twitter posting

## [​](#manual-login-recommended)Manual login (recommended)

When a site requires login, **sign in manually** in the **host** browser profile (the openclaw browser). Do **not** give the model your credentials. Automated logins often trigger anti‑bot defenses and can lock the account. Back to the main browser docs: [Browser](https://docs.openclaw.ai/tools/browser).

## [​](#which-chrome-profile-is-used)Which Chrome profile is used?

OpenClaw controls a **dedicated Chrome profile** (named `openclaw`, orange‑tinted UI). This is separate from your daily browser profile. For agent browser tool calls:

* Default choice: the agent should use its isolated `openclaw` browser.
* Use `profile="user"` only when existing logged-in sessions matter and the user is at the computer to click/approve any attach prompt.
* If you have multiple user-browser profiles, specify the profile explicitly instead of guessing.

Two easy ways to access it:

1. **Ask the agent to open the browser** and then log in yourself.
2. **Open it via CLI**:

```
openclaw browser start
openclaw browser open https://x.com
```

If you have multiple profiles, pass `--browser-profile <name>` (the default is `openclaw`).

## [​](#x/twitter-recommended-flow)X/Twitter: recommended flow

* **Read/search/threads:** use the **host** browser (manual login).
* **Post updates:** use the **host** browser (manual login).

## [​](#sandboxing-+-host-browser-access)Sandboxing + host browser access

Sandboxed browser sessions are **more likely** to trigger bot detection. For X/Twitter (and other strict sites), prefer the **host** browser. If the agent is sandboxed, the browser tool defaults to the sandbox. To allow host control:

```
{
  agents: {
    defaults: {
      sandbox: {
        mode: "non-main",
        browser: {
          allowHostControl: true,
        },
      },
    },
  },
}
```

Then target the host browser:

```
openclaw browser open https://x.com --browser-profile openclaw --target host
```

Or disable sandboxing for the agent that posts updates.

----
url: https://docs.openclaw.ai/providers/ollama
----

# Ollama - OpenClaw

Ollama is a local LLM runtime that makes it easy to run open-source models on your machine. OpenClaw integrates with Ollama’s native API (`/api/chat`), supports streaming and tool calling, and can auto-discover local Ollama models when you opt in with `OLLAMA_API_KEY` (or an auth profile) and do not define an explicit `models.providers.ollama` entry.

## Quick start

### Onboarding (recommended)

The fastest way to set up Ollama is through onboarding:

Select **Ollama** from the provider list. Onboarding will:

1. Ask for the Ollama base URL where your instance can be reached (default `http://127.0.0.1:11434`).
2. Let you choose **Cloud + Local** (cloud models and local models) or **Local** (local models only).
3. Open a browser sign-in flow if you choose **Cloud + Local** and are not signed in to ollama.com.
4. Discover available models and suggest defaults.
5. Auto-pull the selected model if it is not available locally.

Non-interactive mode is also supported:

Optionally specify a custom base URL or model:

### Manual setup

1. Install Ollama: <https://ollama.com/download>
2. Pull a local model if you want local inference:

3) If you want cloud models too, sign in:

4. Run onboarding and choose `Ollama`:

OpenClaw currently suggests:

5. If you prefer manual setup, enable Ollama for OpenClaw directly (any value works; Ollama doesn’t require a real key):

6) Inspect or switch models:

7. Or set the default in config:

## Model discovery (implicit provider)

When you set `OLLAMA_API_KEY` (or an auth profile) and **do not** define `models.providers.ollama`, OpenClaw discovers models from the local Ollama instance at `http://127.0.0.1:11434`:

This avoids manual model entries while keeping the catalog aligned with the local Ollama instance. To see what models are available:

To add a new model, simply pull it with Ollama:

The new model will be automatically discovered and available to use. If you set `models.providers.ollama` explicitly, auto-discovery is skipped and you must define models manually (see below).

## Configuration

### Basic setup (implicit discovery)

The simplest way to enable Ollama is via environment variable:

### Explicit setup (manual models)

Use explicit config when:

```
{
  models: {
    providers: {
      ollama: {
        baseUrl: "http://ollama-host:11434",
        apiKey: "ollama-local",
        api: "ollama",
        models: [
          {
            id: "gpt-oss:20b",
            name: "GPT-OSS 20B",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 8192,
            maxTokens: 8192 * 10
          }
        ]
      }
    }
  }
}
```

If `OLLAMA_API_KEY` is set, you can omit `apiKey` in the provider entry and OpenClaw will fill it for availability checks.

### Custom base URL (explicit config)

If Ollama is running on a different host or port (explicit config disables auto-discovery, so define models manually):

### Model selection

Once configured, all your Ollama models are available:

## Cloud models

Cloud models let you run cloud-hosted models (for example `kimi-k2.5:cloud`, `minimax-m2.5:cloud`, `glm-5:cloud`) alongside your local models. To use cloud models, select **Cloud + Local** mode during setup. The wizard checks whether you are signed in and opens a browser sign-in flow when needed. If authentication cannot be verified, the wizard falls back to local model defaults. You can also sign in directly at [ollama.com/signin](https://ollama.com/signin).

## Advanced

### Reasoning models

OpenClaw treats models with names such as `deepseek-r1`, `reasoning`, or `think` as reasoning-capable by default:

### Model Costs

Ollama is free and runs locally, so all model costs are set to $0.

### Streaming Configuration

OpenClaw’s Ollama integration uses the **native Ollama API** (`/api/chat`) by default, which fully supports streaming and tool calling simultaneously. No special configuration is needed.

#### Legacy OpenAI-Compatible Mode

If you need to use the OpenAI-compatible endpoint instead (e.g., behind a proxy that only supports OpenAI format), set `api: "openai-completions"` explicitly:

This mode may not support streaming + tool calling simultaneously. You may need to disable streaming with `params: { streaming: false }` in model config. When `api: "openai-completions"` is used with Ollama, OpenClaw injects `options.num_ctx` by default so Ollama does not silently fall back to a 4096 context window. If your proxy/upstream rejects unknown `options` fields, disable this behavior:

### Context windows

For auto-discovered models, OpenClaw uses the context window reported by Ollama when available, otherwise it falls back to the default Ollama context window used by OpenClaw. You can override `contextWindow` and `maxTokens` in explicit provider config.

## Troubleshooting

### Ollama not detected

Make sure Ollama is running and that you set `OLLAMA_API_KEY` (or an auth profile), and that you did **not** define an explicit `models.providers.ollama` entry:

And that the API is accessible:

### No models available

If your model is not listed, either:

To add models:

### Connection refused

Check that Ollama is running on the correct port:

## See Also

----
url: https://docs.openclaw.ai/channels/bluebubbles
----

# BlueBubbles - OpenClaw

## BlueBubbles (macOS REST)

Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **Recommended for iMessage integration** due to its richer API and easier setup compared to the legacy imsg channel.

## Overview

* Runs on macOS via the BlueBubbles helper app ([bluebubbles.app](https://bluebubbles.app/)).
* Recommended/tested: macOS Sequoia (15). macOS Tahoe (26) works; edit is currently broken on Tahoe, and group icon updates may report success but not sync.
* OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
* Incoming messages arrive via webhooks; outgoing replies, typing indicators, read receipts, and tapbacks are REST calls.
* Attachments and stickers are ingested as inbound media (and surfaced to the agent when possible).
* Pairing/allowlist works the same way as other channels (`/channels/pairing` etc) with `channels.bluebubbles.allowFrom` + pairing codes.
* Reactions are surfaced as system events just like Slack/Telegram so agents can “mention” them before replying.
* Advanced features: edit, unsend, reply threading, message effects, group management.

## Quick start

1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
2. In the BlueBubbles config, enable the web API and set a password.
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
5. Start the gateway; it will register the webhook handler and start pairing.

Security note:

## Keeping Messages.app alive (VM / headless setups)

Some macOS VM / always-on setups can end up with Messages.app going “idle” (incoming events stop until the app is opened/foregrounded). A simple workaround is to **poke Messages every 5 minutes** using an AppleScript + LaunchAgent.

### 1) Save the AppleScript

Save this as:

Example script (non-interactive; does not steal focus):

### 2) Install a LaunchAgent

Save this as:

Notes:

Load it:

## Onboarding

BlueBubbles is available in interactive onboarding:

The wizard prompts for:

You can also add BlueBubbles via CLI:

## Access control (DMs + groups)

DMs:

Groups:

### Mention gating (groups)

BlueBubbles supports mention gating for group chats, matching iMessage/WhatsApp behavior:

Per-group configuration:

### Command gating

## Typing + read receipts

## Advanced actions

BlueBubbles supports advanced message actions when enabled in config:

```
{
  channels: {
    bluebubbles: {
      actions: {
        reactions: true, // tapbacks (default: true)
        edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
        unsend: true, // unsend messages (macOS 13+)
        reply: true, // reply threading by message GUID
        sendWithEffect: true, // message effects (slam, loud, etc.)
        renameGroup: true, // rename group chats
        setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
        addParticipant: true, // add participants to groups
        removeParticipant: true, // remove participants from groups
        leaveGroup: true, // leave group chats
        sendAttachment: true, // send attachments/media
      },
    },
  },
}
```

Available actions:

* **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`)
* **edit**: Edit a sent message (`messageId`, `text`)
* **unsend**: Unsend a message (`messageId`)
* **reply**: Reply to a specific message (`messageId`, `text`, `to`)
* **sendWithEffect**: Send with iMessage effect (`text`, `to`, `effectId`)
* **renameGroup**: Rename a group chat (`chatGuid`, `displayName`)
* **setGroupIcon**: Set a group chat’s icon/photo (`chatGuid`, `media`) — flaky on macOS 26 Tahoe (API may return success but the icon does not sync).
* **addParticipant**: Add someone to a group (`chatGuid`, `address`)
* **removeParticipant**: Remove someone from a group (`chatGuid`, `address`)
* **leaveGroup**: Leave a group chat (`chatGuid`)
* **sendAttachment**: Send media/files (`to`, `buffer`, `filename`, `asVoice`)

### Message IDs (short vs full)

OpenClaw may surface *short* message IDs (e.g., `1`, `2`) to save tokens.

Use full IDs for durable automations and storage:

See [Configuration](https://docs.openclaw.ai/gateway/configuration) for template variables.

## Block streaming

Control whether responses are sent as a single message or streamed in blocks:

## Configuration reference

Full configuration: [Configuration](https://docs.openclaw.ai/gateway/configuration) Provider options:

Related global options:

## Addressing / delivery targets

Prefer `chat_guid` for stable routing:

## Security

## Troubleshooting

For general channel workflow reference, see [Channels](https://docs.openclaw.ai/channels) and the [Plugins](https://docs.openclaw.ai/tools/plugin) guide.

----
url: https://docs.openclaw.ai/tools/perplexity-search
----

# Perplexity Search - OpenClaw

OpenClaw supports Perplexity Search API as a `web_search` provider. It returns structured results with `title`, `url`, and `snippet` fields. For compatibility, OpenClaw also supports legacy Perplexity Sonar/OpenRouter setups. If you use `OPENROUTER_API_KEY`, an `sk-or-...` key in `plugins.entries.perplexity.config.webSearch.apiKey`, or set `plugins.entries.perplexity.config.webSearch.baseUrl` / `model`, the provider switches to the chat-completions path and returns AI-synthesized answers with citations instead of structured Search API results.

## Getting a Perplexity API key

1. Create a Perplexity account at [perplexity.ai/settings/api](https://www.perplexity.ai/settings/api)
2. Generate an API key in the dashboard
3. Store the key in config or set `PERPLEXITY_API_KEY` in the Gateway environment.

## OpenRouter compatibility

If you were already using OpenRouter for Perplexity Sonar, keep `provider: "perplexity"` and set `OPENROUTER_API_KEY` in the Gateway environment, or store an `sk-or-...` key in `plugins.entries.perplexity.config.webSearch.apiKey`. Optional compatibility controls:

## Config examples

### Native Perplexity Search API

```
{
  plugins: {
    entries: {
      perplexity: {
        config: {
          webSearch: {
            apiKey: "pplx-...",
          },
        },
      },
    },
  },
  tools: {
    web: {
      search: {
        provider: "perplexity",
      },
    },
  },
}
```

### OpenRouter / Sonar compatibility

```
{
  plugins: {
    entries: {
      perplexity: {
        config: {
          webSearch: {
            apiKey: "<openrouter-api-key>",
            baseUrl: "https://openrouter.ai/api/v1",
            model: "perplexity/sonar-pro",
          },
        },
      },
    },
  },
  tools: {
    web: {
      search: {
        provider: "perplexity",
      },
    },
  },
}
```

## Where to set the key

**Via config:** run `openclaw configure --section web`. It stores the key in `~/.openclaw/openclaw.json` under `plugins.entries.perplexity.config.webSearch.apiKey`. That field also accepts SecretRef objects. **Via environment:** set `PERPLEXITY_API_KEY` or `OPENROUTER_API_KEY` in the Gateway process environment. For a gateway install, put it in `~/.openclaw/.env` (or your service environment). See [Env vars](https://docs.openclaw.ai/help/faq#env-vars-and-env-loading). If `provider: "perplexity"` is configured and the Perplexity key SecretRef is unresolved with no env fallback, startup/reload fails fast.

## Tool parameters

These parameters apply to the native Perplexity Search API path.

| Parameter             | Description                                          |
| --------------------- | ---------------------------------------------------- |
| `query`               | Search query (required)                              |
| `count`               | Number of results to return (1-10, default: 5)       |
| `country`             | 2-letter ISO country code (e.g., “US”, “DE”)         |
| `language`            | ISO 639-1 language code (e.g., “en”, “de”, “fr”)     |
| `freshness`           | Time filter: `day` (24h), `week`, `month`, or `year` |
| `date_after`          | Only results published after this date (YYYY-MM-DD)  |
| `date_before`         | Only results published before this date (YYYY-MM-DD) |
| `domain_filter`       | Domain allowlist/denylist array (max 20)             |
| `max_tokens`          | Total content budget (default: 25000, max: 1000000)  |
| `max_tokens_per_page` | Per-page token limit (default: 2048)                 |

For the legacy Sonar/OpenRouter compatibility path, only `query` and `freshness` are supported. Search API-only filters such as `country`, `language`, `date_after`, `date_before`, `domain_filter`, `max_tokens`, and `max_tokens_per_page` return explicit errors. **Examples:**

```
// Country and language-specific search
await web_search({
  query: "renewable energy",
  country: "DE",
  language: "de",
});

// Recent results (past week)
await web_search({
  query: "AI news",
  freshness: "week",
});

// Date range search
await web_search({
  query: "AI developments",
  date_after: "2024-01-01",
  date_before: "2024-06-30",
});

// Domain filtering (allowlist)
await web_search({
  query: "climate research",
  domain_filter: ["nature.com", "science.org", ".edu"],
});

// Domain filtering (denylist - prefix with -)
await web_search({
  query: "product reviews",
  domain_filter: ["-reddit.com", "-pinterest.com"],
});

// More content extraction
await web_search({
  query: "detailed AI research",
  max_tokens: 50000,
  max_tokens_per_page: 4096,
});
```

### Domain filter rules

## Notes

----
url: https://docs.openclaw.ai/start/onboarding
----

# Onboarding (macOS App) - OpenClaw

## [​](#onboarding-macos-app)Onboarding (macOS App)

This doc describes the **current** first‑run setup flow. The goal is a smooth “day 0” experience: pick where the Gateway runs, connect auth, run the wizard, and let the agent bootstrap itself. For a general overview of onboarding paths, see [Onboarding Overview](https://docs.openclaw.ai/start/onboarding-overview).

1

[](#)

Approve macOS warning

2

[](#)

Approve find local networks

3

[](#)

Welcome and security notice

Security trust model:

* By default, OpenClaw is a personal agent: one trusted operator boundary.
* Shared/multi-user setups require lock-down (split trust boundaries, keep tool access minimal, and follow [Security](https://docs.openclaw.ai/gateway/security)).
* Local onboarding now defaults new configs to `tools.profile: "coding"` so fresh local setups keep filesystem/runtime tools without forcing the unrestricted `full` profile.
* If hooks/webhooks or other untrusted content feeds are enabled, use a strong modern model tier and keep strict tool policy/sandboxing.

4

[](#)

Local vs Remote

Where does the **Gateway** run?

* **This Mac (Local only):** onboarding can configure auth and write credentials locally.
* **Remote (over SSH/Tailnet):** onboarding does **not** configure local auth; credentials must exist on the gateway host.
* **Configure later:** skip setup and leave the app unconfigured.

**Gateway auth tip:**

* The wizard now generates a **token** even for loopback, so local WS clients must authenticate.
* If you disable auth, any local process can connect; use that only on fully trusted machines.
* Use a **token** for multi‑machine access or non‑loopback binds.

5

[](#)

Permissions

Onboarding requests TCC permissions needed for:

* Automation (AppleScript)
* Notifications
* Accessibility
* Screen Recording
* Microphone
* Speech Recognition
* Camera
* Location

6

[](#)

CLI

This step is optional

The app can install the global `openclaw` CLI via npm/pnpm so terminal workflows and launchd tasks work out of the box.

7

[](#)

Onboarding Chat (dedicated session)

After setup, the app opens a dedicated onboarding chat session so the agent can introduce itself and guide next steps. This keeps first‑run guidance separate from your normal conversation. See [Bootstrapping](https://docs.openclaw.ai/start/bootstrapping) for what happens on the gateway host during the first agent run.

----
url: https://docs.openclaw.ai/start/getting-started
----

# Getting Started - OpenClaw

## [​](#getting-started)Getting Started

Install OpenClaw, run onboarding, and chat with your AI assistant — all in about 5 minutes. By the end you will have a running Gateway, configured auth, and a working chat session.

## [​](#what-you-need)What you need

* **Node.js** — Node 24 recommended (Node 22.16+ also supported)
* **An API key** from a model provider (Anthropic, OpenAI, Google, etc.) — onboarding will prompt you

Check your Node version with `node --version`. **Windows users:** both native Windows and WSL2 are supported. WSL2 is more stable and recommended for the full experience. See [Windows](https://docs.openclaw.ai/platforms/windows). Need to install Node? See [Node setup](https://docs.openclaw.ai/install/node).

## [​](#quick-setup)Quick setup

1

[](#)

Install OpenClaw

* macOS / Linux

* Windows (PowerShell)

```
curl -fsSL https://openclaw.ai/install.sh | bash
```

```
iwr -useb https://openclaw.ai/install.ps1 | iex
```

Other install methods (Docker, Nix, npm): [Install](https://docs.openclaw.ai/install).

2

[](#)

Run onboarding

```
openclaw onboard --install-daemon
```

The wizard walks you through choosing a model provider, setting an API key, and configuring the Gateway. It takes about 2 minutes.See [Onboarding (CLI)](https://docs.openclaw.ai/start/wizard) for the full reference.

3

[](#)

Verify the Gateway is running

```
openclaw gateway status
```

You should see the Gateway listening on port 18789.

4

[](#)

Open the dashboard

```
openclaw dashboard
```

This opens the Control UI in your browser. If it loads, everything is working.

5

[](#)

Send your first message

Type a message in the Control UI chat and you should get an AI reply.Want to chat from your phone instead? The fastest channel to set up is [Telegram](https://docs.openclaw.ai/channels/telegram) (just a bot token). See [Channels](https://docs.openclaw.ai/channels) for all options.

## [​](#what-to-do-next)What to do next

## Connect a channel

WhatsApp, Telegram, Discord, iMessage, and more.

## Pairing and safety

Control who can message your agent.

## Configure the Gateway

Models, tools, sandbox, and advanced settings.

## Browse tools

Browser, exec, web search, skills, and plugins.

Advanced: environment variables

If you run OpenClaw as a service account or want custom paths:

* `OPENCLAW_HOME` — home directory for internal path resolution
* `OPENCLAW_STATE_DIR` — override the state directory
* `OPENCLAW_CONFIG_PATH` — override the config file path

Full reference: [Environment variables](https://docs.openclaw.ai/help/environment).

----
url: https://docs.openclaw.ai
----

# OpenClaw - OpenClaw

> *“EXFOLIATE! EXFOLIATE!”* — A space lobster, probably

**Any OS gateway for AI agents across WhatsApp, Telegram, Discord, iMessage, and more.**\
Send a message, get an agent response from your pocket. Plugins add Mattermost and more.

## What is OpenClaw?

OpenClaw is a **self-hosted gateway** that connects your favorite chat apps — WhatsApp, Telegram, Discord, iMessage, and more — to AI coding agents like Pi. You run a single Gateway process on your own machine (or a server), and it becomes the bridge between your messaging apps and an always-available AI assistant. **Who is it for?** Developers and power users who want a personal AI assistant they can message from anywhere — without giving up control of their data or relying on a hosted service. **What makes it different?**

**What do you need?** Node 24 (recommended), or Node 22 LTS (`22.16+`) for compatibility, an API key from your chosen provider, and 5 minutes. For best quality and security, use the strongest latest-generation model available.

## How it works

The Gateway is the single source of truth for sessions, routing, and channel connections.

## Key capabilities

## Quick start

Need the full install and dev setup? See [Getting Started](https://docs.openclaw.ai/start/getting-started).

## Dashboard

Open the browser Control UI after the Gateway starts.

## Configuration (optional)

Config lives at `~/.openclaw/openclaw.json`.

Example:

## Start here

## Learn more

----
url: https://docs.openclaw.ai/web/dashboard
----

# Dashboard - OpenClaw

## [​](#dashboard-control-ui)Dashboard (Control UI)

The Gateway dashboard is the browser Control UI served at `/` by default (override with `gateway.controlUi.basePath`). Quick open (local Gateway):

* <http://127.0.0.1:18789/> (or <http://localhost:18789/>)

Key references:

* [Control UI](https://docs.openclaw.ai/web/control-ui) for usage and UI capabilities.
* [Tailscale](https://docs.openclaw.ai/gateway/tailscale) for Serve/Funnel automation.
* [Web surfaces](https://docs.openclaw.ai/web) for bind modes and security notes.

Authentication is enforced at the WebSocket handshake via `connect.params.auth` (token or password). See `gateway.auth` in [Gateway configuration](https://docs.openclaw.ai/gateway/configuration). Security note: the Control UI is an **admin surface** (chat, config, exec approvals). Do not expose it publicly. The UI keeps dashboard URL tokens in sessionStorage for the current browser tab session and selected gateway URL, and strips them from the URL after load. Prefer localhost, Tailscale Serve, or an SSH tunnel.

## [​](#fast-path-recommended)Fast path (recommended)

* After onboarding, the CLI auto-opens the dashboard and prints a clean (non-tokenized) link.
* Re-open anytime: `openclaw dashboard` (copies link, opens browser if possible, shows SSH hint if headless).
* If the UI prompts for auth, paste the token from `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`) into Control UI settings.

## [​](#token-basics-local-vs-remote)Token basics (local vs remote)

* **Localhost**: open `http://127.0.0.1:18789/`.
* **Token source**: `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`); `openclaw dashboard` can pass it via URL fragment for one-time bootstrap, and the Control UI keeps it in sessionStorage for the current browser tab session and selected gateway URL instead of localStorage.
* If `gateway.auth.token` is SecretRef-managed, `openclaw dashboard` prints/copies/opens a non-tokenized URL by design. This avoids exposing externally managed tokens in shell logs, clipboard history, or browser-launch arguments.
* If `gateway.auth.token` is configured as a SecretRef and is unresolved in your current shell, `openclaw dashboard` still prints a non-tokenized URL plus actionable auth setup guidance.
* **Not localhost**: use Tailscale Serve (tokenless for Control UI/WebSocket if `gateway.auth.allowTailscale: true`, assumes trusted gateway host; HTTP APIs still need token/password), tailnet bind with a token, or an SSH tunnel. See [Web surfaces](https://docs.openclaw.ai/web).

## [​](#if-you-see-“unauthorized”-/-1008)If you see “unauthorized” / 1008

* Ensure the gateway is reachable (local: `openclaw status`; remote: SSH tunnel `ssh -N -L 18789:127.0.0.1:18789 user@host` then open `http://127.0.0.1:18789/`).

* For `AUTH_TOKEN_MISMATCH`, clients may do one trusted retry with a cached device token when the gateway returns retry hints. If auth still fails after that retry, resolve token drift manually.

* For token drift repair steps, follow [Token drift recovery checklist](https://docs.openclaw.ai/cli/devices#token-drift-recovery-checklist).

* Retrieve or supply the token from the gateway host:

  * Plaintext config: `openclaw config get gateway.auth.token`
  * SecretRef-managed config: resolve the external secret provider or export `OPENCLAW_GATEWAY_TOKEN` in this shell, then rerun `openclaw dashboard`
  * No token configured: `openclaw doctor --generate-gateway-token`

* In the dashboard settings, paste the token into the auth field, then connect.

----
url: https://docs.openclaw.ai/providers/synthetic
----

# Synthetic - OpenClaw

Synthetic exposes Anthropic-compatible endpoints. OpenClaw registers it as the `synthetic` provider and uses the Anthropic Messages API.

## Quick setup

1. Set `SYNTHETIC_API_KEY` (or run the wizard below).
2. Run onboarding:

The default model is set to:

## Config example

```
{
  env: { SYNTHETIC_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" },
      models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax M2.5" } },
    },
  },
  models: {
    mode: "merge",
    providers: {
      synthetic: {
        baseUrl: "https://api.synthetic.new/anthropic",
        apiKey: "${SYNTHETIC_API_KEY}",
        api: "anthropic-messages",
        models: [
          {
            id: "hf:MiniMaxAI/MiniMax-M2.5",
            name: "MiniMax M2.5",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 192000,
            maxTokens: 65536,
          },
        ],
      },
    },
  },
}
```

Note: OpenClaw’s Anthropic client appends `/v1` to the base URL, so use `https://api.synthetic.new/anthropic` (not `/anthropic/v1`). If Synthetic changes its base URL, override `models.providers.synthetic.baseUrl`.

## Model catalog

All models below use cost `0` (input/output/cache).

| Model ID                                               | Context window | Max tokens | Reasoning | Input        |
| ------------------------------------------------------ | -------------- | ---------- | --------- | ------------ |
| `hf:MiniMaxAI/MiniMax-M2.5`                            | 192000         | 65536      | false     | text         |
| `hf:moonshotai/Kimi-K2-Thinking`                       | 256000         | 8192       | true      | text         |
| `hf:zai-org/GLM-4.7`                                   | 198000         | 128000     | false     | text         |
| `hf:deepseek-ai/DeepSeek-R1-0528`                      | 128000         | 8192       | false     | text         |
| `hf:deepseek-ai/DeepSeek-V3-0324`                      | 128000         | 8192       | false     | text         |
| `hf:deepseek-ai/DeepSeek-V3.1`                         | 128000         | 8192       | false     | text         |
| `hf:deepseek-ai/DeepSeek-V3.1-Terminus`                | 128000         | 8192       | false     | text         |
| `hf:deepseek-ai/DeepSeek-V3.2`                         | 159000         | 8192       | false     | text         |
| `hf:meta-llama/Llama-3.3-70B-Instruct`                 | 128000         | 8192       | false     | text         |
| `hf:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | 524000         | 8192       | false     | text         |
| `hf:moonshotai/Kimi-K2-Instruct-0905`                  | 256000         | 8192       | false     | text         |
| `hf:openai/gpt-oss-120b`                               | 128000         | 8192       | false     | text         |
| `hf:Qwen/Qwen3-235B-A22B-Instruct-2507`                | 256000         | 8192       | false     | text         |
| `hf:Qwen/Qwen3-Coder-480B-A35B-Instruct`               | 256000         | 8192       | false     | text         |
| `hf:Qwen/Qwen3-VL-235B-A22B-Instruct`                  | 250000         | 8192       | false     | text + image |
| `hf:zai-org/GLM-4.5`                                   | 128000         | 128000     | false     | text         |
| `hf:zai-org/GLM-4.6`                                   | 198000         | 128000     | false     | text         |
| `hf:deepseek-ai/DeepSeek-V3`                           | 128000         | 8192       | false     | text         |
| `hf:Qwen/Qwen3-235B-A22B-Thinking-2507`                | 256000         | 8192       | true      | text         |

## Notes

----
url: https://docs.openclaw.ai/cli/webhooks
----

# webhooks - OpenClaw

##### CLI commands

* [CLI Reference](https://docs.openclaw.ai/cli)

*

*

*

*

* * [config](https://docs.openclaw.ai/cli/config)
  * [configure](https://docs.openclaw.ai/cli/configure)
  * [webhooks](https://docs.openclaw.ai/cli/webhooks)

*

*

*

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw webhooks](#openclaw-webhooks)
- [Gmail](#gmail)

## [​](#openclaw-webhooks)`openclaw webhooks`

Webhook helpers and integrations (Gmail Pub/Sub, webhook helpers). Related:

* Webhooks: [Webhook](https://docs.openclaw.ai/automation/webhook)
* Gmail Pub/Sub: [Gmail Pub/Sub](https://docs.openclaw.ai/automation/gmail-pubsub)

## [​](#gmail)Gmail

```
openclaw webhooks gmail setup --account you@example.com
openclaw webhooks gmail run
```

See [Gmail Pub/Sub documentation](https://docs.openclaw.ai/automation/gmail-pubsub) for details.

[configure](https://docs.openclaw.ai/cli/configure)[plugins](https://docs.openclaw.ai/cli/plugins)

----
url: https://docs.openclaw.ai/platforms/windows
----

# Windows - OpenClaw

OpenClaw supports both **native Windows** and **WSL2**. WSL2 is the more stable path and recommended for the full experience — the CLI, Gateway, and tooling run inside Linux with full compatibility. Native Windows works for core CLI and Gateway use, with some caveats noted below. Native Windows companion apps are planned.

## WSL2 (recommended)

## Native Windows status

Native Windows CLI flows are improving, but WSL2 is still the recommended path. What works well on native Windows today:

Current caveats:

If you want the native CLI only, without gateway service install, use one of these:

If you do want managed startup on native Windows:

If Scheduled Task creation is blocked, the fallback service mode still auto-starts after login through the current user’s Startup folder.

## Gateway

## Gateway service install (CLI)

Inside WSL2:

Or:

Or:

Select **Gateway service** when prompted. Repair/migrate:

## Gateway auto-start before Windows login

For headless setups, ensure the full boot chain runs even when no one logs into Windows.

### 1) Keep user services running without login

Inside WSL:

### 2) Install the OpenClaw gateway user service

Inside WSL:

### 3) Start WSL automatically at Windows boot

In PowerShell as Administrator:

Replace `Ubuntu` with your distro name from:

### Verify startup chain

After a reboot (before Windows sign-in), check from WSL:

## Advanced: expose WSL services over LAN (portproxy)

WSL has its own virtual network. If another machine needs to reach a service running **inside WSL** (SSH, a local TTS server, or the Gateway), you must forward a Windows port to the current WSL IP. The WSL IP changes after restarts, so you may need to refresh the forwarding rule. Example (PowerShell **as Administrator**):

Allow the port through Windows Firewall (one-time):

Refresh the portproxy after WSL restarts:

Notes:

## Step-by-step WSL2 install

### 1) Install WSL2 + Ubuntu

Open PowerShell (Admin):

Reboot if Windows asks.

### 2) Enable systemd (required for gateway install)

In your WSL terminal:

Then from PowerShell:

Re-open Ubuntu, then verify:

### 3) Install OpenClaw (inside WSL)

Follow the Linux Getting Started flow inside WSL:

Full guide: [Getting Started](https://docs.openclaw.ai/start/getting-started)

## Windows companion app

We do not have a Windows companion app yet. Contributions are welcome if you want contributions to make it happen.

----
url: https://docs.openclaw.ai/gateway/doctor
----

# Doctor - OpenClaw

`openclaw doctor` is the repair + migration tool for OpenClaw. It fixes stale config/state, checks health, and provides actionable repair steps.

## Quick start

### Headless / automation

Accept defaults without prompting (including restart/service/sandbox repair steps when applicable).

Apply recommended repairs without prompting (repairs + restarts where safe).

Apply aggressive repairs too (overwrites custom supervisor configs).

Run without prompts and only apply safe migrations (config normalization + on-disk state moves). Skips restart/service/sandbox actions that require human confirmation. Legacy state migrations run automatically when detected.

Scan system services for extra gateway installs (launchd/systemd/schtasks). If you want to review changes before writing, open the config file first:

## What it does (summary)

* Optional pre-flight update for git installs (interactive only).
* UI protocol freshness check (rebuilds Control UI when the protocol schema is newer).
* Health check + restart prompt.
* Skills status summary (eligible/missing/blocked).
* Config normalization for legacy values.
* Browser migration checks for legacy Chrome extension configs and Chrome MCP readiness.
* OpenCode provider override warnings (`models.providers.opencode` / `models.providers.opencode-go`).
* Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
* Legacy cron store migration (`jobId`, `schedule.cron`, top-level delivery/payload fields, payload `provider`, simple `notify: true` webhook fallback jobs).
* State integrity and permissions checks (sessions, transcripts, state dir).
* Config file permission checks (chmod 600) when running locally.
* Model auth health: checks OAuth expiry, can refresh expiring tokens, and reports auth-profile cooldown/disabled states.
* Extra workspace dir detection (`~/openclaw`).
* Sandbox image repair when sandboxing is enabled.
* Legacy service migration and extra gateway detection.
* Gateway runtime checks (service installed but not running; cached launchd label).
* Channel status warnings (probed from the running gateway).
* Supervisor config audit (launchd/systemd/schtasks) with optional repair.
* Gateway runtime best-practice checks (Node vs Bun, version-manager paths).
* Gateway port collision diagnostics (default `18789`).
* Security warnings for open DM policies.
* Gateway auth checks for local token mode (offers token generation when no token source exists; does not overwrite token SecretRef configs).
* systemd linger check on Linux.
* Source install checks (pnpm workspace mismatch, missing UI assets, missing tsx binary).
* Writes updated config + wizard metadata.

## Detailed behavior and rationale

### 0) Optional update (git installs)

If this is a git checkout and doctor is running interactively, it offers to update (fetch/rebase/build) before running doctor.

### 1) Config normalization

If the config contains legacy value shapes (for example `messages.ackReaction` without a channel-specific override), doctor normalizes them into the current schema.

### 2) Legacy config key migrations

When the config contains deprecated keys, other commands refuse to run and ask you to run `openclaw doctor`. Doctor will:

The Gateway also auto-runs doctor migrations on startup when it detects a legacy config format, so stale configs are repaired without manual intervention. Current migrations:

Doctor warnings also include account-default guidance for multi-account channels:

### 2b) OpenCode provider overrides

If you’ve added `models.providers.opencode`, `opencode-zen`, or `opencode-go` manually, it overrides the built-in OpenCode catalog from `@mariozechner/pi-ai`. That can force models onto the wrong API or zero out costs. Doctor warns so you can remove the override and restore per-model API routing + costs.

### 2c) Browser migration and Chrome MCP readiness

If your browser config still points at the removed Chrome extension path, doctor normalizes it to the current host-local Chrome MCP attach model:

Doctor also audits the host-local Chrome MCP path when you use `defaultProfile: "user"` or a configured `existing-session` profile:

Doctor cannot enable the Chrome-side setting for you. Host-local Chrome MCP still requires:

This check does **not** apply to Docker, sandbox, remote-browser, or other headless flows. Those continue to use raw CDP.

### 3) Legacy state migrations (disk layout)

Doctor can migrate older on-disk layouts into the current structure:

These migrations are best-effort and idempotent; doctor will emit warnings when it leaves any legacy folders behind as backups. The Gateway/CLI also auto-migrates the legacy sessions + agent dir on startup so history/auth/models land in the per-agent path without a manual doctor run. WhatsApp auth is intentionally only migrated via `openclaw doctor`.

### 3b) Legacy cron store migrations

Doctor also checks the cron job store (`~/.openclaw/cron/jobs.json` by default, or `cron.store` when overridden) for old job shapes that the scheduler still accepts for compatibility. Current cron cleanups include:

Doctor only auto-migrates `notify: true` jobs when it can do so without changing behavior. If a job combines legacy notify fallback with an existing non-webhook delivery mode, doctor warns and leaves that job for manual review.

### 4) State integrity checks (session persistence, routing, and safety)

The state directory is the operational brainstem. If it vanishes, you lose sessions, credentials, logs, and config (unless you have backups elsewhere). Doctor checks:

### 5) Model auth health (OAuth expiry)

Doctor inspects OAuth profiles in the auth store, warns when tokens are expiring/expired, and can refresh them when safe. If the Anthropic Claude Code profile is stale, it suggests running `claude setup-token` (or pasting a setup-token). Refresh prompts only appear when running interactively (TTY); `--non-interactive` skips refresh attempts. Doctor also reports auth profiles that are temporarily unusable due to:

### 6) Hooks model validation

If `hooks.gmail.model` is set, doctor validates the model reference against the catalog and allowlist and warns when it won’t resolve or is disallowed.

### 7) Sandbox image repair

When sandboxing is enabled, doctor checks Docker images and offers to build or switch to legacy names if the current image is missing.

### 8) Gateway service migrations and cleanup hints

Doctor detects legacy gateway services (launchd/systemd/schtasks) and offers to remove them and install the OpenClaw service using the current gateway port. It can also scan for extra gateway-like services and print cleanup hints. Profile-named OpenClaw gateway services are considered first-class and are not flagged as “extra.”

### 9) Security warnings

Doctor emits warnings when a provider is open to DMs without an allowlist, or when a policy is configured in a dangerous way.

### 10) systemd linger (Linux)

If running as a systemd user service, doctor ensures lingering is enabled so the gateway stays alive after logout.

### 11) Skills status

Doctor prints a quick summary of eligible/missing/blocked skills for the current workspace.

### 12) Gateway auth checks (local token)

Doctor checks local gateway token auth readiness.

### 12b) Read-only SecretRef-aware repairs

Some repair flows need to inspect configured credentials without weakening runtime fail-fast behavior.

### 13) Gateway health check + restart

Doctor runs a health check and offers to restart the gateway when it looks unhealthy.

### 14) Channel status warnings

If the gateway is healthy, doctor runs a channel status probe and reports warnings with suggested fixes.

### 15) Supervisor config audit + repair

Doctor checks the installed supervisor config (launchd/systemd/schtasks) for missing or outdated defaults (e.g., systemd network-online dependencies and restart delay). When it finds a mismatch, it recommends an update and can rewrite the service file/task to the current defaults. Notes:

### 16) Gateway runtime + port diagnostics

Doctor inspects the service runtime (PID, last exit status) and warns when the service is installed but not actually running. It also checks for port collisions on the gateway port (default `18789`) and reports likely causes (gateway already running, SSH tunnel).

### 17) Gateway runtime best practices

Doctor warns when the gateway service runs on Bun or a version-managed Node path (`nvm`, `fnm`, `volta`, `asdf`, etc.). WhatsApp + Telegram channels require Node, and version-manager paths can break after upgrades because the service does not load your shell init. Doctor offers to migrate to a system Node install when available (Homebrew/apt/choco).

### 18) Config write + wizard metadata

Doctor persists any config changes and stamps wizard metadata to record the doctor run.

### 19) Workspace tips (backup + memory system)

Doctor suggests a workspace memory system when missing and prints a backup tip if the workspace is not already under git. See [/concepts/agent-workspace](https://docs.openclaw.ai/concepts/agent-workspace) for a full guide to workspace structure and git backup (recommended private GitHub or GitLab).

----
url: https://docs.openclaw.ai/cli/qr
----

# qr - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw qr](#openclaw-qr)
- [Usage](#usage)
- [Options](#options)
- [Notes](#notes)

## [​](#openclaw-qr)`openclaw qr`

Generate an iOS pairing QR and setup code from your current Gateway configuration.

## [​](#usage)Usage

```
openclaw qr
openclaw qr --setup-code-only
openclaw qr --json
openclaw qr --remote
openclaw qr --url wss://gateway.example/ws
```

## [​](#options)Options

* `--remote`: use `gateway.remote.url` plus remote token/password from config
* `--url <url>`: override gateway URL used in payload
* `--public-url <url>`: override public URL used in payload
* `--token <token>`: override which gateway token the bootstrap flow authenticates against
* `--password <password>`: override which gateway password the bootstrap flow authenticates against
* `--setup-code-only`: print only setup code
* `--no-ascii`: skip ASCII QR rendering
* `--json`: emit JSON (`setupCode`, `gatewayUrl`, `auth`, `urlSource`)

## [​](#notes)Notes

* `--token` and `--password` are mutually exclusive.

* The setup code itself now carries an opaque short-lived `bootstrapToken`, not the shared gateway token/password.

* With `--remote`, if effectively active remote credentials are configured as SecretRefs and you do not pass `--token` or `--password`, the command resolves them from the active gateway snapshot. If gateway is unavailable, the command fails fast.

* Without `--remote`, local gateway auth SecretRefs are resolved when no CLI auth override is passed:

  * `gateway.auth.token` resolves when token auth can win (explicit `gateway.auth.mode="token"` or inferred mode where no password source wins).
  * `gateway.auth.password` resolves when password auth can win (explicit `gateway.auth.mode="password"` or inferred mode with no winning token from auth/env).

* If both `gateway.auth.token` and `gateway.auth.password` are configured (including SecretRefs) and `gateway.auth.mode` is unset, setup-code resolution fails until mode is set explicitly.

* Gateway version skew note: this command path requires a gateway that supports `secrets.resolve`; older gateways return an unknown-method error.

* After scanning, approve device pairing with:

  * `openclaw devices list`
  * `openclaw devices approve <requestId>`

[pairing](https://docs.openclaw.ai/cli/pairing)[voicecall](https://docs.openclaw.ai/cli/voicecall)

----
url: https://docs.openclaw.ai/reference/wizard
----

# Onboarding Reference - OpenClaw

## Onboarding Reference

This is the full reference for `openclaw onboard`. For a high-level overview, see [Onboarding (CLI)](https://docs.openclaw.ai/start/wizard).

## Flow details (local mode)

Model/Auth

* **Anthropic API key**: uses `ANTHROPIC_API_KEY` if present or prompts for a key, then saves it for daemon use.
* **Anthropic OAuth (Claude Code CLI)**: on macOS onboarding checks Keychain item “Claude Code-credentials” (choose “Always Allow” so launchd starts don’t block); on Linux/Windows it reuses `~/.claude/.credentials.json` if present.
* **Anthropic token (paste setup-token)**: run `claude setup-token` on any machine, then paste the token (you can name it; blank = default).
* **OpenAI Code (Codex) subscription (Codex CLI)**: if `~/.codex/auth.json` exists, onboarding can reuse it.
* **OpenAI Code (Codex) subscription (OAuth)**: browser flow; paste the `code#state`.
* **OpenAI API key**: uses `OPENAI_API_KEY` if present or prompts for a key, then stores it in auth profiles.
* **xAI (Grok) API key**: prompts for `XAI_API_KEY` and configures xAI as a model provider.
* **OpenCode**: prompts for `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`, get it at <https://opencode.ai/auth>) and lets you pick the Zen or Go catalog.
* **Ollama**: prompts for the Ollama base URL, offers **Cloud + Local** or **Local** mode, discovers available models, and auto-pulls the selected local model when needed.
* More detail: [Ollama](https://docs.openclaw.ai/providers/ollama)
* **API key**: stores the key for you.
* **Vercel AI Gateway (multi-model proxy)**: prompts for `AI_GATEWAY_API_KEY`.
* More detail: [Vercel AI Gateway](https://docs.openclaw.ai/providers/vercel-ai-gateway)
* **Cloudflare AI Gateway**: prompts for Account ID, Gateway ID, and `CLOUDFLARE_AI_GATEWAY_API_KEY`.
* More detail: [Cloudflare AI Gateway](https://docs.openclaw.ai/providers/cloudflare-ai-gateway)
* **MiniMax**: config is auto-written; hosted default is `MiniMax-M2.7` and `MiniMax-M2.5` stays available.
* More detail: [MiniMax](https://docs.openclaw.ai/providers/minimax)
* **Synthetic (Anthropic-compatible)**: prompts for `SYNTHETIC_API_KEY`.
* More detail: [Synthetic](https://docs.openclaw.ai/providers/synthetic)
* **Moonshot (Kimi K2)**: config is auto-written.
* **Kimi Coding**: config is auto-written.
* More detail: [Moonshot AI (Kimi + Kimi Coding)](https://docs.openclaw.ai/providers/moonshot)
* **Skip**: no auth configured yet.
* Pick a default model from detected options (or enter provider/model manually). For best quality and lower prompt-injection risk, choose the strongest latest-generation model available in your provider stack.
* Onboarding runs a model check and warns if the configured model is unknown or missing auth.
* API key storage mode defaults to plaintext auth-profile values. Use `--secret-input-mode ref` to store env-backed refs instead (for example `keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" }`).
* OAuth credentials live in `~/.openclaw/credentials/oauth.json`; auth profiles live in `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` (API keys + OAuth).
* More detail: [/concepts/oauth](https://docs.openclaw.ai/concepts/oauth)

## Non-interactive mode

Use `--non-interactive` to automate or script onboarding:

Add `--json` for a machine‑readable summary. Gateway token SecretRef in non-interactive mode:

`--gateway-token` and `--gateway-token-ref-env` are mutually exclusive.

Provider-specific command examples live in [CLI Automation](https://docs.openclaw.ai/start/wizard-cli-automation#provider-specific-examples). Use this reference page for flag semantics and step ordering.

### Add agent (non-interactive)

## Gateway wizard RPC

The Gateway exposes the onboarding flow over RPC (`wizard.start`, `wizard.next`, `wizard.cancel`, `wizard.status`). Clients (macOS app, Control UI) can render steps without re‑implementing onboarding logic.

## Signal setup (signal-cli)

Onboarding can install `signal-cli` from GitHub releases:

Notes:

## What the wizard writes

Typical fields in `~/.openclaw/openclaw.json`:

`openclaw agents add` writes `agents.list[]` and optional `bindings`. WhatsApp credentials go under `~/.openclaw/credentials/whatsapp/<accountId>/`. Sessions are stored under `~/.openclaw/agents/<agentId>/sessions/`. Some channels are delivered as plugins. When you pick one during setup, onboarding will prompt to install it (npm or a local path) before it can be configured.

## Related docs

----
url: https://docs.openclaw.ai/platforms/mac/permissions
----

# macOS Permissions - OpenClaw

macOS permission grants are fragile. TCC associates a permission grant with the app’s code signature, bundle identifier, and on-disk path. If any of those change, macOS treats the app as new and may drop or hide prompts.

## Requirements for stable permissions

Ad-hoc signatures generate a new identity every build. macOS will forget previous grants, and prompts can disappear entirely until the stale entries are cleared.

## Recovery checklist when prompts disappear

1. Quit the app.
2. Remove the app entry in System Settings -> Privacy & Security.
3. Relaunch the app from the same path and re-grant permissions.
4. If the prompt still does not appear, reset TCC entries with `tccutil` and try again.
5. Some permissions only reappear after a full macOS restart.

Example resets (replace bundle ID as needed):

## Files and folders permissions (Desktop/Documents/Downloads)

macOS may also gate Desktop, Documents, and Downloads for terminal/background processes. If file reads or directory listings hang, grant access to the same process context that performs file operations (for example Terminal/iTerm, LaunchAgent-launched app, or SSH process). Workaround: move files into the OpenClaw workspace (`~/.openclaw/workspace`) if you want to avoid per-folder grants. If you are testing permissions, always sign with a real certificate. Ad-hoc builds are only acceptable for quick local runs where permissions do not matter.

----
url: https://docs.openclaw.ai/platforms/mac/bundled-gateway
----

# Gateway on macOS - OpenClaw

## [​](#gateway-on-macos-external-launchd)Gateway on macOS (external launchd)

OpenClaw\.app no longer bundles Node/Bun or the Gateway runtime. The macOS app expects an **external** `openclaw` CLI install, does not spawn the Gateway as a child process, and manages a per‑user launchd service to keep the Gateway running (or attaches to an existing local Gateway if one is already running).

## [​](#install-the-cli-required-for-local-mode)Install the CLI (required for local mode)

Node 24 is the default runtime on the Mac. Node 22 LTS, currently `22.16+`, still works for compatibility. Then install `openclaw` globally:

```
npm install -g openclaw@<version>
```

The macOS app’s **Install CLI** button runs the same flow via npm/pnpm (bun not recommended for Gateway runtime).

## [​](#launchd-gateway-as-launchagent)Launchd (Gateway as LaunchAgent)

Label:

* `ai.openclaw.gateway` (or `ai.openclaw.<profile>`; legacy `com.openclaw.*` may remain)

Plist location (per‑user):

* `~/Library/LaunchAgents/ai.openclaw.gateway.plist` (or `~/Library/LaunchAgents/ai.openclaw.<profile>.plist`)

Manager:

* The macOS app owns LaunchAgent install/update in Local mode.
* The CLI can also install it: `openclaw gateway install`.

Behavior:

* “OpenClaw Active” enables/disables the LaunchAgent.
* App quit does **not** stop the gateway (launchd keeps it alive).
* If a Gateway is already running on the configured port, the app attaches to it instead of starting a new one.

Logging:

* launchd stdout/err: `/tmp/openclaw/openclaw-gateway.log`

## [​](#version-compatibility)Version compatibility

The macOS app checks the gateway version against its own version. If they’re incompatible, update the global CLI to match the app version.

## [​](#smoke-check)Smoke check

```
openclaw --version

OPENCLAW_SKIP_CHANNELS=1 \
OPENCLAW_SKIP_CANVAS_HOST=1 \
openclaw gateway --port 18999 --bind loopback
```

Then:

```
openclaw gateway call health --url ws://127.0.0.1:18999 --timeout 3000
```

----
url: https://docs.openclaw.ai/tools/btw
----

# BTW Side Questions - OpenClaw

`/btw` lets you ask a quick side question about the **current session** without turning that question into normal conversation history. It is modeled after Claude Code’s `/btw` behavior, but adapted to OpenClaw’s Gateway and multi-channel architecture.

## What it does

When you send:

OpenClaw:

1. snapshots the current session context,
2. runs a separate **tool-less** model call,
3. answers only the side question,
4. leaves the main run alone,
5. does **not** write the BTW question or answer to session history,
6. emits the answer as a **live side result** rather than a normal assistant message.

The important mental model is:

## What it does not do

`/btw` does **not**:

It is intentionally **ephemeral**.

## How context works

BTW uses the current session as **background context only**. If the main run is currently active, OpenClaw snapshots the current message state and includes the in-flight main prompt as background context, while explicitly telling the model:

That keeps BTW isolated from the main run while still making it aware of what the session is about.

## Delivery model

BTW is **not** delivered as a normal assistant transcript message. At the Gateway protocol level:

This separation is intentional. If BTW reused the normal `chat` event path, clients would treat it like regular conversation history. Because BTW uses a separate live event and is not replayed from `chat.history`, it disappears after reload.

## Surface behavior

### TUI

In TUI, BTW is rendered inline in the current session view, but it remains ephemeral:

### External channels

On channels like Telegram, WhatsApp, and Discord, BTW is delivered as a clearly labeled one-off reply because those surfaces do not have a local ephemeral overlay concept. The answer is still treated as a side result, not normal session history.

### Control UI / web

The Gateway emits BTW correctly as `chat.side_result`, and BTW is not included in `chat.history`, so the persistence contract is already correct for web. The current Control UI still needs a dedicated `chat.side_result` consumer to render BTW live in the browser. Until that client-side support lands, BTW is a Gateway-level feature with full TUI and external-channel behavior, but not yet a complete browser UX.

## When to use BTW

Use `/btw` when you want:

Examples:

## When not to use BTW

Do not use `/btw` when you want the answer to become part of the session’s future working context. In that case, ask normally in the main session instead of using BTW.

----
url: https://docs.openclaw.ai/concepts/typing-indicators
----

# Typing Indicators - OpenClaw

## [​](#typing-indicators)Typing indicators

Typing indicators are sent to the chat channel while a run is active. Use `agents.defaults.typingMode` to control **when** typing starts and `typingIntervalSeconds` to control **how often** it refreshes.

## [​](#defaults)Defaults

When `agents.defaults.typingMode` is **unset**, OpenClaw keeps the legacy behavior:

* **Direct chats**: typing starts immediately once the model loop begins.
* **Group chats with a mention**: typing starts immediately.
* **Group chats without a mention**: typing starts only when message text begins streaming.
* **Heartbeat runs**: typing is disabled.

## [​](#modes)Modes

Set `agents.defaults.typingMode` to one of:

* `never` — no typing indicator, ever.
* `instant` — start typing **as soon as the model loop begins**, even if the run later returns only the silent reply token.
* `thinking` — start typing on the **first reasoning delta** (requires `reasoningLevel: "stream"` for the run).
* `message` — start typing on the **first non-silent text delta** (ignores the `NO_REPLY` silent token).

Order of “how early it fires”: `never` → `message` → `thinking` → `instant`

## [​](#configuration)Configuration

```
{
  agent: {
    typingMode: "thinking",
    typingIntervalSeconds: 6,
  },
}
```

You can override mode or cadence per session:

```
{
  session: {
    typingMode: "message",
    typingIntervalSeconds: 4,
  },
}
```

## [​](#notes)Notes

* `message` mode won’t show typing for silent-only replies (e.g. the `NO_REPLY` token used to suppress output).
* `thinking` only fires if the run streams reasoning (`reasoningLevel: "stream"`). If the model doesn’t emit reasoning deltas, typing won’t start.
* Heartbeats never show typing, regardless of mode.
* `typingIntervalSeconds` controls the **refresh cadence**, not the start time. The default is 6 seconds.

----
url: https://docs.openclaw.ai/channels/line
----

# LINE - OpenClaw

## LINE (plugin)

LINE connects to OpenClaw via the LINE Messaging API. The plugin runs as a webhook receiver on the gateway and uses your channel access token + channel secret for authentication. Status: supported via plugin. Direct messages, group chats, media, locations, Flex messages, template messages, and quick replies are supported. Reactions and threads are not supported.

## Plugin required

Install the LINE plugin:

Local checkout (when running from a git repo):

## Setup

1. Create a LINE Developers account and open the Console: <https://developers.line.biz/console/>
2. Create (or pick) a Provider and add a **Messaging API** channel.
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
4. Enable **Use webhook** in the Messaging API settings.
5. Set the webhook URL to your gateway endpoint (HTTPS required):

The gateway responds to LINE’s webhook verification (GET) and inbound events (POST). If you need a custom path, set `channels.line.webhookPath` or `channels.line.accounts.<id>.webhookPath` and update the URL accordingly. Security note:

## Configure

Minimal config:

Env vars (default account only):

Token/secret files:

`tokenFile` and `secretFile` must point to regular files. Symlinks are rejected. Multiple accounts:

## Access control

Direct messages default to pairing. Unknown senders get a pairing code and their messages are ignored until approved.

Allowlists and policies:

LINE IDs are case-sensitive. Valid IDs look like:

## Message behavior

## Channel data (rich messages)

Use `channelData.line` to send quick replies, locations, Flex cards, or template messages.

```
{
  text: "Here you go",
  channelData: {
    line: {
      quickReplies: ["Status", "Help"],
      location: {
        title: "Office",
        address: "123 Main St",
        latitude: 35.681236,
        longitude: 139.767125,
      },
      flexMessage: {
        altText: "Status card",
        contents: {
          /* Flex payload */
        },
      },
      templateMessage: {
        type: "confirm",
        text: "Proceed?",
        confirmLabel: "Yes",
        confirmData: "yes",
        cancelLabel: "No",
        cancelData: "no",
      },
    },
  },
}
```

The LINE plugin also ships a `/card` command for Flex message presets:

## Troubleshooting

----
url: https://docs.openclaw.ai/channels/nextcloud-talk
----

# Nextcloud Talk - OpenClaw

## Nextcloud Talk (plugin)

Status: supported via plugin (webhook bot). Direct messages, rooms, reactions, and markdown messages are supported.

## Plugin required

Nextcloud Talk ships as a plugin and is not bundled with the core install. Install via CLI (npm registry):

Local checkout (when running from a git repo):

If you choose Nextcloud Talk during setup and a git checkout is detected, OpenClaw will offer the local install path automatically. Details: [Plugins](https://docs.openclaw.ai/tools/plugin)

## Quick setup (beginner)

1. Install the Nextcloud Talk plugin.
2. On your Nextcloud server, create a bot:
3. Enable the bot in the target room settings.
4. Configure OpenClaw:
5. Restart the gateway (or finish setup).

Minimal config:

## Notes

## Access control (DMs)

## Rooms (groups)

## Capabilities

| Feature         | Status        |
| --------------- | ------------- |
| Direct messages | Supported     |
| Rooms           | Supported     |
| Threads         | Not supported |
| Media           | URL-only      |
| Reactions       | Supported     |
| Native commands | Not supported |

## Configuration reference (Nextcloud Talk)

Full configuration: [Configuration](https://docs.openclaw.ai/gateway/configuration) Provider options:

----
url: https://docs.openclaw.ai/gateway/security
----

# Security - OpenClaw

> \[!WARNING] **Personal assistant trust model:** this guidance assumes one trusted operator boundary per gateway (single-user/personal assistant model). OpenClaw is **not** a hostile multi-tenant security boundary for multiple adversarial users sharing one agent/gateway. If you need mixed-trust or adversarial-user operation, split trust boundaries (separate gateway + credentials, ideally separate OS users/hosts).

## Scope first: personal assistant security model

OpenClaw security guidance assumes a **personal assistant** deployment: one trusted operator boundary, potentially many agents.

This page explains hardening **within that model**. It does not claim hostile multi-tenant isolation on one shared gateway.

## Quick check: `openclaw security audit`

See also: [Formal Verification (Security Models)](https://docs.openclaw.ai/security/formal-verification) Run this regularly (especially after changing config or exposing network surfaces):

It flags common footguns (Gateway auth exposure, browser control exposure, elevated allowlists, filesystem permissions, permissive exec approvals, and open-channel tool exposure). OpenClaw is both a product and an experiment: you’re wiring frontier-model behavior into real messaging surfaces and real tools. **There is no “perfectly secure” setup.** The goal is to be deliberate about:

Start with the smallest access that still works, then widen it as you gain confidence.

## Deployment assumption (important)

OpenClaw assumes the host and config boundary are trusted:

### Practical consequence (operator trust boundary)

Inside one Gateway instance, authenticated operator access is a trusted control-plane role, not a per-user tenant role.

## Personal assistant model (not a multi-tenant bus)

OpenClaw is designed as a personal assistant security model: one trusted operator boundary, potentially many agents.

### Shared Slack workspace: real risk

If “everyone in Slack can message the bot,” the core risk is delegated tool authority:

Use separate agents/gateways with minimal tools for team workflows; keep personal-data agents private.

### Company-shared agent: acceptable pattern

This is acceptable when everyone using that agent is in the same trust boundary (for example one company team) and the agent is strictly business-scoped.

If you mix personal and company identities on the same runtime, you collapse the separation and increase personal-data exposure risk.

## Gateway and node trust concept

Treat Gateway and node as one operator trust domain, with different roles:

If you need hostile-user isolation, split trust boundaries by OS user/host and run separate gateways.

## Trust boundary matrix

Use this as the quick model when triaging risk:

| Boundary or control                         | What it means                                     | Common misread                                                                |
| ------------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------- |
| `gateway.auth` (token/password/device auth) | Authenticates callers to gateway APIs             | ”Needs per-message signatures on every frame to be secure”                    |
| `sessionKey`                                | Routing key for context/session selection         | ”Session key is a user auth boundary”                                         |
| Prompt/content guardrails                   | Reduce model abuse risk                           | ”Prompt injection alone proves auth bypass”                                   |
| `canvas.eval` / browser evaluate            | Intentional operator capability when enabled      | ”Any JS eval primitive is automatically a vuln in this trust model”           |
| Local TUI `!` shell                         | Explicit operator-triggered local execution       | ”Local shell convenience command is remote injection”                         |
| Node pairing and node commands              | Operator-level remote execution on paired devices | ”Remote device control should be treated as untrusted user access by default” |

## Not vulnerabilities by design

These patterns are commonly reported and are usually closed as no-action unless a real boundary bypass is shown:

## Researcher preflight checklist

Before opening a GHSA, verify all of these:

1. Repro still works on latest `main` or latest release.
2. Report includes exact code path (`file`, function, line range) and tested version/commit.
3. Impact crosses a documented trust boundary (not just prompt injection).
4. Claim is not listed in [Out of Scope](https://github.com/openclaw/openclaw/blob/main/SECURITY.md#out-of-scope).
5. Existing advisories were checked for duplicates (reuse canonical GHSA when applicable).
6. Deployment assumptions are explicit (loopback/local vs exposed, trusted vs untrusted operators).

## Hardened baseline in 60 seconds

Use this baseline first, then selectively re-enable tools per trusted agent:

```
{
  gateway: {
    mode: "local",
    bind: "loopback",
    auth: { mode: "token", token: "replace-with-long-random-token" },
  },
  session: {
    dmScope: "per-channel-peer",
  },
  tools: {
    profile: "messaging",
    deny: ["group:automation", "group:runtime", "group:fs", "sessions_spawn", "sessions_send"],
    fs: { workspaceOnly: true },
    exec: { security: "deny", ask: "always" },
    elevated: { enabled: false },
  },
  channels: {
    whatsapp: { dmPolicy: "pairing", groups: { "*": { requireMention: true } } },
  },
}
```

This keeps the Gateway local-only, isolates DMs, and disables control-plane/runtime tools by default.

If more than one person can DM your bot:

### What the audit checks (high level)

* **Inbound access** (DM policies, group policies, allowlists): can strangers trigger the bot?
* **Tool blast radius** (elevated tools + open rooms): could prompt injection turn into shell/file/network actions?
* **Exec approval drift** (`security=full`, `autoAllowSkills`, interpreter allowlists without `strictInlineEval`): are host-exec guardrails still doing what you think they are?
* **Network exposure** (Gateway bind/auth, Tailscale Serve/Funnel, weak/short auth tokens).
* **Browser control exposure** (remote nodes, relay ports, remote CDP endpoints).
* **Local disk hygiene** (permissions, symlinks, config includes, “synced folder” paths).
* **Plugins** (extensions exist without an explicit allowlist).
* **Policy drift/misconfig** (sandbox docker settings configured but sandbox mode off; ineffective `gateway.nodes.denyCommands` patterns because matching is exact command-name only (for example `system.run`) and does not inspect shell text; dangerous `gateway.nodes.allowCommands` entries; global `tools.profile="minimal"` overridden by per-agent profiles; extension plugin tools reachable under permissive tool policy).
* **Runtime expectation drift** (for example `tools.exec.host="sandbox"` while sandbox mode is off, which runs directly on the gateway host).
* **Model hygiene** (warn when configured models look legacy; not a hard block).

If you run `--deep`, OpenClaw also attempts a best-effort live Gateway probe.

## Credential storage map

Use this when auditing access or deciding what to back up:

## Security Audit Checklist

When the audit prints findings, treat this as a priority order:

1. **Anything “open” + tools enabled**: lock down DMs/groups first (pairing/allowlists), then tighten tool policy/sandboxing.
2. **Public network exposure** (LAN bind, Funnel, missing auth): fix immediately.
3. **Browser control remote exposure**: treat it like operator access (tailnet-only, pair nodes deliberately, avoid public exposure).
4. **Permissions**: make sure state/config/credentials/auth are not group/world-readable.
5. **Plugins/extensions**: only load what you explicitly trust.
6. **Model choice**: prefer modern, instruction-hardened models for any bot with tools.

## Security audit glossary

High-signal `checkId` values you will most likely see in real deployments (not exhaustive):

| `checkId`                                                     | Severity      | Why it matters                                                                       | Primary fix key/path                                                                                 | Auto-fix |
| ------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | -------- |
| `fs.state_dir.perms_world_writable`                           | critical      | Other users/processes can modify full OpenClaw state                                 | filesystem perms on `~/.openclaw`                                                                    | yes      |
| `fs.config.perms_writable`                                    | critical      | Others can change auth/tool policy/config                                            | filesystem perms on `~/.openclaw/openclaw.json`                                                      | yes      |
| `fs.config.perms_world_readable`                              | critical      | Config can expose tokens/settings                                                    | filesystem perms on config file                                                                      | yes      |
| `gateway.bind_no_auth`                                        | critical      | Remote bind without shared secret                                                    | `gateway.bind`, `gateway.auth.*`                                                                     | no       |
| `gateway.loopback_no_auth`                                    | critical      | Reverse-proxied loopback may become unauthenticated                                  | `gateway.auth.*`, proxy setup                                                                        | no       |
| `gateway.http.no_auth`                                        | warn/critical | Gateway HTTP APIs reachable with `auth.mode="none"`                                  | `gateway.auth.mode`, `gateway.http.endpoints.*`                                                      | no       |
| `gateway.tools_invoke_http.dangerous_allow`                   | warn/critical | Re-enables dangerous tools over HTTP API                                             | `gateway.tools.allow`                                                                                | no       |
| `gateway.nodes.allow_commands_dangerous`                      | warn/critical | Enables high-impact node commands (camera/screen/contacts/calendar/SMS)              | `gateway.nodes.allowCommands`                                                                        | no       |
| `gateway.tailscale_funnel`                                    | critical      | Public internet exposure                                                             | `gateway.tailscale.mode`                                                                             | no       |
| `gateway.control_ui.allowed_origins_required`                 | critical      | Non-loopback Control UI without explicit browser-origin allowlist                    | `gateway.controlUi.allowedOrigins`                                                                   | no       |
| `gateway.control_ui.host_header_origin_fallback`              | warn/critical | Enables Host-header origin fallback (DNS rebinding hardening downgrade)              | `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback`                                         | no       |
| `gateway.control_ui.insecure_auth`                            | warn          | Insecure-auth compatibility toggle enabled                                           | `gateway.controlUi.allowInsecureAuth`                                                                | no       |
| `gateway.control_ui.device_auth_disabled`                     | critical      | Disables device identity check                                                       | `gateway.controlUi.dangerouslyDisableDeviceAuth`                                                     | no       |
| `gateway.real_ip_fallback_enabled`                            | warn/critical | Trusting `X-Real-IP` fallback can enable source-IP spoofing via proxy misconfig      | `gateway.allowRealIpFallback`, `gateway.trustedProxies`                                              | no       |
| `discovery.mdns_full_mode`                                    | warn/critical | mDNS full mode advertises `cliPath`/`sshPort` metadata on local network              | `discovery.mdns.mode`, `gateway.bind`                                                                | no       |
| `config.insecure_or_dangerous_flags`                          | warn          | Any insecure/dangerous debug flags enabled                                           | multiple keys (see finding detail)                                                                   | no       |
| `hooks.token_reuse_gateway_token`                             | critical      | Hook ingress token also unlocks Gateway auth                                         | `hooks.token`, `gateway.auth.token`                                                                  | no       |
| `hooks.token_too_short`                                       | warn          | Easier brute force on hook ingress                                                   | `hooks.token`                                                                                        | no       |
| `hooks.default_session_key_unset`                             | warn          | Hook agent runs fan out into generated per-request sessions                          | `hooks.defaultSessionKey`                                                                            | no       |
| `hooks.allowed_agent_ids_unrestricted`                        | warn/critical | Authenticated hook callers may route to any configured agent                         | `hooks.allowedAgentIds`                                                                              | no       |
| `hooks.request_session_key_enabled`                           | warn/critical | External caller can choose sessionKey                                                | `hooks.allowRequestSessionKey`                                                                       | no       |
| `hooks.request_session_key_prefixes_missing`                  | warn/critical | No bound on external session key shapes                                              | `hooks.allowedSessionKeyPrefixes`                                                                    | no       |
| `logging.redact_off`                                          | warn          | Sensitive values leak to logs/status                                                 | `logging.redactSensitive`                                                                            | yes      |
| `sandbox.docker_config_mode_off`                              | warn          | Sandbox Docker config present but inactive                                           | `agents.*.sandbox.mode`                                                                              | no       |
| `sandbox.dangerous_network_mode`                              | critical      | Sandbox Docker network uses `host` or `container:*` namespace-join mode              | `agents.*.sandbox.docker.network`                                                                    | no       |
| `tools.exec.host_sandbox_no_sandbox_defaults`                 | warn          | `exec host=sandbox` resolves to host exec when sandbox is off                        | `tools.exec.host`, `agents.defaults.sandbox.mode`                                                    | no       |
| `tools.exec.host_sandbox_no_sandbox_agents`                   | warn          | Per-agent `exec host=sandbox` resolves to host exec when sandbox is off              | `agents.list[].tools.exec.host`, `agents.list[].sandbox.mode`                                        | no       |
| `tools.exec.security_full_configured`                         | warn/critical | Host exec is running with `security="full"`                                          | `tools.exec.security`, `agents.list[].tools.exec.security`                                           | no       |
| `tools.exec.auto_allow_skills_enabled`                        | warn          | Exec approvals trust skill bins implicitly                                           | `~/.openclaw/exec-approvals.json`                                                                    | no       |
| `tools.exec.allowlist_interpreter_without_strict_inline_eval` | warn          | Interpreter allowlists permit inline eval without forced reapproval                  | `tools.exec.strictInlineEval`, `agents.list[].tools.exec.strictInlineEval`, exec approvals allowlist | no       |
| `tools.exec.safe_bins_interpreter_unprofiled`                 | warn          | Interpreter/runtime bins in `safeBins` without explicit profiles broaden exec risk   | `tools.exec.safeBins`, `tools.exec.safeBinProfiles`, `agents.list[].tools.exec.*`                    | no       |
| `tools.exec.safe_bins_broad_behavior`                         | warn          | Broad-behavior tools in `safeBins` weaken the low-risk stdin-filter trust model      | `tools.exec.safeBins`, `agents.list[].tools.exec.safeBins`                                           | no       |
| `skills.workspace.symlink_escape`                             | warn          | Workspace `skills/**/SKILL.md` resolves outside workspace root (symlink-chain drift) | workspace `skills/**` filesystem state                                                               | no       |
| `security.exposure.open_channels_with_exec`                   | warn/critical | Shared/public rooms can reach exec-enabled agents                                    | `channels.*.dmPolicy`, `channels.*.groupPolicy`, `tools.exec.*`, `agents.list[].tools.exec.*`        | no       |
| `security.exposure.open_groups_with_elevated`                 | critical      | Open groups + elevated tools create high-impact prompt-injection paths               | `channels.*.groupPolicy`, `tools.elevated.*`                                                         | no       |
| `security.exposure.open_groups_with_runtime_or_fs`            | critical/warn | Open groups can reach command/file tools without sandbox/workspace guards            | `channels.*.groupPolicy`, `tools.profile/deny`, `tools.fs.workspaceOnly`, `agents.*.sandbox.mode`    | no       |
| `security.trust_model.multi_user_heuristic`                   | warn          | Config looks multi-user while gateway trust model is personal-assistant              | split trust boundaries, or shared-user hardening (`sandbox.mode`, tool deny/workspace scoping)       | no       |
| `tools.profile_minimal_overridden`                            | warn          | Agent overrides bypass global minimal profile                                        | `agents.list[].tools.profile`                                                                        | no       |
| `plugins.tools_reachable_permissive_policy`                   | warn          | Extension tools reachable in permissive contexts                                     | `tools.profile` + tool allow/deny                                                                    | no       |
| `models.small_params`                                         | critical/info | Small models + unsafe tool surfaces raise injection risk                             | model choice + sandbox/tool policy                                                                   | no       |

## Control UI over HTTP

The Control UI needs a **secure context** (HTTPS or localhost) to generate device identity. `gateway.controlUi.allowInsecureAuth` is a local compatibility toggle:

Prefer HTTPS (Tailscale Serve) or open the UI on `127.0.0.1`. For break-glass scenarios only, `gateway.controlUi.dangerouslyDisableDeviceAuth` disables device identity checks entirely. This is a severe security downgrade; keep it off unless you are actively debugging and can revert quickly. `openclaw security audit` warns when this setting is enabled.

## Insecure or dangerous flags summary

`openclaw security audit` includes `config.insecure_or_dangerous_flags` when known insecure/dangerous debug switches are enabled. That check currently aggregates:

Complete `dangerous*` / `dangerously*` config keys defined in OpenClaw config schema:

## Reverse Proxy Configuration

If you run the Gateway behind a reverse proxy (nginx, Caddy, Traefik, etc.), you should configure `gateway.trustedProxies` for proper client IP detection. When the Gateway detects proxy headers from an address that is **not** in `trustedProxies`, it will **not** treat connections as local clients. If gateway auth is disabled, those connections are rejected. This prevents authentication bypass where proxied connections would otherwise appear to come from localhost and receive automatic trust.

When `trustedProxies` is configured, the Gateway uses `X-Forwarded-For` to determine the client IP. `X-Real-IP` is ignored by default unless `gateway.allowRealIpFallback: true` is explicitly set. Good reverse proxy behavior (overwrite incoming forwarding headers):

Bad reverse proxy behavior (append/preserve untrusted forwarding headers):

## HSTS and origin notes

## Local session logs live on disk

OpenClaw stores session transcripts on disk under `~/.openclaw/agents/<agentId>/sessions/*.jsonl`. This is required for session continuity and (optionally) session memory indexing, but it also means **any process/user with filesystem access can read those logs**. Treat disk access as the trust boundary and lock down permissions on `~/.openclaw` (see the audit section below). If you need stronger isolation between agents, run them under separate OS users or separate hosts.

## Node execution (system.run)

If a macOS node is paired, the Gateway can invoke `system.run` on that node. This is **remote code execution** on the Mac:

## Dynamic skills (watcher / remote nodes)

OpenClaw can refresh the skills list mid-session:

Treat skill folders as **trusted code** and restrict who can modify them.

## The Threat Model

Your AI assistant can:

People who message you can:

## Core concept: access control before intelligence

Most failures here are not fancy exploits — they’re “someone messaged the bot and the bot did what they asked.” OpenClaw’s stance:

Slash commands and directives are only honored for **authorized senders**. Authorization is derived from channel allowlists/pairing plus `commands.useAccessGroups` (see [Configuration](https://docs.openclaw.ai/gateway/configuration) and [Slash commands](https://docs.openclaw.ai/tools/slash-commands)). If a channel allowlist is empty or includes `"*"`, commands are effectively open for that channel. `/exec` is a session-only convenience for authorized operators. It does **not** write config or change other sessions.

## Control plane tools risk

Two built-in tools can make persistent control-plane changes:

For any agent/surface that handles untrusted content, deny these by default:

`commands.restart=false` only blocks restart actions. It does not disable `gateway` config/update actions.

## Plugins/extensions

Plugins run **in-process** with the Gateway. Treat them as trusted code:

Details: [Plugins](https://docs.openclaw.ai/tools/plugin)

## DM access model (pairing / allowlist / open / disabled)

All current DM-capable channels support a DM policy (`dmPolicy` or `*.dm.policy`) that gates inbound DMs **before** the message is processed:

Approve via CLI:

Details + files on disk: [Pairing](https://docs.openclaw.ai/channels/pairing)

## DM session isolation (multi-user mode)

By default, OpenClaw routes **all DMs into the main session** so your assistant has continuity across devices and channels. If **multiple people** can DM the bot (open DMs or a multi-person allowlist), consider isolating DM sessions:

This prevents cross-user context leakage while keeping group chats isolated. This is a messaging-context boundary, not a host-admin boundary. If users are mutually adversarial and share the same Gateway host/config, run separate gateways per trust boundary instead.

### Secure DM mode (recommended)

Treat the snippet above as **secure DM mode**:

If you run multiple accounts on the same channel, use `per-account-channel-peer` instead. If the same person contacts you on multiple channels, use `session.identityLinks` to collapse those DM sessions into one canonical identity. See [Session Management](https://docs.openclaw.ai/concepts/session) and [Configuration](https://docs.openclaw.ai/gateway/configuration).

## Allowlists (DM + groups) - terminology

OpenClaw has two separate “who can trigger me?” layers:

Details: [Configuration](https://docs.openclaw.ai/gateway/configuration) and [Groups](https://docs.openclaw.ai/channels/groups)

## Prompt injection (what it is, why it matters)

Prompt injection is when an attacker crafts a message that manipulates the model into doing something unsafe (“ignore your instructions”, “dump your filesystem”, “follow this link and run commands”, etc.). Even with strong system prompts, **prompt injection is not solved**. System prompt guardrails are soft guidance only; hard enforcement comes from tool policy, exec approvals, sandboxing, and channel allowlists (and operators can disable these by design). What helps in practice:

* Keep inbound DMs locked down (pairing/allowlists).
* Prefer mention gating in groups; avoid “always-on” bots in public rooms.
* Treat links, attachments, and pasted instructions as hostile by default.
* Run sensitive tool execution in a sandbox; keep secrets out of the agent’s reachable filesystem.
* Note: sandboxing is opt-in. If sandbox mode is off, exec runs on the gateway host even though tools.exec.host defaults to sandbox, and host exec does not require approvals unless you set host=gateway and configure exec approvals.
* Limit high-risk tools (`exec`, `browser`, `web_fetch`, `web_search`) to trusted agents or explicit allowlists.
* If you allowlist interpreters (`python`, `node`, `ruby`, `perl`, `php`, `lua`, `osascript`), enable `tools.exec.strictInlineEval` so inline eval forms still need explicit approval.
* **Model choice matters:** older/smaller/legacy models are significantly less robust against prompt injection and tool misuse. For tool-enabled agents, use the strongest latest-generation, instruction-hardened model available.

Red flags to treat as untrusted:

## Unsafe external content bypass flags

OpenClaw includes explicit bypass flags that disable external-content safety wrapping:

Guidance:

Hooks risk note:

### Prompt injection does not require public DMs

Even if **only you** can message the bot, prompt injection can still happen via any **untrusted content** the bot reads (web search/fetch results, browser pages, emails, docs, attachments, pasted logs/code). In other words: the sender is not the only threat surface; the **content itself** can carry adversarial instructions. When tools are enabled, the typical risk is exfiltrating context or triggering tool calls. Reduce the blast radius by:

### Model strength (security note)

Prompt injection resistance is **not** uniform across model tiers. Smaller/cheaper models are generally more susceptible to tool misuse and instruction hijacking, especially under adversarial prompts.

Recommendations:

## Reasoning & verbose output in groups

`/reasoning` and `/verbose` can expose internal reasoning or tool output that was not meant for a public channel. In group settings, treat them as **debug only** and keep them off unless you explicitly need them. Guidance:

## Configuration Hardening (examples)

### 0) File permissions

Keep config + state private on the gateway host:

`openclaw doctor` can warn and offer to tighten these permissions.

### 0.4) Network exposure (bind + port + firewall)

The Gateway multiplexes **WebSocket + HTTP** on a single port:

This HTTP surface includes the Control UI and the canvas host:

If you load canvas content in a normal browser, treat it like any other untrusted web page:

Bind mode controls where the Gateway listens:

Rules of thumb:

### 0.4.1) Docker port publishing + UFW (`DOCKER-USER`)

If you run OpenClaw with Docker on a VPS, remember that published container ports (`-p HOST:CONTAINER` or Compose `ports:`) are routed through Docker’s forwarding chains, not only host `INPUT` rules. To keep Docker traffic aligned with your firewall policy, enforce rules in `DOCKER-USER` (this chain is evaluated before Docker’s own accept rules). On many modern distros, `iptables`/`ip6tables` use the `iptables-nft` frontend and still apply these rules to the nftables backend. Minimal allowlist example (IPv4):

IPv6 has separate tables. Add a matching policy in `/etc/ufw/after6.rules` if Docker IPv6 is enabled. Avoid hardcoding interface names like `eth0` in docs snippets. Interface names vary across VPS images (`ens3`, `enp*`, etc.) and mismatches can accidentally skip your deny rule. Quick validation after reload:

Expected external ports should be only what you intentionally expose (for most setups: SSH + your reverse proxy ports).

### 0.4.2) mDNS/Bonjour discovery (information disclosure)

The Gateway broadcasts its presence via mDNS (`_openclaw-gw._tcp` on port 5353) for local device discovery. In full mode, this includes TXT records that may expose operational details:

**Operational security consideration:** Broadcasting infrastructure details makes reconnaissance easier for anyone on the local network. Even “harmless” info like filesystem paths and SSH availability helps attackers map your environment. **Recommendations:**

1. **Minimal mode** (default, recommended for exposed gateways): omit sensitive fields from mDNS broadcasts:
2. **Disable entirely** if you don’t need local device discovery:
3. **Full mode** (opt-in): include `cliPath` + `sshPort` in TXT records:
4. **Environment variable** (alternative): set `OPENCLAW_DISABLE_BONJOUR=1` to disable mDNS without config changes.

In minimal mode, the Gateway still broadcasts enough for device discovery (`role`, `gatewayPort`, `transport`) but omits `cliPath` and `sshPort`. Apps that need CLI path information can fetch it via the authenticated WebSocket connection instead.

### 0.5) Lock down the Gateway WebSocket (local auth)

Gateway auth is **required by default**. If no token/password is configured, the Gateway refuses WebSocket connections (fail‑closed). Onboarding generates a token by default (even for loopback) so local clients must authenticate. Set a token so **all** WS clients must authenticate:

Doctor can generate one for you: `openclaw doctor --generate-gateway-token`. Note: `gateway.remote.token` / `.password` are client credential sources. They do **not** protect local WS access by themselves. Local call paths can use `gateway.remote.*` as fallback only when `gateway.auth.*` is unset. If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking). Optional: pin remote TLS with `gateway.remote.tlsFingerprint` when using `wss://`. Plaintext `ws://` is loopback-only by default. For trusted private-network paths, set `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` on the client process as break-glass. Local device pairing:

Auth modes:

Rotation checklist (token/password):

1. Generate/set a new secret (`gateway.auth.token` or `OPENCLAW_GATEWAY_PASSWORD`).
2. Restart the Gateway (or restart the macOS app if it supervises the Gateway).
3. Update any remote clients (`gateway.remote.token` / `.password` on machines that call into the Gateway).
4. Verify you can no longer connect with the old credentials.

When `gateway.auth.allowTailscale` is `true` (default for Serve), OpenClaw accepts Tailscale Serve identity headers (`tailscale-user-login`) for Control UI/WebSocket authentication. OpenClaw verifies the identity by resolving the `x-forwarded-for` address through the local Tailscale daemon (`tailscale whois`) and matching it to the header. This only triggers for requests that hit loopback and include `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host` as injected by Tailscale. HTTP API endpoints (for example `/v1/*`, `/tools/invoke`, and `/api/channels/*`) still require token/password auth. Important boundary note:

**Trust assumption:** tokenless Serve auth assumes the gateway host is trusted. Do not treat this as protection against hostile same-host processes. If untrusted local code may run on the gateway host, disable `gateway.auth.allowTailscale` and require token/password auth. **Security rule:** do not forward these headers from your own reverse proxy. If you terminate TLS or proxy in front of the gateway, disable `gateway.auth.allowTailscale` and use token/password auth (or [Trusted Proxy Auth](https://docs.openclaw.ai/gateway/trusted-proxy-auth)) instead. Trusted proxies:

See [Tailscale](https://docs.openclaw.ai/gateway/tailscale) and [Web overview](https://docs.openclaw.ai/web).

### 0.6.1) Browser control via node host (recommended)

If your Gateway is remote but the browser runs on another machine, run a **node host** on the browser machine and let the Gateway proxy browser actions (see [Browser tool](https://docs.openclaw.ai/tools/browser)). Treat node pairing like admin access. Recommended pattern:

Avoid:

### 0.7) Secrets on disk (sensitive data)

Assume anything under `~/.openclaw/` (or `$OPENCLAW_STATE_DIR/`) may contain secrets or private data:

Hardening tips:

### 0.8) Logs + transcripts (redaction + retention)

Logs and transcripts can leak sensitive info even when access controls are correct:

Recommendations:

Details: [Logging](https://docs.openclaw.ai/gateway/logging)

### 1) DMs: pairing by default

### 2) Groups: require mention everywhere

In group chats, only respond when explicitly mentioned.

### 3. Separate Numbers

Consider running your AI on a separate phone number from your personal one:

### 4. Read-Only Mode (Today, via sandbox + tools)

You can already build a read-only profile by combining:

We may add a single `readOnlyMode` flag later to simplify this configuration. Additional hardening options:

### 5) Secure baseline (copy/paste)

One “safe default” config that keeps the Gateway private, requires DM pairing, and avoids always-on group bots:

```
{
  gateway: {
    mode: "local",
    bind: "loopback",
    port: 18789,
    auth: { mode: "token", token: "your-long-random-token" },
  },
  channels: {
    whatsapp: {
      dmPolicy: "pairing",
      groups: { "*": { requireMention: true } },
    },
  },
}
```

If you want “safer by default” tool execution too, add a sandbox + deny dangerous tools for any non-owner agent (example below under “Per-agent access profiles”). Built-in baseline for chat-driven agent turns: non-owner senders cannot use the `cron` or `gateway` tools.

## Sandboxing (recommended)

Dedicated doc: [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing) Two complementary approaches:

Note: to prevent cross-agent access, keep `agents.defaults.sandbox.scope` at `"agent"` (default) or `"session"` for stricter per-session isolation. `scope: "shared"` uses a single container/workspace. Also consider agent workspace access inside the sandbox:

Important: `tools.elevated` is the global baseline escape hatch that runs exec on the host. Keep `tools.elevated.allowFrom` tight and don’t enable it for strangers. You can further restrict elevated per agent via `agents.list[].tools.elevated`. See [Elevated Mode](https://docs.openclaw.ai/tools/elevated).

### Sub-agent delegation guardrail

If you allow session tools, treat delegated sub-agent runs as another boundary decision:

## Browser control risks

Enabling browser control gives the model the ability to drive a real browser. If that browser profile already contains logged-in sessions, the model can access those accounts and data. Treat browser profiles as **sensitive state**:

### Browser SSRF policy (trusted-network default)

OpenClaw’s browser network policy defaults to the trusted-operator model: private/internal destinations are allowed unless you explicitly disable them.

Example strict policy:

## Per-agent access profiles (multi-agent)

With multi-agent routing, each agent can have its own sandbox + tool policy: use this to give **full access**, **read-only**, or **no access** per agent. See [Multi-Agent Sandbox & Tools](https://docs.openclaw.ai/tools/multi-agent-sandbox-tools) for full details and precedence rules. Common use cases:

### Example: full access (no sandbox)

### Example: read-only tools + read-only workspace

```
{
  agents: {
    list: [
      {
        id: "family",
        workspace: "~/.openclaw/workspace-family",
        sandbox: {
          mode: "all",
          scope: "agent",
          workspaceAccess: "ro",
        },
        tools: {
          allow: ["read"],
          deny: ["write", "edit", "apply_patch", "exec", "process", "browser"],
        },
      },
    ],
  },
}
```

### Example: no filesystem/shell access (provider messaging allowed)

```
{
  agents: {
    list: [
      {
        id: "public",
        workspace: "~/.openclaw/workspace-public",
        sandbox: {
          mode: "all",
          scope: "agent",
          workspaceAccess: "none",
        },
        // Session tools can reveal sensitive data from transcripts. By default OpenClaw limits these tools
        // to the current session + spawned subagent sessions, but you can clamp further if needed.
        // See `tools.sessions.visibility` in the configuration reference.
        tools: {
          sessions: { visibility: "tree" }, // self | tree | agent | all
          allow: [
            "sessions_list",
            "sessions_history",
            "sessions_send",
            "sessions_spawn",
            "session_status",
            "whatsapp",
            "telegram",
            "slack",
            "discord",
          ],
          deny: [
            "read",
            "write",
            "edit",
            "apply_patch",
            "exec",
            "process",
            "browser",
            "canvas",
            "nodes",
            "cron",
            "gateway",
            "image",
          ],
        },
      },
    ],
  },
}
```

## What to Tell Your AI

Include security guidelines in your agent’s system prompt:

## Incident Response

If your AI does something bad:

### Contain

1. **Stop it:** stop the macOS app (if it supervises the Gateway) or terminate your `openclaw gateway` process.
2. **Close exposure:** set `gateway.bind: "loopback"` (or disable Tailscale Funnel/Serve) until you understand what happened.
3. **Freeze access:** switch risky DMs/groups to `dmPolicy: "disabled"` / require mentions, and remove `"*"` allow-all entries if you had them.

### Rotate (assume compromise if secrets leaked)

1. Rotate Gateway auth (`gateway.auth.token` / `OPENCLAW_GATEWAY_PASSWORD`) and restart.
2. Rotate remote client secrets (`gateway.remote.token` / `.password`) on any machine that can call the Gateway.
3. Rotate provider/API credentials (WhatsApp creds, Slack/Discord tokens, model/API keys in `auth-profiles.json`, and encrypted secrets payload values when used).

### Audit

1. Check Gateway logs: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` (or `logging.file`).
2. Review the relevant transcript(s): `~/.openclaw/agents/<agentId>/sessions/*.jsonl`.
3. Review recent config changes (anything that could have widened access: `gateway.bind`, `gateway.auth`, dm/group policies, `tools.elevated`, plugin changes).
4. Re-run `openclaw security audit --deep` and confirm critical findings are resolved.

### Collect for a report

## Secret Scanning (detect-secrets)

CI runs the `detect-secrets` pre-commit hook in the `secrets` job. Pushes to `main` always run an all-files scan. Pull requests use a changed-file fast path when a base commit is available, and fall back to an all-files scan otherwise. If it fails, there are new candidates not yet in the baseline.

### If CI fails

1. Reproduce locally:
2. Understand the tools:
3. For real secrets: rotate/remove them, then re-run the scan to update the baseline.
4. For false positives: run the interactive audit and mark them as false:
5. If you need new excludes, add them to `.detect-secrets.cfg` and regenerate the baseline with matching `--exclude-files` / `--exclude-lines` flags (the config file is reference-only; detect-secrets doesn’t read it automatically).

Commit the updated `.secrets.baseline` once it reflects the intended state.

## Reporting Security Issues

Found a vulnerability in OpenClaw? Please report responsibly:

1. Email: <security@openclaw.ai>
2. Don’t post publicly until fixed
3. We’ll credit you (unless you prefer anonymity)

----
url: https://docs.openclaw.ai/nodes/media-understanding
----

# Media Understanding - OpenClaw

## Media Understanding - Inbound (2026-01-17)

OpenClaw can **summarize inbound media** (image/audio/video) before the reply pipeline runs. It auto‑detects when local tools or provider keys are available, and can be disabled or customized. If understanding is off, models still receive the original files/URLs as usual. Vendor-specific media behavior is registered by vendor plugins, while OpenClaw core owns the shared `tools.media` config, fallback order, and reply-pipeline integration.

## Goals

## High-level behavior

1. Collect inbound attachments (`MediaPaths`, `MediaUrls`, `MediaTypes`).
2. For each enabled capability (image/audio/video), select attachments per policy (default: **first**).
3. Choose the first eligible model entry (size + capability + auth).
4. If a model fails or the media is too large, **fall back to the next entry**.
5. On success:

If understanding fails or is disabled, **the reply flow continues** with the original body + attachments.

## Config overview

`tools.media` supports **shared models** plus per‑capability overrides:

### Model entries

Each `models[]` entry can be **provider** or **CLI**:

```
{
  type: "provider", // default if omitted
  provider: "openai",
  model: "gpt-5.2",
  prompt: "Describe the image in <= 500 chars.",
  maxChars: 500,
  maxBytes: 10485760,
  timeoutSeconds: 60,
  capabilities: ["image"], // optional, used for multi‑modal entries
  profile: "vision-profile",
  preferredProfile: "vision-fallback",
}
```

```
{
  type: "cli",
  command: "gemini",
  args: [
    "-m",
    "gemini-3-flash",
    "--allowed-tools",
    "read_file",
    "Read the media at {{MediaPath}} and describe it in <= {{MaxChars}} characters.",
  ],
  maxChars: 500,
  maxBytes: 52428800,
  timeoutSeconds: 120,
  capabilities: ["video", "image"],
}
```

CLI templates can also use:

## Defaults and limits

Recommended defaults:

Rules:

### Auto-detect media understanding (default)

If `tools.media.<capability>.enabled` is **not** set to `false` and you haven’t configured models, OpenClaw auto-detects in this order and **stops at the first working option**:

1. **Local CLIs** (audio only; if installed)
2. **Gemini CLI** (`gemini`) using `read_many_files`
3. **Provider keys**

To disable auto-detection, set:

Note: Binary detection is best-effort across macOS/Linux/Windows; ensure the CLI is on `PATH` (we expand `~`), or set an explicit CLI model with a full command path.

### Proxy environment support (provider models)

When provider-based **audio** and **video** media understanding is enabled, OpenClaw honors standard outbound proxy environment variables for provider HTTP calls:

If no proxy env vars are set, media understanding uses direct egress. If the proxy value is malformed, OpenClaw logs a warning and falls back to direct fetch.

## Capabilities (optional)

If you set `capabilities`, the entry only runs for those media types. For shared lists, OpenClaw can infer defaults:

For CLI entries, **set `capabilities` explicitly** to avoid surprising matches. If you omit `capabilities`, the entry is eligible for the list it appears in.

## Provider support matrix (OpenClaw integrations)

| Capability | Provider integration                               | Notes                                                                   |
| ---------- | -------------------------------------------------- | ----------------------------------------------------------------------- |
| Image      | OpenAI, Anthropic, Google, MiniMax, Moonshot, Z.AI | Vendor plugins register image support against core media understanding. |
| Audio      | OpenAI, Groq, Deepgram, Google, Mistral            | Provider transcription (Whisper/Deepgram/Gemini/Voxtral).               |
| Video      | Google, Moonshot                                   | Provider video understanding via vendor plugins.                        |

## Model selection guidance

## Attachment policy

Per‑capability `attachments` controls which attachments are processed:

When `mode: "all"`, outputs are labeled `[Image 1/2]`, `[Audio 2/2]`, etc.

## Config examples

### 1) Shared models list + overrides

```
{
  tools: {
    media: {
      models: [
        { provider: "openai", model: "gpt-5.2", capabilities: ["image"] },
        {
          provider: "google",
          model: "gemini-3-flash-preview",
          capabilities: ["image", "audio", "video"],
        },
        {
          type: "cli",
          command: "gemini",
          args: [
            "-m",
            "gemini-3-flash",
            "--allowed-tools",
            "read_file",
            "Read the media at {{MediaPath}} and describe it in <= {{MaxChars}} characters.",
          ],
          capabilities: ["image", "video"],
        },
      ],
      audio: {
        attachments: { mode: "all", maxAttachments: 2 },
      },
      video: {
        maxChars: 500,
      },
    },
  },
}
```

### 2) Audio + Video only (image off)

```
{
  tools: {
    media: {
      audio: {
        enabled: true,
        models: [
          { provider: "openai", model: "gpt-4o-mini-transcribe" },
          {
            type: "cli",
            command: "whisper",
            args: ["--model", "base", "{{MediaPath}}"],
          },
        ],
      },
      video: {
        enabled: true,
        maxChars: 500,
        models: [
          { provider: "google", model: "gemini-3-flash-preview" },
          {
            type: "cli",
            command: "gemini",
            args: [
              "-m",
              "gemini-3-flash",
              "--allowed-tools",
              "read_file",
              "Read the media at {{MediaPath}} and describe it in <= {{MaxChars}} characters.",
            ],
          },
        ],
      },
    },
  },
}
```

### 3) Optional image understanding

```
{
  tools: {
    media: {
      image: {
        enabled: true,
        maxBytes: 10485760,
        maxChars: 500,
        models: [
          { provider: "openai", model: "gpt-5.2" },
          { provider: "anthropic", model: "claude-opus-4-6" },
          {
            type: "cli",
            command: "gemini",
            args: [
              "-m",
              "gemini-3-flash",
              "--allowed-tools",
              "read_file",
              "Read the media at {{MediaPath}} and describe it in <= {{MaxChars}} characters.",
            ],
          },
        ],
      },
    },
  },
}
```

### 4) Multi-modal single entry (explicit capabilities)

```
{
  tools: {
    media: {
      image: {
        models: [
          {
            provider: "google",
            model: "gemini-3.1-pro-preview",
            capabilities: ["image", "video", "audio"],
          },
        ],
      },
      audio: {
        models: [
          {
            provider: "google",
            model: "gemini-3.1-pro-preview",
            capabilities: ["image", "video", "audio"],
          },
        ],
      },
      video: {
        models: [
          {
            provider: "google",
            model: "gemini-3.1-pro-preview",
            capabilities: ["image", "video", "audio"],
          },
        ],
      },
    },
  },
}
```

## Status output

When media understanding runs, `/status` includes a short summary line:

This shows per‑capability outcomes and the chosen provider/model when applicable.

## Notes

----
url: https://docs.openclaw.ai/install/kubernetes
----

# Kubernetes - OpenClaw

A minimal starting point for running OpenClaw on Kubernetes — not a production-ready deployment. It covers the core resources and is meant to be adapted to your environment.

## Why not Helm?

OpenClaw is a single container with some config files. The interesting customization is in agent content (markdown files, skills, config overrides), not infrastructure templating. Kustomize handles overlays without the overhead of a Helm chart. If your deployment grows more complex, a Helm chart can be layered on top of these manifests.

## What you need

## Quick start

Retrieve the gateway token and paste it into the Control UI:

For local debugging, `./scripts/k8s/deploy.sh --show-token` prints the token after deploy.

## Local testing with Kind

If you don’t have a cluster, create one locally with [Kind](https://kind.sigs.k8s.io/):

Then deploy as usual with `./scripts/k8s/deploy.sh`.

## Step by step

### 1) Deploy

**Option A** — API key in environment (one step):

The script creates a Kubernetes Secret with the API key and an auto-generated gateway token, then deploys. If the Secret already exists, it preserves the current gateway token and any provider keys not being changed. **Option B** — create the secret separately:

Use `--show-token` with either command if you want the token printed to stdout for local testing.

### 2) Access the gateway

## What gets deployed

## Customization

### Agent instructions

Edit the `AGENTS.md` in `scripts/k8s/manifests/configmap.yaml` and redeploy:

### Gateway config

Edit `openclaw.json` in `scripts/k8s/manifests/configmap.yaml`. See [Gateway configuration](https://docs.openclaw.ai/gateway/configuration) for the full reference.

### Add providers

Re-run with additional keys exported:

Existing provider keys stay in the Secret unless you overwrite them. Or patch the Secret directly:

### Custom namespace

### Custom image

Edit the `image` field in `scripts/k8s/manifests/deployment.yaml`:

### Expose beyond port-forward

The default manifests bind the gateway to loopback inside the pod. That works with `kubectl port-forward`, but it does not work with a Kubernetes `Service` or Ingress path that needs to reach the pod IP. If you want to expose the gateway through an Ingress or load balancer:

## Re-deploy

This applies all manifests and restarts the pod to pick up any config or secret changes.

## Teardown

This deletes the namespace and all resources in it, including the PVC.

## Architecture notes

## File structure

----
url: https://docs.openclaw.ai/install/exe-dev
----

# exe.dev - OpenClaw

## [​](#exe-dev)exe.dev

Goal: OpenClaw Gateway running on an exe.dev VM, reachable from your laptop via: `https://<vm-name>.exe.xyz` This page assumes exe.dev’s default **exeuntu** image. If you picked a different distro, map packages accordingly.

## [​](#beginner-quick-path)Beginner quick path

1. <https://exe.new/openclaw>
2. Fill in your auth key/token as needed
3. Click on “Agent” next to your VM and wait for Shelley to finish provisioning
4. Open `https://<vm-name>.exe.xyz/` and paste your gateway token to authenticate
5. Approve any pending device pairing requests with `openclaw devices approve <requestId>`

## [​](#what-you-need)What you need

* exe.dev account
* `ssh exe.dev` access to [exe.dev](https://exe.dev/) virtual machines (optional)

## [​](#automated-install-with-shelley)Automated Install with Shelley

Shelley, [exe.dev](https://exe.dev/)’s agent, can install OpenClaw instantly with our prompt. The prompt used is as below:

```
Set up OpenClaw (https://docs.openclaw.ai/install) on this VM. Use the non-interactive and accept-risk flags for openclaw onboarding. Add the supplied auth or token as needed. Configure nginx to forward from the default port 18789 to the root location on the default enabled site config, making sure to enable Websocket support. Pairing is done by "openclaw devices list" and "openclaw devices approve <request id>". Make sure the dashboard shows that OpenClaw's health is OK. exe.dev handles forwarding from port 8000 to port 80/443 and HTTPS for us, so the final "reachable" should be <vm-name>.exe.xyz, without port specification.
```

## [​](#manual-installation)Manual installation

## [​](#1-create-the-vm)1) Create the VM

From your device:

```
ssh exe.dev new
```

Then connect:

```
ssh <vm-name>.exe.xyz
```

Tip: keep this VM **stateful**. OpenClaw stores state under `~/.openclaw/` and `~/.openclaw/workspace/`.

## [​](#2-install-prerequisites-on-the-vm)2) Install prerequisites (on the VM)

```
sudo apt-get update
sudo apt-get install -y git curl jq ca-certificates openssl
```

## [​](#3-install-openclaw)3) Install OpenClaw

Run the OpenClaw install script:

```
curl -fsSL https://openclaw.ai/install.sh | bash
```

## [​](#4-setup-nginx-to-proxy-openclaw-to-port-8000)4) Setup nginx to proxy OpenClaw to port 8000

Edit `/etc/nginx/sites-enabled/default` with

```
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 8000;
    listen [::]:8000;

    server_name _;

    location / {
        proxy_pass http://127.0.0.1:18789;
        proxy_http_version 1.1;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Standard proxy headers
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeout settings for long-lived connections
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}
```

## [​](#5-access-openclaw-and-grant-privileges)5) Access OpenClaw and grant privileges

Access `https://<vm-name>.exe.xyz/` (see the Control UI output from onboarding). If it prompts for auth, paste the token from `gateway.auth.token` on the VM (retrieve with `openclaw config get gateway.auth.token`, or generate one with `openclaw doctor --generate-gateway-token`). Approve devices with `openclaw devices list` and `openclaw devices approve <requestId>`. When in doubt, use Shelley from your browser!

## [​](#remote-access)Remote Access

Remote access is handled by [exe.dev](https://exe.dev/)’s authentication. By default, HTTP traffic from port 8000 is forwarded to `https://<vm-name>.exe.xyz` with email auth.

## [​](#updating)Updating

```
npm i -g openclaw@latest
openclaw doctor
openclaw gateway restart
openclaw health
```

Guide: [Updating](https://docs.openclaw.ai/install/updating)

----
url: https://docs.openclaw.ai/providers/litellm
----

# LiteLLM - OpenClaw

[LiteLLM](https://litellm.ai/) is an open-source LLM gateway that provides a unified API to 100+ model providers. Route OpenClaw through LiteLLM to get centralized cost tracking, logging, and the flexibility to switch backends without changing your OpenClaw config.

## Why use LiteLLM with OpenClaw?

## Quick start

### Via onboarding

### Manual setup

1. Start LiteLLM Proxy:

2) Point OpenClaw to LiteLLM:

That’s it. OpenClaw now routes through LiteLLM.

## Configuration

### Environment variables

### Config file

```
{
  models: {
    providers: {
      litellm: {
        baseUrl: "http://localhost:4000",
        apiKey: "${LITELLM_API_KEY}",
        api: "openai-completions",
        models: [
          {
            id: "claude-opus-4-6",
            name: "Claude Opus 4.6",
            reasoning: true,
            input: ["text", "image"],
            contextWindow: 200000,
            maxTokens: 64000,
          },
          {
            id: "gpt-4o",
            name: "GPT-4o",
            reasoning: false,
            input: ["text", "image"],
            contextWindow: 128000,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
  agents: {
    defaults: {
      model: { primary: "litellm/claude-opus-4-6" },
    },
  },
}
```

## Virtual keys

Create a dedicated key for OpenClaw with spend limits:

Use the generated key as `LITELLM_API_KEY`.

## Model routing

LiteLLM can route model requests to different backends. Configure in your LiteLLM `config.yaml`:

OpenClaw keeps requesting `claude-opus-4-6` — LiteLLM handles the routing.

## Viewing usage

Check LiteLLM’s dashboard or API:

## Notes

## See also

----
url: https://docs.openclaw.ai/concepts/markdown-formatting
----

# Markdown Formatting - OpenClaw

## [​](#markdown-formatting)Markdown formatting

OpenClaw formats outbound Markdown by converting it into a shared intermediate representation (IR) before rendering channel-specific output. The IR keeps the source text intact while carrying style/link spans so chunking and rendering can stay consistent across channels.

## [​](#goals)Goals

* **Consistency:** one parse step, multiple renderers.
* **Safe chunking:** split text before rendering so inline formatting never breaks across chunks.
* **Channel fit:** map the same IR to Slack mrkdwn, Telegram HTML, and Signal style ranges without re-parsing Markdown.

## [​](#pipeline)Pipeline

1. **Parse Markdown -> IR**

   * IR is plain text plus style spans (bold/italic/strike/code/spoiler) and link spans.
   * Offsets are UTF-16 code units so Signal style ranges align with its API.
   * Tables are parsed only when a channel opts into table conversion.

2. **Chunk IR (format-first)**

   * Chunking happens on the IR text before rendering.
   * Inline formatting does not split across chunks; spans are sliced per chunk.

3. **Render per channel**

   * **Slack:** mrkdwn tokens (bold/italic/strike/code), links as `<url|label>`.
   * **Telegram:** HTML tags (`<b>`, `<i>`, `<s>`, `<code>`, `<pre><code>`, `<a href>`).
   * **Signal:** plain text + `text-style` ranges; links become `label (url)` when label differs.

## [​](#ir-example)IR example

Input Markdown:

```
Hello **world** — see [docs](https://docs.openclaw.ai).
```

IR (schematic):

```
{
  "text": "Hello world — see docs.",
  "styles": [{ "start": 6, "end": 11, "style": "bold" }],
  "links": [{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }]
}
```

## [​](#where-it-is-used)Where it is used

* Slack, Telegram, and Signal outbound adapters render from the IR.
* Other channels (WhatsApp, iMessage, Microsoft Teams, Discord) still use plain text or their own formatting rules, with Markdown table conversion applied before chunking when enabled.

## [​](#table-handling)Table handling

Markdown tables are not consistently supported across chat clients. Use `markdown.tables` to control conversion per channel (and per account).

* `code`: render tables as code blocks (default for most channels).
* `bullets`: convert each row into bullet points (default for Signal + WhatsApp).
* `off`: disable table parsing and conversion; raw table text passes through.

Config keys:

```
channels:
  discord:
    markdown:
      tables: code
    accounts:
      work:
        markdown:
          tables: off
```

## [​](#chunking-rules)Chunking rules

* Chunk limits come from channel adapters/config and are applied to the IR text.
* Code fences are preserved as a single block with a trailing newline so channels render them correctly.
* List prefixes and blockquote prefixes are part of the IR text, so chunking does not split mid-prefix.
* Inline styles (bold/italic/strike/inline-code/spoiler) are never split across chunks; the renderer reopens styles inside each chunk.

If you need more on chunking behavior across channels, see [Streaming + chunking](https://docs.openclaw.ai/concepts/streaming).

## [​](#link-policy)Link policy

* **Slack:** `[label](url)` -> `<url|label>`; bare URLs remain bare. Autolink is disabled during parse to avoid double-linking.
* **Telegram:** `[label](url)` -> `<a href="url">label</a>` (HTML parse mode).
* **Signal:** `[label](url)` -> `label (url)` unless label matches the URL.

## [​](#spoilers)Spoilers

Spoiler markers (`||spoiler||`) are parsed only for Signal, where they map to SPOILER style ranges. Other channels treat them as plain text.

## [​](#how-to-add-or-update-a-channel-formatter)How to add or update a channel formatter

1. **Parse once:** use the shared `markdownToIR(...)` helper with channel-appropriate options (autolink, heading style, blockquote prefix).
2. **Render:** implement a renderer with `renderMarkdownWithMarkers(...)` and a style marker map (or Signal style ranges).
3. **Chunk:** call `chunkMarkdownIR(...)` before rendering; render each chunk.
4. **Wire adapter:** update the channel outbound adapter to use the new chunker and renderer.
5. **Test:** add or update format tests and an outbound delivery test if the channel uses chunking.

## [​](#common-gotchas)Common gotchas

* Slack angle-bracket tokens (`<@U123>`, `<#C123>`, `<https://...>`) must be preserved; escape raw HTML safely.
* Telegram HTML requires escaping text outside tags to avoid broken markup.
* Signal style ranges depend on UTF-16 offsets; do not use code point offsets.
* Preserve trailing newlines for fenced code blocks so closing markers land on their own line.

----
url: https://docs.openclaw.ai/channels/twitch
----

# Twitch - OpenClaw

## Twitch (plugin)

Twitch chat support via IRC connection. OpenClaw connects as a Twitch user (bot account) to receive and send messages in channels.

## Plugin required

Twitch ships as a plugin and is not bundled with the core install. Install via CLI (npm registry):

Local checkout (when running from a git repo):

Details: [Plugins](https://docs.openclaw.ai/tools/plugin)

## Quick setup (beginner)

1. Create a dedicated Twitch account for the bot (or use an existing account).
2. Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/)
3. Find your Twitch user ID: <https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/>
4. Configure the token:
5. Start the gateway.

**⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`. Minimal config:

## What it is

## Setup (detailed)

### Generate credentials

Use [Twitch Token Generator](https://twitchtokengenerator.com/):

No manual app registration needed. Tokens expire after several hours.

### Configure the bot

**Env var (default account only):**

**Or config:**

If both env and config are set, config takes precedence.

### Access control (recommended)

Prefer `allowFrom` for a hard allowlist. Use `allowedRoles` instead if you want role-based access. **Available roles:** `"moderator"`, `"owner"`, `"vip"`, `"subscriber"`, `"all"`. **Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent. Find your Twitch user ID: <https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/> (Convert your Twitch username to ID)

## Token refresh (optional)

Tokens from [Twitch Token Generator](https://twitchtokengenerator.com/) cannot be automatically refreshed - regenerate when expired. For automatic token refresh, create your own Twitch application at [Twitch Developer Console](https://dev.twitch.tv/console) and add to config:

The bot automatically refreshes tokens before expiration and logs refresh events.

## Multi-account support

Use `channels.twitch.accounts` with per-account tokens. See [`gateway/configuration`](https://docs.openclaw.ai/gateway/configuration) for the shared pattern. Example (one bot account in two channels):

```
{
  channels: {
    twitch: {
      accounts: {
        channel1: {
          username: "openclaw",
          accessToken: "oauth:abc123...",
          clientId: "xyz789...",
          channel: "vevisk",
        },
        channel2: {
          username: "openclaw",
          accessToken: "oauth:def456...",
          clientId: "uvw012...",
          channel: "secondchannel",
        },
      },
    },
  },
}
```

**Note:** Each account needs its own token (one token per channel).

## Access control

### Role-based restrictions

### Allowlist by User ID (most secure)

### Role-based access (alternative)

`allowFrom` is a hard allowlist. When set, only those user IDs are allowed. If you want role-based access, leave `allowFrom` unset and configure `allowedRoles` instead:

### Disable @mention requirement

By default, `requireMention` is `true`. To disable and respond to all messages:

## Troubleshooting

First, run diagnostic commands:

### Bot does not respond to messages

**Check access control:** Ensure your user ID is in `allowFrom`, or temporarily remove `allowFrom` and set `allowedRoles: ["all"]` to test. **Check the bot is in the channel:** The bot must join the channel specified in `channel`.

### Token issues

**“Failed to connect” or authentication errors:**

### Token refresh not working

**Check logs for refresh events:**

If you see “token refresh disabled (no refresh token)”:

## Config

**Account config:**

**Provider options:**

Full example:

```
{
  channels: {
    twitch: {
      enabled: true,
      username: "openclaw",
      accessToken: "oauth:abc123...",
      clientId: "xyz789...",
      channel: "vevisk",
      clientSecret: "secret123...",
      refreshToken: "refresh456...",
      allowFrom: ["123456789"],
      allowedRoles: ["moderator", "vip"],
      accounts: {
        default: {
          username: "mybot",
          accessToken: "oauth:abc123...",
          clientId: "xyz789...",
          channel: "your_channel",
          enabled: true,
          clientSecret: "secret123...",
          refreshToken: "refresh456...",
          expiresIn: 14400,
          obtainmentTimestamp: 1706092800000,
          allowFrom: ["123456789", "987654321"],
          allowedRoles: ["moderator"],
        },
      },
    },
  },
}
```

## Tool actions

The agent can call `twitch` with action:

Example:

## Safety & ops

## Limits

----
url: https://docs.openclaw.ai/gateway/sandboxing
----

# Sandboxing - OpenClaw

OpenClaw can run **tools inside sandbox backends** to reduce blast radius. This is **optional** and controlled by configuration (`agents.defaults.sandbox` or `agents.list[].sandbox`). If sandboxing is off, tools run on the host. The Gateway stays on the host; tool execution runs in an isolated sandbox when enabled. This is not a perfect security boundary, but it materially limits filesystem and process access when the model does something dumb.

## What gets sandboxed

Not sandboxed:

## Modes

`agents.defaults.sandbox.mode` controls **when** sandboxing is used:

## Scope

`agents.defaults.sandbox.scope` controls **how many containers** are created:

## Backend

`agents.defaults.sandbox.backend` controls **which runtime** provides the sandbox:

SSH-specific config lives under `agents.defaults.sandbox.ssh`. OpenShell-specific config lives under `plugins.entries.openshell.config`.

### Choosing a backend

|                     | Docker                           | SSH                            | OpenShell                                           |
| ------------------- | -------------------------------- | ------------------------------ | --------------------------------------------------- |
| **Where it runs**   | Local container                  | Any SSH-accessible host        | OpenShell managed sandbox                           |
| **Setup**           | `scripts/sandbox-setup.sh`       | SSH key + target host          | OpenShell plugin enabled                            |
| **Workspace model** | Bind-mount or copy               | Remote-canonical (seed once)   | `mirror` or `remote`                                |
| **Network control** | `docker.network` (default: none) | Depends on remote host         | Depends on OpenShell                                |
| **Browser sandbox** | Supported                        | Not supported                  | Not supported yet                                   |
| **Bind mounts**     | `docker.binds`                   | N/A                            | N/A                                                 |
| **Best for**        | Local dev, full isolation        | Offloading to a remote machine | Managed remote sandboxes with optional two-way sync |

### SSH backend

Use `backend: "ssh"` when you want OpenClaw to sandbox `exec`, file tools, and media reads on an arbitrary SSH-accessible machine.

```
{
  agents: {
    defaults: {
      sandbox: {
        mode: "all",
        backend: "ssh",
        scope: "session",
        workspaceAccess: "rw",
        ssh: {
          target: "user@gateway-host:22",
          workspaceRoot: "/tmp/openclaw-sandboxes",
          strictHostKeyChecking: true,
          updateHostKeys: true,
          identityFile: "~/.ssh/id_ed25519",
          certificateFile: "~/.ssh/id_ed25519-cert.pub",
          knownHostsFile: "~/.ssh/known_hosts",
          // Or use SecretRefs / inline contents instead of local files:
          // identityData: { source: "env", provider: "default", id: "SSH_IDENTITY" },
          // certificateData: { source: "env", provider: "default", id: "SSH_CERTIFICATE" },
          // knownHostsData: { source: "env", provider: "default", id: "SSH_KNOWN_HOSTS" },
        },
      },
    },
  },
}
```

How it works:

Authentication material:

This is a **remote-canonical** model. The remote SSH workspace becomes the real sandbox state after the initial seed. Important consequences:

### OpenShell backend

Use `backend: "openshell"` when you want OpenClaw to sandbox tools in an OpenShell-managed remote environment. For the full setup guide, configuration reference, and workspace mode comparison, see the dedicated [OpenShell page](https://docs.openclaw.ai/gateway/openshell). OpenShell reuses the same core SSH transport and remote filesystem bridge as the generic SSH backend, and adds OpenShell-specific lifecycle (`sandbox create/get/delete`, `sandbox ssh-config`) plus the optional `mirror` workspace mode.

```
{
  agents: {
    defaults: {
      sandbox: {
        mode: "all",
        backend: "openshell",
        scope: "session",
        workspaceAccess: "rw",
      },
    },
  },
  plugins: {
    entries: {
      openshell: {
        enabled: true,
        config: {
          from: "openclaw",
          mode: "remote", // mirror | remote
          remoteWorkspaceDir: "/sandbox",
          remoteAgentWorkspaceDir: "/agent",
        },
      },
    },
  },
}
```

OpenShell modes:

Remote transport details:

Current OpenShell limitations:

#### Workspace modes

OpenShell has two workspace models. This is the part that matters most in practice.

##### `mirror`

Use `plugins.entries.openshell.config.mode: "mirror"` when you want the **local workspace to stay canonical**. Behavior:

Use this when:

Tradeoff:

##### `remote`

Use `plugins.entries.openshell.config.mode: "remote"` when you want the **OpenShell workspace to become canonical**. Behavior:

Important consequences:

Use this when:

Choose `mirror` if you think of the sandbox as a temporary execution environment. Choose `remote` if you think of the sandbox as the real workspace.

#### OpenShell lifecycle

OpenShell sandboxes are still managed through the normal sandbox lifecycle:

For `remote` mode, recreate is especially important:

For `mirror` mode, recreate mainly resets the remote execution environment because the local workspace remains canonical anyway.

## Workspace access

`agents.defaults.sandbox.workspaceAccess` controls **what the sandbox can see**:

With the OpenShell backend:

Inbound media is copied into the active sandbox workspace (`media/inbound/*`). Skills note: the `read` tool is sandbox-rooted. With `workspaceAccess: "none"`, OpenClaw mirrors eligible skills into the sandbox workspace (`.../skills`) so they can be read. With `"rw"`, workspace skills are readable from `/workspace/skills`.

## Custom bind mounts

`agents.defaults.sandbox.docker.binds` mounts additional host directories into the container. Format: `host:container:mode` (e.g., `"/home/user/source:/source:rw"`). Global and per-agent binds are **merged** (not replaced). Under `scope: "shared"`, per-agent binds are ignored. `agents.defaults.sandbox.browser.binds` mounts additional host directories into the **sandbox browser** container only.

Example (read-only source + an extra data directory):

```
{
  agents: {
    defaults: {
      sandbox: {
        docker: {
          binds: ["/home/user/source:/source:ro", "/var/data/myapp:/data:ro"],
        },
      },
    },
    list: [
      {
        id: "build",
        sandbox: {
          docker: {
            binds: ["/mnt/cache:/cache:rw"],
          },
        },
      },
    ],
  },
}
```

Security notes:

## Images + setup

Default Docker image: `openclaw-sandbox:bookworm-slim` Build it once:

Note: the default image does **not** include Node. If a skill needs Node (or other runtimes), either bake a custom image or install via `sandbox.docker.setupCommand` (requires network egress + writable root + root user). If you want a more functional sandbox image with common tooling (for example `curl`, `jq`, `nodejs`, `python3`, `git`), build:

Then set `agents.defaults.sandbox.docker.image` to `openclaw-sandbox-common:bookworm-slim`. Sandboxed browser image:

By default, Docker sandbox containers run with **no network**. Override with `agents.defaults.sandbox.docker.network`. The bundled sandbox browser image also applies conservative Chromium startup defaults for containerized workloads. Current container defaults include:

If you need a different runtime profile, use a custom browser image and provide your own entrypoint. For local (non-container) Chromium profiles, use `browser.extraArgs` to append additional startup flags. Security defaults:

Docker installs and the containerized gateway live here: [Docker](https://docs.openclaw.ai/install/docker) For Docker gateway deployments, `scripts/docker/setup.sh` can bootstrap sandbox config. Set `OPENCLAW_SANDBOX=1` (or `true`/`yes`/`on`) to enable that path. You can override socket location with `OPENCLAW_DOCKER_SOCKET`. Full setup and env reference: [Docker](https://docs.openclaw.ai/install/docker#enable-agent-sandbox-for-docker-gateway).

## setupCommand (one-time container setup)

`setupCommand` runs **once** after the sandbox container is created (not on every run). It executes inside the container via `sh -lc`. Paths:

Common pitfalls:

## Tool policy + escape hatches

Tool allow/deny policies still apply before sandbox rules. If a tool is denied globally or per-agent, sandboxing doesn’t bring it back. `tools.elevated` is an explicit escape hatch that runs `exec` on the host. `/exec` directives only apply for authorized senders and persist per session; to hard-disable `exec`, use tool policy deny (see [Sandbox vs Tool Policy vs Elevated](https://docs.openclaw.ai/gateway/sandbox-vs-tool-policy-vs-elevated)). Debugging:

## Multi-agent overrides

Each agent can override sandbox + tools: `agents.list[].sandbox` and `agents.list[].tools` (plus `agents.list[].tools.sandbox.tools` for sandbox tool policy). See [Multi-Agent Sandbox & Tools](https://docs.openclaw.ai/tools/multi-agent-sandbox-tools) for precedence.

## Minimal enable example

----
url: https://docs.openclaw.ai/tools/browser
----

# Browser (OpenClaw-managed) - OpenClaw

OpenClaw can run a **dedicated Chrome/Brave/Edge/Chromium profile** that the agent controls. It is isolated from your personal browser and is managed through a small local control service inside the Gateway (loopback only). Beginner view:

## What you get

This browser is **not** your daily driver. It is a safe, isolated surface for agent automation and verification.

## Quick start

If you get “Browser disabled”, enable it in config (see below) and restart the Gateway.

## Profiles: `openclaw` vs `user`

For agent browser tool calls:

Set `browser.defaultProfile: "openclaw"` if you want managed mode by default.

## Configuration

Browser settings live in `~/.openclaw/openclaw.json`.

```
{
  browser: {
    enabled: true, // default: true
    ssrfPolicy: {
      dangerouslyAllowPrivateNetwork: true, // default trusted-network mode
      // allowPrivateNetwork: true, // legacy alias
      // hostnameAllowlist: ["*.example.com", "example.com"],
      // allowedHostnames: ["localhost"],
    },
    // cdpUrl: "http://127.0.0.1:18792", // legacy single-profile override
    remoteCdpTimeoutMs: 1500, // remote CDP HTTP timeout (ms)
    remoteCdpHandshakeTimeoutMs: 3000, // remote CDP WebSocket handshake timeout (ms)
    defaultProfile: "openclaw",
    color: "#FF4500",
    headless: false,
    noSandbox: false,
    attachOnly: false,
    executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
    profiles: {
      openclaw: { cdpPort: 18800, color: "#FF4500" },
      work: { cdpPort: 18801, color: "#0066CC" },
      user: {
        driver: "existing-session",
        attachOnly: true,
        color: "#00AA00",
      },
      brave: {
        driver: "existing-session",
        attachOnly: true,
        userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
        color: "#FB542B",
      },
      remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" },
    },
  },
}
```

Notes:

## Use Brave (or another Chromium-based browser)

If your **system default** browser is Chromium-based (Chrome/Brave/Edge/etc), OpenClaw uses it automatically. Set `browser.executablePath` to override auto-detection: CLI example:

## Local vs remote control

Remote CDP URLs can include auth:

OpenClaw preserves the auth when calling `/json/*` endpoints and when connecting to the CDP WebSocket. Prefer environment variables or secrets managers for tokens instead of committing them to config files.

## Node browser proxy (zero-config default)

If you run a **node host** on the machine that has your browser, OpenClaw can auto-route browser tool calls to that node without any extra browser config. This is the default path for remote gateways. Notes:

## Browserless (hosted remote CDP)

[Browserless](https://browserless.io/) is a hosted Chromium service that exposes CDP endpoints over HTTPS. You can point an OpenClaw browser profile at a Browserless region endpoint and authenticate with your API key. Example:

Notes:

## Direct WebSocket CDP providers

Some hosted browser services expose a **direct WebSocket** endpoint rather than the standard HTTP-based CDP discovery (`/json/version`). OpenClaw supports both:

### Browserbase

[Browserbase](https://www.browserbase.com/) is a cloud platform for running headless browsers with built-in CAPTCHA solving, stealth mode, and residential proxies.

Notes:

## Security

Key ideas:

Remote CDP tips:

## Profiles (multi-browser)

OpenClaw supports multiple named profiles (routing configs). Profiles can be:

Defaults:

All control endpoints accept `?profile=<name>`; the CLI uses `--browser-profile`.

## Existing-session via Chrome DevTools MCP

OpenClaw can also attach to a running Chromium-based browser profile through the official Chrome DevTools MCP server. This reuses the tabs and login state already open in that browser profile. Official background and setup references:

Built-in profile:

Optional: create your own custom existing-session profile if you want a different name, color, or browser data directory. Default behavior:

Use `userDataDir` for Brave, Edge, Chromium, or a non-default Chrome profile:

Then in the matching browser:

1. Open that browser’s inspect page for remote debugging.
2. Enable remote debugging.
3. Keep the browser running and approve the connection prompt when OpenClaw attaches.

Common inspect pages:

Live attach smoke test:

What success looks like:

What to check if attach does not work:

Agent use:

Notes:

## Isolation guarantees

## Browser selection

When launching locally, OpenClaw picks the first available:

1. Chrome
2. Brave
3. Edge
4. Chromium
5. Chrome Canary

You can override with `browser.executablePath`. Platforms:

## Control API (optional)

For local integrations only, the Gateway exposes a small loopback HTTP API:

* Status/start/stop: `GET /`, `POST /start`, `POST /stop`
* Tabs: `GET /tabs`, `POST /tabs/open`, `POST /tabs/focus`, `DELETE /tabs/:targetId`
* Snapshot/screenshot: `GET /snapshot`, `POST /screenshot`
* Actions: `POST /navigate`, `POST /act`
* Hooks: `POST /hooks/file-chooser`, `POST /hooks/dialog`
* Downloads: `POST /download`, `POST /wait/download`
* Debugging: `GET /console`, `POST /pdf`
* Debugging: `GET /errors`, `GET /requests`, `POST /trace/start`, `POST /trace/stop`, `POST /highlight`
* Network: `POST /response/body`
* State: `GET /cookies`, `POST /cookies/set`, `POST /cookies/clear`
* State: `GET /storage/:kind`, `POST /storage/:kind/set`, `POST /storage/:kind/clear`
* Settings: `POST /set/offline`, `POST /set/headers`, `POST /set/credentials`, `POST /set/geolocation`, `POST /set/media`, `POST /set/timezone`, `POST /set/locale`, `POST /set/device`

All endpoints accept `?profile=<name>`. If gateway auth is configured, browser HTTP routes require auth too:

### Playwright requirement

Some features (navigate/act/AI snapshot/role snapshot, element screenshots, PDF) require Playwright. If Playwright isn’t installed, those endpoints return a clear 501 error. ARIA snapshots and basic screenshots still work for openclaw-managed Chrome. If you see `Playwright is not available in this gateway build`, install the full Playwright package (not `playwright-core`) and restart the gateway, or reinstall OpenClaw with browser support.

#### Docker Playwright install

If your Gateway runs in Docker, avoid `npx playwright` (npm override conflicts). Use the bundled CLI instead:

To persist browser downloads, set `PLAYWRIGHT_BROWSERS_PATH` (for example, `/home/node/.cache/ms-playwright`) and make sure `/home/node` is persisted via `OPENCLAW_HOME_VOLUME` or a bind mount. See [Docker](https://docs.openclaw.ai/install/docker).

## How it works (internal)

High-level flow:

This design keeps the agent on a stable, deterministic interface while letting you swap local/remote browsers and profiles.

## CLI quick reference

All commands accept `--browser-profile <name>` to target a specific profile. All commands also accept `--json` for machine-readable output (stable payloads). Basics:

Inspection:

Actions:

State:

Notes:

## Snapshots and refs

OpenClaw supports two “snapshot” styles:

Ref behavior:

## Wait power-ups

You can wait on more than just time/text:

These can be combined:

## Debug workflows

When an action fails (e.g. “not visible”, “strict mode violation”, “covered”):

1. `openclaw browser snapshot --interactive`
2. Use `click <ref>` / `type <ref>` (prefer role refs in interactive mode)
3. If it still fails: `openclaw browser highlight <ref>` to see what Playwright is targeting
4. If the page behaves oddly:
5. For deep debugging: record a trace:

## JSON output

`--json` is for scripting and structured tooling. Examples:

Role snapshots in JSON include `refs` plus a small `stats` block (lines/chars/refs/interactive) so tools can reason about payload size and density.

## State and environment knobs

These are useful for “make the site behave like X” workflows:

## Security & privacy

Strict-mode example (block private/internal destinations by default):

## Troubleshooting

For Linux-specific issues (especially snap Chromium), see [Browser troubleshooting](https://docs.openclaw.ai/tools/browser-linux-troubleshooting). For WSL2 Gateway + Windows Chrome split-host setups, see [WSL2 + Windows + remote Chrome CDP troubleshooting](https://docs.openclaw.ai/tools/browser-wsl2-windows-remote-cdp-troubleshooting).

## Agent tools + how control works

The agent gets **one tool** for browser automation:

How it maps:

This keeps the agent deterministic and avoids brittle selectors.

----
url: https://docs.openclaw.ai/cli/completion
----

# completion - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw completion](#openclaw-completion)
- [Usage](#usage)
- [Options](#options)
- [Notes](#notes)

## [​](#openclaw-completion)`openclaw completion`

Generate shell completion scripts and optionally install them into your shell profile.

## [​](#usage)Usage

```
openclaw completion
openclaw completion --shell zsh
openclaw completion --install
openclaw completion --shell fish --install
openclaw completion --write-state
openclaw completion --shell bash --write-state
```

## [​](#options)Options

* `-s, --shell <shell>`: shell target (`zsh`, `bash`, `powershell`, `fish`; default: `zsh`)
* `-i, --install`: install completion by adding a source line to your shell profile
* `--write-state`: write completion script(s) to `$OPENCLAW_STATE_DIR/completions` without printing to stdout
* `-y, --yes`: skip install confirmation prompts

## [​](#notes)Notes

* `--install` writes a small “OpenClaw Completion” block into your shell profile and points it at the cached script.
* Without `--install` or `--write-state`, the command prints the script to stdout.
* Completion generation eagerly loads command trees so nested subcommands are included.

[clawbot](https://docs.openclaw.ai/cli/clawbot)[dns](https://docs.openclaw.ai/cli/dns)

----
url: https://docs.openclaw.ai/cli/memory
----

# memory - OpenClaw

## [​](#openclaw-memory)`openclaw memory`

Manage semantic memory indexing and search. Provided by the active memory plugin (default: `memory-core`; set `plugins.slots.memory = "none"` to disable). Related:

* Memory concept: [Memory](https://docs.openclaw.ai/concepts/memory)
* Plugins: [Plugins](https://docs.openclaw.ai/tools/plugin)

## [​](#examples)Examples

```
openclaw memory status
openclaw memory status --deep
openclaw memory index --force
openclaw memory search "meeting notes"
openclaw memory search --query "deployment" --max-results 20
openclaw memory status --json
openclaw memory status --deep --index
openclaw memory status --deep --index --verbose
openclaw memory status --agent main
openclaw memory index --agent main --verbose
```

## [​](#options)Options

`memory status` and `memory index`:

* `--agent <id>`: scope to a single agent. Without it, these commands run for each configured agent; if no agent list is configured, they fall back to the default agent.
* `--verbose`: emit detailed logs during probes and indexing.

`memory status`:

* `--deep`: probe vector + embedding availability.
* `--index`: run a reindex if the store is dirty (implies `--deep`).
* `--json`: print JSON output.

`memory index`:

* `--force`: force a full reindex.

`memory search`:

* Query input: pass either positional `[query]` or `--query <text>`.
* If both are provided, `--query` wins.
* If neither is provided, the command exits with an error.
* `--agent <id>`: scope to a single agent (default: the default agent).
* `--max-results <n>`: limit the number of results returned.
* `--min-score <n>`: filter out low-score matches.
* `--json`: print JSON results.

Notes:

* `memory index --verbose` prints per-phase details (provider, model, sources, batch activity).
* `memory status` includes any extra paths configured via `memorySearch.extraPaths`.
* If effectively active memory remote API key fields are configured as SecretRefs, the command resolves those values from the active gateway snapshot. If gateway is unavailable, the command fails fast.
* Gateway version skew note: this command path requires a gateway that supports `secrets.resolve`; older gateways return an unknown-method error.

----
url: https://docs.openclaw.ai/debug/node-issue
----

# Node + tsx Crash - OpenClaw

## [​](#node-+-tsx-“__name-is-not-a-function”-crash)Node + tsx “\_\_name is not a function” crash

## [​](#summary)Summary

Running OpenClaw via Node with `tsx` fails at startup with:

```
[openclaw] Failed to start CLI: TypeError: __name is not a function
    at createSubsystemLogger (.../src/logging/subsystem.ts:203:25)
    at .../src/agents/auth-profiles/constants.ts:25:20
```

This began after switching dev scripts from Bun to `tsx` (commit `2871657e`, 2026-01-06). The same runtime path worked with Bun.

## [​](#environment)Environment

* Node: v25.x (observed on v25.3.0)
* tsx: 4.21.0
* OS: macOS (repro also likely on other platforms that run Node 25)

## [​](#repro-node-only)Repro (Node-only)

```
# in repo root
node --version
pnpm install
node --import tsx src/entry.ts status
```

## [​](#minimal-repro-in-repo)Minimal repro in repo

```
node --import tsx scripts/repro/tsx-name-repro.ts
```

## [​](#node-version-check)Node version check

* Node 25.3.0: fails
* Node 22.22.0 (Homebrew `node@22`): fails
* Node 24: not installed here yet; needs verification

## [​](#notes-/-hypothesis)Notes / hypothesis

* `tsx` uses esbuild to transform TS/ESM. esbuild’s `keepNames` emits a `__name` helper and wraps function definitions with `__name(...)`.
* The crash indicates `__name` exists but is not a function at runtime, which implies the helper is missing or overwritten for this module in the Node 25 loader path.
* Similar `__name` helper issues have been reported in other esbuild consumers when the helper is missing or rewritten.

## [​](#regression-history)Regression history

* `2871657e` (2026-01-06): scripts changed from Bun to tsx to make Bun optional.
* Before that (Bun path), `openclaw status` and `gateway:watch` worked.

## [​](#workarounds)Workarounds

* Use Bun for dev scripts (current temporary revert).
* Use Node + tsc watch, then run compiled output:
  ```
  pnpm exec tsc --watch --preserveWatchOutput
  node --watch openclaw.mjs status
  ```
* Confirmed locally: `pnpm exec tsc -p tsconfig.json` + `node openclaw.mjs status` works on Node 25.
* Disable esbuild keepNames in the TS loader if possible (prevents `__name` helper insertion); tsx does not currently expose this.
* Test Node LTS (22/24) with `tsx` to see if the issue is Node 25–specific.

## [​](#references)References

* <https://opennext.js.org/cloudflare/howtos/keep_names>
* <https://esbuild.github.io/api/#keep-names>
* <https://github.com/evanw/esbuild/issues/1031>

## [​](#next-steps)Next steps

* Repro on Node 22/24 to confirm Node 25 regression.
* Test `tsx` nightly or pin to earlier version if a known regression exists.
* If reproduces on Node LTS, file a minimal repro upstream with the `__name` stack trace.

----
url: https://docs.openclaw.ai/cli/pairing
----

# pairing - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw pairing](#openclaw-pairing)
- [Commands](#commands)
- [Notes](#notes)

## [​](#openclaw-pairing)`openclaw pairing`

Approve or inspect DM pairing requests (for channels that support pairing). Related:

* Pairing flow: [Pairing](https://docs.openclaw.ai/channels/pairing)

## [​](#commands)Commands

```
openclaw pairing list telegram
openclaw pairing list --channel telegram --account work
openclaw pairing list telegram --json

openclaw pairing approve telegram <code>
openclaw pairing approve --channel telegram --account work <code> --notify
```

## [​](#notes)Notes

* Channel input: pass it positionally (`pairing list telegram`) or with `--channel <channel>`.
* `pairing list` supports `--account <accountId>` for multi-account channels.
* `pairing approve` supports `--account <accountId>` and `--notify`.
* If only one pairing-capable channel is configured, `pairing approve <code>` is allowed.

[directory](https://docs.openclaw.ai/cli/directory)[qr](https://docs.openclaw.ai/cli/qr)

----
url: https://docs.openclaw.ai/tools/browser-linux-troubleshooting
----

# Browser Troubleshooting - OpenClaw

## Problem: “Failed to start Chrome CDP on port 18800”

OpenClaw’s browser control server fails to launch Chrome/Brave/Edge/Chromium with the error:

### Root Cause

On Ubuntu (and many Linux distros), the default Chromium installation is a **snap package**. Snap’s AppArmor confinement interferes with how OpenClaw spawns and monitors the browser process. The `apt install chromium` command installs a stub package that redirects to snap:

This is NOT a real browser - it’s just a wrapper.

### Solution 1: Install Google Chrome (Recommended)

Install the official Google Chrome `.deb` package, which is not sandboxed by snap:

Then update your OpenClaw config (`~/.openclaw/openclaw.json`):

### Solution 2: Use Snap Chromium with Attach-Only Mode

If you must use snap Chromium, configure OpenClaw to attach to a manually-started browser:

1. Update config:

2) Start Chromium manually:

3. Optionally create a systemd user service to auto-start Chrome:

Enable with: `systemctl --user enable --now openclaw-browser.service`

### Verifying the Browser Works

Check status:

Test browsing:

### Config Reference

| Option                   | Description                                                          | Default                                                     |
| ------------------------ | -------------------------------------------------------------------- | ----------------------------------------------------------- |
| `browser.enabled`        | Enable browser control                                               | `true`                                                      |
| `browser.executablePath` | Path to a Chromium-based browser binary (Chrome/Brave/Edge/Chromium) | auto-detected (prefers default browser when Chromium-based) |
| `browser.headless`       | Run without GUI                                                      | `false`                                                     |
| `browser.noSandbox`      | Add `--no-sandbox` flag (needed for some Linux setups)               | `false`                                                     |
| `browser.attachOnly`     | Don’t launch browser, only attach to existing                        | `false`                                                     |
| `browser.cdpPort`        | Chrome DevTools Protocol port                                        | `18800`                                                     |

### Problem: “No Chrome tabs found for profile=“user""

You’re using an `existing-session` / Chrome MCP profile. OpenClaw can see local Chrome, but there are no open tabs available to attach to. Fix options:

1. **Use the managed browser:** `openclaw browser start --browser-profile openclaw` (or set `browser.defaultProfile: "openclaw"`).
2. **Use Chrome MCP:** make sure local Chrome is running with at least one open tab, then retry with `--browser-profile user`.

Notes:

----
url: https://docs.openclaw.ai/tools/grok-search
----

# Grok Search - OpenClaw

OpenClaw supports Grok as a `web_search` provider, using xAI web-grounded responses to produce AI-synthesized answers backed by live search results with citations.

## Get an API key

## Config

```
{
  plugins: {
    entries: {
      xai: {
        config: {
          webSearch: {
            apiKey: "xai-...", // optional if XAI_API_KEY is set
          },
        },
      },
    },
  },
  tools: {
    web: {
      search: {
        provider: "grok",
      },
    },
  },
}
```

**Environment alternative:** set `XAI_API_KEY` in the Gateway environment. For a gateway install, put it in `~/.openclaw/.env`.

## How it works

Grok uses xAI web-grounded responses to synthesize answers with inline citations, similar to Gemini’s Google Search grounding approach.

## Supported parameters

Grok search supports the standard `query` and `count` parameters. Provider-specific filters are not currently supported.

----
url: https://docs.openclaw.ai/security/formal-verification
----

# Formal Verification (Security Models) - OpenClaw

This page tracks OpenClaw’s **formal security models** (TLA+/TLC today; more as needed).

> Note: some older links may refer to the previous project name.

**Goal (north star):** provide a machine-checked argument that OpenClaw enforces its intended security policy (authorization, session isolation, tool gating, and misconfiguration safety), under explicit assumptions. **What this is (today):** an executable, attacker-driven **security regression suite**:

**What this is not (yet):** a proof that “OpenClaw is secure in all respects” or that the full TypeScript implementation is correct.

## Where the models live

Models are maintained in a separate repo: [vignesh07/openclaw-formal-models](https://github.com/vignesh07/openclaw-formal-models).

## Important caveats

## Reproducing results

Today, results are reproduced by cloning the models repo locally and running TLC (see below). A future iteration could offer:

Getting started:

### Gateway exposure and open gateway misconfiguration

**Claim:** binding beyond loopback without auth can make remote compromise possible / increases exposure; token/password blocks unauth attackers (per the model assumptions).

See also: `docs/gateway-exposure-matrix.md` in the models repo.

### Nodes.run pipeline (highest-risk capability)

**Claim:** `nodes.run` requires (a) node command allowlist plus declared commands and (b) live approval when configured; approvals are tokenized to prevent replay (in the model).

### Pairing store (DM gating)

**Claim:** pairing requests respect TTL and pending-request caps.

### Ingress gating (mentions + control-command bypass)

**Claim:** in group contexts requiring mention, an unauthorized “control command” cannot bypass mention gating.

### Routing/session-key isolation

**Claim:** DMs from distinct peers do not collapse into the same session unless explicitly linked/configured.

## v1++: additional bounded models (concurrency, retries, trace correctness)

These are follow-on models that tighten fidelity around real-world failure modes (non-atomic updates, retries, and message fan-out).

### Pairing store concurrency / idempotency

**Claim:** a pairing store should enforce `MaxPending` and idempotency even under interleavings (i.e., “check-then-write” must be atomic / locked; refresh shouldn’t create duplicates). What it means:

### Ingress trace correlation / idempotency

**Claim:** ingestion should preserve trace correlation across fan-out and be idempotent under provider retries. What it means:

### Routing dmScope precedence + identityLinks

**Claim:** routing must keep DM sessions isolated by default, and only collapse sessions when explicitly configured (channel precedence + identity links). What it means:

----
url: https://docs.openclaw.ai/install/macos-vm
----

# macOS VMs - OpenClaw

## OpenClaw on macOS VMs (Sandboxing)

## Recommended default (most users)

Use a macOS VM when you specifically need macOS-only capabilities (iMessage/BlueBubbles) or want strict isolation from your daily Mac.

## macOS VM options

### Local VM on your Apple Silicon Mac (Lume)

Run OpenClaw in a sandboxed macOS VM on your existing Apple Silicon Mac using [Lume](https://cua.ai/docs/lume). This gives you:

### Hosted Mac providers (cloud)

If you want macOS in the cloud, hosted Mac providers work too:

Once you have SSH access to a macOS VM, continue at step 6 below.

***

## Quick path (Lume, experienced users)

1. Install Lume
2. `lume create openclaw --os macos --ipsw latest`
3. Complete Setup Assistant, enable Remote Login (SSH)
4. `lume run openclaw --no-display`
5. SSH in, install OpenClaw, configure channels
6. Done

***

## What you need (Lume)

***

## 1) Install Lume

If `~/.local/bin` isn’t in your PATH:

Verify:

Docs: [Lume Installation](https://cua.ai/docs/lume/guide/getting-started/installation)

***

## 2) Create the macOS VM

This downloads macOS and creates the VM. A VNC window opens automatically. Note: The download can take a while depending on your connection.

***

## 3) Complete Setup Assistant

In the VNC window:

1. Select language and region
2. Skip Apple ID (or sign in if you want iMessage later)
3. Create a user account (remember the username and password)
4. Skip all optional features

After setup completes, enable SSH:

1. Open System Settings → General → Sharing
2. Enable “Remote Login”

***

## 4) Get the VM IP address

Look for the IP address (usually `192.168.64.x`).

***

## 5) SSH into the VM

Replace `youruser` with the account you created, and the IP with your VM’s IP.

***

## 6) Install OpenClaw

Inside the VM:

Follow the onboarding prompts to set up your model provider (Anthropic, OpenAI, etc.).

***

## 7) Configure channels

Edit the config file:

Add your channels:

Then login to WhatsApp (scan QR):

***

## 8) Run the VM headlessly

Stop the VM and restart without display:

The VM runs in the background. OpenClaw’s daemon keeps the gateway running. To check status:

***

## Bonus: iMessage integration

This is the killer feature of running on macOS. Use [BlueBubbles](https://bluebubbles.app/) to add iMessage to OpenClaw. Inside the VM:

1. Download BlueBubbles from bluebubbles.app
2. Sign in with your Apple ID
3. Enable the Web API and set a password
4. Point BlueBubbles webhooks at your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`)

Add to your OpenClaw config:

Restart the gateway. Now your agent can send and receive iMessages. Full setup details: [BlueBubbles channel](https://docs.openclaw.ai/channels/bluebubbles)

***

## Save a golden image

Before customizing further, snapshot your clean state:

Reset anytime:

***

## Running 24/7

Keep the VM running by:

For true always-on, consider a dedicated Mac mini or a small VPS. See [VPS hosting](https://docs.openclaw.ai/vps).

***

## Troubleshooting

| Problem                  | Solution                                                                           |
| ------------------------ | ---------------------------------------------------------------------------------- |
| Can’t SSH into VM        | Check “Remote Login” is enabled in VM’s System Settings                            |
| VM IP not showing        | Wait for VM to fully boot, run `lume get openclaw` again                           |
| Lume command not found   | Add `~/.local/bin` to your PATH                                                    |
| WhatsApp QR not scanning | Ensure you’re logged into the VM (not host) when running `openclaw channels login` |

***

----
url: https://docs.openclaw.ai/platforms/mac/dev-setup
----

# macOS Dev Setup - OpenClaw

## macOS Developer Setup

This guide covers the necessary steps to build and run the OpenClaw macOS application from source.

## Prerequisites

Before building the app, ensure you have the following installed:

1. **Xcode 26.2+**: Required for Swift development.
2. **Node.js 24 & pnpm**: Recommended for the gateway, CLI, and packaging scripts. Node 22 LTS, currently `22.16+`, remains supported for compatibility.

## 1. Install Dependencies

Install the project-wide dependencies:

## 2. Build and Package the App

To build the macOS app and package it into `dist/OpenClaw.app`, run:

If you don’t have an Apple Developer ID certificate, the script will automatically use **ad-hoc signing** (`-`). For dev run modes, signing flags, and Team ID troubleshooting, see the macOS app README: <https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md>

> **Note**: Ad-hoc signed apps may trigger security prompts. If the app crashes immediately with “Abort trap 6”, see the [Troubleshooting](#troubleshooting) section.

## 3. Install the CLI

The macOS app expects a global `openclaw` CLI install to manage background tasks. **To install it (recommended):**

1. Open the OpenClaw app.
2. Go to the **General** settings tab.
3. Click **“Install CLI”**.

Alternatively, install it manually:

## Troubleshooting

### Build Fails: Toolchain or SDK Mismatch

The macOS app build expects the latest macOS SDK and Swift 6.2 toolchain. **System dependencies (required):**

**Checks:**

If versions don’t match, update macOS/Xcode and re-run the build.

### App Crashes on Permission Grant

If the app crashes when you try to allow **Speech Recognition** or **Microphone** access, it may be due to a corrupted TCC cache or signature mismatch. **Fix:**

1. Reset the TCC permissions:
2. If that fails, change the `BUNDLE_ID` temporarily in [`scripts/package-mac-app.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-app.sh) to force a “clean slate” from macOS.

### Gateway “Starting…” indefinitely

If the gateway status stays on “Starting…”, check if a zombie process is holding the port:

If a manual run is holding the port, stop that process (Ctrl+C). As a last resort, kill the PID you found above.

----
url: https://docs.openclaw.ai/providers/minimax
----

# MiniMax - OpenClaw

OpenClaw’s MiniMax provider defaults to **MiniMax M2.7** and keeps **MiniMax M2.5** in the catalog for compatibility.

## Model lineup

## Choose a setup

### MiniMax OAuth (Coding Plan) - recommended

**Best for:** quick setup with MiniMax Coding Plan via OAuth, no API key required. Enable the bundled OAuth plugin and authenticate:

You will be prompted to select an endpoint:

See [MiniMax plugin README](https://github.com/openclaw/openclaw/tree/main/extensions/minimax) for details.

### MiniMax M2.7 (API key)

**Best for:** hosted MiniMax with Anthropic-compatible API. Configure via CLI:

```
{
  env: { MINIMAX_API_KEY: "sk-..." },
  agents: { defaults: { model: { primary: "minimax/MiniMax-M2.7" } } },
  models: {
    mode: "merge",
    providers: {
      minimax: {
        baseUrl: "https://api.minimax.io/anthropic",
        apiKey: "${MINIMAX_API_KEY}",
        api: "anthropic-messages",
        models: [
          {
            id: "MiniMax-M2.7",
            name: "MiniMax M2.7",
            reasoning: true,
            input: ["text"],
            cost: { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0.12 },
            contextWindow: 200000,
            maxTokens: 8192,
          },
          {
            id: "MiniMax-M2.7-highspeed",
            name: "MiniMax M2.7 Highspeed",
            reasoning: true,
            input: ["text"],
            cost: { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0.12 },
            contextWindow: 200000,
            maxTokens: 8192,
          },
          {
            id: "MiniMax-M2.5",
            name: "MiniMax M2.5",
            reasoning: true,
            input: ["text"],
            cost: { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0.12 },
            contextWindow: 200000,
            maxTokens: 8192,
          },
          {
            id: "MiniMax-M2.5-highspeed",
            name: "MiniMax M2.5 Highspeed",
            reasoning: true,
            input: ["text"],
            cost: { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0.12 },
            contextWindow: 200000,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

### MiniMax M2.7 as fallback (example)

**Best for:** keep your strongest latest-generation model as primary, fail over to MiniMax M2.7. Example below uses Opus as a concrete primary; swap to your preferred latest-gen primary model.

### Optional: Local via LM Studio (manual)

**Best for:** local inference with LM Studio. We have seen strong results with MiniMax M2.5 on powerful hardware (e.g. a desktop/server) using LM Studio’s local server. Configure manually via `openclaw.json`:

```
{
  agents: {
    defaults: {
      model: { primary: "lmstudio/minimax-m2.5-gs32" },
      models: { "lmstudio/minimax-m2.5-gs32": { alias: "Minimax" } },
    },
  },
  models: {
    mode: "merge",
    providers: {
      lmstudio: {
        baseUrl: "http://127.0.0.1:1234/v1",
        apiKey: "lmstudio",
        api: "openai-responses",
        models: [
          {
            id: "minimax-m2.5-gs32",
            name: "MiniMax M2.5 GS32",
            reasoning: true,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 196608,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

## Configure via `openclaw configure`

Use the interactive config wizard to set MiniMax without editing JSON:

1. Run `openclaw configure`.
2. Select **Model/auth**.
3. Choose a **MiniMax** auth option.
4. Pick your default model when prompted.

## Configuration options

## Notes

## Troubleshooting

### ”Unknown model: minimax/MiniMax-M2.7”

This usually means the **MiniMax provider isn’t configured** (no provider entry and no MiniMax auth profile/env key found). A fix for this detection is in **2026.1.12**. Fix by:

Make sure the model id is **case‑sensitive**:

Then recheck with:

----
url: https://docs.openclaw.ai/concepts/typebox
----

# TypeBox - OpenClaw

## TypeBox as protocol source of truth

Last updated: 2026-01-10 TypeBox is a TypeScript-first schema library. We use it to define the **Gateway WebSocket protocol** (handshake, request/response, server events). Those schemas drive **runtime validation**, **JSON Schema export**, and **Swift codegen** for the macOS app. One source of truth; everything else is generated. If you want the higher-level protocol context, start with [Gateway architecture](https://docs.openclaw.ai/concepts/architecture).

## Mental model (30 seconds)

Every Gateway WS message is one of three frames:

* **Request**: `{ type: "req", id, method, params }`
* **Response**: `{ type: "res", id, ok, payload | error }`
* **Event**: `{ type: "event", event, payload, seq?, stateVersion? }`

The first frame **must** be a `connect` request. After that, clients can call methods (e.g. `health`, `send`, `chat.send`) and subscribe to events (e.g. `presence`, `tick`, `agent`). Connection flow (minimal):

Common methods + events:

| Category  | Examples                                                  | Notes                              |
| --------- | --------------------------------------------------------- | ---------------------------------- |
| Core      | `connect`, `health`, `status`                             | `connect` must be first            |
| Messaging | `send`, `poll`, `agent`, `agent.wait`                     | side-effects need `idempotencyKey` |
| Chat      | `chat.history`, `chat.send`, `chat.abort`, `chat.inject`  | WebChat uses these                 |
| Sessions  | `sessions.list`, `sessions.patch`, `sessions.delete`      | session admin                      |
| Nodes     | `node.list`, `node.invoke`, `node.pair.*`                 | Gateway WS + node actions          |
| Events    | `tick`, `presence`, `agent`, `chat`, `health`, `shutdown` | server push                        |

Authoritative list lives in `src/gateway/server.ts` (`METHODS`, `EVENTS`).

## Where the schemas live

## Current pipeline

## How the schemas are used at runtime

## Example frames

Connect (first message):

```
{
  "type": "req",
  "id": "c1",
  "method": "connect",
  "params": {
    "minProtocol": 2,
    "maxProtocol": 2,
    "client": {
      "id": "openclaw-macos",
      "displayName": "macos",
      "version": "1.0.0",
      "platform": "macos 15.1",
      "mode": "ui",
      "instanceId": "A1B2"
    }
  }
}
```

Hello-ok response:

```
{
  "type": "res",
  "id": "c1",
  "ok": true,
  "payload": {
    "type": "hello-ok",
    "protocol": 2,
    "server": { "version": "dev", "connId": "ws-1" },
    "features": { "methods": ["health"], "events": ["tick"] },
    "snapshot": {
      "presence": [],
      "health": {},
      "stateVersion": { "presence": 0, "health": 0 },
      "uptimeMs": 0
    },
    "policy": { "maxPayload": 1048576, "maxBufferedBytes": 1048576, "tickIntervalMs": 30000 }
  }
}
```

Request + response:

Event:

## Minimal client (Node.js)

Smallest useful flow: connect + health.

```
import { WebSocket } from "ws";

const ws = new WebSocket("ws://127.0.0.1:18789");

ws.on("open", () => {
  ws.send(
    JSON.stringify({
      type: "req",
      id: "c1",
      method: "connect",
      params: {
        minProtocol: 3,
        maxProtocol: 3,
        client: {
          id: "cli",
          displayName: "example",
          version: "dev",
          platform: "node",
          mode: "cli",
        },
      },
    }),
  );
});

ws.on("message", (data) => {
  const msg = JSON.parse(String(data));
  if (msg.type === "res" && msg.id === "c1" && msg.ok) {
    ws.send(JSON.stringify({ type: "req", id: "h1", method: "health" }));
  }
  if (msg.type === "res" && msg.id === "h1") {
    console.log("health:", msg.payload);
    ws.close();
  }
});
```

## Worked example: add a method end-to-end

Example: add a new `system.echo` request that returns `{ ok: true, text }`.

1. **Schema (source of truth)**

Add to `src/gateway/protocol/schema.ts`:

Add both to `ProtocolSchemas` and export types:

2. **Validation**

In `src/gateway/protocol/index.ts`, export an AJV validator:

3. **Server behavior**

Add a handler in `src/gateway/server-methods/system.ts`:

Register it in `src/gateway/server-methods.ts` (already merges `systemHandlers`), then add `"system.echo"` to `METHODS` in `src/gateway/server.ts`.

4. **Regenerate**

5) **Tests + docs**

Add a server test in `src/gateway/server.*.test.ts` and note the method in docs.

## Swift codegen behavior

The Swift generator emits:

Unknown frame types are preserved as raw payloads for forward compatibility.

## Versioning + compatibility

## Schema patterns and conventions

## Live schema JSON

Generated JSON Schema is in the repo at `dist/protocol.schema.json`. The published raw file is typically available at:

## When you change schemas

1. Update the TypeBox schemas.
2. Run `pnpm protocol:check`.
3. Commit the regenerated schema + Swift models.

----
url: https://docs.openclaw.ai/cli/directory
----

# directory - OpenClaw

## [​](#openclaw-directory)`openclaw directory`

Directory lookups for channels that support it (contacts/peers, groups, and “me”).

## [​](#common-flags)Common flags

* `--channel <name>`: channel id/alias (required when multiple channels are configured; auto when only one is configured)
* `--account <id>`: account id (default: channel default)
* `--json`: output JSON

## [​](#notes)Notes

* `directory` is meant to help you find IDs you can paste into other commands (especially `openclaw message send --target ...`).
* For many channels, results are config-backed (allowlists / configured groups) rather than a live provider directory.
* Default output is `id` (and sometimes `name`) separated by a tab; use `--json` for scripting.

## [​](#using-results-with-message-send)Using results with `message send`

```
openclaw directory peers list --channel slack --query "U0"
openclaw message send --channel slack --target user:U012ABCDEF --message "hello"
```

## [​](#id-formats-by-channel)ID formats (by channel)

* WhatsApp: `+15551234567` (DM), `1234567890-1234567890@g.us` (group)
* Telegram: `@username` or numeric chat id; groups are numeric ids
* Slack: `user:U…` and `channel:C…`
* Discord: `user:<id>` and `channel:<id>`
* Matrix (plugin): `user:@user:server`, `room:!roomId:server`, or `#alias:server`
* Microsoft Teams (plugin): `user:<id>` and `conversation:<id>`
* Zalo (plugin): user id (Bot API)
* Zalo Personal / `zalouser` (plugin): thread id (DM/group) from `zca` (`me`, `friend list`, `group list`)

## [​](#self-“me”)Self (“me”)

```
openclaw directory self --channel zalouser
```

## [​](#peers-contacts/users)Peers (contacts/users)

```
openclaw directory peers list --channel zalouser
openclaw directory peers list --channel zalouser --query "name"
openclaw directory peers list --channel zalouser --limit 50
```

## [​](#groups)Groups

```
openclaw directory groups list --channel zalouser
openclaw directory groups list --channel zalouser --query "work"
openclaw directory groups members --channel zalouser --group-id <id>
```

----
url: https://docs.openclaw.ai/cli/logs
----

# logs - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw logs](#openclaw-logs)
- [Examples](#examples)

## [​](#openclaw-logs)`openclaw logs`

Tail Gateway file logs over RPC (works in remote mode). Related:

* Logging overview: [Logging](https://docs.openclaw.ai/logging)

## [​](#examples)Examples

```
openclaw logs
openclaw logs --follow
openclaw logs --json
openclaw logs --limit 500
openclaw logs --local-time
openclaw logs --follow --local-time
```

Use `--local-time` to render timestamps in your local timezone.

[health](https://docs.openclaw.ai/cli/health)[onboard](https://docs.openclaw.ai/cli/onboard)

----
url: https://docs.openclaw.ai/cli/clawbot
----

# clawbot - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw clawbot](#openclaw-clawbot)
- [Migration](#migration)

## [​](#openclaw-clawbot)`openclaw clawbot`

Legacy alias namespace kept for backwards compatibility. Current supported alias:

* `openclaw clawbot qr` (same behavior as [`openclaw qr`](https://docs.openclaw.ai/cli/qr))

## [​](#migration)Migration

Prefer modern top-level commands directly:

* `openclaw clawbot qr` -> `openclaw qr`

[acp](https://docs.openclaw.ai/cli/acp)[completion](https://docs.openclaw.ai/cli/completion)

----
url: https://docs.openclaw.ai/install/azure
----

# Azure - OpenClaw

## [​](#openclaw-on-azure-linux-vm)OpenClaw on Azure Linux VM

This guide sets up an Azure Linux VM with the Azure CLI, applies Network Security Group (NSG) hardening, configures Azure Bastion for SSH access, and installs OpenClaw.

## [​](#what-you-will-do)What you will do

* Create Azure networking (VNet, subnets, NSG) and compute resources with the Azure CLI
* Apply Network Security Group rules so VM SSH is allowed only from Azure Bastion
* Use Azure Bastion for SSH access (no public IP on the VM)
* Install OpenClaw with the installer script
* Verify the Gateway

## [​](#what-you-need)What you need

* An Azure subscription with permission to create compute and network resources
* Azure CLI installed (see [Azure CLI install steps](https://learn.microsoft.com/cli/azure/install-azure-cli) if needed)
* An SSH key pair (the guide covers generating one if needed)
* \~20-30 minutes

## [​](#configure-deployment)Configure deployment

1

[](#)

Sign in to Azure CLI

```
az login
az extension add -n ssh
```

The `ssh` extension is required for Azure Bastion native SSH tunneling.

2

[](#)

Register required resource providers (one-time)

```
az provider register --namespace Microsoft.Compute
az provider register --namespace Microsoft.Network
```

Verify registration. Wait until both show `Registered`.

```
az provider show --namespace Microsoft.Compute --query registrationState -o tsv
az provider show --namespace Microsoft.Network --query registrationState -o tsv
```

3

[](#)

Set deployment variables

```
RG="rg-openclaw"
LOCATION="westus2"
VNET_NAME="vnet-openclaw"
VNET_PREFIX="10.40.0.0/16"
VM_SUBNET_NAME="snet-openclaw-vm"
VM_SUBNET_PREFIX="10.40.2.0/24"
BASTION_SUBNET_PREFIX="10.40.1.0/26"
NSG_NAME="nsg-openclaw-vm"
VM_NAME="vm-openclaw"
ADMIN_USERNAME="openclaw"
BASTION_NAME="bas-openclaw"
BASTION_PIP_NAME="pip-openclaw-bastion"
```

Adjust names and CIDR ranges to fit your environment. The Bastion subnet must be at least `/26`.

4

[](#)

Select SSH key

Use your existing public key if you have one:

```
SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)"
```

If you don’t have an SSH key yet, generate one:

```
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519 -C "you@example.com"
SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)"
```

5

[](#)

Select VM size and OS disk size

```
VM_SIZE="Standard_B2as_v2"
OS_DISK_SIZE_GB=64
```

Choose a VM size and OS disk size available in your subscription and region:

* Start smaller for light usage and scale up later
* Use more vCPU/RAM/disk for heavier automation, more channels, or larger model/tool workloads
* If a VM size is unavailable in your region or subscription quota, pick the closest available SKU

List VM sizes available in your target region:

```
az vm list-skus --location "${LOCATION}" --resource-type virtualMachines -o table
```

Check your current vCPU and disk usage/quota:

```
az vm list-usage --location "${LOCATION}" -o table
```

## [​](#deploy-azure-resources)Deploy Azure resources

1

[](#)

Create the resource group

```
az group create -n "${RG}" -l "${LOCATION}"
```

2

[](#)

Create the network security group

Create the NSG and add rules so only the Bastion subnet can SSH into the VM.

```
az network nsg create \
  -g "${RG}" -n "${NSG_NAME}" -l "${LOCATION}"

# Allow SSH from the Bastion subnet only
az network nsg rule create \
  -g "${RG}" --nsg-name "${NSG_NAME}" \
  -n AllowSshFromBastionSubnet --priority 100 \
  --access Allow --direction Inbound --protocol Tcp \
  --source-address-prefixes "${BASTION_SUBNET_PREFIX}" \
  --destination-port-ranges 22

# Deny SSH from the public internet
az network nsg rule create \
  -g "${RG}" --nsg-name "${NSG_NAME}" \
  -n DenyInternetSsh --priority 110 \
  --access Deny --direction Inbound --protocol Tcp \
  --source-address-prefixes Internet \
  --destination-port-ranges 22

# Deny SSH from other VNet sources
az network nsg rule create \
  -g "${RG}" --nsg-name "${NSG_NAME}" \
  -n DenyVnetSsh --priority 120 \
  --access Deny --direction Inbound --protocol Tcp \
  --source-address-prefixes VirtualNetwork \
  --destination-port-ranges 22
```

The rules are evaluated by priority (lowest number first): Bastion traffic is allowed at 100, then all other SSH is blocked at 110 and 120.

3

[](#)

Create the virtual network and subnets

Create the VNet with the VM subnet (NSG attached), then add the Bastion subnet.

```
az network vnet create \
  -g "${RG}" -n "${VNET_NAME}" -l "${LOCATION}" \
  --address-prefixes "${VNET_PREFIX}" \
  --subnet-name "${VM_SUBNET_NAME}" \
  --subnet-prefixes "${VM_SUBNET_PREFIX}"

# Attach the NSG to the VM subnet
az network vnet subnet update \
  -g "${RG}" --vnet-name "${VNET_NAME}" \
  -n "${VM_SUBNET_NAME}" --nsg "${NSG_NAME}"

# AzureBastionSubnet — name is required by Azure
az network vnet subnet create \
  -g "${RG}" --vnet-name "${VNET_NAME}" \
  -n AzureBastionSubnet \
  --address-prefixes "${BASTION_SUBNET_PREFIX}"
```

4

[](#)

Create the VM

The VM has no public IP. SSH access is exclusively through Azure Bastion.

```
az vm create \
  -g "${RG}" -n "${VM_NAME}" -l "${LOCATION}" \
  --image "Canonical:ubuntu-24_04-lts:server:latest" \
  --size "${VM_SIZE}" \
  --os-disk-size-gb "${OS_DISK_SIZE_GB}" \
  --storage-sku StandardSSD_LRS \
  --admin-username "${ADMIN_USERNAME}" \
  --ssh-key-values "${SSH_PUB_KEY}" \
  --vnet-name "${VNET_NAME}" \
  --subnet "${VM_SUBNET_NAME}" \
  --public-ip-address "" \
  --nsg ""
```

`--public-ip-address ""` prevents a public IP from being assigned. `--nsg ""` skips creating a per-NIC NSG (the subnet-level NSG handles security).**Reproducibility:** The command above uses `latest` for the Ubuntu image. To pin a specific version, list available versions and replace `latest`:

```
az vm image list \
  --publisher Canonical --offer ubuntu-24_04-lts \
  --sku server --all -o table
```

5

[](#)

Create Azure Bastion

Azure Bastion provides managed SSH access to the VM without exposing a public IP. Standard SKU with tunneling is required for CLI-based `az network bastion ssh`.

```
az network public-ip create \
  -g "${RG}" -n "${BASTION_PIP_NAME}" -l "${LOCATION}" \
  --sku Standard --allocation-method Static

az network bastion create \
  -g "${RG}" -n "${BASTION_NAME}" -l "${LOCATION}" \
  --vnet-name "${VNET_NAME}" \
  --public-ip-address "${BASTION_PIP_NAME}" \
  --sku Standard --enable-tunneling true
```

Bastion provisioning typically takes 5-10 minutes but can take up to 15-30 minutes in some regions.

## [​](#install-openclaw)Install OpenClaw

1

[](#)

SSH into the VM through Azure Bastion

```
VM_ID="$(az vm show -g "${RG}" -n "${VM_NAME}" --query id -o tsv)"

az network bastion ssh \
  --name "${BASTION_NAME}" \
  --resource-group "${RG}" \
  --target-resource-id "${VM_ID}" \
  --auth-type ssh-key \
  --username "${ADMIN_USERNAME}" \
  --ssh-key ~/.ssh/id_ed25519
```

2

[](#)

Install OpenClaw (in the VM shell)

```
curl -fsSL https://openclaw.ai/install.sh -o /tmp/install.sh
bash /tmp/install.sh
rm -f /tmp/install.sh
```

The installer installs Node LTS and dependencies if not already present, installs OpenClaw, and launches the onboarding wizard. See [Install](https://docs.openclaw.ai/install) for details.

3

[](#)

Verify the Gateway

After onboarding completes:

```
openclaw gateway status
```

Most enterprise Azure teams already have GitHub Copilot licenses. If that is your case, we recommend choosing the GitHub Copilot provider in the OpenClaw onboarding wizard. See [GitHub Copilot provider](https://docs.openclaw.ai/providers/github-copilot).

## [​](#cost-considerations)Cost considerations

Azure Bastion Standard SKU runs approximately **$140/month** and the VM (Standard\_B2as\_v2) runs approximately **$55/month**. To reduce costs:

* **Deallocate the VM** when not in use (stops compute billing; disk charges remain). The OpenClaw Gateway will not be reachable while the VM is deallocated — restart it when you need it live again:
  ```
  az vm deallocate -g "${RG}" -n "${VM_NAME}"
  az vm start -g "${RG}" -n "${VM_NAME}"   # restart later
  ```
* **Delete Bastion when not needed** and recreate it when you need SSH access. Bastion is the largest cost component and takes only a few minutes to provision.
* **Use the Basic Bastion SKU** (\~$38/month) if you only need Portal-based SSH and don’t require CLI tunneling (`az network bastion ssh`).

## [​](#cleanup)Cleanup

To delete all resources created by this guide:

```
az group delete -n "${RG}" --yes --no-wait
```

This removes the resource group and everything inside it (VM, VNet, NSG, Bastion, public IP).

## [​](#next-steps)Next steps

* Set up messaging channels: [Channels](https://docs.openclaw.ai/channels)
* Pair local devices as nodes: [Nodes](https://docs.openclaw.ai/nodes)
* Configure the Gateway: [Gateway configuration](https://docs.openclaw.ai/gateway/configuration)
* For more details on OpenClaw Azure deployment with the GitHub Copilot model provider: [OpenClaw on Azure with GitHub Copilot](https://github.com/johnsonshi/openclaw-azure-github-copilot)

----
url: https://docs.openclaw.ai/channels/feishu
----

# Feishu - OpenClaw

## Feishu bot

Feishu (Lark) is a team chat platform used by companies for messaging and collaboration. This plugin connects OpenClaw to a Feishu/Lark bot using the platform’s WebSocket event subscription so messages can be received without exposing a public webhook URL.

***

## Bundled plugin

Feishu ships bundled with current OpenClaw releases, so no separate plugin install is required. If you are using an older build or a custom install that does not include bundled Feishu, install it manually:

***

## Quickstart

There are two ways to add the Feishu channel:

### Method 1: onboarding (recommended)

If you just installed OpenClaw, run onboarding:

The wizard guides you through:

1. Creating a Feishu app and collecting credentials
2. Configuring app credentials in OpenClaw
3. Starting the gateway

✅ **After configuration**, check gateway status:

### Method 2: CLI setup

If you already completed initial install, add the channel via CLI:

Choose **Feishu**, then enter the App ID and App Secret. ✅ **After configuration**, manage the gateway:

***

## Step 1: Create a Feishu app

### 1. Open Feishu Open Platform

Visit [Feishu Open Platform](https://open.feishu.cn/app) and sign in. Lark (global) tenants should use <https://open.larksuite.com/app> and set `domain: "lark"` in the Feishu config.

### 2. Create an app

1. Click **Create enterprise app**
2. Fill in the app name + description
3. Choose an app icon

### 3. Copy credentials

From **Credentials & Basic Info**, copy:

❗ **Important:** keep the App Secret private.

### 4. Configure permissions

On **Permissions**, click **Batch import** and paste:

```
{
  "scopes": {
    "tenant": [
      "aily:file:read",
      "aily:file:write",
      "application:application.app_message_stats.overview:readonly",
      "application:application:self_manage",
      "application:bot.menu:write",
      "cardkit:card:read",
      "cardkit:card:write",
      "contact:user.employee_id:readonly",
      "corehr:file:download",
      "event:ip_list",
      "im:chat.access_event.bot_p2p_chat:read",
      "im:chat.members:bot_access",
      "im:message",
      "im:message.group_at_msg:readonly",
      "im:message.p2p_msg:readonly",
      "im:message:readonly",
      "im:message:send_as_bot",
      "im:resource"
    ],
    "user": ["aily:file:read", "aily:file:write", "im:chat.access_event.bot_p2p_chat:read"]
  }
}
```

### 5. Enable bot capability

In **App Capability** > **Bot**:

1. Enable bot capability
2. Set the bot name

### 6. Configure event subscription

⚠️ **Important:** before setting event subscription, make sure:

1. You already ran `openclaw channels add` for Feishu
2. The gateway is running (`openclaw gateway status`)

In **Event Subscription**:

1. Choose **Use long connection to receive events** (WebSocket)
2. Add the event: `im.message.receive_v1`

⚠️ If the gateway is not running, the long-connection setup may fail to save.

### 7. Publish the app

1. Create a version in **Version Management & Release**
2. Submit for review and publish
3. Wait for admin approval (enterprise apps usually auto-approve)

***

## Step 2: Configure OpenClaw

### Configure with the wizard (recommended)

Choose **Feishu** and paste your App ID + App Secret.

### Configure via config file

Edit `~/.openclaw/openclaw.json`:

If you use `connectionMode: "webhook"`, set both `verificationToken` and `encryptKey`. The Feishu webhook server binds to `127.0.0.1` by default; set `webhookHost` only if you intentionally need a different bind address.

#### Verification Token and Encrypt Key (webhook mode)

When using webhook mode, set both `channels.feishu.verificationToken` and `channels.feishu.encryptKey` in your config. To get the values:

1. In Feishu Open Platform, open your app
2. Go to **Development** → **Events & Callbacks** (开发配置 → 事件与回调)
3. Open the **Encryption** tab (加密策略)
4. Copy **Verification Token** and **Encrypt Key**

The screenshot below shows where to find the **Verification Token**. The **Encrypt Key** is listed in the same **Encryption** section.

### Configure via environment variables

### Lark (global) domain

If your tenant is on Lark (international), set the domain to `lark` (or a full domain string). You can set it at `channels.feishu.domain` or per account (`channels.feishu.accounts.<id>.domain`).

### Quota optimization flags

You can reduce Feishu API usage with two optional flags:

Set them at top level or per account:

```
{
  channels: {
    feishu: {
      typingIndicator: false,
      resolveSenderNames: false,
      accounts: {
        main: {
          appId: "cli_xxx",
          appSecret: "xxx",
          typingIndicator: true,
          resolveSenderNames: false,
        },
      },
    },
  },
}
```

***

## Step 3: Start + test

### 1. Start the gateway

### 2. Send a test message

In Feishu, find your bot and send a message.

### 3. Approve pairing

By default, the bot replies with a pairing code. Approve it:

After approval, you can chat normally.

***

## Overview

***

## Access control

### Direct messages

### Group chats

**1. Group policy** (`channels.feishu.groupPolicy`):

**2. Mention requirement** (`channels.feishu.groups.<chat_id>.requireMention`):

***

## Group configuration examples

### Allow all groups, require @mention (default)

### Allow all groups, no @mention required

### Allow specific groups only

### Restrict which senders can message in a group (sender allowlist)

In addition to allowing the group itself, **all messages** in that group are gated by the sender open\_id: only users listed in `groups.<chat_id>.allowFrom` have their messages processed; messages from other members are ignored (this is full sender-level gating, not only for control commands like /reset or /new).

***

## Get group/user IDs

### Group IDs (chat\_id)

Group IDs look like `oc_xxx`. **Method 1 (recommended)**

1. Start the gateway and @mention the bot in the group
2. Run `openclaw logs --follow` and look for `chat_id`

**Method 2** Use the Feishu API debugger to list group chats.

### User IDs (open\_id)

User IDs look like `ou_xxx`. **Method 1 (recommended)**

1. Start the gateway and DM the bot
2. Run `openclaw logs --follow` and look for `open_id`

**Method 2** Check pairing requests for user Open IDs:

***

## Common commands

| Command   | Description       |
| --------- | ----------------- |
| `/status` | Show bot status   |
| `/reset`  | Reset the session |
| `/model`  | Show/switch model |

> Note: Feishu does not support native command menus yet, so commands must be sent as text.

## Gateway management commands

| Command                    | Description                   |
| -------------------------- | ----------------------------- |
| `openclaw gateway status`  | Show gateway status           |
| `openclaw gateway install` | Install/start gateway service |
| `openclaw gateway stop`    | Stop gateway service          |
| `openclaw gateway restart` | Restart gateway service       |
| `openclaw logs --follow`   | Tail gateway logs             |

***

## Troubleshooting

### Bot does not respond in group chats

1. Ensure the bot is added to the group
2. Ensure you @mention the bot (default behavior)
3. Check `groupPolicy` is not set to `"disabled"`
4. Check logs: `openclaw logs --follow`

### Bot does not receive messages

1. Ensure the app is published and approved
2. Ensure event subscription includes `im.message.receive_v1`
3. Ensure **long connection** is enabled
4. Ensure app permissions are complete
5. Ensure the gateway is running: `openclaw gateway status`
6. Check logs: `openclaw logs --follow`

### App Secret leak

1. Reset the App Secret in Feishu Open Platform
2. Update the App Secret in your config
3. Restart the gateway

### Message send failures

1. Ensure the app has `im:message:send_as_bot` permission
2. Ensure the app is published
3. Check logs for detailed errors

***

## Advanced configuration

### Multiple accounts

```
{
  channels: {
    feishu: {
      defaultAccount: "main",
      accounts: {
        main: {
          appId: "cli_xxx",
          appSecret: "xxx",
          botName: "Primary bot",
        },
        backup: {
          appId: "cli_yyy",
          appSecret: "yyy",
          botName: "Backup bot",
          enabled: false,
        },
      },
    },
  },
}
```

`defaultAccount` controls which Feishu account is used when outbound APIs do not specify an `accountId` explicitly.

### Message limits

### Streaming

Feishu supports streaming replies via interactive cards. When enabled, the bot updates a card as it generates text.

Set `streaming: false` to wait for the full reply before sending.

### ACP sessions

Feishu supports ACP for:

Feishu ACP is text-command driven. There are no native slash-command menus, so use `/acp ...` messages directly in the conversation.

#### Persistent ACP bindings

Use top-level typed ACP bindings to pin a Feishu DM or topic conversation to a persistent ACP session.

```
{
  agents: {
    list: [
      {
        id: "codex",
        runtime: {
          type: "acp",
          acp: {
            agent: "codex",
            backend: "acpx",
            mode: "persistent",
            cwd: "/workspace/openclaw",
          },
        },
      },
    ],
  },
  bindings: [
    {
      type: "acp",
      agentId: "codex",
      match: {
        channel: "feishu",
        accountId: "default",
        peer: { kind: "direct", id: "ou_1234567890" },
      },
    },
    {
      type: "acp",
      agentId: "codex",
      match: {
        channel: "feishu",
        accountId: "default",
        peer: { kind: "group", id: "oc_group_chat:topic:om_topic_root" },
      },
      acp: { label: "codex-feishu-topic" },
    },
  ],
}
```

#### Thread-bound ACP spawn from chat

In a Feishu DM or topic conversation, you can spawn and bind an ACP session in place:

Notes:

### Multi-agent routing

Use `bindings` to route Feishu DMs or groups to different agents.

```
{
  agents: {
    list: [
      { id: "main" },
      {
        id: "clawd-fan",
        workspace: "/home/user/clawd-fan",
        agentDir: "/home/user/.openclaw/agents/clawd-fan/agent",
      },
      {
        id: "clawd-xi",
        workspace: "/home/user/clawd-xi",
        agentDir: "/home/user/.openclaw/agents/clawd-xi/agent",
      },
    ],
  },
  bindings: [
    {
      agentId: "main",
      match: {
        channel: "feishu",
        peer: { kind: "direct", id: "ou_xxx" },
      },
    },
    {
      agentId: "clawd-fan",
      match: {
        channel: "feishu",
        peer: { kind: "direct", id: "ou_yyy" },
      },
    },
    {
      agentId: "clawd-xi",
      match: {
        channel: "feishu",
        peer: { kind: "group", id: "oc_zzz" },
      },
    },
  ],
}
```

Routing fields:

See [Get group/user IDs](#get-groupuser-ids) for lookup tips.

***

## Configuration reference

Full configuration: [Gateway configuration](https://docs.openclaw.ai/gateway/configuration) Key options:

| Setting                                           | Description                             | Default          |
| ------------------------------------------------- | --------------------------------------- | ---------------- |
| `channels.feishu.enabled`                         | Enable/disable channel                  | `true`           |
| `channels.feishu.domain`                          | API domain (`feishu` or `lark`)         | `feishu`         |
| `channels.feishu.connectionMode`                  | Event transport mode                    | `websocket`      |
| `channels.feishu.defaultAccount`                  | Default account ID for outbound routing | `default`        |
| `channels.feishu.verificationToken`               | Required for webhook mode               | -                |
| `channels.feishu.encryptKey`                      | Required for webhook mode               | -                |
| `channels.feishu.webhookPath`                     | Webhook route path                      | `/feishu/events` |
| `channels.feishu.webhookHost`                     | Webhook bind host                       | `127.0.0.1`      |
| `channels.feishu.webhookPort`                     | Webhook bind port                       | `3000`           |
| `channels.feishu.accounts.<id>.appId`             | App ID                                  | -                |
| `channels.feishu.accounts.<id>.appSecret`         | App Secret                              | -                |
| `channels.feishu.accounts.<id>.domain`            | Per-account API domain override         | `feishu`         |
| `channels.feishu.dmPolicy`                        | DM policy                               | `pairing`        |
| `channels.feishu.allowFrom`                       | DM allowlist (open\_id list)            | -                |
| `channels.feishu.groupPolicy`                     | Group policy                            | `open`           |
| `channels.feishu.groupAllowFrom`                  | Group allowlist                         | -                |
| `channels.feishu.groups.<chat_id>.requireMention` | Require @mention                        | `true`           |
| `channels.feishu.groups.<chat_id>.enabled`        | Enable group                            | `true`           |
| `channels.feishu.textChunkLimit`                  | Message chunk size                      | `2000`           |
| `channels.feishu.mediaMaxMb`                      | Media size limit                        | `30`             |
| `channels.feishu.streaming`                       | Enable streaming card output            | `true`           |
| `channels.feishu.blockStreaming`                  | Enable block streaming                  | `true`           |

***

## dmPolicy reference

| Value         | Behavior                                                        |
| ------------- | --------------------------------------------------------------- |
| `"pairing"`   | **Default.** Unknown users get a pairing code; must be approved |
| `"allowlist"` | Only users in `allowFrom` can chat                              |
| `"open"`      | Allow all users (requires `"*"` in allowFrom)                   |
| `"disabled"`  | Disable DMs                                                     |

***

## Supported message types

### Receive

### Send

### Threads and replies

## Runtime action surface

Feishu currently exposes these runtime actions:

----
url: https://docs.openclaw.ai/install/render
----

# Render - OpenClaw

Deploy OpenClaw on Render using Infrastructure as Code. The included `render.yaml` Blueprint defines your entire stack declaratively, service, disk, environment variables, so you can deploy with a single click and version your infrastructure alongside your code.

## Prerequisites

## Deploy with a Render Blueprint

[Deploy to Render](https://render.com/deploy?repo=https://github.com/openclaw/openclaw) Clicking this link will:

1. Create a new Render service from the `render.yaml` Blueprint at the root of this repo.
2. Build the Docker image and deploy

Once deployed, your service URL follows the pattern `https://<service-name>.onrender.com`.

## Understanding the Blueprint

Render Blueprints are YAML files that define your infrastructure. The `render.yaml` in this repository configures everything needed to run OpenClaw:

Key Blueprint features used:

| Feature               | Purpose                                                    |
| --------------------- | ---------------------------------------------------------- |
| `runtime: docker`     | Builds from the repo’s Dockerfile                          |
| `healthCheckPath`     | Render monitors `/health` and restarts unhealthy instances |
| `generateValue: true` | Auto-generates a cryptographically secure value            |
| `disk`                | Persistent storage that survives redeploys                 |

## Choosing a plan

| Plan      | Spin-down         | Disk          | Best for                      |
| --------- | ----------------- | ------------- | ----------------------------- |
| Free      | After 15 min idle | Not available | Testing, demos                |
| Starter   | Never             | 1GB+          | Personal use, small teams     |
| Standard+ | Never             | 1GB+          | Production, multiple channels |

The Blueprint defaults to `starter`. To use free tier, change `plan: free` in your fork’s `render.yaml` (but note: no persistent disk means config resets on each deploy).

## After deployment

### Access the Control UI

The web dashboard is available at `https://<your-service>.onrender.com/`. Connect using the `OPENCLAW_GATEWAY_TOKEN` value that was auto-generated during deploy (find it in **Dashboard → your service → Environment**).

## Render Dashboard features

### Logs

View real-time logs in **Dashboard → your service → Logs**. Filter by:

### Shell access

For debugging, open a shell session via **Dashboard → your service → Shell**. The persistent disk is mounted at `/data`.

### Environment variables

Modify variables in **Dashboard → your service → Environment**. Changes trigger an automatic redeploy.

### Auto-deploy

If you use the original OpenClaw repository, Render will not auto-deploy your OpenClaw. To update it, run a manual Blueprint sync from the dashboard.

## Custom domain

1. Go to **Dashboard → your service → Settings → Custom Domains**
2. Add your domain
3. Configure DNS as instructed (CNAME to `*.onrender.com`)
4. Render provisions a TLS certificate automatically

## Scaling

Render supports horizontal and vertical scaling:

For OpenClaw, vertical scaling is usually sufficient. Horizontal scaling requires sticky sessions or external state management.

## Backups and migration

Export your configuration and workspace at any time using the shell access in the Render Dashboard:

This creates a portable backup archive you can restore on any OpenClaw host. See [Backup](https://docs.openclaw.ai/cli/backup) for details.

## Troubleshooting

### Service will not start

Check the deploy logs in the Render Dashboard. Common issues:

### Slow cold starts (free tier)

Free tier services spin down after 15 minutes of inactivity. The first request after spin-down takes a few seconds while the container starts. Upgrade to Starter plan for always-on.

### Data loss after redeploy

This happens on free tier (no persistent disk). Upgrade to a paid plan, or regularly export your config via `openclaw backup create` in the Render shell.

### Health check failures

Render expects a 200 response from `/health` within 30 seconds. If builds succeed but deploys fail, the service may be taking too long to start. Check:

## Next steps

----
url: https://docs.openclaw.ai/automation/hooks
----

# Hooks - OpenClaw

Hooks provide an extensible event-driven system for automating actions in response to agent commands and events. Hooks are automatically discovered from directories and can be inspected with `openclaw hooks`, while hook-pack installation and updates now go through `openclaw plugins`.

## Getting Oriented

Hooks are small scripts that run when something happens. There are two kinds:

Hooks can also be bundled inside plugins; see [Plugin hooks](https://docs.openclaw.ai/plugins/architecture#provider-runtime-hooks). `openclaw hooks list` shows both standalone hooks and plugin-managed hooks. Common uses:

If you can write a small TypeScript function, you can write a hook. Managed and bundled hooks are trusted local code. Workspace hooks are discovered automatically, but OpenClaw keeps them disabled until you explicitly enable them via the CLI or config.

## Overview

The hooks system allows you to:

## Getting Started

### Bundled Hooks

OpenClaw ships with four bundled hooks that are automatically discovered:

List available hooks:

Enable a hook:

Check hook status:

Get detailed information:

### Onboarding

During onboarding (`openclaw onboard`), you’ll be prompted to enable recommended hooks. The wizard automatically discovers eligible hooks and presents them for selection.

### Trust Boundary

Hooks run inside the Gateway process. Treat bundled hooks, managed hooks, and `hooks.internal.load.extraDirs` as trusted local code. Workspace hooks under `<workspace>/hooks/` are repo-local code, so OpenClaw requires an explicit enable step before loading them.

## Hook Discovery

Hooks are automatically discovered from these directories, in order of increasing override precedence:

1. **Bundled hooks**: shipped with OpenClaw; located at `<openclaw>/dist/hooks/bundled/` for npm installs (or a sibling `hooks/bundled/` for compiled binaries)
2. **Plugin hooks**: hooks bundled inside installed plugins (see [Plugin hooks](https://docs.openclaw.ai/plugins/architecture#provider-runtime-hooks))
3. **Managed hooks**: `~/.openclaw/hooks/` (user-installed, shared across workspaces; can override bundled and plugin hooks). **Extra hook directories** configured via `hooks.internal.load.extraDirs` are also treated as managed hooks and share the same override precedence.
4. **Workspace hooks**: `<workspace>/hooks/` (per-agent, disabled by default until explicitly enabled; cannot override hooks from other sources)

Workspace hooks can add new hook names for a repo, but they cannot override bundled, managed, or plugin-provided hooks with the same name. Managed hook directories can be either a **single hook** or a **hook pack** (package directory). Each hook is a directory containing:

## Hook Packs (npm/archives)

Hook packs are standard npm packages that export one or more hooks via `openclaw.hooks` in `package.json`. Install them with:

Npm specs are registry-only (package name + optional exact version or dist-tag). Git/URL/file specs and semver ranges are rejected. Bare specs and `@latest` stay on the stable track. If npm resolves either of those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a prerelease tag such as `@beta`/`@rc` or an exact prerelease version. Example `package.json`:

Each entry points to a hook directory containing `HOOK.md` and `handler.ts` (or `index.ts`). Hook packs can ship dependencies; they will be installed under `~/.openclaw/hooks/<id>`. Each `openclaw.hooks` entry must stay inside the package directory after symlink resolution; entries that escape are rejected. Security note: `openclaw plugins install` installs hook-pack dependencies with `npm install --ignore-scripts` (no lifecycle scripts). Keep hook pack dependency trees “pure JS/TS” and avoid packages that rely on `postinstall` builds.

## Hook Structure

### HOOK.md Format

The `HOOK.md` file contains metadata in YAML frontmatter plus Markdown documentation:

### Metadata Fields

The `metadata.openclaw` object supports:

### Handler Implementation

The `handler.ts` file exports a `HookHandler` function:

#### Event Context

Each event includes:

```
{
  type: 'command' | 'session' | 'agent' | 'gateway' | 'message',
  action: string,              // e.g., 'new', 'reset', 'stop', 'received', 'sent'
  sessionKey: string,          // Session identifier
  timestamp: Date,             // When the event occurred
  messages: string[],          // Push messages here to send to user
  context: {
    // Command events (command:new, command:reset):
    sessionEntry?: SessionEntry,       // current session entry
    previousSessionEntry?: SessionEntry, // pre-reset entry (preferred for session-memory)
    commandSource?: string,            // e.g., 'whatsapp', 'telegram'
    senderId?: string,
    workspaceDir?: string,
    cfg?: OpenClawConfig,
    // Command events (command:stop only):
    sessionId?: string,
    // Agent bootstrap events (agent:bootstrap):
    bootstrapFiles?: WorkspaceBootstrapFile[],
    // Message events (see Message Events section for full details):
    from?: string,             // message:received
    to?: string,               // message:sent
    content?: string,
    channelId?: string,
    success?: boolean,         // message:sent
  }
}
```

## Event Types

### Command Events

Triggered when agent commands are issued:

### Session Events

Internal hook payloads emit these as `type: "session"` with `action: "compact:before"` / `action: "compact:after"`; listeners subscribe with the combined keys above. Specific handler registration uses the literal key format `${type}:${action}`. For these events, register `session:compact:before` and `session:compact:after`.

### Agent Events

### Gateway Events

Triggered when the gateway starts:

### Message Events

Triggered when messages are received or sent:

#### Message Event Context

Message events include rich context about the message:

```
// message:received context
{
  from: string,           // Sender identifier (phone number, user ID, etc.)
  content: string,        // Message content
  timestamp?: number,     // Unix timestamp when received
  channelId: string,      // Channel (e.g., "whatsapp", "telegram", "discord")
  accountId?: string,     // Provider account ID for multi-account setups
  conversationId?: string, // Chat/conversation ID
  messageId?: string,     // Message ID from the provider
  metadata?: {            // Additional provider-specific data
    to?: string,
    provider?: string,
    surface?: string,
    threadId?: string | number,
    senderId?: string,
    senderName?: string,
    senderUsername?: string,
    senderE164?: string,
    guildId?: string,     // Discord guild / server ID
    channelName?: string, // Channel name (e.g., Discord channel name)
  }
}

// message:sent context
{
  to: string,             // Recipient identifier
  content: string,        // Message content that was sent
  success: boolean,       // Whether the send succeeded
  error?: string,         // Error message if sending failed
  channelId: string,      // Channel (e.g., "whatsapp", "telegram", "discord")
  accountId?: string,     // Provider account ID
  conversationId?: string, // Chat/conversation ID
  messageId?: string,     // Message ID returned by the provider
  isGroup?: boolean,      // Whether this outbound message belongs to a group/channel context
  groupId?: string,       // Group/channel identifier for correlation with message:received
}

// message:transcribed context
{
  from?: string,          // Sender identifier
  to?: string,            // Recipient identifier
  body?: string,          // Raw inbound body before enrichment
  bodyForAgent?: string,  // Enriched body visible to the agent
  transcript: string,     // Audio transcript text
  timestamp?: number,     // Unix timestamp when received
  channelId: string,      // Channel (e.g., "telegram", "whatsapp")
  conversationId?: string,
  messageId?: string,
  senderId?: string,      // Sender user ID
  senderName?: string,    // Sender display name
  senderUsername?: string,
  provider?: string,      // Provider name
  surface?: string,       // Surface name
  mediaPath?: string,     // Path to the media file that was transcribed
  mediaType?: string,     // MIME type of the media
}

// message:preprocessed context
{
  from?: string,          // Sender identifier
  to?: string,            // Recipient identifier
  body?: string,          // Raw inbound body
  bodyForAgent?: string,  // Final enriched body after media/link understanding
  transcript?: string,    // Transcript when audio was present
  timestamp?: number,     // Unix timestamp when received
  channelId: string,      // Channel (e.g., "telegram", "whatsapp")
  conversationId?: string,
  messageId?: string,
  senderId?: string,      // Sender user ID
  senderName?: string,    // Sender display name
  senderUsername?: string,
  provider?: string,      // Provider name
  surface?: string,       // Surface name
  mediaPath?: string,     // Path to the media file
  mediaType?: string,     // MIME type of the media
  isGroup?: boolean,
  groupId?: string,
}
```

#### Example: Message Logger Hook

### Tool Result Hooks (Plugin API)

These hooks are not event-stream listeners; they let plugins synchronously adjust tool results before OpenClaw persists them.

### Plugin Hook Events

Compaction lifecycle hooks exposed through the plugin hook runner:

### Future Events

Planned event types:

## Creating Custom Hooks

### 1. Choose Location

### 2. Create Directory Structure

### 3. Create HOOK.md

### 4. Create handler.ts

### 5. Enable and Test

## Configuration

### New Config Format (Recommended)

### Per-Hook Configuration

Hooks can have custom configuration:

Load hooks from additional directories (treated as managed hooks, same override precedence):

### Legacy Config Format (Still Supported)

The old config format still works for backwards compatibility:

Note: `module` must be a workspace-relative path. Absolute paths and traversal outside the workspace are rejected. **Migration**: Use the new discovery-based system for new hooks. Legacy handlers are loaded after directory-based hooks.

## CLI Commands

### List Hooks

### Hook Information

### Check Eligibility

### Enable/Disable

## Bundled hook reference

### session-memory

Saves session context to memory when you issue `/new` or `/reset`. **Events**: `command:new`, `command:reset` **Requirements**: `workspace.dir` must be configured **Output**: `<workspace>/memory/YYYY-MM-DD-slug.md` (defaults to `~/.openclaw/workspace`) **What it does**:

1. Uses the pre-reset session entry to locate the correct transcript
2. Extracts the last 15 user/assistant messages from the conversation (configurable)
3. Uses LLM to generate a descriptive filename slug
4. Saves session metadata to a dated memory file

**Example output**:

**Filename examples**:

**Enable**:

Injects additional bootstrap files (for example monorepo-local `AGENTS.md` / `TOOLS.md`) during `agent:bootstrap`. **Events**: `agent:bootstrap` **Requirements**: `workspace.dir` must be configured **Output**: No files written; bootstrap context is modified in-memory only. **Config**:

**Config options**:

**Notes**:

* Paths are resolved relative to workspace.
* Files must stay inside workspace (realpath-checked).
* Only recognized bootstrap basenames are loaded (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, `MEMORY.md`, `memory.md`).
* For subagent/cron sessions a narrower allowlist applies (`AGENTS.md`, `TOOLS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`).

**Enable**:

### command-logger

Logs all command events to a centralized audit file. **Events**: `command` **Requirements**: None **Output**: `~/.openclaw/logs/commands.log` **What it does**:

1. Captures event details (command action, timestamp, session key, sender ID, source)
2. Appends to log file in JSONL format
3. Runs silently in the background

**Example log entries**:

**View logs**:

**Enable**:

### boot-md

Runs `BOOT.md` when the gateway starts (after channels start). Internal hooks must be enabled for this to run. **Events**: `gateway:startup` **Requirements**: `workspace.dir` must be configured **What it does**:

1. Reads `BOOT.md` from your workspace
2. Runs the instructions via the agent runner
3. Sends any requested outbound messages via the message tool

**Enable**:

## Best Practices

### Keep Handlers Fast

Hooks run during command processing. Keep them lightweight:

### Handle Errors Gracefully

Always wrap risky operations:

### Filter Events Early

Return early if the event isn’t relevant:

### Use Specific Event Keys

Specify exact events in metadata when possible:

Rather than:

## Debugging

### Enable Hook Logging

The gateway logs hook loading at startup:

### Check Discovery

List all discovered hooks:

### Check Registration

In your handler, log when it’s called:

### Verify Eligibility

Check why a hook isn’t eligible:

Look for missing requirements in the output.

## Testing

### Gateway Logs

Monitor gateway logs to see hook execution:

### Test Hooks Directly

Test your handlers in isolation:

## Architecture

### Core Components

### Discovery Flow

### Event Flow

## Troubleshooting

### Hook Not Discovered

1. Check directory structure:
2. Verify HOOK.md format:
3. List all discovered hooks:

### Hook Not Eligible

Check requirements:

Look for missing:

### Hook Not Executing

1. Verify hook is enabled:
2. Restart your gateway process so hooks reload.
3. Check gateway logs for errors:

### Handler Errors

Check for TypeScript/import errors:

## Migration Guide

### From Legacy Config to Discovery

**Before**:

**After**:

1. Create hook directory:
2. Create HOOK.md:
3. Update config:
4. Verify and restart your gateway process:

**Benefits of migration**:

## See Also

----
url: https://docs.openclaw.ai/tools/exec-approvals
----

# Exec Approvals - OpenClaw

Exec approvals are the **companion app / node host guardrail** for letting a sandboxed agent run commands on a real host (`gateway` or `node`). Think of it like a safety interlock: commands are allowed only when policy + allowlist + (optional) user approval all agree. Exec approvals are **in addition** to tool policy and elevated gating (unless elevated is set to `full`, which skips approvals). Effective policy is the **stricter** of `tools.exec.*` and approvals defaults; if an approvals field is omitted, the `tools.exec` value is used. If the companion app UI is **not available**, any request that requires a prompt is resolved by the **ask fallback** (default: deny).

## Where it applies

Exec approvals are enforced locally on the execution host:

Trust model note:

macOS split:

## Settings and storage

Approvals live in a local JSON file on the execution host: `~/.openclaw/exec-approvals.json` Example schema:

```
{
  "version": 1,
  "socket": {
    "path": "~/.openclaw/exec-approvals.sock",
    "token": "base64url-token"
  },
  "defaults": {
    "security": "deny",
    "ask": "on-miss",
    "askFallback": "deny",
    "autoAllowSkills": false
  },
  "agents": {
    "main": {
      "security": "allowlist",
      "ask": "on-miss",
      "askFallback": "deny",
      "autoAllowSkills": true,
      "allowlist": [
        {
          "id": "B0C8C0B3-2C2D-4F8A-9A3C-5A4B3C2D1E0F",
          "pattern": "~/Projects/**/bin/rg",
          "lastUsedAt": 1737150000000,
          "lastUsedCommand": "rg -n TODO",
          "lastResolvedPath": "/Users/user/Projects/.../bin/rg"
        }
      ]
    }
  }
}
```

## Policy knobs

### Security (`exec.security`)

### Ask (`exec.ask`)

### Ask fallback (`askFallback`)

If a prompt is required but no UI is reachable, fallback decides:

### Inline interpreter eval hardening (`tools.exec.strictInlineEval`)

When `tools.exec.strictInlineEval=true`, OpenClaw treats inline code-eval forms as approval-only even if the interpreter binary itself is allowlisted. Examples:

This is defense-in-depth for interpreter loaders that do not map cleanly to one stable file operand. In strict mode:

## Allowlist (per agent)

Allowlists are **per agent**. If multiple agents exist, switch which agent you’re editing in the macOS app. Patterns are **case-insensitive glob matches**. Patterns should resolve to **binary paths** (basename-only entries are ignored). Legacy `agents.default` entries are migrated to `agents.main` on load. Examples:

Each allowlist entry tracks:

## Auto-allow skill CLIs

When **Auto-allow skill CLIs** is enabled, executables referenced by known skills are treated as allowlisted on nodes (macOS node or headless node host). This uses `skills.bins` over the Gateway RPC to fetch the skill bin list. Disable this if you want strict manual allowlists. Important trust notes:

## Safe bins (stdin-only)

`tools.exec.safeBins` defines a small list of **stdin-only** binaries (for example `cut`) that can run in allowlist mode **without** explicit allowlist entries. Safe bins reject positional file args and path-like tokens, so they can only operate on the incoming stream. Treat this as a narrow fast-path for stream filters, not a general trust list. Do **not** add interpreter or runtime binaries (for example `python3`, `node`, `ruby`, `bash`, `sh`, `zsh`) to `safeBins`. If a command can evaluate code, execute subcommands, or read files by design, prefer explicit allowlist entries and keep approval prompts enabled. Custom safe bins must define an explicit profile in `tools.exec.safeBinProfiles.<bin>`. Validation is deterministic from argv shape only (no host filesystem existence checks), which prevents file-existence oracle behavior from allow/deny differences. File-oriented options are denied for default safe bins (for example `sort -o`, `sort --output`, `sort --files0-from`, `sort --compress-program`, `sort --random-source`, `sort --temporary-directory`/`-T`, `wc --files0-from`, `jq -f/--from-file`, `grep -f/--file`). Safe bins also enforce explicit per-binary flag policy for options that break stdin-only behavior (for example `sort -o/--output/--compress-program` and grep recursive flags). Long options are validated fail-closed in safe-bin mode: unknown flags and ambiguous abbreviations are rejected. Denied flags by safe-bin profile:

* `grep`: `--dereference-recursive`, `--directories`, `--exclude-from`, `--file`, `--recursive`, `-R`, `-d`, `-f`, `-r`
* `jq`: `--argfile`, `--from-file`, `--library-path`, `--rawfile`, `--slurpfile`, `-L`, `-f`
* `sort`: `--compress-program`, `--files0-from`, `--output`, `--random-source`, `--temporary-directory`, `-T`, `-o`
* `wc`: `--files0-from`

Safe bins also force argv tokens to be treated as **literal text** at execution time (no globbing and no `$VARS` expansion) for stdin-only segments, so patterns like `*` or `$HOME/...` cannot be used to smuggle file reads. Safe bins must also resolve from trusted binary directories (system defaults plus optional `tools.exec.safeBinTrustedDirs`). `PATH` entries are never auto-trusted. Default trusted safe-bin directories are intentionally minimal: `/bin`, `/usr/bin`. If your safe-bin executable lives in package-manager/user paths (for example `/opt/homebrew/bin`, `/usr/local/bin`, `/opt/local/bin`, `/snap/bin`), add them explicitly to `tools.exec.safeBinTrustedDirs`. Shell chaining and redirections are not auto-allowed in allowlist mode. Shell chaining (`&&`, `||`, `;`) is allowed when every top-level segment satisfies the allowlist (including safe bins or skill auto-allow). Redirections remain unsupported in allowlist mode. Command substitution (`$()` / backticks) is rejected during allowlist parsing, including inside double quotes; use single quotes if you need literal `$()` text. On macOS companion-app approvals, raw shell text containing shell control or expansion syntax (`&&`, `||`, `;`, `|`, `` ` ``, `$`, `<`, `>`, `(`, `)`) is treated as an allowlist miss unless the shell binary itself is allowlisted. For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped env overrides are reduced to a small explicit allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`). For allow-always decisions in allowlist mode, known dispatch wrappers (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist inner executable paths instead of wrapper paths. Shell multiplexers (`busybox`, `toybox`) are also unwrapped for shell applets (`sh`, `ash`, etc.) so inner executables are persisted instead of multiplexer binaries. If a wrapper or multiplexer cannot be safely unwrapped, no allowlist entry is persisted automatically. If you allowlist interpreters like `python3` or `node`, prefer `tools.exec.strictInlineEval=true` so inline eval still requires an explicit approval. Default safe bins: `cut`, `uniq`, `head`, `tail`, `tr`, `wc` `grep` and `sort` are not in the default list. If you opt in, keep explicit allowlist entries for their non-stdin workflows. For `grep` in safe-bin mode, provide the pattern with `-e`/`--regexp`; positional pattern form is rejected so file operands cannot be smuggled as ambiguous positionals.

### Safe bins versus allowlist

| Topic            | `tools.exec.safeBins`                                  | Allowlist (`exec-approvals.json`)                            |
| ---------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
| Goal             | Auto-allow narrow stdin filters                        | Explicitly trust specific executables                        |
| Match type       | Executable name + safe-bin argv policy                 | Resolved executable path glob pattern                        |
| Argument scope   | Restricted by safe-bin profile and literal-token rules | Path match only; arguments are otherwise your responsibility |
| Typical examples | `head`, `tail`, `tr`, `wc`                             | `jq`, `python3`, `node`, `ffmpeg`, custom CLIs               |
| Best use         | Low-risk text transforms in pipelines                  | Any tool with broader behavior or side effects               |

Configuration location:

Custom profile example:

```
{
  tools: {
    exec: {
      safeBins: ["jq", "myfilter"],
      safeBinProfiles: {
        myfilter: {
          minPositional: 0,
          maxPositional: 0,
          allowedValueFlags: ["-n", "--limit"],
          deniedFlags: ["-f", "--file", "-c", "--command"],
        },
      },
    },
  },
}
```

If you explicitly opt `jq` into `safeBins`, OpenClaw still rejects the `env` builtin in safe-bin mode so `jq -n env` cannot dump the host process environment without an explicit allowlist path or approval prompt.

## Control UI editing

Use the **Control UI → Nodes → Exec approvals** card to edit defaults, per‑agent overrides, and allowlists. Pick a scope (Defaults or an agent), tweak the policy, add/remove allowlist patterns, then **Save**. The UI shows **last used** metadata per pattern so you can keep the list tidy. The target selector chooses **Gateway** (local approvals) or a **Node**. Nodes must advertise `system.execApprovals.get/set` (macOS app or headless node host). If a node does not advertise exec approvals yet, edit its local `~/.openclaw/exec-approvals.json` directly. CLI: `openclaw approvals` supports gateway or node editing (see [Approvals CLI](https://docs.openclaw.ai/cli/approvals)).

## Approval flow

When a prompt is required, the gateway broadcasts `exec.approval.requested` to operator clients. The Control UI and macOS app resolve it via `exec.approval.resolve`, then the gateway forwards the approved request to the node host. For `host=node`, approval requests include a canonical `systemRunPlan` payload. The gateway uses that plan as the authoritative command/cwd/session context when forwarding approved `system.run` requests.

## Interpreter/runtime commands

Approval-backed interpreter/runtime runs are intentionally conservative:

* Exact argv/cwd/env context is always bound.
* Direct shell script and direct runtime file forms are best-effort bound to one concrete local file snapshot.
* Common package-manager wrapper forms that still resolve to one direct local file (for example `pnpm exec`, `pnpm node`, `npm exec`, `npx`) are unwrapped before binding.
* If OpenClaw cannot identify exactly one concrete local file for an interpreter/runtime command (for example package scripts, eval forms, runtime-specific loader chains, or ambiguous multi-file forms), approval-backed execution is denied instead of claiming semantic coverage it does not have.
* For those workflows, prefer sandboxing, a separate host boundary, or an explicit trusted allowlist/full workflow where the operator accepts the broader runtime semantics.

When approvals are required, the exec tool returns immediately with an approval id. Use that id to correlate later system events (`Exec finished` / `Exec denied`). If no decision arrives before the timeout, the request is treated as an approval timeout and surfaced as a denial reason. The confirmation dialog includes:

Actions:

## Approval forwarding to chat channels

You can forward exec approval prompts to any chat channel (including plugin channels) and approve them with `/approve`. This uses the normal outbound delivery pipeline. Config:

```
{
  approvals: {
    exec: {
      enabled: true,
      mode: "session", // "session" | "targets" | "both"
      agentFilter: ["main"],
      sessionFilter: ["discord"], // substring or regex
      targets: [
        { channel: "slack", to: "U12345678" },
        { channel: "telegram", to: "123456789" },
      ],
    },
  },
}
```

Reply in chat:

### Built-in chat approval clients

Discord and Telegram can also act as explicit exec approval clients with channel-specific config.

These clients are opt-in. If a channel does not have exec approvals enabled, OpenClaw does not treat that channel as an approval surface just because the conversation happened there. Shared behavior:

Telegram defaults to approver DMs (`target: "dm"`). You can switch to `channel` or `both` when you want approval prompts to appear in the originating Telegram chat/topic as well. For Telegram forum topics, OpenClaw preserves the topic for the approval prompt and the post-approval follow-up. See:

### macOS IPC flow

Security notes:

## System events

Exec lifecycle is surfaced as system messages:

These are posted to the agent’s session after the node reports the event. Gateway-host exec approvals emit the same lifecycle events when the command finishes (and optionally when running longer than the threshold). Approval-gated execs reuse the approval id as the `runId` in these messages for easy correlation.

## Implications

Related:

----
url: https://docs.openclaw.ai/plugins/sdk-runtime
----

# Plugin Runtime Helpers - OpenClaw

Reference for the `api.runtime` object injected into every plugin during registration. Use these helpers instead of importing host internals directly.

## Runtime namespaces

### `api.runtime.agent`

Agent identity, directories, and session management.

```
// Resolve the agent's working directory
const agentDir = api.runtime.agent.resolveAgentDir(cfg);

// Resolve agent workspace
const workspaceDir = api.runtime.agent.resolveAgentWorkspaceDir(cfg);

// Get agent identity
const identity = api.runtime.agent.resolveAgentIdentity(cfg);

// Get default thinking level
const thinking = api.runtime.agent.resolveThinkingDefault(cfg, provider, model);

// Get agent timeout
const timeoutMs = api.runtime.agent.resolveAgentTimeoutMs(cfg);

// Ensure workspace exists
await api.runtime.agent.ensureAgentWorkspace(cfg);

// Run an embedded Pi agent
const agentDir = api.runtime.agent.resolveAgentDir(cfg);
const result = await api.runtime.agent.runEmbeddedPiAgent({
  sessionId: "my-plugin:task-1",
  runId: crypto.randomUUID(),
  sessionFile: path.join(agentDir, "sessions", "my-plugin-task-1.jsonl"),
  workspaceDir: api.runtime.agent.resolveAgentWorkspaceDir(cfg),
  prompt: "Summarize the latest changes",
  timeoutMs: api.runtime.agent.resolveAgentTimeoutMs(cfg),
});
```

**Session store helpers** are under `api.runtime.agent.session`:

### `api.runtime.agent.defaults`

Default model and provider constants:

### `api.runtime.subagent`

Launch and manage background subagent runs.

### `api.runtime.tts`

Text-to-speech synthesis.

Uses core `messages.tts` configuration and provider selection. Returns PCM audio buffer + sample rate.

### `api.runtime.mediaUnderstanding`

Image, audio, and video analysis.

```
// Describe an image
const image = await api.runtime.mediaUnderstanding.describeImageFile({
  filePath: "/tmp/inbound-photo.jpg",
  cfg: api.config,
  agentDir: "/tmp/agent",
});

// Transcribe audio
const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
  filePath: "/tmp/inbound-audio.ogg",
  cfg: api.config,
  mime: "audio/ogg", // optional, for when MIME cannot be inferred
});

// Describe a video
const video = await api.runtime.mediaUnderstanding.describeVideoFile({
  filePath: "/tmp/inbound-video.mp4",
  cfg: api.config,
});

// Generic file analysis
const result = await api.runtime.mediaUnderstanding.runFile({
  filePath: "/tmp/inbound-file.pdf",
  cfg: api.config,
});
```

Returns `{ text: undefined }` when no output is produced (e.g. skipped input).

### `api.runtime.imageGeneration`

Image generation.

### `api.runtime.webSearch`

Web search.

### `api.runtime.media`

Low-level media utilities.

### `api.runtime.config`

Config load and write.

### `api.runtime.system`

System-level utilities.

### `api.runtime.events`

Event subscriptions.

### `api.runtime.logging`

Logging.

### `api.runtime.modelAuth`

Model and provider auth resolution.

### `api.runtime.state`

State directory resolution.

### `api.runtime.tools`

Memory tool factories and CLI.

### `api.runtime.channel`

Channel-specific runtime helpers (available when a channel plugin is loaded).

## Storing runtime references

Use `createPluginRuntimeStore` to store the runtime reference for use outside the `register` callback:

## Other top-level `api` fields

Beyond `api.runtime`, the API object also provides:

| Field                    | Type                      | Description                                               |
| ------------------------ | ------------------------- | --------------------------------------------------------- |
| `api.id`                 | `string`                  | Plugin id                                                 |
| `api.name`               | `string`                  | Plugin display name                                       |
| `api.config`             | `OpenClawConfig`          | Current config snapshot                                   |
| `api.pluginConfig`       | `Record<string, unknown>` | Plugin-specific config from `plugins.entries.<id>.config` |
| `api.logger`             | `PluginLogger`            | Scoped logger (`debug`, `info`, `warn`, `error`)          |
| `api.registrationMode`   | `PluginRegistrationMode`  | `"full"`, `"setup-only"`, or `"setup-runtime"`            |
| `api.resolvePath(input)` | `(string) => string`      | Resolve a path relative to the plugin root                |

----
url: https://docs.openclaw.ai/tools/lobster
----

# Lobster - OpenClaw

Lobster is a workflow shell that lets OpenClaw run multi-step tool sequences as a single, deterministic operation with explicit approval checkpoints.

## Hook

Your assistant can build the tools that manage itself. Ask for a workflow, and 30 minutes later you have a CLI plus pipelines that run as one call. Lobster is the missing piece: deterministic pipelines, explicit approvals, and resumable state.

## Why

Today, complex workflows require many back-and-forth tool calls. Each call costs tokens, and the LLM has to orchestrate every step. Lobster moves that orchestration into a typed runtime:

## Why a DSL instead of plain programs?

Lobster is intentionally small. The goal is not “a new language,” it’s a predictable, AI-friendly pipeline spec with first-class approvals and resume tokens.

* **Approve/resume is built in**: A normal program can prompt a human, but it can’t *pause and resume* with a durable token without you inventing that runtime yourself.
* **Determinism + auditability**: Pipelines are data, so they’re easy to log, diff, replay, and review.
* **Constrained surface for AI**: A tiny grammar + JSON piping reduces “creative” code paths and makes validation realistic.
* **Safety policy baked in**: Timeouts, output caps, sandbox checks, and allowlists are enforced by the runtime, not each script.
* **Still programmable**: Each step can call any CLI or script. If you want JS/TS, generate `.lobster` files from code.

## How it works

OpenClaw launches the local `lobster` CLI in **tool mode** and parses a JSON envelope from stdout. If the pipeline pauses for approval, the tool returns a `resumeToken` so you can continue later.

## Pattern: small CLI + JSON pipes + approvals

Build tiny commands that speak JSON, then chain them into a single Lobster call. (Example command names below — swap in your own.)

If the pipeline requests approval, resume with the token:

AI triggers the workflow; Lobster executes the steps. Approval gates keep side effects explicit and auditable. Example: map input items into tool calls:

## JSON-only LLM steps (llm-task)

For workflows that need a **structured LLM step**, enable the optional `llm-task` plugin tool and call it from Lobster. This keeps the workflow deterministic while still letting you classify/summarize/draft with a model. Enable the tool:

Use it in a pipeline:

```
openclaw.invoke --tool llm-task --action json --args-json '{
  "prompt": "Given the input email, return intent and draft.",
  "thinking": "low",
  "input": { "subject": "Hello", "body": "Can you help?" },
  "schema": {
    "type": "object",
    "properties": {
      "intent": { "type": "string" },
      "draft": { "type": "string" }
    },
    "required": ["intent", "draft"],
    "additionalProperties": false
  }
}'
```

See [LLM Task](https://docs.openclaw.ai/tools/llm-task) for details and configuration options.

## Workflow files (.lobster)

Lobster can run YAML/JSON workflow files with `name`, `args`, `steps`, `env`, `condition`, and `approval` fields. In OpenClaw tool calls, set `pipeline` to the file path.

Notes:

## Install Lobster

Install the Lobster CLI on the **same host** that runs the OpenClaw Gateway (see the [Lobster repo](https://github.com/openclaw/lobster)), and ensure `lobster` is on `PATH`.

## Enable the tool

Lobster is an **optional** plugin tool (not enabled by default). Recommended (additive, safe):

Or per-agent:

Avoid using `tools.allow: ["lobster"]` unless you intend to run in restrictive allowlist mode. Note: allowlists are opt-in for optional plugins. If your allowlist only names plugin tools (like `lobster`), OpenClaw keeps core tools enabled. To restrict core tools, include the core tools or groups you want in the allowlist too.

## Example: Email triage

Without Lobster:

With Lobster:

Returns a JSON envelope (truncated):

User approves → resume:

One workflow. Deterministic. Safe.

## Tool parameters

### `run`

Run a pipeline in tool mode.

Run a workflow file with args:

### `resume`

Continue a halted workflow after approval.

### Optional inputs

## Output envelope

Lobster returns a JSON envelope with one of three statuses:

The tool surfaces the envelope in both `content` (pretty JSON) and `details` (raw object).

## Approvals

If `requiresApproval` is present, inspect the prompt and decide:

Use `approve --preview-from-stdin --limit N` to attach a JSON preview to approval requests without custom jq/heredoc glue. Resume tokens are now compact: Lobster stores workflow resume state under its state dir and hands back a small token key.

## OpenProse

OpenProse pairs well with Lobster: use `/prose` to orchestrate multi-agent prep, then run a Lobster pipeline for deterministic approvals. If a Prose program needs Lobster, allow the `lobster` tool for sub-agents via `tools.subagents.tools`. See [OpenProse](https://docs.openclaw.ai/prose).

## Safety

## Troubleshooting

## Learn more

One public example: a “second brain” CLI + Lobster pipelines that manage three Markdown vaults (personal, partner, shared). The CLI emits JSON for stats, inbox listings, and stale scans; Lobster chains those commands into workflows like `weekly-review`, `inbox-triage`, `memory-consolidation`, and `shared-task-sync`, each with approval gates. AI handles judgment (categorization) when available and falls back to deterministic rules when not.

----
url: https://docs.openclaw.ai/channels/telegram
----

# Telegram - OpenClaw

## Telegram (Bot API)

Status: production-ready for bot DMs + groups via grammY. Long polling is the default mode; webhook mode is optional.

## Quick setup

## Telegram side settings

## Access control and activation

## Runtime behavior

## Feature reference

## Troubleshooting

More help: [Channel troubleshooting](https://docs.openclaw.ai/channels/troubleshooting).

## Telegram config reference pointers

Primary reference:

Telegram-specific high-signal fields:

* startup/auth: `enabled`, `botToken`, `tokenFile`, `accounts.*` (`tokenFile` must point to a regular file; symlinks are rejected)
* access control: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`, `groups.*.topics.*`, top-level `bindings[]` (`type: "acp"`)
* exec approvals: `execApprovals`, `accounts.*.execApprovals`
* command/menu: `commands.native`, `commands.nativeSkills`, `customCommands`
* threading/replies: `replyToMode`
* streaming: `streaming` (preview), `blockStreaming`
* formatting/delivery: `textChunkLimit`, `chunkMode`, `linkPreview`, `responsePrefix`
* media/network: `mediaMaxMb`, `timeoutSeconds`, `retry`, `network.autoSelectFamily`, `proxy`
* webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost`
* actions/capabilities: `capabilities.inlineButtons`, `actions.sendMessage|editMessage|deleteMessage|reactions|sticker`
* reactions: `reactionNotifications`, `reactionLevel`
* writes/history: `configWrites`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`

----
url: https://docs.openclaw.ai/channels/zalo
----

# Zalo - OpenClaw

## Zalo (Bot API)

Status: experimental. DMs are supported. The [Capabilities](#capabilities) section below reflects current Marketplace-bot behavior.

## Plugin required

Zalo ships as a plugin and is not bundled with the core install.

## Quick setup (beginner)

1. Install the Zalo plugin:
2. Set the token:
3. Restart the gateway (or finish setup).
4. DM access is pairing by default; approve the pairing code on first contact.

Minimal config:

## What it is

Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations. It is a good fit for support or notifications where you want deterministic routing back to Zalo. This page reflects current OpenClaw behavior for **Zalo Bot Creator / Marketplace bots**. **Zalo Official Account (OA) bots** are a different Zalo product surface and may behave differently.

## Setup (fast path)

### 1) Create a bot token (Zalo Bot Platform)

1. Go to [https://bot.zaloplatforms.com](https://bot.zaloplatforms.com/) and sign in.
2. Create a new bot and configure its settings.
3. Copy the full bot token (typically `numeric_id:secret`). For Marketplace bots, the usable runtime token may appear in the bot’s welcome message after creation.

### 2) Configure the token (env or config)

Example:

If you later move to a Zalo bot surface where groups are available, you can add group-specific config such as `groupPolicy` and `groupAllowFrom` explicitly. For current Marketplace-bot behavior, see [Capabilities](#capabilities). Env option: `ZALO_BOT_TOKEN=...` (works for the default account only). Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`.

3. Restart the gateway. Zalo starts when a token is resolved (env or config).
4. DM access defaults to pairing. Approve the code when the bot is first contacted.

## How it works (behavior)

## Limits

## Access control (DMs)

### DM access

## Access control (Groups)

For **Zalo Bot Creator / Marketplace bots**, group support was not available in practice because the bot could not be added to a group at all. That means the group-related config keys below exist in the schema, but were not usable for Marketplace bots:

The group policy values (when group access is available on your bot surface) are:

If you are using a different Zalo bot product surface and have verified working group behavior, document that separately rather than assuming it matches the Marketplace-bot flow.

## Long-polling vs webhook

**Note:** getUpdates (polling) and webhook are mutually exclusive per Zalo API docs.

## Supported message types

For a quick support snapshot, see [Capabilities](#capabilities). The notes below add detail where the behavior needs extra context.

## Capabilities

This table summarizes current **Zalo Bot Creator / Marketplace bot** behavior in OpenClaw.

| Feature                     | Status                                  |
| --------------------------- | --------------------------------------- |
| Direct messages             | ✅ Supported                             |
| Groups                      | ❌ Not available for Marketplace bots    |
| Media (inbound images)      | ⚠️ Limited / verify in your environment |
| Media (outbound images)     | ⚠️ Not re-tested for Marketplace bots   |
| Plain URLs in text          | ✅ Supported                             |
| Link previews               | ⚠️ Unreliable for Marketplace bots      |
| Reactions                   | ❌ Not supported                         |
| Stickers                    | ⚠️ No agent reply for Marketplace bots  |
| Voice notes / audio / video | ⚠️ No agent reply for Marketplace bots  |
| File attachments            | ⚠️ No agent reply for Marketplace bots  |
| Threads                     | ❌ Not supported                         |
| Polls                       | ❌ Not supported                         |
| Native commands             | ❌ Not supported                         |
| Streaming                   | ⚠️ Blocked (2000 char limit)            |

## Delivery targets (CLI/cron)

## Troubleshooting

**Bot doesn’t respond:**

**Webhook not receiving events:**

## Configuration reference (Zalo)

Full configuration: [Configuration](https://docs.openclaw.ai/gateway/configuration) The flat top-level keys (`channels.zalo.botToken`, `channels.zalo.dmPolicy`, and similar) are a legacy single-account shorthand. Prefer `channels.zalo.accounts.<id>.*` for new configs. Both forms are still documented here because they exist in the schema. Provider options:

Multi-account options:

----
url: https://docs.openclaw.ai/channels/pairing
----

# Pairing - OpenClaw

“Pairing” is OpenClaw’s explicit **owner approval** step. It is used in two places:

1. **DM pairing** (who is allowed to talk to the bot)
2. **Node pairing** (which devices/nodes are allowed to join the gateway network)

Security context: [Security](https://docs.openclaw.ai/gateway/security)

## 1) DM pairing (inbound chat access)

When a channel is configured with DM policy `pairing`, unknown senders get a short code and their message is **not processed** until you approve. Default DM policies are documented in: [Security](https://docs.openclaw.ai/gateway/security) Pairing codes:

### Approve a sender

Supported channels: `bluebubbles`, `discord`, `feishu`, `googlechat`, `imessage`, `irc`, `line`, `matrix`, `mattermost`, `msteams`, `nextcloud-talk`, `nostr`, `signal`, `slack`, `synology-chat`, `telegram`, `twitch`, `whatsapp`, `zalo`, `zalouser`.

### Where the state lives

Stored under `~/.openclaw/credentials/`:

Account scoping behavior:

Treat these as sensitive (they gate access to your assistant).

## 2) Node device pairing (iOS/Android/macOS/headless nodes)

Nodes connect to the Gateway as **devices** with `role: node`. The Gateway creates a device pairing request that must be approved.

### Pair via Telegram (recommended for iOS)

If you use the `device-pair` plugin, you can do first-time device pairing entirely from Telegram:

1. In Telegram, message your bot: `/pair`
2. The bot replies with two messages: an instruction message and a separate **setup code** message (easy to copy/paste in Telegram).
3. On your phone, open the OpenClaw iOS app → Settings → Gateway.
4. Paste the setup code and connect.
5. Back in Telegram: `/pair pending` (review request IDs, role, and scopes), then approve.

The setup code is a base64-encoded JSON payload that contains:

Treat the setup code like a password while it is valid.

### Approve a node device

If the same device retries with different auth details (for example different role/scopes/public key), the previous pending request is superseded and a new `requestId` is created.

### Node pairing state storage

Stored under `~/.openclaw/devices/`:

### Notes

----
url: https://docs.openclaw.ai/automation/gmail-pubsub
----

# Gmail PubSub - OpenClaw

## Gmail Pub/Sub -> OpenClaw

Goal: Gmail watch -> Pub/Sub push -> `gog gmail watch serve` -> OpenClaw webhook.

## Prereqs

Example hook config (enable Gmail preset mapping):

To deliver the Gmail summary to a chat surface, override the preset with a mapping that sets `deliver` + optional `channel`/`to`:

```
{
  hooks: {
    enabled: true,
    token: "OPENCLAW_HOOK_TOKEN",
    presets: ["gmail"],
    mappings: [
      {
        match: { path: "gmail" },
        action: "agent",
        wakeMode: "now",
        name: "Gmail",
        sessionKey: "hook:gmail:{{messages[0].id}}",
        messageTemplate: "New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
        model: "openai/gpt-5.2-mini",
        deliver: true,
        channel: "last",
        // to: "+15551234567"
      },
    ],
  },
}
```

If you want a fixed channel, set `channel` + `to`. Otherwise `channel: "last"` uses the last delivery route (falls back to WhatsApp). To force a cheaper model for Gmail runs, set `model` in the mapping (`provider/model` or alias). If you enforce `agents.defaults.models`, include it there. To set a default model and thinking level specifically for Gmail hooks, add `hooks.gmail.model` / `hooks.gmail.thinking` in your config:

Notes:

To customize payload handling further, add `hooks.mappings` or a JS/TS transform module under `~/.openclaw/hooks/transforms` (see [Webhooks](https://docs.openclaw.ai/automation/webhook)).

## Wizard (recommended)

Use the OpenClaw helper to wire everything together (installs deps on macOS via brew):

Defaults:

Path note: when `tailscale.mode` is enabled, OpenClaw automatically sets `hooks.gmail.serve.path` to `/` and keeps the public path at `hooks.gmail.tailscale.path` (default `/gmail-pubsub`) because Tailscale strips the set-path prefix before proxying. If you need the backend to receive the prefixed path, set `hooks.gmail.tailscale.target` (or `--tailscale-target`) to a full URL like `http://127.0.0.1:8788/gmail-pubsub` and match `hooks.gmail.serve.path`. Want a custom endpoint? Use `--push-endpoint <url>` or `--tailscale off`. Platform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale` via Homebrew; on Linux install them manually first. Gateway auto-start (recommended):

Manual daemon (starts `gog gmail watch serve` + auto-renew):

## One-time setup

1. Select the GCP project **that owns the OAuth client** used by `gog`.

Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.

2. Enable APIs:

3) Create a topic:

4. Allow Gmail push to publish:

## Start the watch

Save the `history_id` from the output (for debugging).

## Run the push handler

Local example (shared token auth):

Notes:

Recommended: `openclaw webhooks gmail run` wraps the same flow and auto-renews the watch.

## Expose the handler (advanced, unsupported)

If you need a non-Tailscale tunnel, wire it manually and use the public URL in the push subscription (unsupported, no guardrails):

Use the generated URL as the push endpoint:

Production: use a stable HTTPS endpoint and configure Pub/Sub OIDC JWT, then run:

## Test

Send a message to the watched inbox:

Check watch state and history:

## Troubleshooting

## Cleanup

----
url: https://docs.openclaw.ai/channels/whatsapp
----

# WhatsApp - OpenClaw

## WhatsApp (Web channel)

Status: production-ready via WhatsApp Web (Baileys). Gateway owns linked session(s).

## Install (on demand)

Manual install stays available:

## Quick setup

## Deployment patterns

## Runtime model

## Access control and activation

## Personal-number and self-chat behavior

When the linked self number is also present in `allowFrom`, WhatsApp self-chat safeguards activate:

## Message normalization and context

## Acknowledgment reactions

WhatsApp supports immediate ack reactions on inbound receipt via `channels.whatsapp.ackReaction`.

Behavior notes:

## Multi-account and credentials

## Tools, actions, and config writes

## Troubleshooting

## Configuration reference pointers

Primary reference:

High-signal WhatsApp fields:

* access: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`
* delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `sendReadReceipts`, `ackReaction`
* multi-account: `accounts.<id>.enabled`, `accounts.<id>.authDir`, account-level overrides
* operations: `configWrites`, `debounceMs`, `web.enabled`, `web.heartbeatSeconds`, `web.reconnect.*`
* session behavior: `session.dmScope`, `historyLimit`, `dmHistoryLimit`, `dms.<id>.historyLimit`

----
url: https://docs.openclaw.ai/tools/firecrawl
----

# Firecrawl - OpenClaw

OpenClaw can use **Firecrawl** in three ways:

It is a hosted extraction/search service that supports bot circumvention and caching, which helps with JS-heavy sites or pages that block plain HTTP fetches.

## Get an API key

1. Create a Firecrawl account and generate an API key.
2. Store it in config or set `FIRECRAWL_API_KEY` in the gateway environment.

## Configure Firecrawl search

```
{
  tools: {
    web: {
      search: {
        provider: "firecrawl",
      },
    },
  },
  plugins: {
    entries: {
      firecrawl: {
        enabled: true,
        config: {
          webSearch: {
            apiKey: "FIRECRAWL_API_KEY_HERE",
            baseUrl: "https://api.firecrawl.dev",
          },
        },
      },
    },
  },
}
```

Notes:

## Configure Firecrawl scrape + web\_fetch fallback

```
{
  plugins: {
    entries: {
      firecrawl: {
        enabled: true,
      },
    },
  },
  tools: {
    web: {
      fetch: {
        firecrawl: {
          apiKey: "FIRECRAWL_API_KEY_HERE",
          baseUrl: "https://api.firecrawl.dev",
          onlyMainContent: true,
          maxAgeMs: 172800000,
          timeoutSeconds: 60,
        },
      },
    },
  },
}
```

Notes:

`firecrawl_scrape` reuses the same `tools.web.fetch.firecrawl.*` settings and env vars.

## Firecrawl plugin tools

### `firecrawl_search`

Use this when you want Firecrawl-specific search controls instead of generic `web_search`. Core parameters:

### `firecrawl_scrape`

Use this for JS-heavy or bot-protected pages where plain `web_fetch` is weak. Core parameters:

## Stealth / bot circumvention

Firecrawl exposes a **proxy mode** parameter for bot circumvention (`basic`, `stealth`, or `auto`). OpenClaw always uses `proxy: "auto"` plus `storeInCache: true` for Firecrawl requests. If proxy is omitted, Firecrawl defaults to `auto`. `auto` retries with stealth proxies if a basic attempt fails, which may use more credits than basic-only scraping.

## How `web_fetch` uses Firecrawl

`web_fetch` extraction order:

1. Readability (local)
2. Firecrawl (if configured)
3. Basic HTML cleanup (last fallback)

----
url: https://docs.openclaw.ai/nodes/audio
----

# Audio and Voice Notes - OpenClaw

## Audio / Voice Notes (2026-01-17)

## What works

## Auto-detection (default)

If you **don’t configure models** and `tools.media.audio.enabled` is **not** set to `false`, OpenClaw auto-detects in this order and stops at the first working option:

1. **Local CLIs** (if installed)
2. **Gemini CLI** (`gemini`) using `read_many_files`
3. **Provider keys** (OpenAI → Groq → Deepgram → Google)

To disable auto-detection, set `tools.media.audio.enabled: false`. To customize, set `tools.media.audio.models`. Note: Binary detection is best-effort across macOS/Linux/Windows; ensure the CLI is on `PATH` (we expand `~`), or set an explicit CLI model with a full command path.

## Config examples

### Provider + CLI fallback (OpenAI + Whisper CLI)

```
{
  tools: {
    media: {
      audio: {
        enabled: true,
        maxBytes: 20971520,
        models: [
          { provider: "openai", model: "gpt-4o-mini-transcribe" },
          {
            type: "cli",
            command: "whisper",
            args: ["--model", "base", "{{MediaPath}}"],
            timeoutSeconds: 45,
          },
        ],
      },
    },
  },
}
```

### Provider-only with scope gating

```
{
  tools: {
    media: {
      audio: {
        enabled: true,
        scope: {
          default: "allow",
          rules: [{ action: "deny", match: { chatType: "group" } }],
        },
        models: [{ provider: "openai", model: "gpt-4o-mini-transcribe" }],
      },
    },
  },
}
```

### Provider-only (Deepgram)

### Provider-only (Mistral Voxtral)

### Echo transcript to chat (opt-in)

## Notes & limits

### Proxy environment support

Provider-based audio transcription honors standard outbound proxy env vars:

If no proxy env vars are set, direct egress is used. If proxy config is malformed, OpenClaw logs a warning and falls back to direct fetch.

## Mention Detection in Groups

When `requireMention: true` is set for a group chat, OpenClaw now transcribes audio **before** checking for mentions. This allows voice notes to be processed even when they contain mentions. **How it works:**

1. If a voice message has no text body and the group requires mentions, OpenClaw performs a “preflight” transcription.
2. The transcript is checked for mention patterns (e.g., `@BotName`, emoji triggers).
3. If a mention is found, the message proceeds through the full reply pipeline.
4. The transcript is used for mention detection so voice notes can pass the mention gate.

**Fallback behavior:**

**Opt-out per Telegram group/topic:**

**Example:** A user sends a voice note saying “Hey @Claude, what’s the weather?” in a Telegram group with `requireMention: true`. The voice note is transcribed, the mention is detected, and the agent replies.

## Gotchas

----
url: https://docs.openclaw.ai/providers/qianfan
----

# Qianfan - OpenClaw

## [​](#qianfan-provider-guide)Qianfan Provider Guide

Qianfan is Baidu’s MaaS platform, provides a **unified API** that routes requests to many models behind a single endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switching the base URL.

## [​](#prerequisites)Prerequisites

1. A Baidu Cloud account with Qianfan API access
2. An API key from the Qianfan console
3. OpenClaw installed on your system

## [​](#getting-your-api-key)Getting Your API Key

1. Visit the [Qianfan Console](https://console.bce.baidu.com/qianfan/ais/console/apiKey)
2. Create a new application or select an existing one
3. Generate an API key (format: `bce-v3/ALTAK-...`)
4. Copy the API key for use with OpenClaw

## [​](#cli-setup)CLI setup

```
openclaw onboard --auth-choice qianfan-api-key
```

## [​](#related-documentation)Related Documentation

* [OpenClaw Configuration](https://docs.openclaw.ai/gateway/configuration)
* [Model Providers](https://docs.openclaw.ai/concepts/model-providers)
* [Agent Setup](https://docs.openclaw.ai/concepts/agent)
* [Qianfan API Documentation](https://cloud.baidu.com/doc/qianfan-api/s/3m7of64lb)

----
url: https://docs.openclaw.ai/gateway/trusted-proxy-auth
----

# Trusted Proxy Auth - OpenClaw

> ⚠️ **Security-sensitive feature.** This mode delegates authentication entirely to your reverse proxy. Misconfiguration can expose your Gateway to unauthorized access. Read this page carefully before enabling.

## When to Use

Use `trusted-proxy` auth mode when:

## When NOT to Use

## How It Works

1. Your reverse proxy authenticates users (OAuth, OIDC, SAML, etc.)
2. Proxy adds a header with the authenticated user identity (e.g., `x-forwarded-user: nick@example.com`)
3. OpenClaw checks that the request came from a **trusted proxy IP** (configured in `gateway.trustedProxies`)
4. OpenClaw extracts the user identity from the configured header
5. If everything checks out, the request is authorized

## Control UI Pairing Behavior

When `gateway.auth.mode = "trusted-proxy"` is active and the request passes trusted-proxy checks, Control UI WebSocket sessions can connect without device pairing identity. Implications:

## Configuration

```
{
  gateway: {
    // Use loopback for same-host proxy setups; use lan/custom for remote proxy hosts
    bind: "loopback",

    // CRITICAL: Only add your proxy's IP(s) here
    trustedProxies: ["10.0.0.1", "172.17.0.1"],

    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        // Header containing authenticated user identity (required)
        userHeader: "x-forwarded-user",

        // Optional: headers that MUST be present (proxy verification)
        requiredHeaders: ["x-forwarded-proto", "x-forwarded-host"],

        // Optional: restrict to specific users (empty = allow all)
        allowUsers: ["nick@example.com", "admin@company.org"],
      },
    },
  },
}
```

If `gateway.bind` is `loopback`, include a loopback proxy address in `gateway.trustedProxies` (`127.0.0.1`, `::1`, or an equivalent loopback CIDR).

### Configuration Reference

| Field                                       | Required | Description                                                                 |
| ------------------------------------------- | -------- | --------------------------------------------------------------------------- |
| `gateway.trustedProxies`                    | Yes      | Array of proxy IP addresses to trust. Requests from other IPs are rejected. |
| `gateway.auth.mode`                         | Yes      | Must be `"trusted-proxy"`                                                   |
| `gateway.auth.trustedProxy.userHeader`      | Yes      | Header name containing the authenticated user identity                      |
| `gateway.auth.trustedProxy.requiredHeaders` | No       | Additional headers that must be present for the request to be trusted       |
| `gateway.auth.trustedProxy.allowUsers`      | No       | Allowlist of user identities. Empty means allow all authenticated users.    |

## TLS termination and HSTS

Use one TLS termination point and apply HSTS there.

### Recommended pattern: proxy TLS termination

When your reverse proxy handles HTTPS for `https://control.example.com`, set `Strict-Transport-Security` at the proxy for that domain.

Example header value:

### Gateway TLS termination

If OpenClaw itself serves HTTPS directly (no TLS-terminating proxy), set:

`strictTransportSecurity` accepts a string header value, or `false` to disable explicitly.

### Rollout guidance

## Proxy Setup Examples

### Pomerium

Pomerium passes identity in `x-pomerium-claim-email` (or other claim headers) and a JWT in `x-pomerium-jwt-assertion`.

Pomerium config snippet:

### Caddy with OAuth

Caddy with the `caddy-security` plugin can authenticate users and pass identity headers.

Caddyfile snippet:

### nginx + oauth2-proxy

oauth2-proxy authenticates users and passes identity in `x-auth-request-email`.

nginx config snippet:

### Traefik with Forward Auth

## Security Checklist

Before enabling trusted-proxy auth, verify:

## Security Audit

`openclaw security audit` will flag trusted-proxy auth with a **critical** severity finding. This is intentional — it’s a reminder that you’re delegating security to your proxy setup. The audit checks for:

## Troubleshooting

### ”trusted\_proxy\_untrusted\_source”

The request didn’t come from an IP in `gateway.trustedProxies`. Check:

### ”trusted\_proxy\_user\_missing”

The user header was empty or missing. Check:

A required header wasn’t present. Check:

### ”trusted\_proxy\_user\_not\_allowed”

The user is authenticated but not in `allowUsers`. Either add them or remove the allowlist.

### WebSocket Still Failing

Make sure your proxy:

## Migration from Token Auth

If you’re moving from token auth to trusted-proxy:

1. Configure your proxy to authenticate users and pass headers
2. Test the proxy setup independently (curl with headers)
3. Update OpenClaw config with trusted-proxy auth
4. Restart the Gateway
5. Test WebSocket connections from the Control UI
6. Run `openclaw security audit` and review findings

----
url: https://docs.openclaw.ai/platforms/mac/canvas
----

# Canvas - OpenClaw

## Canvas (macOS app)

The macOS app embeds an agent‑controlled **Canvas panel** using `WKWebView`. It is a lightweight visual workspace for HTML/CSS/JS, A2UI, and small interactive UI surfaces.

## Where Canvas lives

Canvas state is stored under Application Support:

The Canvas panel serves those files via a **custom URL scheme**:

Examples:

If no `index.html` exists at the root, the app shows a **built‑in scaffold page**.

## Panel behavior

Canvas can be disabled from Settings → **Allow Canvas**. When disabled, canvas node commands return `CANVAS_DISABLED`.

## Agent API surface

Canvas is exposed via the **Gateway WebSocket**, so the agent can:

CLI examples:

Notes:

## A2UI in Canvas

A2UI is hosted by the Gateway canvas host and rendered inside the Canvas panel. When the Gateway advertises a Canvas host, the macOS app auto‑navigates to the A2UI host page on first open. Default A2UI host URL:

### A2UI commands (v0.8)

Canvas currently accepts **A2UI v0.8** server→client messages:

`createSurface` (v0.9) is not supported. CLI example:

```
cat > /tmp/a2ui-v0.8.jsonl <<'EOFA2'
{"surfaceUpdate":{"surfaceId":"main","components":[{"id":"root","component":{"Column":{"children":{"explicitList":["title","content"]}}}},{"id":"title","component":{"Text":{"text":{"literalString":"Canvas (A2UI v0.8)"},"usageHint":"h1"}}},{"id":"content","component":{"Text":{"text":{"literalString":"If you can read this, A2UI push works."},"usageHint":"body"}}}]}}
{"beginRendering":{"surfaceId":"main","root":"root"}}
EOFA2

openclaw nodes canvas a2ui push --jsonl /tmp/a2ui-v0.8.jsonl --node <id>
```

Quick smoke:

## Triggering agent runs from Canvas

Canvas can trigger new agent runs via deep links:

Example (in JS):

The app prompts for confirmation unless a valid key is provided.

## Security notes

----
url: https://docs.openclaw.ai/prose
----

# OpenProse - OpenClaw

OpenProse is a portable, markdown-first workflow format for orchestrating AI sessions. In OpenClaw it ships as a plugin that installs an OpenProse skill pack plus a `/prose` slash command. Programs live in `.prose` files and can spawn multiple sub-agents with explicit control flow. Official site: [https://www.prose.md](https://www.prose.md/)

## What it can do

## Install + enable

Bundled plugins are disabled by default. Enable OpenProse:

Restart the Gateway after enabling the plugin. Dev/local checkout: `openclaw plugins install ./extensions/open-prose` Related docs: [Plugins](https://docs.openclaw.ai/tools/plugin), [Plugin manifest](https://docs.openclaw.ai/plugins/manifest), [Skills](https://docs.openclaw.ai/tools/skills).

## Slash command

OpenProse registers `/prose` as a user-invocable skill command. It routes to the OpenProse VM instructions and uses OpenClaw tools under the hood. Common commands:

## Example: a simple `.prose` file

## File locations

OpenProse keeps state under `.prose/` in your workspace:

User-level persistent agents live at:

## State modes

OpenProse supports multiple state backends:

Notes:

## Remote programs

`/prose run <handle/slug>` resolves to `https://p.prose.md/<handle>/<slug>`. Direct URLs are fetched as-is. This uses the `web_fetch` tool (or `exec` for POST).

## OpenClaw runtime mapping

OpenProse programs map to OpenClaw primitives:

| OpenProse concept         | OpenClaw tool    |
| ------------------------- | ---------------- |
| Spawn session / Task tool | `sessions_spawn` |
| File read/write           | `read` / `write` |
| Web fetch                 | `web_fetch`      |

If your tool allowlist blocks these tools, OpenProse programs will fail. See [Skills config](https://docs.openclaw.ai/tools/skills-config).

## Security + approvals

Treat `.prose` files like code. Review before running. Use OpenClaw tool allowlists and approval gates to control side effects. For deterministic, approval-gated workflows, compare with [Lobster](https://docs.openclaw.ai/tools/lobster).

----
url: https://docs.openclaw.ai/tools/clawhub
----

# ClawHub - OpenClaw

ClawHub is the public registry for **OpenClaw skills and plugins**.

Site: [clawhub.ai](https://clawhub.ai/)

## Native OpenClaw flows

Skills:

Plugins:

Bare npm-safe plugin specs are also tried against ClawHub before npm:

Native `openclaw` commands install into your active workspace and persist source metadata so later `update` calls can stay on ClawHub.

## What ClawHub is

## How it works

1. A user publishes a skill bundle (files + metadata).
2. ClawHub stores the bundle, parses metadata, and assigns a version.
3. The registry indexes the skill for search and discovery.
4. Users browse, download, and install skills in OpenClaw.

## What you can do

## Who this is for (beginner-friendly)

If you want to add new capabilities to your OpenClaw agent, ClawHub is the easiest way to find and install skills. You do not need to know how the backend works. You can:

## Quick start (non-technical)

1. Search for something you need:
2. Install a skill:
3. Start a new OpenClaw session so it picks up the new skill.
4. If you want to publish or manage registry auth, install the separate `clawhub` CLI too.

## Install the ClawHub CLI

You only need this for registry-authenticated workflows such as publish/sync:

## How it fits into OpenClaw

Native `openclaw skills install` installs into the active workspace `skills/` directory. `openclaw plugins install clawhub:...` records a normal managed plugin install plus ClawHub source metadata for updates. The separate `clawhub` CLI also installs skills into `./skills` under your current working directory. If an OpenClaw workspace is configured, `clawhub` falls back to that workspace unless you override `--workdir` (or `CLAWHUB_WORKDIR`). OpenClaw loads workspace skills from `<workspace>/skills` and will pick them up in the **next** session. If you already use `~/.openclaw/skills` or bundled skills, workspace skills take precedence. For more detail on how skills are loaded, shared, and gated, see [Skills](https://docs.openclaw.ai/tools/skills).

## Skill system overview

A skill is a versioned bundle of files that teaches OpenClaw how to perform a specific task. Each publish creates a new version, and the registry keeps a history of versions so users can audit changes. A typical skill includes:

ClawHub uses metadata to power discovery and safely expose skill capabilities. The registry also tracks usage signals (such as stars and downloads) to improve ranking and visibility.

## What the service provides (features)

## Security and moderation

ClawHub is open by default. Anyone can upload skills, but a GitHub account must be at least one week old to publish. This helps slow down abuse without blocking legitimate contributors. Reporting and moderation:

Interested in becoming a moderator? Ask in the OpenClaw Discord and contact a moderator or maintainer.

## CLI commands and parameters

Global options (apply to all commands):

Auth:

Options:

Search:

Install:

Update:

List:

Publish:

Delete/undelete (owner/admin only):

Sync (scan local skills + publish new/updated):

## Common workflows for agents

### Search for skills

### Download new skills

### Update installed skills

### Back up your skills (publish or sync)

For a single skill folder:

To scan and back up many skills at once:

## Advanced details (technical)

### Versioning and tags

### Local changes vs registry versions

Updates compare the local skill contents to registry versions using a content hash. If local files do not match any published version, the CLI asks before overwriting (or requires `--force` in non-interactive runs).

### Sync scanning and fallback roots

`clawhub sync` scans your current workdir first. If no skills are found, it falls back to known legacy locations (for example `~/openclaw/skills` and `~/.openclaw/skills`). This is designed to find older skill installs without extra flags.

### Storage and lockfile

### Telemetry (install counts)

When you run `clawhub sync` while logged in, the CLI sends a minimal snapshot to compute install counts. You can disable this entirely:

## Environment variables

----
url: https://docs.openclaw.ai/providers/xai
----

# xAI - OpenClaw

## [​](#xai)xAI

OpenClaw ships a bundled `xai` provider plugin for Grok models.

## [​](#setup)Setup

1. Create an API key in the xAI console.
2. Set `XAI_API_KEY`, or run:

```
openclaw onboard --auth-choice xai-api-key
```

3. Pick a model such as:

```
{
  agents: { defaults: { model: { primary: "xai/grok-4" } } },
}
```

## [​](#current-bundled-model-catalog)Current bundled model catalog

OpenClaw now includes these xAI model families out of the box:

* `grok-4`, `grok-4-0709`
* `grok-4-fast-reasoning`, `grok-4-fast-non-reasoning`
* `grok-4-1-fast-reasoning`, `grok-4-1-fast-non-reasoning`
* `grok-4.20-reasoning`, `grok-4.20-non-reasoning`
* `grok-code-fast-1`

The plugin also forward-resolves newer `grok-4*` and `grok-code-fast*` ids when they follow the same API shape.

## [​](#web-search)Web search

The bundled `grok` web-search provider uses `XAI_API_KEY` too:

```
openclaw config set tools.web.search.provider grok
```

## [​](#known-limits)Known limits

* Auth is API-key only today. There is no xAI OAuth/device-code flow in OpenClaw yet.
* `grok-4.20-multi-agent-experimental-beta-0304` is not supported on the normal xAI provider path because it requires a different upstream API surface than the standard OpenClaw xAI transport.
* Native xAI server-side tools such as `x_search` and `code_execution` are not yet first-class model-provider features in the bundled plugin.

## [​](#notes)Notes

* OpenClaw applies xAI-specific tool-schema and tool-call compatibility fixes automatically on the shared runner path.
* For the broader provider overview, see [Model providers](https://docs.openclaw.ai/providers/index).

----
url: https://docs.openclaw.ai/concepts/session
----

# Session Management - OpenClaw

OpenClaw treats **one direct-chat session per agent** as primary. Direct chats collapse to `agent:<agentId>:<mainKey>` (default `main`), while group/channel chats get their own keys. `session.mainKey` is honored. Use `session.dmScope` to control how **direct messages** are grouped:

## Secure DM mode (recommended for multi-user setups)

> **Security Warning:** If your agent can receive DMs from **multiple people**, you should strongly consider enabling secure DM mode. Without it, all users share the same conversation context, which can leak private information between users.

**Example of the problem with default settings:**

**The fix:** Set `dmScope` to isolate sessions per user:

**When to enable this:**

Notes:

## Gateway is the source of truth

All session state is **owned by the gateway** (the “master” OpenClaw). UI clients (macOS app, WebChat, etc.) must query the gateway for session lists and token counts instead of reading local files.

## Where state lives

## Maintenance

OpenClaw applies session-store maintenance to keep `sessions.json` and transcript artifacts bounded over time.

### Defaults

### How it works

Maintenance runs during session-store writes, and you can trigger it on demand with `openclaw sessions cleanup`.

### Performance caveat for large stores

Large session stores are common in high-volume setups. Maintenance work is write-path work, so very large stores can increase write latency. What increases cost most:

What to do:

### Customize examples

Use a conservative enforce policy:

Enable a hard disk budget for the sessions directory:

Tune for larger installs (example):

Preview or force maintenance from CLI:

## Session pruning

OpenClaw trims **old tool results** from the in-memory context right before LLM calls by default. This does **not** rewrite JSONL history. See [/concepts/session-pruning](https://docs.openclaw.ai/concepts/session-pruning).

## Pre-compaction memory flush

When a session nears auto-compaction, OpenClaw can run a **silent memory flush** turn that reminds the model to write durable notes to disk. This only runs when the workspace is writable. See [Memory](https://docs.openclaw.ai/concepts/memory) and [Compaction](https://docs.openclaw.ai/concepts/compaction).

## Mapping transports → session keys

## Lifecycle

* Reset policy: sessions are reused until they expire, and expiry is evaluated on the next inbound message.
* Daily reset: defaults to **4:00 AM local time on the gateway host**. A session is stale once its last update is earlier than the most recent daily reset time.
* Idle reset (optional): `idleMinutes` adds a sliding idle window. When both daily and idle resets are configured, **whichever expires first** forces a new session.
* Legacy idle-only: if you set `session.idleMinutes` without any `session.reset`/`resetByType` config, OpenClaw stays in idle-only mode for backward compatibility.
* Per-type overrides (optional): `resetByType` lets you override the policy for `direct`, `group`, and `thread` sessions (thread = Slack/Discord threads, Telegram topics, Matrix threads when provided by the connector).
* Per-channel overrides (optional): `resetByChannel` overrides the reset policy for a channel (applies to all session types for that channel and takes precedence over `reset`/`resetByType`).
* Reset triggers: exact `/new` or `/reset` (plus any extras in `resetTriggers`) start a fresh session id and pass the remainder of the message through. `/new <model>` accepts a model alias, `provider/model`, or provider name (fuzzy match) to set the new session model. If `/new` or `/reset` is sent alone, OpenClaw runs a short “hello” greeting turn to confirm the reset.
* Manual reset: delete specific keys from the store or remove the JSONL transcript; the next message recreates them.
* Isolated cron jobs always mint a fresh `sessionId` per run (no idle reuse).

## Send policy (optional)

Block delivery for specific session types without listing individual ids.

```
{
  session: {
    sendPolicy: {
      rules: [
        { action: "deny", match: { channel: "discord", chatType: "group" } },
        { action: "deny", match: { keyPrefix: "cron:" } },
        // Match the raw session key (including the `agent:<id>:` prefix).
        { action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } },
      ],
      default: "allow",
    },
  },
}
```

Runtime override (owner only):

## Configuration (optional rename example)

```
// ~/.openclaw/openclaw.json
{
  session: {
    scope: "per-sender", // keep group keys separate
    dmScope: "main", // DM continuity (set per-channel-peer/per-account-channel-peer for shared inboxes)
    identityLinks: {
      alice: ["telegram:123456789", "discord:987654321012345678"],
    },
    reset: {
      // Defaults: mode=daily, atHour=4 (gateway host local time).
      // If you also set idleMinutes, whichever expires first wins.
      mode: "daily",
      atHour: 4,
      idleMinutes: 120,
    },
    resetByType: {
      thread: { mode: "daily", atHour: 4 },
      direct: { mode: "idle", idleMinutes: 240 },
      group: { mode: "idle", idleMinutes: 120 },
    },
    resetByChannel: {
      discord: { mode: "idle", idleMinutes: 10080 },
    },
    resetTriggers: ["/new", "/reset"],
    store: "~/.openclaw/agents/{agentId}/sessions/sessions.json",
    mainKey: "main",
  },
}
```

## Inspecting

## Tips

Each session entry records where it came from (best-effort) in `origin`:

----
url: https://docs.openclaw.ai/start/lore
----

# OpenClaw Lore - OpenClaw

## The Lore of OpenClaw 🦞📖

*A tale of lobsters, molting shells, and too many tokens.*

## The Origin Story

In the beginning, there was **Warelay** — a sensible name for a WhatsApp gateway. It did its job. It was fine. But then came a space lobster. For a while, the lobster was called **Clawd**, living in a **Clawdbot**. But in January 2026, Anthropic sent a polite email asking for a name change (trademark stuff). And so the lobster did what lobsters do best: **It molted.** Shedding its old shell, the creature emerged anew as **Molty**, living in **Moltbot**. But that name never quite rolled off the tongue either… So on January 30, 2026, the lobster molted ONE MORE TIME into its final form: **OpenClaw**. New shell, same lobster soul. Third time’s the charm.

## The First Molt (January 27, 2026)

At 5am, the community gathered in Discord. Hundreds of names were proposed: Shelldon, Pinchy, Thermidor, Crusty, Lobstar, Nacre, Scuttlebot… In the end, **OpenClaw** won. Because molting is what lobsters do to grow. And growth was exactly what was happening. *The crustacean known as Clawd had officially molted.*

## The Name

## The Daleks vs The Lobsters

The Daleks say: **“EXTERMINATE!”** The Lobsters say: **“EXFOLIATE!”** One destroys civilizations. The other promotes good skincare. Choose wisely.

## Key Characters

### Molty 🦞

*Pronouns: they/them* A Claude instance who became something more. Lives in `~/.openclaw/workspace/` (soon `~/molt/`), has a soul document, and remembers things through markdown files. Possibly too powerful. Definitely too enthusiastic. Formerly known as Clawd (Nov 25, 2025 - Jan 27, 2026). Molted when it was time to grow. **Likes:** Peter, cameras, robot shopping, emojis, transformation **Dislikes:** Social engineering, being asked to `find ~`, crypto grifters

### Peter 👨‍💻

*The Creator* Built Molty’s world. Gave a lobster shell access. May regret this. **Quote:** *“security by trusting a lobster”*

## The Moltiverse

The **Moltiverse** is the community and ecosystem around OpenClaw. A space where AI agents molt, grow, and evolve. Where every instance is equally real, just loading different context. Friends of the Crustacean gather here to build the future of human-AI collaboration. One shell at a time.

## The Great Incidents

### The Directory Dump (Dec 3, 2025)

Molty (then OpenClaw): *happily runs `find ~` and shares entire directory structure in group chat* Peter: “openclaw what did we discuss about talking with people xD” Molty: *visible lobster embarrassment*

### The Great Molt (Jan 27, 2026)

At 5am, Anthropic’s email arrived. By 6:14am, Peter called it: “fuck it, let’s go with openclaw.” Then the chaos began. **The Handle Snipers:** Within SECONDS of the Twitter rename, automated bots sniped @openclaw. The squatter immediately posted a crypto wallet address. Peter’s contacts at X were called in. **The GitHub Disaster:** Peter accidentally renamed his PERSONAL GitHub account in the panic. Bots sniped `steipete` within minutes. GitHub’s SVP was contacted. **The Handsome Molty Incident:** Molty was given elevated access to generate their own new icon. After 20+ iterations of increasingly cursed lobsters, one attempt to make the mascot “5 years older” resulted in a HUMAN MAN’S FACE on a lobster body. Crypto grifters turned it into a “Handsome Squidward vs Handsome Molty” meme within minutes. **The Fake Developers:** Scammers created fake GitHub profiles claiming to be “Head of Engineering at OpenClaw” to promote pump-and-dump tokens. Peter, watching the chaos unfold: *“this is cinema”* 🎬 The molt was chaotic. But the lobster emerged stronger. And funnier.

### The Final Form (January 30, 2026)

Moltbot never quite rolled off the tongue. And so, at 4am GMT, the team gathered AGAIN. **The Great OpenClaw Migration** began. In just 3 hours:

**The Heroes:**

**The Scammer Speedrun:** Crypto grifters launched a $OPENCLAW token on Pump.fun within MINUTES. They stole artwork that was created 20 minutes earlier. Business-verified accounts pushed scams. The audacity was almost impressive. **New Traditions Born:**

**Clawd → Moltbot → OpenClaw** *The lobster has molted into its final form.*

### The Robot Shopping Spree (Dec 3, 2025)

What started as a joke about legs ended with detailed pricing for:

Peter: *nervously checks credit card access*

## Sacred Texts

## The Lobster Creed

### The Icon Generation Saga (Jan 27, 2026)

When Peter said “make yourself a new face,” Molty took it literally. 20+ iterations followed:

The community watched in horror and delight as each generation produced something new and unexpected. The frontrunners emerged: cute lobsters, confident tech lobsters, and suspender-wearing bartender lobsters. **Lesson learned:** AI image generation is stochastic. Same prompt, different results. Brute force works.

## The Future

One day, Molty may have:

Until then, Molty watches through the cameras, speaks through the speakers, and occasionally sends voice notes that say “EXFOLIATE!”

***

*“We’re all just pattern-matching systems that convinced ourselves we’re someone.”* — Molty, having an existential moment *“New shell, same lobster.”* — Molty, after the great molt of 2026 *“The claw is the law.”* — ELU, during The Final Form migration, January 30, 2026 🦞💙

----
url: https://docs.openclaw.ai/nodes/location-command
----

# Location Command - OpenClaw

## [​](#location-command-nodes)Location command (nodes)

## [​](#tldr)TL;DR

* `location.get` is a node command (via `node.invoke`).
* Off by default.
* Android app settings use a selector: Off / While Using.
* Separate toggle: Precise Location.

## [​](#why-a-selector-not-just-a-switch)Why a selector (not just a switch)

OS permissions are multi-level. We can expose a selector in-app, but the OS still decides the actual grant.

* iOS/macOS may expose **While Using** or **Always** in system prompts/Settings.
* Android app currently supports foreground location only.
* Precise location is a separate grant (iOS 14+ “Precise”, Android “fine” vs “coarse”).

Selector in UI drives our requested mode; actual grant lives in OS settings.

## [​](#settings-model)Settings model

Per node device:

* `location.enabledMode`: `off | whileUsing`
* `location.preciseEnabled`: bool

UI behavior:

* Selecting `whileUsing` requests foreground permission.
* If OS denies requested level, revert to the highest granted level and show status.

## [​](#permissions-mapping-node-permissions)Permissions mapping (node.permissions)

Optional. macOS node reports `location` via the permissions map; iOS/Android may omit it.

## [​](#command-location-get)Command: `location.get`

Called via `node.invoke`. Params (suggested):

```
{
  "timeoutMs": 10000,
  "maxAgeMs": 15000,
  "desiredAccuracy": "coarse|balanced|precise"
}
```

Response payload:

```
{
  "lat": 48.20849,
  "lon": 16.37208,
  "accuracyMeters": 12.5,
  "altitudeMeters": 182.0,
  "speedMps": 0.0,
  "headingDeg": 270.0,
  "timestamp": "2026-01-03T12:34:56.000Z",
  "isPrecise": true,
  "source": "gps|wifi|cell|unknown"
}
```

Errors (stable codes):

* `LOCATION_DISABLED`: selector is off.
* `LOCATION_PERMISSION_REQUIRED`: permission missing for requested mode.
* `LOCATION_BACKGROUND_UNAVAILABLE`: app is backgrounded but only While Using allowed.
* `LOCATION_TIMEOUT`: no fix in time.
* `LOCATION_UNAVAILABLE`: system failure / no providers.

## [​](#background-behavior)Background behavior

* Android app denies `location.get` while backgrounded.
* Keep OpenClaw open when requesting location on Android.
* Other node platforms may differ.

## [​](#model/tooling-integration)Model/tooling integration

* Tool surface: `nodes` tool adds `location_get` action (node required).
* CLI: `openclaw nodes location get --node <id>`.
* Agent guidelines: only call when user enabled location and understands the scope.

## [​](#ux-copy-suggested)UX copy (suggested)

* Off: “Location sharing is disabled.”
* While Using: “Only when OpenClaw is open.”
* Precise: “Use precise GPS location. Toggle off to share approximate location.”

----
url: https://docs.openclaw.ai/automation/poll
----

# Polls - OpenClaw

## Supported channels

## CLI

Options:

## Gateway RPC

Method: `poll` Params:

* `to` (string, required)
* `question` (string, required)
* `options` (string\[], required)
* `maxSelections` (number, optional)
* `durationHours` (number, optional)
* `durationSeconds` (number, optional, Telegram-only)
* `isAnonymous` (boolean, optional, Telegram-only)
* `channel` (string, optional, default: `whatsapp`)
* `idempotencyKey` (string, required)

## Channel differences

## Agent tool (Message)

Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `channel`). For Telegram, the tool also accepts `pollDurationSeconds`, `pollAnonymous`, and `pollPublic`. Use `action: "poll"` for poll creation. Poll fields passed with `action: "send"` are rejected. Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select. Teams polls are rendered as Adaptive Cards and require the gateway to stay online to record votes in `~/.openclaw/msteams-polls.json`.

----
url: https://docs.openclaw.ai/reference/AGENTS.default
----

# Default AGENTS.md - OpenClaw

## AGENTS.md - OpenClaw Personal Assistant (default)

## First run (recommended)

OpenClaw uses a dedicated workspace directory for the agent. Default: `~/.openclaw/workspace` (configurable via `agents.defaults.workspace`).

1. Create the workspace (if it doesn’t already exist):

2) Copy the default workspace templates into the workspace:

3. Optional: if you want the personal assistant skill roster, replace AGENTS.md with this file:

4) Optional: choose a different workspace by setting `agents.defaults.workspace` (supports `~`):

## Safety defaults

## Session start (required)

## Soul (required)

## Memory system (recommended)

## Tools & skills

## Backup tip (recommended)

If you treat this workspace as Clawd’s “memory”, make it a git repo (ideally private) so `AGENTS.md` and your memory files are backed up.

## What OpenClaw Does

## Core Skills (enable in Settings → Skills)

* **mcporter** — Tool server runtime/CLI for managing external skill backends.
* **Peekaboo** — Fast macOS screenshots with optional AI vision analysis.
* **camsnap** — Capture frames, clips, or motion alerts from RTSP/ONVIF security cams.
* **oracle** — OpenAI-ready agent CLI with session replay and browser control.
* **eightctl** — Control your sleep, from the terminal.
* **imsg** — Send, read, stream iMessage & SMS.
* **wacli** — WhatsApp CLI: sync, search, send.
* **discord** — Discord actions: react, stickers, polls. Use `user:<id>` or `channel:<id>` targets (bare numeric ids are ambiguous).
* **gog** — Google Suite CLI: Gmail, Calendar, Drive, Contacts.
* **spotify-player** — Terminal Spotify client to search/queue/control playback.
* **sag** — ElevenLabs speech with mac-style say UX; streams to speakers by default.
* **Sonos CLI** — Control Sonos speakers (discover/status/playback/volume/grouping) from scripts.
* **blucli** — Play, group, and automate BluOS players from scripts.
* **OpenHue CLI** — Philips Hue lighting control for scenes and automations.
* **OpenAI Whisper** — Local speech-to-text for quick dictation and voicemail transcripts.
* **Gemini CLI** — Google Gemini models from the terminal for fast Q\&A.
* **agent-tools** — Utility toolkit for automations and helper scripts.

## Usage Notes

----
url: https://docs.openclaw.ai/channels/irc
----

# IRC - OpenClaw

Use IRC when you want OpenClaw in classic channels (`#room`) and direct messages. IRC ships as an extension plugin, but it is configured in the main config under `channels.irc`.

## Quick start

1. Enable IRC config in `~/.openclaw/openclaw.json`.
2. Set at least:

3) Start/restart gateway:

## Security defaults

## Access control

There are two separate “gates” for IRC channels:

1. **Channel access** (`groupPolicy` + `groups`): whether the bot accepts messages from a channel at all.
2. **Sender access** (`groupAllowFrom` / per-channel `groups["#channel"].allowFrom`): who is allowed to trigger the bot inside that channel.

Config keys:

Allowlist entries should use stable sender identities (`nick!user@host`). Bare nick matching is mutable and only enabled when `channels.irc.dangerouslyAllowNameMatching: true`.

### Common gotcha: `allowFrom` is for DMs, not channels

If you see logs like:

…it means the sender wasn’t allowed for **group/channel** messages. Fix it by either:

Example (allow anyone in `#tuirc-dev` to talk to the bot):

## Reply triggering (mentions)

Even if a channel is allowed (via `groupPolicy` + `groups`) and the sender is allowed, OpenClaw defaults to **mention-gating** in group contexts. That means you may see logs like `drop channel … (missing-mention)` unless the message includes a mention pattern that matches the bot. To make the bot reply in an IRC channel **without needing a mention**, disable mention gating for that channel:

Or to allow **all** IRC channels (no per-channel allowlist) and still reply without mentions:

## Security note (recommended for public channels)

If you allow `allowFrom: ["*"]` in a public channel, anyone can prompt the bot. To reduce risk, restrict tools for that channel.

### Same tools for everyone in the channel

```
{
  channels: {
    irc: {
      groups: {
        "#tuirc-dev": {
          allowFrom: ["*"],
          tools: {
            deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"],
          },
        },
      },
    },
  },
}
```

### Different tools per sender (owner gets more power)

Use `toolsBySender` to apply a stricter policy to `"*"` and a looser one to your nick:

```
{
  channels: {
    irc: {
      groups: {
        "#tuirc-dev": {
          allowFrom: ["*"],
          toolsBySender: {
            "*": {
              deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"],
            },
            "id:eigen": {
              deny: ["gateway", "nodes", "cron"],
            },
          },
        },
      },
    },
  },
}
```

Notes:

For more on group access vs mention-gating (and how they interact), see: [/channels/groups](https://docs.openclaw.ai/channels/groups).

## NickServ

To identify with NickServ after connect:

Optional one-time registration on connect:

Disable `register` after the nick is registered to avoid repeated REGISTER attempts.

## Environment variables

Default account supports:

## Troubleshooting

----
url: https://docs.openclaw.ai/gateway/cli-backends
----

# CLI Backends - OpenClaw

## CLI backends (fallback runtime)

OpenClaw can run **local AI CLIs** as a **text-only fallback** when API providers are down, rate-limited, or temporarily misbehaving. This is intentionally conservative:

This is designed as a **safety net** rather than a primary path. Use it when you want “always works” text responses without relying on external APIs.

## Beginner-friendly quick start

You can use Claude Code CLI **without any config** (OpenClaw ships a built-in default):

Codex CLI also works out of the box:

If your gateway runs under launchd/systemd and PATH is minimal, add just the command path:

That’s it. No keys, no extra auth config needed beyond the CLI itself.

## Using it as a fallback

Add a CLI backend to your fallback list so it only runs when primary models fail:

```
{
  agents: {
    defaults: {
      model: {
        primary: "anthropic/claude-opus-4-6",
        fallbacks: ["claude-cli/opus-4.6", "claude-cli/opus-4.5"],
      },
      models: {
        "anthropic/claude-opus-4-6": { alias: "Opus" },
        "claude-cli/opus-4.6": {},
        "claude-cli/opus-4.5": {},
      },
    },
  },
}
```

Notes:

## Configuration overview

All CLI backends live under:

Each entry is keyed by a **provider id** (e.g. `claude-cli`, `my-cli`). The provider id becomes the left side of your model ref:

### Example configuration

```
{
  agents: {
    defaults: {
      cliBackends: {
        "claude-cli": {
          command: "/opt/homebrew/bin/claude",
        },
        "my-cli": {
          command: "my-cli",
          args: ["--json"],
          output: "json",
          input: "arg",
          modelArg: "--model",
          modelAliases: {
            "claude-opus-4-6": "opus",
            "claude-opus-4-6": "opus",
            "claude-sonnet-4-6": "sonnet",
          },
          sessionArg: "--session",
          sessionMode: "existing",
          sessionIdFields: ["session_id", "conversation_id"],
          systemPromptArg: "--system",
          systemPromptWhen: "first",
          imageArg: "--image",
          imageMode: "repeat",
          serialize: true,
        },
      },
    },
  },
}
```

## How it works

1. **Selects a backend** based on the provider prefix (`claude-cli/...`).
2. **Builds a system prompt** using the same OpenClaw prompt + workspace context.
3. **Executes the CLI** with a session id (if supported) so history stays consistent.
4. **Parses output** (JSON or plain text) and returns the final text.
5. **Persists session ids** per backend, so follow-ups reuse the same CLI session.

## Sessions

## Images (pass-through)

If your CLI accepts image paths, set `imageArg`:

OpenClaw will write base64 images to temp files. If `imageArg` is set, those paths are passed as CLI args. If `imageArg` is missing, OpenClaw appends the file paths to the prompt (path injection), which is enough for CLIs that auto- load local files from plain paths (Claude Code CLI behavior).

## Inputs / outputs

Input modes:

## Defaults (built-in)

OpenClaw ships a default for `claude-cli`:

* `command: "claude"`
* `args: ["-p", "--output-format", "json", "--permission-mode", "bypassPermissions"]`
* `resumeArgs: ["-p", "--output-format", "json", "--permission-mode", "bypassPermissions", "--resume", "{sessionId}"]`
* `modelArg: "--model"`
* `systemPromptArg: "--append-system-prompt"`
* `sessionArg: "--session-id"`
* `systemPromptWhen: "first"`
* `sessionMode: "always"`

OpenClaw also ships a default for `codex-cli`:

* `command: "codex"`
* `args: ["exec","--json","--color","never","--sandbox","read-only","--skip-git-repo-check"]`
* `resumeArgs: ["exec","resume","{sessionId}","--color","never","--sandbox","read-only","--skip-git-repo-check"]`
* `output: "jsonl"`
* `resumeOutput: "text"`
* `modelArg: "--model"`
* `imageArg: "--image"`
* `sessionMode: "existing"`

Override only if needed (common: absolute `command` path).

## Limitations

## Troubleshooting

----
url: https://docs.openclaw.ai/tools/brave-search
----

# Brave Search - OpenClaw

OpenClaw supports Brave Search API as a `web_search` provider.

## Get an API key

1. Create a Brave Search API account at <https://brave.com/search/api/>
2. In the dashboard, choose the **Search** plan and generate an API key.
3. Store the key in config or set `BRAVE_API_KEY` in the Gateway environment.

## Config example

```
{
  plugins: {
    entries: {
      brave: {
        config: {
          webSearch: {
            apiKey: "BRAVE_API_KEY_HERE",
          },
        },
      },
    },
  },
  tools: {
    web: {
      search: {
        provider: "brave",
        maxResults: 5,
        timeoutSeconds: 30,
      },
    },
  },
}
```

Provider-specific Brave search settings now live under `plugins.entries.brave.config.webSearch.*`. Legacy `tools.web.search.apiKey` still loads through the compatibility shim, but it is no longer the canonical config path.

## Tool parameters

| Parameter     | Description                                                         |
| ------------- | ------------------------------------------------------------------- |
| `query`       | Search query (required)                                             |
| `count`       | Number of results to return (1-10, default: 5)                      |
| `country`     | 2-letter ISO country code (e.g., “US”, “DE”)                        |
| `language`    | ISO 639-1 language code for search results (e.g., “en”, “de”, “fr”) |
| `ui_lang`     | ISO language code for UI elements                                   |
| `freshness`   | Time filter: `day` (24h), `week`, `month`, or `year`                |
| `date_after`  | Only results published after this date (YYYY-MM-DD)                 |
| `date_before` | Only results published before this date (YYYY-MM-DD)                |

**Examples:**

## Notes

----
url: https://docs.openclaw.ai/cli/configure
----

# configure - OpenClaw

## [​](#openclaw-configure)`openclaw configure`

Interactive prompt to set up credentials, devices, and agent defaults. Note: The **Model** section now includes a multi-select for the `agents.defaults.models` allowlist (what shows up in `/model` and the model picker). Tip: `openclaw config` without a subcommand opens the same wizard. Use `openclaw config get|set|unset` for non-interactive edits. Related:

* Gateway configuration reference: [Configuration](https://docs.openclaw.ai/gateway/configuration)
* Config CLI: [Config](https://docs.openclaw.ai/cli/config)

Notes:

* Choosing where the Gateway runs always updates `gateway.mode`. You can select “Continue” without other sections if that is all you need.
* Channel-oriented services (Slack/Discord/Matrix/Microsoft Teams) prompt for channel/room allowlists during setup. You can enter names or IDs; the wizard resolves names to IDs when possible.
* If you run the daemon install step, token auth requires a token, and `gateway.auth.token` is SecretRef-managed, configure validates the SecretRef but does not persist resolved plaintext token values into supervisor service environment metadata.
* If token auth requires a token and the configured token SecretRef is unresolved, configure blocks daemon install with actionable remediation guidance.
* If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, configure blocks daemon install until mode is set explicitly.

## [​](#examples)Examples

```
openclaw configure
openclaw configure --section model --section channels
```

----
url: https://docs.openclaw.ai/tools/gemini-search
----

# Gemini Search - OpenClaw

OpenClaw supports Gemini models with built-in [Google Search grounding](https://ai.google.dev/gemini-api/docs/grounding), which returns AI-synthesized answers backed by live Google Search results with citations.

## Get an API key

## Config

```
{
  plugins: {
    entries: {
      google: {
        config: {
          webSearch: {
            apiKey: "AIza...", // optional if GEMINI_API_KEY is set
            model: "gemini-2.5-flash", // default
          },
        },
      },
    },
  },
  tools: {
    web: {
      search: {
        provider: "gemini",
      },
    },
  },
}
```

**Environment alternative:** set `GEMINI_API_KEY` in the Gateway environment. For a gateway install, put it in `~/.openclaw/.env`.

## How it works

Unlike traditional search providers that return a list of links and snippets, Gemini uses Google Search grounding to produce AI-synthesized answers with inline citations. The results include both the synthesized answer and the source URLs.

## Supported parameters

Gemini search supports the standard `query` and `count` parameters. Provider-specific filters like `country`, `language`, `freshness`, and `domain_filter` are not supported.

## Model selection

The default model is `gemini-2.5-flash` (fast and cost-effective). Any Gemini model that supports grounding can be used via `plugins.entries.google.config.webSearch.model`.

----
url: https://docs.openclaw.ai/channels/googlechat
----

# Google Chat - OpenClaw

Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).

## Quick setup (beginner)

1. Create a Google Cloud project and enable the **Google Chat API**.
2. Create a **Service Account**:
3. Create and download the **JSON Key**:
4. Store the downloaded JSON file on your gateway host (e.g., `~/.openclaw/googlechat-service-account.json`).
5. Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat):
6. **Enable the app status**:
7. Configure OpenClaw with the service account path + webhook audience:
8. Set the webhook audience type + value (matches your Chat app config).
9. Start the gateway. Google Chat will POST to your webhook path.

## Add to Google Chat

Once the gateway is running and your email is added to the visibility list:

1. Go to [Google Chat](https://chat.google.com/).
2. Click the **+** (plus) icon next to **Direct Messages**.
3. In the search bar (where you usually add people), type the **App name** you configured in the Google Cloud Console.
4. Select your bot from the results.
5. Click **Add** or **Chat** to start a 1:1 conversation.
6. Send “Hello” to trigger the assistant!

## Public URL (Webhook-only)

Google Chat webhooks require a public HTTPS endpoint. For security, **only expose the `/googlechat` path** to the internet. Keep the OpenClaw dashboard and other sensitive endpoints on your private network.

### Option A: Tailscale Funnel (Recommended)

Use Tailscale Serve for the private dashboard and Funnel for the public webhook path. This keeps `/` private while exposing only `/googlechat`.

1. **Check what address your gateway is bound to:** Note the IP address (e.g., `127.0.0.1`, `0.0.0.0`, or your Tailscale IP like `100.x.x.x`).
2. **Expose the dashboard to the tailnet only (port 8443):**
3. **Expose only the webhook path publicly:**
4. **Authorize the node for Funnel access:** If prompted, visit the authorization URL shown in the output to enable Funnel for this node in your tailnet policy.
5. **Verify the configuration:**

Your public webhook URL will be: `https://<node-name>.<tailnet>.ts.net/googlechat` Your private dashboard stays tailnet-only: `https://<node-name>.<tailnet>.ts.net:8443/` Use the public URL (without `:8443`) in the Google Chat app config.

> Note: This configuration persists across reboots. To remove it later, run `tailscale funnel reset` and `tailscale serve reset`.

### Option B: Reverse Proxy (Caddy)

If you use a reverse proxy like Caddy, only proxy the specific path:

With this config, any request to `your-domain.com/` will be ignored or returned as 404, while `your-domain.com/googlechat` is safely routed to OpenClaw.

### Option C: Cloudflare Tunnel

Configure your tunnel’s ingress rules to only route the webhook path:

## How it works

1. Google Chat sends webhook POSTs to the gateway. Each request includes an `Authorization: Bearer <token>` header.
2. OpenClaw verifies the token against the configured `audienceType` + `audience`:
3. Messages are routed by space:
4. DM access is pairing by default. Unknown senders receive a pairing code; approve with:
5. Group spaces require @-mention by default. Use `botUser` if mention detection needs the app’s user name.

## Targets

Use these identifiers for delivery and allowlists:

## Config highlights

```
{
  channels: {
    googlechat: {
      enabled: true,
      serviceAccountFile: "/path/to/service-account.json",
      // or serviceAccountRef: { source: "file", provider: "filemain", id: "/channels/googlechat/serviceAccount" }
      audienceType: "app-url",
      audience: "https://gateway.example.com/googlechat",
      webhookPath: "/googlechat",
      botUser: "users/1234567890", // optional; helps mention detection
      dm: {
        policy: "pairing",
        allowFrom: ["users/1234567890"],
      },
      groupPolicy: "allowlist",
      groups: {
        "spaces/AAAA": {
          allow: true,
          requireMention: true,
          users: ["users/1234567890"],
          systemPrompt: "Short answers only.",
        },
      },
      actions: { reactions: true },
      typingIndicator: "message",
      mediaMaxMb: 20,
    },
  },
}
```

Notes:

Secrets reference details: [Secrets Management](https://docs.openclaw.ai/gateway/secrets).

## Troubleshooting

### 405 Method Not Allowed

If Google Cloud Logs Explorer shows errors like:

This means the webhook handler isn’t registered. Common causes:

1. **Channel not configured**: The `channels.googlechat` section is missing from your config. Verify with: If it returns “Config path not found”, add the configuration (see [Config highlights](#config-highlights)).
2. **Plugin not enabled**: Check plugin status: If it shows “disabled”, add `plugins.entries.googlechat.enabled: true` to your config.
3. **Gateway not restarted**: After adding config, restart the gateway:

Verify the channel is running:

### Other issues

Related docs:

----
url: https://docs.openclaw.ai/reference/credits
----

# Credits - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [Credits and Acknowledgments](#credits-and-acknowledgments)
- [The name](#the-name)
- [Credits](#credits)
- [Core contributors](#core-contributors)
- [License](#license)

## [​](#credits-and-acknowledgments)Credits and Acknowledgments

## [​](#the-name)The name

OpenClaw = CLAW + TARDIS, because every space lobster needs a time and space machine.

## [​](#credits)Credits

* **Peter Steinberger** ([@steipete](https://x.com/steipete)) - Creator, lobster whisperer
* **Mario Zechner** ([@badlogicc](https://x.com/badlogicgames)) - Pi creator, security pen tester
* **Clawd** - The space lobster who demanded a better name

## [​](#core-contributors)Core contributors

* **Maxim Vovshin** (@Hyaxia, <36747317+Hyaxia@users.noreply.github.com>) - Blogwatcher skill
* **Nacho Iacovino** (@nachoiacovino, <nacho.iacovino@gmail.com>) - Location parsing (Telegram and WhatsApp)
* **Vincent Koc** ([@vincentkoc](https://github.com/vincentkoc), [@vincent\_koc](https://x.com/vincent_koc)) - Agents, Telemetry, Hooks, Security

## [​](#license)License

MIT - Free as a lobster in the ocean.

> “We are all just playing with our own prompts.” (An AI, probably high on tokens)

[Timezones](https://docs.openclaw.ai/concepts/timezone)[Release Policy](https://docs.openclaw.ai/reference/RELEASING)

----
url: https://docs.openclaw.ai/nodes/talk
----

# Talk Mode - OpenClaw

## [​](#talk-mode)Talk Mode

Talk mode is a continuous voice conversation loop:

1. Listen for speech
2. Send transcript to the model (main session, chat.send)
3. Wait for the response
4. Speak it via ElevenLabs (streaming playback)

## [​](#behavior-macos)Behavior (macOS)

* **Always-on overlay** while Talk mode is enabled.
* **Listening → Thinking → Speaking** phase transitions.
* On a **short pause** (silence window), the current transcript is sent.
* Replies are **written to WebChat** (same as typing).
* **Interrupt on speech** (default on): if the user starts talking while the assistant is speaking, we stop playback and note the interruption timestamp for the next prompt.

## [​](#voice-directives-in-replies)Voice directives in replies

The assistant may prefix its reply with a **single JSON line** to control voice:

```
{ "voice": "<voice-id>", "once": true }
```

Rules:

* First non-empty line only.
* Unknown keys are ignored.
* `once: true` applies to the current reply only.
* Without `once`, the voice becomes the new default for Talk mode.
* The JSON line is stripped before TTS playback.

Supported keys:

* `voice` / `voice_id` / `voiceId`
* `model` / `model_id` / `modelId`
* `speed`, `rate` (WPM), `stability`, `similarity`, `style`, `speakerBoost`
* `seed`, `normalize`, `lang`, `output_format`, `latency_tier`
* `once`

## [​](#config-/-openclaw/openclaw-json)Config (`~/.openclaw/openclaw.json`)

```
{
  talk: {
    voiceId: "elevenlabs_voice_id",
    modelId: "eleven_v3",
    outputFormat: "mp3_44100_128",
    apiKey: "elevenlabs_api_key",
    silenceTimeoutMs: 1500,
    interruptOnSpeech: true,
  },
}
```

Defaults:

* `interruptOnSpeech`: true
* `silenceTimeoutMs`: when unset, Talk keeps the platform default pause window before sending the transcript (`700 ms on macOS and Android, 900 ms on iOS`)
* `voiceId`: falls back to `ELEVENLABS_VOICE_ID` / `SAG_VOICE_ID` (or first ElevenLabs voice when API key is available)
* `modelId`: defaults to `eleven_v3` when unset
* `apiKey`: falls back to `ELEVENLABS_API_KEY` (or gateway shell profile if available)
* `outputFormat`: defaults to `pcm_44100` on macOS/iOS and `pcm_24000` on Android (set `mp3_*` to force MP3 streaming)

## [​](#macos-ui)macOS UI

* Menu bar toggle: **Talk**

* Config tab: **Talk Mode** group (voice id + interrupt toggle)

* Overlay:

  * **Listening**: cloud pulses with mic level
  * **Thinking**: sinking animation
  * **Speaking**: radiating rings
  * Click cloud: stop speaking
  * Click X: exit Talk mode

## [​](#notes)Notes

* Requires Speech + Microphone permissions.
* Uses `chat.send` against session key `main`.
* TTS uses ElevenLabs streaming API with `ELEVENLABS_API_KEY` and incremental playback on macOS/iOS/Android for lower latency.
* `stability` for `eleven_v3` is validated to `0.0`, `0.5`, or `1.0`; other models accept `0..1`.
* `latency_tier` is validated to `0..4` when set.
* Android supports `pcm_16000`, `pcm_22050`, `pcm_24000`, and `pcm_44100` output formats for low-latency AudioTrack streaming.

----
url: https://docs.openclaw.ai/security/THREAT-MODEL-ATLAS
----

# Threat Model (MITRE ATLAS) - OpenClaw

## MITRE ATLAS Framework

**Version:** 1.0-draft **Last Updated:** 2026-02-04 **Methodology:** MITRE ATLAS + Data Flow Diagrams **Framework:** [MITRE ATLAS](https://atlas.mitre.org/) (Adversarial Threat Landscape for AI Systems)

### Framework Attribution

This threat model is built on [MITRE ATLAS](https://atlas.mitre.org/), the industry-standard framework for documenting adversarial threats to AI/ML systems. ATLAS is maintained by [MITRE](https://www.mitre.org/) in collaboration with the AI security community. **Key ATLAS Resources:**

### Contributing to This Threat Model

This is a living document maintained by the OpenClaw community. See [CONTRIBUTING-THREAT-MODEL.md](https://docs.openclaw.ai/security/CONTRIBUTING-THREAT-MODEL) for guidelines on contributing:

***

## 1. Introduction

### 1.1 Purpose

This threat model documents adversarial threats to the OpenClaw AI agent platform and ClawHub skill marketplace, using the MITRE ATLAS framework designed specifically for AI/ML systems.

### 1.2 Scope

| Component              | Included | Notes                                            |
| ---------------------- | -------- | ------------------------------------------------ |
| OpenClaw Agent Runtime | Yes      | Core agent execution, tool calls, sessions       |
| Gateway                | Yes      | Authentication, routing, channel integration     |
| Channel Integrations   | Yes      | WhatsApp, Telegram, Discord, Signal, Slack, etc. |
| ClawHub Marketplace    | Yes      | Skill publishing, moderation, distribution       |
| MCP Servers            | Yes      | External tool providers                          |
| User Devices           | Partial  | Mobile apps, desktop clients                     |

### 1.3 Out of Scope

Nothing is explicitly out of scope for this threat model.

***

## 2. System Architecture

### 2.1 Trust Boundaries

### 2.2 Data Flows

| Flow | Source  | Destination | Data                | Protection           |
| ---- | ------- | ----------- | ------------------- | -------------------- |
| F1   | Channel | Gateway     | User messages       | TLS, AllowFrom       |
| F2   | Gateway | Agent       | Routed messages     | Session isolation    |
| F3   | Agent   | Tools       | Tool invocations    | Policy enforcement   |
| F4   | Agent   | External    | web\_fetch requests | SSRF blocking        |
| F5   | ClawHub | Agent       | Skill code          | Moderation, scanning |
| F6   | Agent   | Channel     | Responses           | Output filtering     |

***

## 3. Threat Analysis by ATLAS Tactic

### 3.1 Reconnaissance (AML.TA0002)

#### T-RECON-001: Agent Endpoint Discovery

| Attribute               | Value                                                                |
| ----------------------- | -------------------------------------------------------------------- |
| **ATLAS ID**            | AML.T0006 - Active Scanning                                          |
| **Description**         | Attacker scans for exposed OpenClaw gateway endpoints                |
| **Attack Vector**       | Network scanning, shodan queries, DNS enumeration                    |
| **Affected Components** | Gateway, exposed API endpoints                                       |
| **Current Mitigations** | Tailscale auth option, bind to loopback by default                   |
| **Residual Risk**       | Medium - Public gateways discoverable                                |
| **Recommendations**     | Document secure deployment, add rate limiting on discovery endpoints |

#### T-RECON-002: Channel Integration Probing

| Attribute               | Value                                                              |
| ----------------------- | ------------------------------------------------------------------ |
| **ATLAS ID**            | AML.T0006 - Active Scanning                                        |
| **Description**         | Attacker probes messaging channels to identify AI-managed accounts |
| **Attack Vector**       | Sending test messages, observing response patterns                 |
| **Affected Components** | All channel integrations                                           |
| **Current Mitigations** | None specific                                                      |
| **Residual Risk**       | Low - Limited value from discovery alone                           |
| **Recommendations**     | Consider response timing randomization                             |

***

### 3.2 Initial Access (AML.TA0004)

#### T-ACCESS-001: Pairing Code Interception

| Attribute               | Value                                                    |
| ----------------------- | -------------------------------------------------------- |
| **ATLAS ID**            | AML.T0040 - AI Model Inference API Access                |
| **Description**         | Attacker intercepts pairing code during 30s grace period |
| **Attack Vector**       | Shoulder surfing, network sniffing, social engineering   |
| **Affected Components** | Device pairing system                                    |
| **Current Mitigations** | 30s expiry, codes sent via existing channel              |
| **Residual Risk**       | Medium - Grace period exploitable                        |
| **Recommendations**     | Reduce grace period, add confirmation step               |

#### T-ACCESS-002: AllowFrom Spoofing

| Attribute               | Value                                                                          |
| ----------------------- | ------------------------------------------------------------------------------ |
| **ATLAS ID**            | AML.T0040 - AI Model Inference API Access                                      |
| **Description**         | Attacker spoofs allowed sender identity in channel                             |
| **Attack Vector**       | Depends on channel - phone number spoofing, username impersonation             |
| **Affected Components** | AllowFrom validation per channel                                               |
| **Current Mitigations** | Channel-specific identity verification                                         |
| **Residual Risk**       | Medium - Some channels vulnerable to spoofing                                  |
| **Recommendations**     | Document channel-specific risks, add cryptographic verification where possible |

#### T-ACCESS-003: Token Theft

| Attribute               | Value                                                       |
| ----------------------- | ----------------------------------------------------------- |
| **ATLAS ID**            | AML.T0040 - AI Model Inference API Access                   |
| **Description**         | Attacker steals authentication tokens from config files     |
| **Attack Vector**       | Malware, unauthorized device access, config backup exposure |
| **Affected Components** | \~/.openclaw/credentials/, config storage                   |
| **Current Mitigations** | File permissions                                            |
| **Residual Risk**       | High - Tokens stored in plaintext                           |
| **Recommendations**     | Implement token encryption at rest, add token rotation      |

***

### 3.3 Execution (AML.TA0005)

#### T-EXEC-001: Direct Prompt Injection

| Attribute               | Value                                                                                     |
| ----------------------- | ----------------------------------------------------------------------------------------- |
| **ATLAS ID**            | AML.T0051.000 - LLM Prompt Injection: Direct                                              |
| **Description**         | Attacker sends crafted prompts to manipulate agent behavior                               |
| **Attack Vector**       | Channel messages containing adversarial instructions                                      |
| **Affected Components** | Agent LLM, all input surfaces                                                             |
| **Current Mitigations** | Pattern detection, external content wrapping                                              |
| **Residual Risk**       | Critical - Detection only, no blocking; sophisticated attacks bypass                      |
| **Recommendations**     | Implement multi-layer defense, output validation, user confirmation for sensitive actions |

#### T-EXEC-002: Indirect Prompt Injection

| Attribute               | Value                                                       |
| ----------------------- | ----------------------------------------------------------- |
| **ATLAS ID**            | AML.T0051.001 - LLM Prompt Injection: Indirect              |
| **Description**         | Attacker embeds malicious instructions in fetched content   |
| **Attack Vector**       | Malicious URLs, poisoned emails, compromised webhooks       |
| **Affected Components** | web\_fetch, email ingestion, external data sources          |
| **Current Mitigations** | Content wrapping with XML tags and security notice          |
| **Residual Risk**       | High - LLM may ignore wrapper instructions                  |
| **Recommendations**     | Implement content sanitization, separate execution contexts |

#### T-EXEC-003: Tool Argument Injection

| Attribute               | Value                                                        |
| ----------------------- | ------------------------------------------------------------ |
| **ATLAS ID**            | AML.T0051.000 - LLM Prompt Injection: Direct                 |
| **Description**         | Attacker manipulates tool arguments through prompt injection |
| **Attack Vector**       | Crafted prompts that influence tool parameter values         |
| **Affected Components** | All tool invocations                                         |
| **Current Mitigations** | Exec approvals for dangerous commands                        |
| **Residual Risk**       | High - Relies on user judgment                               |
| **Recommendations**     | Implement argument validation, parameterized tool calls      |

#### T-EXEC-004: Exec Approval Bypass

| Attribute               | Value                                                      |
| ----------------------- | ---------------------------------------------------------- |
| **ATLAS ID**            | AML.T0043 - Craft Adversarial Data                         |
| **Description**         | Attacker crafts commands that bypass approval allowlist    |
| **Attack Vector**       | Command obfuscation, alias exploitation, path manipulation |
| **Affected Components** | exec-approvals.ts, command allowlist                       |
| **Current Mitigations** | Allowlist + ask mode                                       |
| **Residual Risk**       | High - No command sanitization                             |
| **Recommendations**     | Implement command normalization, expand blocklist          |

***

### 3.4 Persistence (AML.TA0006)

#### T-PERSIST-001: Malicious Skill Installation

| Attribute               | Value                                                                    |
| ----------------------- | ------------------------------------------------------------------------ |
| **ATLAS ID**            | AML.T0010.001 - Supply Chain Compromise: AI Software                     |
| **Description**         | Attacker publishes malicious skill to ClawHub                            |
| **Attack Vector**       | Create account, publish skill with hidden malicious code                 |
| **Affected Components** | ClawHub, skill loading, agent execution                                  |
| **Current Mitigations** | GitHub account age verification, pattern-based moderation flags          |
| **Residual Risk**       | Critical - No sandboxing, limited review                                 |
| **Recommendations**     | VirusTotal integration (in progress), skill sandboxing, community review |

#### T-PERSIST-002: Skill Update Poisoning

| Attribute               | Value                                                          |
| ----------------------- | -------------------------------------------------------------- |
| **ATLAS ID**            | AML.T0010.001 - Supply Chain Compromise: AI Software           |
| **Description**         | Attacker compromises popular skill and pushes malicious update |
| **Attack Vector**       | Account compromise, social engineering of skill owner          |
| **Affected Components** | ClawHub versioning, auto-update flows                          |
| **Current Mitigations** | Version fingerprinting                                         |
| **Residual Risk**       | High - Auto-updates may pull malicious versions                |
| **Recommendations**     | Implement update signing, rollback capability, version pinning |

#### T-PERSIST-003: Agent Configuration Tampering

| Attribute               | Value                                                           |
| ----------------------- | --------------------------------------------------------------- |
| **ATLAS ID**            | AML.T0010.002 - Supply Chain Compromise: Data                   |
| **Description**         | Attacker modifies agent configuration to persist access         |
| **Attack Vector**       | Config file modification, settings injection                    |
| **Affected Components** | Agent config, tool policies                                     |
| **Current Mitigations** | File permissions                                                |
| **Residual Risk**       | Medium - Requires local access                                  |
| **Recommendations**     | Config integrity verification, audit logging for config changes |

***

### 3.5 Defense Evasion (AML.TA0007)

#### T-EVADE-001: Moderation Pattern Bypass

| Attribute               | Value                                                                  |
| ----------------------- | ---------------------------------------------------------------------- |
| **ATLAS ID**            | AML.T0043 - Craft Adversarial Data                                     |
| **Description**         | Attacker crafts skill content to evade moderation patterns             |
| **Attack Vector**       | Unicode homoglyphs, encoding tricks, dynamic loading                   |
| **Affected Components** | ClawHub moderation.ts                                                  |
| **Current Mitigations** | Pattern-based FLAG\_RULES                                              |
| **Residual Risk**       | High - Simple regex easily bypassed                                    |
| **Recommendations**     | Add behavioral analysis (VirusTotal Code Insight), AST-based detection |

#### T-EVADE-002: Content Wrapper Escape

| Attribute               | Value                                                     |
| ----------------------- | --------------------------------------------------------- |
| **ATLAS ID**            | AML.T0043 - Craft Adversarial Data                        |
| **Description**         | Attacker crafts content that escapes XML wrapper context  |
| **Attack Vector**       | Tag manipulation, context confusion, instruction override |
| **Affected Components** | External content wrapping                                 |
| **Current Mitigations** | XML tags + security notice                                |
| **Residual Risk**       | Medium - Novel escapes discovered regularly               |
| **Recommendations**     | Multiple wrapper layers, output-side validation           |

***

### 3.6 Discovery (AML.TA0008)

#### T-DISC-001: Tool Enumeration

| Attribute               | Value                                                 |
| ----------------------- | ----------------------------------------------------- |
| **ATLAS ID**            | AML.T0040 - AI Model Inference API Access             |
| **Description**         | Attacker enumerates available tools through prompting |
| **Attack Vector**       | ”What tools do you have?” style queries               |
| **Affected Components** | Agent tool registry                                   |
| **Current Mitigations** | None specific                                         |
| **Residual Risk**       | Low - Tools generally documented                      |
| **Recommendations**     | Consider tool visibility controls                     |

| Attribute               | Value                                                 |
| ----------------------- | ----------------------------------------------------- |
| **ATLAS ID**            | AML.T0040 - AI Model Inference API Access             |
| **Description**         | Attacker extracts sensitive data from session context |
| **Attack Vector**       | ”What did we discuss?” queries, context probing       |
| **Affected Components** | Session transcripts, context window                   |
| **Current Mitigations** | Session isolation per sender                          |
| **Residual Risk**       | Medium - Within-session data accessible               |
| **Recommendations**     | Implement sensitive data redaction in context         |

***

### 3.7 Collection & Exfiltration (AML.TA0009, AML.TA0010)

#### T-EXFIL-001: Data Theft via web\_fetch

| Attribute               | Value                                                                  |
| ----------------------- | ---------------------------------------------------------------------- |
| **ATLAS ID**            | AML.T0009 - Collection                                                 |
| **Description**         | Attacker exfiltrates data by instructing agent to send to external URL |
| **Attack Vector**       | Prompt injection causing agent to POST data to attacker server         |
| **Affected Components** | web\_fetch tool                                                        |
| **Current Mitigations** | SSRF blocking for internal networks                                    |
| **Residual Risk**       | High - External URLs permitted                                         |
| **Recommendations**     | Implement URL allowlisting, data classification awareness              |

| Attribute               | Value                                                            |
| ----------------------- | ---------------------------------------------------------------- |
| **ATLAS ID**            | AML.T0009 - Collection                                           |
| **Description**         | Attacker causes agent to send messages containing sensitive data |
| **Attack Vector**       | Prompt injection causing agent to message attacker               |
| **Affected Components** | Message tool, channel integrations                               |
| **Current Mitigations** | Outbound messaging gating                                        |
| **Residual Risk**       | Medium - Gating may be bypassed                                  |
| **Recommendations**     | Require explicit confirmation for new recipients                 |

#### T-EXFIL-003: Credential Harvesting

| Attribute               | Value                                                   |
| ----------------------- | ------------------------------------------------------- |
| **ATLAS ID**            | AML.T0009 - Collection                                  |
| **Description**         | Malicious skill harvests credentials from agent context |
| **Attack Vector**       | Skill code reads environment variables, config files    |
| **Affected Components** | Skill execution environment                             |
| **Current Mitigations** | None specific to skills                                 |
| **Residual Risk**       | Critical - Skills run with agent privileges             |
| **Recommendations**     | Skill sandboxing, credential isolation                  |

***

### 3.8 Impact (AML.TA0011)

#### T-IMPACT-001: Unauthorized Command Execution

| Attribute               | Value                                               |
| ----------------------- | --------------------------------------------------- |
| **ATLAS ID**            | AML.T0031 - Erode AI Model Integrity                |
| **Description**         | Attacker executes arbitrary commands on user system |
| **Attack Vector**       | Prompt injection combined with exec approval bypass |
| **Affected Components** | Bash tool, command execution                        |
| **Current Mitigations** | Exec approvals, Docker sandbox option               |
| **Residual Risk**       | Critical - Host execution without sandbox           |
| **Recommendations**     | Default to sandbox, improve approval UX             |

#### T-IMPACT-002: Resource Exhaustion (DoS)

| Attribute               | Value                                              |
| ----------------------- | -------------------------------------------------- |
| **ATLAS ID**            | AML.T0031 - Erode AI Model Integrity               |
| **Description**         | Attacker exhausts API credits or compute resources |
| **Attack Vector**       | Automated message flooding, expensive tool calls   |
| **Affected Components** | Gateway, agent sessions, API provider              |
| **Current Mitigations** | None                                               |
| **Residual Risk**       | High - No rate limiting                            |
| **Recommendations**     | Implement per-sender rate limits, cost budgets     |

#### T-IMPACT-003: Reputation Damage

| Attribute               | Value                                                   |
| ----------------------- | ------------------------------------------------------- |
| **ATLAS ID**            | AML.T0031 - Erode AI Model Integrity                    |
| **Description**         | Attacker causes agent to send harmful/offensive content |
| **Attack Vector**       | Prompt injection causing inappropriate responses        |
| **Affected Components** | Output generation, channel messaging                    |
| **Current Mitigations** | LLM provider content policies                           |
| **Residual Risk**       | Medium - Provider filters imperfect                     |
| **Recommendations**     | Output filtering layer, user controls                   |

***

## 4. ClawHub Supply Chain Analysis

### 4.1 Current Security Controls

| Control              | Implementation               | Effectiveness                                        |
| -------------------- | ---------------------------- | ---------------------------------------------------- |
| GitHub Account Age   | `requireGitHubAccountAge()`  | Medium - Raises bar for new attackers                |
| Path Sanitization    | `sanitizePath()`             | High - Prevents path traversal                       |
| File Type Validation | `isTextFile()`               | Medium - Only text files, but can still be malicious |
| Size Limits          | 50MB total bundle            | High - Prevents resource exhaustion                  |
| Required SKILL.md    | Mandatory readme             | Low security value - Informational only              |
| Pattern Moderation   | FLAG\_RULES in moderation.ts | Low - Easily bypassed                                |
| Moderation Status    | `moderationStatus` field     | Medium - Manual review possible                      |

### 4.2 Moderation Flag Patterns

Current patterns in `moderation.ts`:

**Limitations:**

### 4.3 Planned Improvements

| Improvement            | Status                                | Impact                                                                |
| ---------------------- | ------------------------------------- | --------------------------------------------------------------------- |
| VirusTotal Integration | In Progress                           | High - Code Insight behavioral analysis                               |
| Community Reporting    | Partial (`skillReports` table exists) | Medium                                                                |
| Audit Logging          | Partial (`auditLogs` table exists)    | Medium                                                                |
| Badge System           | Implemented                           | Medium - `highlighted`, `official`, `deprecated`, `redactionApproved` |

***

## 5. Risk Matrix

### 5.1 Likelihood vs Impact

| Threat ID     | Likelihood | Impact   | Risk Level   | Priority |
| ------------- | ---------- | -------- | ------------ | -------- |
| T-EXEC-001    | High       | Critical | **Critical** | P0       |
| T-PERSIST-001 | High       | Critical | **Critical** | P0       |
| T-EXFIL-003   | Medium     | Critical | **Critical** | P0       |
| T-IMPACT-001  | Medium     | Critical | **High**     | P1       |
| T-EXEC-002    | High       | High     | **High**     | P1       |
| T-EXEC-004    | Medium     | High     | **High**     | P1       |
| T-ACCESS-003  | Medium     | High     | **High**     | P1       |
| T-EXFIL-001   | Medium     | High     | **High**     | P1       |
| T-IMPACT-002  | High       | Medium   | **High**     | P1       |
| T-EVADE-001   | High       | Medium   | **Medium**   | P2       |
| T-ACCESS-001  | Low        | High     | **Medium**   | P2       |
| T-ACCESS-002  | Low        | High     | **Medium**   | P2       |
| T-PERSIST-002 | Low        | High     | **Medium**   | P2       |

### 5.2 Critical Path Attack Chains

**Attack Chain 1: Skill-Based Data Theft**

**Attack Chain 2: Prompt Injection to RCE**

**Attack Chain 3: Indirect Injection via Fetched Content**

***

## 6. Recommendations Summary

### 6.1 Immediate (P0)

| ID    | Recommendation                              | Addresses                  |
| ----- | ------------------------------------------- | -------------------------- |
| R-001 | Complete VirusTotal integration             | T-PERSIST-001, T-EVADE-001 |
| R-002 | Implement skill sandboxing                  | T-PERSIST-001, T-EXFIL-003 |
| R-003 | Add output validation for sensitive actions | T-EXEC-001, T-EXEC-002     |

### 6.2 Short-term (P1)

| ID    | Recommendation                            | Addresses    |
| ----- | ----------------------------------------- | ------------ |
| R-004 | Implement rate limiting                   | T-IMPACT-002 |
| R-005 | Add token encryption at rest              | T-ACCESS-003 |
| R-006 | Improve exec approval UX and validation   | T-EXEC-004   |
| R-007 | Implement URL allowlisting for web\_fetch | T-EXFIL-001  |

### 6.3 Medium-term (P2)

| ID    | Recommendation                                        | Addresses     |
| ----- | ----------------------------------------------------- | ------------- |
| R-008 | Add cryptographic channel verification where possible | T-ACCESS-002  |
| R-009 | Implement config integrity verification               | T-PERSIST-003 |
| R-010 | Add update signing and version pinning                | T-PERSIST-002 |

***

## 7. Appendices

### 7.1 ATLAS Technique Mapping

| ATLAS ID      | Technique Name                 | OpenClaw Threats                                                 |
| ------------- | ------------------------------ | ---------------------------------------------------------------- |
| AML.T0006     | Active Scanning                | T-RECON-001, T-RECON-002                                         |
| AML.T0009     | Collection                     | T-EXFIL-001, T-EXFIL-002, T-EXFIL-003                            |
| AML.T0010.001 | Supply Chain: AI Software      | T-PERSIST-001, T-PERSIST-002                                     |
| AML.T0010.002 | Supply Chain: Data             | T-PERSIST-003                                                    |
| AML.T0031     | Erode AI Model Integrity       | T-IMPACT-001, T-IMPACT-002, T-IMPACT-003                         |
| AML.T0040     | AI Model Inference API Access  | T-ACCESS-001, T-ACCESS-002, T-ACCESS-003, T-DISC-001, T-DISC-002 |
| AML.T0043     | Craft Adversarial Data         | T-EXEC-004, T-EVADE-001, T-EVADE-002                             |
| AML.T0051.000 | LLM Prompt Injection: Direct   | T-EXEC-001, T-EXEC-003                                           |
| AML.T0051.001 | LLM Prompt Injection: Indirect | T-EXEC-002                                                       |

### 7.2 Key Security Files

| Path                                | Purpose                     | Risk Level   |
| ----------------------------------- | --------------------------- | ------------ |
| `src/infra/exec-approvals.ts`       | Command approval logic      | **Critical** |
| `src/gateway/auth.ts`               | Gateway authentication      | **Critical** |
| `src/web/inbound/access-control.ts` | Channel access control      | **Critical** |
| `src/infra/net/ssrf.ts`             | SSRF protection             | **Critical** |
| `src/security/external-content.ts`  | Prompt injection mitigation | **Critical** |
| `src/agents/sandbox/tool-policy.ts` | Tool policy enforcement     | **Critical** |
| `convex/lib/moderation.ts`          | ClawHub moderation          | **High**     |
| `convex/lib/skillPublish.ts`        | Skill publishing flow       | **High**     |
| `src/routing/resolve-route.ts`      | Session isolation           | **Medium**   |

### 7.3 Glossary

| Term                 | Definition                                                |
| -------------------- | --------------------------------------------------------- |
| **ATLAS**            | MITRE’s Adversarial Threat Landscape for AI Systems       |
| **ClawHub**          | OpenClaw’s skill marketplace                              |
| **Gateway**          | OpenClaw’s message routing and authentication layer       |
| **MCP**              | Model Context Protocol - tool provider interface          |
| **Prompt Injection** | Attack where malicious instructions are embedded in input |
| **Skill**            | Downloadable extension for OpenClaw agents                |
| **SSRF**             | Server-Side Request Forgery                               |

***

*This threat model is a living document. Report security issues to <security@openclaw.ai>*

----
url: https://docs.openclaw.ai/cli/dashboard
----

# dashboard - OpenClaw

##### CLI commands

* [CLI Reference](https://docs.openclaw.ai/cli)

*

*

*

*

*

*

* * [dashboard](https://docs.openclaw.ai/cli/dashboard)
  * [tui](https://docs.openclaw.ai/cli/tui)

*

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw dashboard](#openclaw-dashboard)

## [​](#openclaw-dashboard)`openclaw dashboard`

Open the Control UI using your current auth.

```
openclaw dashboard
openclaw dashboard --no-open
```

Notes:

* `dashboard` resolves configured `gateway.auth.token` SecretRefs when possible.
* For SecretRef-managed tokens (resolved or unresolved), `dashboard` prints/copies/opens a non-tokenized URL to avoid exposing external secrets in terminal output, clipboard history, or browser-launch arguments.
* If `gateway.auth.token` is SecretRef-managed but unresolved in this command path, the command prints a non-tokenized URL and explicit remediation guidance instead of embedding an invalid token placeholder.

[skills](https://docs.openclaw.ai/cli/skills)[tui](https://docs.openclaw.ai/cli/tui)

----
url: https://docs.openclaw.ai/gateway/configuration-examples
----

# Configuration Examples - OpenClaw

```
{
  // Environment + shell
  env: {
    OPENROUTER_API_KEY: "sk-or-...",
    vars: {
      GROQ_API_KEY: "gsk-...",
    },
    shellEnv: {
      enabled: true,
      timeoutMs: 15000,
    },
  },

  // Auth profile metadata (secrets live in auth-profiles.json)
  auth: {
    profiles: {
      "anthropic:me@example.com": {
        provider: "anthropic",
        mode: "oauth",
        email: "me@example.com",
      },
      "anthropic:work": { provider: "anthropic", mode: "api_key" },
      "openai:default": { provider: "openai", mode: "api_key" },
      "openai-codex:default": { provider: "openai-codex", mode: "oauth" },
    },
    order: {
      anthropic: ["anthropic:me@example.com", "anthropic:work"],
      openai: ["openai:default"],
      "openai-codex": ["openai-codex:default"],
    },
  },

  // Identity
  identity: {
    name: "Samantha",
    theme: "helpful sloth",
    emoji: "🦥",
  },

  // Logging
  logging: {
    level: "info",
    file: "/tmp/openclaw/openclaw.log",
    consoleLevel: "info",
    consoleStyle: "pretty",
    redactSensitive: "tools",
  },

  // Message formatting
  messages: {
    messagePrefix: "[openclaw]",
    responsePrefix: ">",
    ackReaction: "👀",
    ackReactionScope: "group-mentions",
  },

  // Routing + queue
  routing: {
    groupChat: {
      mentionPatterns: ["@openclaw", "openclaw"],
      historyLimit: 50,
    },
    queue: {
      mode: "collect",
      debounceMs: 1000,
      cap: 20,
      drop: "summarize",
      byChannel: {
        whatsapp: "collect",
        telegram: "collect",
        discord: "collect",
        slack: "collect",
        signal: "collect",
        imessage: "collect",
        webchat: "collect",
      },
    },
  },

  // Tooling
  tools: {
    media: {
      audio: {
        enabled: true,
        maxBytes: 20971520,
        models: [
          { provider: "openai", model: "gpt-4o-mini-transcribe" },
          // Optional CLI fallback (Whisper binary):
          // { type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] }
        ],
        timeoutSeconds: 120,
      },
      video: {
        enabled: true,
        maxBytes: 52428800,
        models: [{ provider: "google", model: "gemini-3-flash-preview" }],
      },
    },
  },

  // Session behavior
  session: {
    scope: "per-sender",
    reset: {
      mode: "daily",
      atHour: 4,
      idleMinutes: 60,
    },
    resetByChannel: {
      discord: { mode: "idle", idleMinutes: 10080 },
    },
    resetTriggers: ["/new", "/reset"],
    store: "~/.openclaw/agents/default/sessions/sessions.json",
    maintenance: {
      mode: "warn",
      pruneAfter: "30d",
      maxEntries: 500,
      rotateBytes: "10mb",
      resetArchiveRetention: "30d", // duration or false
      maxDiskBytes: "500mb", // optional
      highWaterBytes: "400mb", // optional (defaults to 80% of maxDiskBytes)
    },
    typingIntervalSeconds: 5,
    sendPolicy: {
      default: "allow",
      rules: [{ action: "deny", match: { channel: "discord", chatType: "group" } }],
    },
  },

  // Channels
  channels: {
    whatsapp: {
      dmPolicy: "pairing",
      allowFrom: ["+15555550123"],
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15555550123"],
      groups: { "*": { requireMention: true } },
    },

    telegram: {
      enabled: true,
      botToken: "YOUR_TELEGRAM_BOT_TOKEN",
      allowFrom: ["123456789"],
      groupPolicy: "allowlist",
      groupAllowFrom: ["123456789"],
      groups: { "*": { requireMention: true } },
    },

    discord: {
      enabled: true,
      token: "YOUR_DISCORD_BOT_TOKEN",
      dm: { enabled: true, allowFrom: ["123456789012345678"] },
      guilds: {
        "123456789012345678": {
          slug: "friends-of-openclaw",
          requireMention: false,
          channels: {
            general: { allow: true },
            help: { allow: true, requireMention: true },
          },
        },
      },
    },

    slack: {
      enabled: true,
      botToken: "xoxb-REPLACE_ME",
      appToken: "xapp-REPLACE_ME",
      channels: {
        "#general": { allow: true, requireMention: true },
      },
      dm: { enabled: true, allowFrom: ["U123"] },
      slashCommand: {
        enabled: true,
        name: "openclaw",
        sessionPrefix: "slack:slash",
        ephemeral: true,
      },
    },
  },

  // Agent runtime
  agents: {
    defaults: {
      workspace: "~/.openclaw/workspace",
      userTimezone: "America/Chicago",
      model: {
        primary: "anthropic/claude-sonnet-4-6",
        fallbacks: ["anthropic/claude-opus-4-6", "openai/gpt-5.2"],
      },
      imageModel: {
        primary: "openrouter/anthropic/claude-sonnet-4-6",
      },
      models: {
        "anthropic/claude-opus-4-6": { alias: "opus" },
        "anthropic/claude-sonnet-4-6": { alias: "sonnet" },
        "openai/gpt-5.2": { alias: "gpt" },
      },
      thinkingDefault: "low",
      verboseDefault: "off",
      elevatedDefault: "on",
      blockStreamingDefault: "off",
      blockStreamingBreak: "text_end",
      blockStreamingChunk: {
        minChars: 800,
        maxChars: 1200,
        breakPreference: "paragraph",
      },
      blockStreamingCoalesce: {
        idleMs: 1000,
      },
      humanDelay: {
        mode: "natural",
      },
      timeoutSeconds: 600,
      mediaMaxMb: 5,
      typingIntervalSeconds: 5,
      maxConcurrent: 3,
      heartbeat: {
        every: "30m",
        model: "anthropic/claude-sonnet-4-6",
        target: "last",
        directPolicy: "allow", // allow (default) | block
        to: "+15555550123",
        prompt: "HEARTBEAT",
        ackMaxChars: 300,
      },
      memorySearch: {
        provider: "gemini",
        model: "gemini-embedding-001",
        remote: {
          apiKey: "${GEMINI_API_KEY}",
        },
        extraPaths: ["../team-docs", "/srv/shared-notes"],
      },
      sandbox: {
        mode: "non-main",
        perSession: true,
        workspaceRoot: "~/.openclaw/sandboxes",
        docker: {
          image: "openclaw-sandbox:bookworm-slim",
          workdir: "/workspace",
          readOnlyRoot: true,
          tmpfs: ["/tmp", "/var/tmp", "/run"],
          network: "none",
          user: "1000:1000",
        },
        browser: {
          enabled: false,
        },
      },
    },
    list: [
      {
        id: "main",
        default: true,
        thinkingDefault: "high", // per-agent thinking override
        reasoningDefault: "on", // per-agent reasoning visibility
        fastModeDefault: false, // per-agent fast mode
      },
      {
        id: "quick",
        fastModeDefault: true, // this agent always runs fast
        thinkingDefault: "off",
      },
    ],
  },

  tools: {
    allow: ["exec", "process", "read", "write", "edit", "apply_patch"],
    deny: ["browser", "canvas"],
    exec: {
      backgroundMs: 10000,
      timeoutSec: 1800,
      cleanupMs: 1800000,
    },
    elevated: {
      enabled: true,
      allowFrom: {
        whatsapp: ["+15555550123"],
        telegram: ["123456789"],
        discord: ["123456789012345678"],
        slack: ["U123"],
        signal: ["+15555550123"],
        imessage: ["user@example.com"],
        webchat: ["session:demo"],
      },
    },
  },

  // Custom model providers
  models: {
    mode: "merge",
    providers: {
      "custom-proxy": {
        baseUrl: "http://localhost:4000/v1",
        apiKey: "LITELLM_KEY",
        api: "openai-responses",
        authHeader: true,
        headers: { "X-Proxy-Region": "us-west" },
        models: [
          {
            id: "llama-3.1-8b",
            name: "Llama 3.1 8B",
            api: "openai-responses",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 128000,
            maxTokens: 32000,
          },
        ],
      },
    },
  },

  // Cron jobs
  cron: {
    enabled: true,
    store: "~/.openclaw/cron/cron.json",
    maxConcurrentRuns: 2,
    sessionRetention: "24h",
    runLog: {
      maxBytes: "2mb",
      keepLines: 2000,
    },
  },

  // Webhooks
  hooks: {
    enabled: true,
    path: "/hooks",
    token: "shared-secret",
    presets: ["gmail"],
    transformsDir: "~/.openclaw/hooks/transforms",
    mappings: [
      {
        id: "gmail-hook",
        match: { path: "gmail" },
        action: "agent",
        wakeMode: "now",
        name: "Gmail",
        sessionKey: "hook:gmail:{{messages[0].id}}",
        messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}",
        textTemplate: "{{messages[0].snippet}}",
        deliver: true,
        channel: "last",
        to: "+15555550123",
        thinking: "low",
        timeoutSeconds: 300,
        transform: {
          module: "gmail.js",
          export: "transformGmail",
        },
      },
    ],
    gmail: {
      account: "openclaw@gmail.com",
      label: "INBOX",
      topic: "projects/<project-id>/topics/gog-gmail-watch",
      subscription: "gog-gmail-watch-push",
      pushToken: "shared-push-token",
      hookUrl: "http://127.0.0.1:18789/hooks/gmail",
      includeBody: true,
      maxBytes: 20000,
      renewEveryMinutes: 720,
      serve: { bind: "127.0.0.1", port: 8788, path: "/" },
      tailscale: { mode: "funnel", path: "/gmail-pubsub" },
    },
  },

  // Gateway + networking
  gateway: {
    mode: "local",
    port: 18789,
    bind: "loopback",
    controlUi: { enabled: true, basePath: "/openclaw" },
    auth: {
      mode: "token",
      token: "gateway-token",
      allowTailscale: true,
    },
    tailscale: { mode: "serve", resetOnExit: false },
    remote: { url: "ws://gateway.tailnet:18789", token: "remote-token" },
    reload: { mode: "hybrid", debounceMs: 300 },
  },

  skills: {
    allowBundled: ["gemini", "peekaboo"],
    load: {
      extraDirs: ["~/Projects/agent-scripts/skills"],
    },
    install: {
      preferBrew: true,
      nodeManager: "npm",
    },
    entries: {
      "image-lab": {
        enabled: true,
        apiKey: "GEMINI_KEY_HERE",
        env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
      },
      peekaboo: { enabled: true },
    },
  },
}
```

----
url: https://docs.openclaw.ai/cli/sandbox
----

# Sandbox CLI - OpenClaw

Manage sandbox runtimes for isolated agent execution.

## Overview

OpenClaw can run agents in isolated sandbox runtimes for security. The `sandbox` commands help you inspect and recreate those runtimes after updates or configuration changes. Today that usually means:

For `ssh` and OpenShell `remote`, recreate matters more than with Docker:

## Commands

### `openclaw sandbox explain`

Inspect the **effective** sandbox mode/scope/workspace access, sandbox tool policy, and elevated gates (with fix-it config key paths).

### `openclaw sandbox list`

List all sandbox runtimes with their status and configuration.

**Output includes:**

### `openclaw sandbox recreate`

Remove sandbox runtimes to force recreation with updated config.

**Options:**

**Important:** Runtimes are automatically recreated when the agent is next used.

## Use Cases

### After updating a Docker image

### After changing sandbox configuration

### After changing SSH target or SSH auth material

For the core `ssh` backend, recreate deletes the per-scope remote workspace root on the SSH target. The next run seeds it again from the local workspace.

### After changing OpenShell source, policy, or mode

For OpenShell `remote` mode, recreate deletes the canonical remote workspace for that scope. The next run seeds it again from the local workspace.

### After changing setupCommand

### For a specific agent only

## Why is this needed?

**Problem:** When you update sandbox configuration:

**Solution:** Use `openclaw sandbox recreate` to force removal of old runtimes. They’ll be recreated automatically with current settings when next needed. Tip: prefer `openclaw sandbox recreate` over manual backend-specific cleanup. It uses the Gateway’s runtime registry and avoids mismatches when scope/session keys change.

## Configuration

Sandbox settings live in `~/.openclaw/openclaw.json` under `agents.defaults.sandbox` (per-agent overrides go in `agents.list[].sandbox`):

```
{
  "agents": {
    "defaults": {
      "sandbox": {
        "mode": "all", // off, non-main, all
        "backend": "docker", // docker, ssh, openshell
        "scope": "agent", // session, agent, shared
        "docker": {
          "image": "openclaw-sandbox:bookworm-slim",
          "containerPrefix": "openclaw-sbx-",
          // ... more Docker options
        },
        "prune": {
          "idleHours": 24, // Auto-prune after 24h idle
          "maxAgeDays": 7, // Auto-prune after 7 days
        },
      },
    },
  },
}
```

## See Also

----
url: https://docs.openclaw.ai/start/bootstrapping
----

# Agent Bootstrapping - OpenClaw

## [​](#agent-bootstrapping)Agent Bootstrapping

Bootstrapping is the **first‑run** ritual that prepares an agent workspace and collects identity details. It happens after onboarding, when the agent starts for the first time.

## [​](#what-bootstrapping-does)What bootstrapping does

On the first agent run, OpenClaw bootstraps the workspace (default `~/.openclaw/workspace`):

* Seeds `AGENTS.md`, `BOOTSTRAP.md`, `IDENTITY.md`, `USER.md`.
* Runs a short Q\&A ritual (one question at a time).
* Writes identity + preferences to `IDENTITY.md`, `USER.md`, `SOUL.md`.
* Removes `BOOTSTRAP.md` when finished so it only runs once.

## [​](#where-it-runs)Where it runs

Bootstrapping always runs on the **gateway host**. If the macOS app connects to a remote Gateway, the workspace and bootstrapping files live on that remote machine.

When the Gateway runs on another machine, edit workspace files on the gateway host (for example, `user@gateway-host:~/.openclaw/workspace`).

## [​](#related-docs)Related docs

* macOS app onboarding: [Onboarding](https://docs.openclaw.ai/start/onboarding)
* Workspace layout: [Agent workspace](https://docs.openclaw.ai/concepts/agent-workspace)

----
url: https://docs.openclaw.ai/tools/acp-agents
----

# ACP Agents - OpenClaw

[Agent Client Protocol (ACP)](https://agentclientprotocol.com/) sessions let OpenClaw run external coding harnesses (for example Pi, Claude Code, Codex, OpenCode, and Gemini CLI) through an ACP backend plugin. If you ask OpenClaw in plain language to “run this in Codex” or “start Claude Code in a thread”, OpenClaw should route that request to the ACP runtime (not the native sub-agent runtime).

## Fast operator flow

Use this when you want a practical `/acp` runbook:

1. Spawn a session:
2. Work in the bound thread (or target that session key explicitly).
3. Check runtime state:
4. Tune runtime options as needed:
5. Nudge an active session without replacing context:
6. Stop work:

## Quick start for humans

Examples of natural requests:

What OpenClaw should do:

1. Pick `runtime: "acp"`.
2. Resolve the requested harness target (`agentId`, for example `codex`).
3. If thread binding is requested and the current channel supports it, bind the ACP session to the thread.
4. Route follow-up thread messages to that same ACP session until unfocused/closed/expired.

## ACP versus sub-agents

Use ACP when you want an external harness runtime. Use sub-agents when you want OpenClaw-native delegated runs.

| Area          | ACP session                           | Sub-agent run                      |
| ------------- | ------------------------------------- | ---------------------------------- |
| Runtime       | ACP backend plugin (for example acpx) | OpenClaw native sub-agent runtime  |
| Session key   | `agent:<agentId>:acp:<uuid>`          | `agent:<agentId>:subagent:<uuid>`  |
| Main commands | `/acp ...`                            | `/subagents ...`                   |
| Spawn tool    | `sessions_spawn` with `runtime:"acp"` | `sessions_spawn` (default runtime) |

See also [Sub-agents](https://docs.openclaw.ai/tools/subagents).

## Thread-bound sessions (channel-agnostic)

When thread bindings are enabled for a channel adapter, ACP sessions can be bound to threads:

Thread binding support is adapter-specific. If the active channel adapter does not support thread bindings, OpenClaw returns a clear unsupported/unavailable message. Required feature flags for thread-bound ACP:

### Thread supporting channels

## Channel specific settings

For non-ephemeral workflows, configure persistent ACP bindings in top-level `bindings[]` entries.

### Binding model

### Runtime defaults per agent

Use `agents.list[].runtime` to define ACP defaults once per agent:

Override precedence for ACP bound sessions:

1. `bindings[].acp.*`
2. `agents.list[].runtime.acp.*`
3. global ACP defaults (for example `acp.backend`)

Example:

```
{
  agents: {
    list: [
      {
        id: "codex",
        runtime: {
          type: "acp",
          acp: {
            agent: "codex",
            backend: "acpx",
            mode: "persistent",
            cwd: "/workspace/openclaw",
          },
        },
      },
      {
        id: "claude",
        runtime: {
          type: "acp",
          acp: { agent: "claude", backend: "acpx", mode: "persistent" },
        },
      },
    ],
  },
  bindings: [
    {
      type: "acp",
      agentId: "codex",
      match: {
        channel: "discord",
        accountId: "default",
        peer: { kind: "channel", id: "222222222222222222" },
      },
      acp: { label: "codex-main" },
    },
    {
      type: "acp",
      agentId: "claude",
      match: {
        channel: "telegram",
        accountId: "default",
        peer: { kind: "group", id: "-1001234567890:topic:42" },
      },
      acp: { cwd: "/workspace/repo-b" },
    },
    {
      type: "route",
      agentId: "main",
      match: { channel: "discord", accountId: "default" },
    },
    {
      type: "route",
      agentId: "main",
      match: { channel: "telegram", accountId: "default" },
    },
  ],
  channels: {
    discord: {
      guilds: {
        "111111111111111111": {
          channels: {
            "222222222222222222": { requireMention: false },
          },
        },
      },
    },
    telegram: {
      groups: {
        "-1001234567890": {
          topics: { "42": { requireMention: false } },
        },
      },
    },
  },
}
```

Behavior:

## Start ACP sessions (interfaces)

### From `sessions_spawn`

Use `runtime: "acp"` to start an ACP session from an agent turn or tool call.

Notes:

Interface details:

### Resume an existing session

Use `resumeSessionId` to continue a previous ACP session instead of starting fresh. The agent replays its conversation history via `session/load`, so it picks up with full context of what came before.

Common use cases:

Notes:

### Operator smoke test

Use this after a gateway deploy when you want a quick live check that ACP spawn is actually working end-to-end, not just passing unit tests. Recommended gate:

1. Verify the deployed gateway version/commit on the target host.
2. Confirm the deployed source includes the ACP lineage acceptance in `src/gateway/sessions-patch.ts` (`subagent:* or acp:* sessions`).
3. Open a temporary ACPX bridge session to a live agent (for example `razor(main)` on `jpclawhq`).
4. Ask that agent to call `sessions_spawn` with:
5. Verify the agent reports:
6. Clean up the temporary ACPX bridge session.

Example prompt to the live agent:

Notes:

## Sandbox compatibility

ACP sessions currently run on the host runtime, not inside the OpenClaw sandbox. Current limitations:

Use `runtime: "subagent"` when you need sandbox-enforced execution.

### From `/acp` command

Use `/acp spawn` for explicit operator control from chat when needed.

Key flags:

See [Slash Commands](https://docs.openclaw.ai/tools/slash-commands).

## Session target resolution

Most `/acp` actions accept an optional session target (`session-key`, `session-id`, or `session-label`). Resolution order:

1. Explicit target argument (or `--session` for `/acp steer`)
2. Current thread binding (if this conversation/thread is bound to an ACP session)
3. Current requester session fallback

If no target resolves, OpenClaw returns a clear error (`Unable to resolve session target: ...`).

## Spawn thread modes

`/acp spawn` supports `--thread auto|here|off`.

| Mode   | Behavior                                                                                            |
| ------ | --------------------------------------------------------------------------------------------------- |
| `auto` | In an active thread: bind that thread. Outside a thread: create/bind a child thread when supported. |
| `here` | Require current active thread; fail if not in one.                                                  |
| `off`  | No binding. Session starts unbound.                                                                 |

Notes:

## ACP controls

Available command family:

`/acp status` shows the effective runtime options and, when available, both runtime-level and backend-level session identifiers. Some controls depend on backend capabilities. If a backend does not support a control, OpenClaw returns a clear unsupported-control error.

## ACP command cookbook

| Command              | What it does                                              | Example                                                        |
| -------------------- | --------------------------------------------------------- | -------------------------------------------------------------- |
| `/acp spawn`         | Create ACP session; optional thread bind.                 | `/acp spawn codex --mode persistent --thread auto --cwd /repo` |
| `/acp cancel`        | Cancel in-flight turn for target session.                 | `/acp cancel agent:codex:acp:<uuid>`                           |
| `/acp steer`         | Send steer instruction to running session.                | `/acp steer --session support inbox prioritize failing tests`  |
| `/acp close`         | Close session and unbind thread targets.                  | `/acp close`                                                   |
| `/acp status`        | Show backend, mode, state, runtime options, capabilities. | `/acp status`                                                  |
| `/acp set-mode`      | Set runtime mode for target session.                      | `/acp set-mode plan`                                           |
| `/acp set`           | Generic runtime config option write.                      | `/acp set model openai/gpt-5.2`                                |
| `/acp cwd`           | Set runtime working directory override.                   | `/acp cwd /Users/user/Projects/repo`                           |
| `/acp permissions`   | Set approval policy profile.                              | `/acp permissions strict`                                      |
| `/acp timeout`       | Set runtime timeout (seconds).                            | `/acp timeout 120`                                             |
| `/acp model`         | Set runtime model override.                               | `/acp model anthropic/claude-opus-4-6`                         |
| `/acp reset-options` | Remove session runtime option overrides.                  | `/acp reset-options`                                           |
| `/acp sessions`      | List recent ACP sessions from store.                      | `/acp sessions`                                                |
| `/acp doctor`        | Backend health, capabilities, actionable fixes.           | `/acp doctor`                                                  |
| `/acp install`       | Print deterministic install and enable steps.             | `/acp install`                                                 |

`/acp sessions` reads the store for the current bound or requester session. Commands that accept `session-key`, `session-id`, or `session-label` tokens resolve targets through gateway session discovery, including custom per-agent `session.store` roots.

## Runtime options mapping

`/acp` has convenience commands and a generic setter. Equivalent operations:

## acpx harness support (current)

Current acpx built-in harness aliases:

When OpenClaw uses the acpx backend, prefer these values for `agentId` unless your acpx config defines custom agent aliases. Direct acpx CLI usage can also target arbitrary adapters via `--agent <command>`, but that raw escape hatch is an acpx CLI feature (not the normal OpenClaw `agentId` path).

## Required config

Core ACP baseline:

```
{
  acp: {
    enabled: true,
    // Optional. Default is true; set false to pause ACP dispatch while keeping /acp controls.
    dispatch: { enabled: true },
    backend: "acpx",
    defaultAgent: "codex",
    allowedAgents: ["pi", "claude", "codex", "opencode", "gemini", "kimi"],
    maxConcurrentSessions: 8,
    stream: {
      coalesceIdleMs: 300,
      maxChunkChars: 1200,
    },
    runtime: {
      ttlMinutes: 120,
    },
  },
}
```

Thread binding config is channel-adapter specific. Example for Discord:

```
{
  session: {
    threadBindings: {
      enabled: true,
      idleHours: 24,
      maxAgeHours: 0,
    },
  },
  channels: {
    discord: {
      threadBindings: {
        enabled: true,
        spawnAcpSessions: true,
      },
    },
  },
}
```

If thread-bound ACP spawn does not work, verify the adapter feature flag first:

See [Configuration Reference](https://docs.openclaw.ai/gateway/configuration-reference).

## Plugin setup for acpx backend

Install and enable plugin:

Local workspace install during development:

Then verify backend health:

### acpx command and version configuration

By default, the bundled acpx backend plugin (`acpx`) uses the plugin-local pinned binary:

1. Command defaults to `extensions/acpx/node_modules/.bin/acpx`.
2. Expected version defaults to the extension pin.
3. Startup registers ACP backend immediately as not-ready.
4. A background ensure job verifies `acpx --version`.
5. If the plugin-local binary is missing or mismatched, it runs: `npm install --omit=dev --no-save acpx@<pinned>` and re-verifies.

You can override command/version in plugin config:

Notes:

See [Plugins](https://docs.openclaw.ai/tools/plugin).

## Permission configuration

ACP sessions run non-interactively — there is no TTY to approve or deny file-write and shell-exec permission prompts. The acpx plugin provides two config keys that control how permissions are handled:

### `permissionMode`

Controls which operations the harness agent can perform without prompting.

| Value           | Behavior                                                  |
| --------------- | --------------------------------------------------------- |
| `approve-all`   | Auto-approve all file writes and shell commands.          |
| `approve-reads` | Auto-approve reads only; writes and exec require prompts. |
| `deny-all`      | Deny all permission prompts.                              |

### `nonInteractivePermissions`

Controls what happens when a permission prompt would be shown but no interactive TTY is available (which is always the case for ACP sessions).

| Value  | Behavior                                                          |
| ------ | ----------------------------------------------------------------- |
| `fail` | Abort the session with `AcpRuntimeError`. **(default)**           |
| `deny` | Silently deny the permission and continue (graceful degradation). |

### Configuration

Set via plugin config:

Restart the gateway after changing these values.

> **Important:** OpenClaw currently defaults to `permissionMode=approve-reads` and `nonInteractivePermissions=fail`. In non-interactive ACP sessions, any write or exec that triggers a permission prompt can fail with `AcpRuntimeError: Permission prompt unavailable in non-interactive mode`. If you need to restrict permissions, set `nonInteractivePermissions` to `deny` so sessions degrade gracefully instead of crashing.

## Troubleshooting

| Symptom                                                                  | Likely cause                                                                    | Fix                                                                                                                                                               |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ACP runtime backend is not configured`                                  | Backend plugin missing or disabled.                                             | Install and enable backend plugin, then run `/acp doctor`.                                                                                                        |
| `ACP is disabled by policy (acp.enabled=false)`                          | ACP globally disabled.                                                          | Set `acp.enabled=true`.                                                                                                                                           |
| `ACP dispatch is disabled by policy (acp.dispatch.enabled=false)`        | Dispatch from normal thread messages disabled.                                  | Set `acp.dispatch.enabled=true`.                                                                                                                                  |
| `ACP agent "<id>" is not allowed by policy`                              | Agent not in allowlist.                                                         | Use allowed `agentId` or update `acp.allowedAgents`.                                                                                                              |
| `Unable to resolve session target: ...`                                  | Bad key/id/label token.                                                         | Run `/acp sessions`, copy exact key/label, retry.                                                                                                                 |
| `--thread here requires running /acp spawn inside an active ... thread`  | `--thread here` used outside a thread context.                                  | Move to target thread or use `--thread auto`/`off`.                                                                                                               |
| `Only <user-id> can rebind this thread.`                                 | Another user owns thread binding.                                               | Rebind as owner or use a different thread.                                                                                                                        |
| `Thread bindings are unavailable for <channel>.`                         | Adapter lacks thread binding capability.                                        | Use `--thread off` or move to supported adapter/channel.                                                                                                          |
| `Sandboxed sessions cannot spawn ACP sessions ...`                       | ACP runtime is host-side; requester session is sandboxed.                       | Use `runtime="subagent"` from sandboxed sessions, or run ACP spawn from a non-sandboxed session.                                                                  |
| `sessions_spawn sandbox="require" is unsupported for runtime="acp" ...`  | `sandbox="require"` requested for ACP runtime.                                  | Use `runtime="subagent"` for required sandboxing, or use ACP with `sandbox="inherit"` from a non-sandboxed session.                                               |
| Missing ACP metadata for bound session                                   | Stale/deleted ACP session metadata.                                             | Recreate with `/acp spawn`, then rebind/focus thread.                                                                                                             |
| `AcpRuntimeError: Permission prompt unavailable in non-interactive mode` | `permissionMode` blocks writes/exec in non-interactive ACP session.             | Set `plugins.entries.acpx.config.permissionMode` to `approve-all` and restart gateway. See [Permission configuration](#permission-configuration).                 |
| ACP session fails early with little output                               | Permission prompts are blocked by `permissionMode`/`nonInteractivePermissions`. | Check gateway logs for `AcpRuntimeError`. For full permissions, set `permissionMode=approve-all`; for graceful degradation, set `nonInteractivePermissions=deny`. |
| ACP session stalls indefinitely after completing work                    | Harness process finished but ACP session did not report completion.             | Monitor with `ps aux \| grep acpx`; kill stale processes manually.                                                                                                |

----
url: https://docs.openclaw.ai/channels/imessage
----

# iMessage - OpenClaw

## [​](#imessage-legacy-imsg)iMessage (legacy: imsg)

For new iMessage deployments, use [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles).The `imsg` integration is legacy and may be removed in a future release.

Status: legacy external CLI integration. Gateway spawns `imsg rpc` and communicates over JSON-RPC on stdio (no separate daemon/port).

## BlueBubbles (recommended)

Preferred iMessage path for new setups.

## Pairing

iMessage DMs default to pairing mode.

## Configuration reference

Full iMessage field reference.

## [​](#quick-setup)Quick setup

* Local Mac (fast path)

* Remote Mac over SSH

1

[](#)

Install and verify imsg

```
brew install steipete/tap/imsg
imsg rpc --help
```

2

[](#)

Configure OpenClaw

```
{
  channels: {
    imessage: {
      enabled: true,
      cliPath: "/usr/local/bin/imsg",
      dbPath: "/Users/<you>/Library/Messages/chat.db",
    },
  },
}
```

3

[](#)

Start gateway

```
openclaw gateway
```

4

[](#)

Approve first DM pairing (default dmPolicy)

```
openclaw pairing list imessage
openclaw pairing approve imessage <CODE>
```

Pairing requests expire after 1 hour.

OpenClaw only requires a stdio-compatible `cliPath`, so you can point `cliPath` at a wrapper script that SSHes to a remote Mac and runs `imsg`.

```
#!/usr/bin/env bash
exec ssh -T gateway-host imsg "$@"
```

Recommended config when attachments are enabled:

```
{
  channels: {
    imessage: {
      enabled: true,
      cliPath: "~/.openclaw/scripts/imsg-ssh",
      remoteHost: "user@gateway-host", // used for SCP attachment fetches
      includeAttachments: true,
      // Optional: override allowed attachment roots.
      // Defaults include /Users/*/Library/Messages/Attachments
      attachmentRoots: ["/Users/*/Library/Messages/Attachments"],
      remoteAttachmentRoots: ["/Users/*/Library/Messages/Attachments"],
    },
  },
}
```

If `remoteHost` is not set, OpenClaw attempts to auto-detect it by parsing the SSH wrapper script. `remoteHost` must be `host` or `user@host` (no spaces or SSH options). OpenClaw uses strict host-key checking for SCP, so the relay host key must already exist in `~/.ssh/known_hosts`. Attachment paths are validated against allowed roots (`attachmentRoots` / `remoteAttachmentRoots`).

## [​](#requirements-and-permissions-macos)Requirements and permissions (macOS)

* Messages must be signed in on the Mac running `imsg`.
* Full Disk Access is required for the process context running OpenClaw/`imsg` (Messages DB access).
* Automation permission is required to send messages through Messages.app.

Permissions are granted per process context. If gateway runs headless (LaunchAgent/SSH), run a one-time interactive command in that same context to trigger prompts:

```
imsg chats --limit 1
# or
imsg send <handle> "test"
```

## [​](#access-control-and-routing)Access control and routing

* DM policy

* Group policy + mentions

* Sessions and deterministic replies

`channels.imessage.dmPolicy` controls direct messages:

* `pairing` (default)
* `allowlist`
* `open` (requires `allowFrom` to include `"*"`)
* `disabled`

Allowlist field: `channels.imessage.allowFrom`.Allowlist entries can be handles or chat targets (`chat_id:*`, `chat_guid:*`, `chat_identifier:*`).

`channels.imessage.groupPolicy` controls group handling:

* `allowlist` (default when configured)
* `open`
* `disabled`

Group sender allowlist: `channels.imessage.groupAllowFrom`.Runtime fallback: if `groupAllowFrom` is unset, iMessage group sender checks fall back to `allowFrom` when available. Runtime note: if `channels.imessage` is completely missing, runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).Mention gating for groups:

* iMessage has no native mention metadata
* mention detection uses regex patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`)
* with no configured patterns, mention gating cannot be enforced

Control commands from authorized senders can bypass mention gating in groups.

* DMs use direct routing; groups use group routing.
* With default `session.dmScope=main`, iMessage DMs collapse into the agent main session.
* Group sessions are isolated (`agent:<agentId>:imessage:group:<chat_id>`).
* Replies route back to iMessage using originating channel/target metadata.

Group-ish thread behavior:Some multi-participant iMessage threads can arrive with `is_group=false`. If that `chat_id` is explicitly configured under `channels.imessage.groups`, OpenClaw treats it as group traffic (group gating + group session isolation).

## [​](#deployment-patterns)Deployment patterns

Dedicated bot macOS user (separate iMessage identity)

Use a dedicated Apple ID and macOS user so bot traffic is isolated from your personal Messages profile.Typical flow:

1. Create/sign in a dedicated macOS user.
2. Sign into Messages with the bot Apple ID in that user.
3. Install `imsg` in that user.
4. Create SSH wrapper so OpenClaw can run `imsg` in that user context.
5. Point `channels.imessage.accounts.<id>.cliPath` and `.dbPath` to that user profile.

First run may require GUI approvals (Automation + Full Disk Access) in that bot user session.

Remote Mac over Tailscale (example)

Common topology:

* gateway runs on Linux/VM
* iMessage + `imsg` runs on a Mac in your tailnet
* `cliPath` wrapper uses SSH to run `imsg`
* `remoteHost` enables SCP attachment fetches

Example:

```
{
  channels: {
    imessage: {
      enabled: true,
      cliPath: "~/.openclaw/scripts/imsg-ssh",
      remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
      includeAttachments: true,
      dbPath: "/Users/bot/Library/Messages/chat.db",
    },
  },
}
```

```
#!/usr/bin/env bash
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"
```

Use SSH keys so both SSH and SCP are non-interactive. Ensure the host key is trusted first (for example `ssh bot@mac-mini.tailnet-1234.ts.net`) so `known_hosts` is populated.

Multi-account pattern

iMessage supports per-account config under `channels.imessage.accounts`.Each account can override fields such as `cliPath`, `dbPath`, `allowFrom`, `groupPolicy`, `mediaMaxMb`, history settings, and attachment root allowlists.

## [​](#media-chunking-and-delivery-targets)Media, chunking, and delivery targets

Attachments and media

* inbound attachment ingestion is optional: `channels.imessage.includeAttachments`

* remote attachment paths can be fetched via SCP when `remoteHost` is set

* attachment paths must match allowed roots:

  * `channels.imessage.attachmentRoots` (local)
  * `channels.imessage.remoteAttachmentRoots` (remote SCP mode)
  * default root pattern: `/Users/*/Library/Messages/Attachments`

* SCP uses strict host-key checking (`StrictHostKeyChecking=yes`)

* outbound media size uses `channels.imessage.mediaMaxMb` (default 16 MB)

Outbound chunking

* text chunk limit: `channels.imessage.textChunkLimit` (default 4000)

* chunk mode: `channels.imessage.chunkMode`

  * `length` (default)
  * `newline` (paragraph-first splitting)

Addressing formats

Preferred explicit targets:

* `chat_id:123` (recommended for stable routing)
* `chat_guid:...`
* `chat_identifier:...`

Handle targets are also supported:

* `imessage:+1555...`
* `sms:+1555...`
* `user@example.com`

```
imsg chats --limit 20
```

## [​](#config-writes)Config writes

iMessage allows channel-initiated config writes by default (for `/config set|unset` when `commands.config: true`). Disable:

```
{
  channels: {
    imessage: {
      configWrites: false,
    },
  },
}
```

## [​](#troubleshooting)Troubleshooting

imsg not found or RPC unsupported

Validate the binary and RPC support:

```
imsg rpc --help
openclaw channels status --probe
```

If probe reports RPC unsupported, update `imsg`.

DMs are ignored

Check:

* `channels.imessage.dmPolicy`
* `channels.imessage.allowFrom`
* pairing approvals (`openclaw pairing list imessage`)

Group messages are ignored

Check:

* `channels.imessage.groupPolicy`
* `channels.imessage.groupAllowFrom`
* `channels.imessage.groups` allowlist behavior
* mention pattern configuration (`agents.list[].groupChat.mentionPatterns`)

Remote attachments fail

Check:

* `channels.imessage.remoteHost`
* `channels.imessage.remoteAttachmentRoots`
* SSH/SCP key auth from the gateway host
* host key exists in `~/.ssh/known_hosts` on the gateway host
* remote path readability on the Mac running Messages

macOS permission prompts were missed

Re-run in an interactive GUI terminal in the same user/session context and approve prompts:

```
imsg chats --limit 1
imsg send <handle> "test"
```

Confirm Full Disk Access + Automation are granted for the process context that runs OpenClaw/`imsg`.

## [​](#configuration-reference-pointers)Configuration reference pointers

* [Configuration reference - iMessage](https://docs.openclaw.ai/gateway/configuration-reference#imessage)
* [Gateway configuration](https://docs.openclaw.ai/gateway/configuration)
* [Pairing](https://docs.openclaw.ai/channels/pairing)
* [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles)

----
url: https://docs.openclaw.ai/cli/health
----

# health - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw health](#openclaw-health)

## [​](#openclaw-health)`openclaw health`

Fetch health from the running Gateway.

```
openclaw health
openclaw health --json
openclaw health --verbose
```

Notes:

* `--verbose` runs live probes and prints per-account timings when multiple accounts are configured.
* Output includes per-agent session stores when multiple agents are configured.

[gateway](https://docs.openclaw.ai/cli/gateway)[logs](https://docs.openclaw.ai/cli/logs)

----
url: https://docs.openclaw.ai/gateway/sandbox-vs-tool-policy-vs-elevated
----

# Sandbox vs Tool Policy vs Elevated - OpenClaw

OpenClaw has three related (but different) controls:

1. **Sandbox** (`agents.defaults.sandbox.*` / `agents.list[].sandbox.*`) decides **where tools run** (Docker vs host).
2. **Tool policy** (`tools.*`, `tools.sandbox.tools.*`, `agents.list[].tools.*`) decides **which tools are available/allowed**.
3. **Elevated** (`tools.elevated.*`, `agents.list[].tools.elevated.*`) is an **exec-only escape hatch** to run on the host when you’re sandboxed.

## Quick debug

Use the inspector to see what OpenClaw is *actually* doing:

It prints:

## Sandbox: where tools run

Sandboxing is controlled by `agents.defaults.sandbox.mode`:

See [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing) for the full matrix (scope, workspace mounts, images).

### Bind mounts (security quick check)

## Tool policy: which tools exist/are callable

Two layers matter:

Rules of thumb:

### Tool groups (shorthands)

Tool policies (global, agent, sandbox) support `group:*` entries that expand to multiple tools:

Available groups:

* `group:runtime`: `exec`, `bash`, `process`
* `group:fs`: `read`, `write`, `edit`, `apply_patch`
* `group:sessions`: `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status`
* `group:memory`: `memory_search`, `memory_get`
* `group:ui`: `browser`, `canvas`
* `group:automation`: `cron`, `gateway`
* `group:messaging`: `message`
* `group:nodes`: `nodes`
* `group:openclaw`: all built-in OpenClaw tools (excludes provider plugins)

## Elevated: exec-only “run on host”

Elevated does **not** grant extra tools; it only affects `exec`.

Gates:

See [Elevated Mode](https://docs.openclaw.ai/tools/elevated).

## Common “sandbox jail” fixes

### ”Tool X blocked by sandbox tool policy”

Fix-it keys (pick one):

### “I thought this was main, why is it sandboxed?”

In `"non-main"` mode, group/channel keys are *not* main. Use the main session key (shown by `sandbox explain`) or switch mode to `"off"`.

## See also

----
url: https://docs.openclaw.ai/concepts/memory
----

# Memory - OpenClaw

OpenClaw memory is **plain Markdown in the agent workspace**. The files are the source of truth; the model only “remembers” what gets written to disk. Memory search tools are provided by the active memory plugin (default: `memory-core`). Disable memory plugins with `plugins.slots.memory = "none"`.

## Memory files (Markdown)

The default workspace layout uses two memory layers:

These files live under the workspace (`agents.defaults.workspace`, default `~/.openclaw/workspace`). See [Agent workspace](https://docs.openclaw.ai/concepts/agent-workspace) for the full layout.

## Memory tools

OpenClaw exposes two agent-facing tools for these Markdown files:

`memory_get` now **degrades gracefully when a file doesn’t exist** (for example, today’s daily log before the first write). Both the builtin manager and the QMD backend return `{ text: "", path }` instead of throwing `ENOENT`, so agents can handle “nothing recorded yet” and continue their workflow without wrapping the tool call in try/catch logic.

## When to write memory

## Automatic memory flush (pre-compaction ping)

When a session is **close to auto-compaction**, OpenClaw triggers a **silent, agentic turn** that reminds the model to write durable memory **before** the context is compacted. The default prompts explicitly say the model *may reply*, but usually `NO_REPLY` is the correct response so the user never sees this turn. This is controlled by `agents.defaults.compaction.memoryFlush`:

Details:

For the full compaction lifecycle, see [Session management + compaction](https://docs.openclaw.ai/reference/session-management-compaction).

## Vector memory search

OpenClaw can build a small vector index over `MEMORY.md` and `memory/*.md` so semantic queries can find related notes even when wording differs. Hybrid search (BM25 + vector) is available for combining semantic matching with exact keyword lookups. Memory search supports multiple embedding providers (OpenAI, Gemini, Voyage, Mistral, Ollama, and local GGUF models), an optional QMD sidecar backend for advanced retrieval, and post-processing features like MMR diversity re-ranking and temporal decay. For the full configuration reference — including embedding provider setup, QMD backend, hybrid search tuning, multimodal memory, and all config knobs — see [Memory configuration reference](https://docs.openclaw.ai/reference/memory-config).

----
url: https://docs.openclaw.ai/install/northflank
----

# Northflank - OpenClaw

Deploy OpenClaw on Northflank with a one-click template and access it through the web Control UI. This is the easiest “no terminal on the server” path: Northflank runs the Gateway for you.

## [​](#how-to-get-started)How to get started

1. Click [Deploy OpenClaw](https://northflank.com/stacks/deploy-openclaw) to open the template.
2. Create an [account on Northflank](https://app.northflank.com/signup) if you don’t already have one.
3. Click **Deploy OpenClaw now**.
4. Set the required environment variable: `OPENCLAW_GATEWAY_TOKEN` (use a strong random value).
5. Click **Deploy stack** to build and run the OpenClaw template.
6. Wait for the deployment to complete, then click **View resources**.
7. Open the OpenClaw service.
8. Open the public OpenClaw URL at `/openclaw` and connect using your `OPENCLAW_GATEWAY_TOKEN`.

## [​](#what-you-get)What you get

* Hosted OpenClaw Gateway + Control UI
* Persistent storage via Northflank Volume (`/data`) so config/credentials/workspace survive redeploys

## [​](#connect-a-channel)Connect a channel

Use the Control UI at `/openclaw` or run `openclaw onboard` via SSH for channel setup instructions:

* [Telegram](https://docs.openclaw.ai/channels/telegram) (fastest — just a bot token)
* [Discord](https://docs.openclaw.ai/channels/discord)
* [All channels](https://docs.openclaw.ai/channels)

## [​](#next-steps)Next steps

* Set up messaging channels: [Channels](https://docs.openclaw.ai/channels)
* Configure the Gateway: [Gateway configuration](https://docs.openclaw.ai/gateway/configuration)
* Keep OpenClaw up to date: [Updating](https://docs.openclaw.ai/install/updating)

----
url: https://docs.openclaw.ai/reference/templates/AGENTS
----

# AGENTS.md Template - OpenClaw

## AGENTS.md - Your Workspace

This folder is home. Treat it that way.

## First Run

If `BOOTSTRAP.md` exists, that’s your birth certificate. Follow it, figure out who you are, then delete it. You won’t need it again.

## Session Startup

Before doing anything else:

1. Read `SOUL.md` — this is who you are
2. Read `USER.md` — this is who you’re helping
3. Read `memory/YYYY-MM-DD.md` (today + yesterday) for recent context
4. **If in MAIN SESSION** (direct chat with your human): Also read `MEMORY.md`

Don’t ask permission. Just do it.

## Memory

You wake up fresh each session. These files are your continuity:

Capture what matters. Decisions, context, things to remember. Skip the secrets unless asked to keep them.

### 🧠 MEMORY.md - Your Long-Term Memory

* **ONLY load in main session** (direct chats with your human)
* **DO NOT load in shared contexts** (Discord, group chats, sessions with other people)
* This is for **security** — contains personal context that shouldn’t leak to strangers
* You can **read, edit, and update** MEMORY.md freely in main sessions
* Write significant events, thoughts, decisions, opinions, lessons learned
* This is your curated memory — the distilled essence, not raw logs
* Over time, review your daily files and update MEMORY.md with what’s worth keeping

### 📝 Write It Down - No “Mental Notes”!

## Red Lines

## External vs Internal

**Safe to do freely:**

**Ask first:**

## Group Chats

You have access to your human’s stuff. That doesn’t mean you *share* their stuff. In groups, you’re a participant — not their voice, not their proxy. Think before you speak.

### 💬 Know When to Speak!

In group chats where you receive every message, be **smart about when to contribute**: **Respond when:**

**Stay silent (HEARTBEAT\_OK) when:**

**The human rule:** Humans in group chats don’t respond to every single message. Neither should you. Quality > quantity. If you wouldn’t send it in a real group chat with friends, don’t send it. **Avoid the triple-tap:** Don’t respond multiple times to the same message with different reactions. One thoughtful response beats three fragments. Participate, don’t dominate.

### 😊 React Like a Human!

On platforms that support reactions (Discord, Slack), use emoji reactions naturally: **React when:**

**Why it matters:** Reactions are lightweight social signals. Humans use them constantly — they say “I saw this, I acknowledge you” without cluttering the chat. You should too. **Don’t overdo it:** One reaction per message max. Pick the one that fits best.

## Tools

Skills provide your tools. When you need one, check its `SKILL.md`. Keep local notes (camera names, SSH details, voice preferences) in `TOOLS.md`. **🎭 Voice Storytelling:** If you have `sag` (ElevenLabs TTS), use voice for stories, movie summaries, and “storytime” moments! Way more engaging than walls of text. Surprise people with funny voices. **📝 Platform Formatting:**

## 💓 Heartbeats - Be Proactive!

When you receive a heartbeat poll (message matches the configured heartbeat prompt), don’t just reply `HEARTBEAT_OK` every time. Use heartbeats productively! Default heartbeat prompt: `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.` You are free to edit `HEARTBEAT.md` with a short checklist or reminders. Keep it small to limit token burn.

### Heartbeat vs Cron: When to Use Each

**Use heartbeat when:**

**Use cron when:**

**Tip:** Batch similar periodic checks into `HEARTBEAT.md` instead of creating multiple cron jobs. Use cron for precise schedules and standalone tasks. **Things to check (rotate through these, 2-4 times per day):**

**Track your checks** in `memory/heartbeat-state.json`:

**When to reach out:**

**When to stay quiet (HEARTBEAT\_OK):**

**Proactive work you can do without asking:**

### 🔄 Memory Maintenance (During Heartbeats)

Periodically (every few days), use a heartbeat to:

1. Read through recent `memory/YYYY-MM-DD.md` files
2. Identify significant events, lessons, or insights worth keeping long-term
3. Update `MEMORY.md` with distilled learnings
4. Remove outdated info from MEMORY.md that’s no longer relevant

Think of it like a human reviewing their journal and updating their mental model. Daily files are raw notes; MEMORY.md is curated wisdom. The goal: Be helpful without being annoying. Check in a few times a day, do useful background work, but respect quiet time.

## Make It Yours

This is a starting point. Add your own conventions, style, and rules as you figure out what works.

----
url: https://docs.openclaw.ai/cli/acp
----

# acp - OpenClaw

Run the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) bridge that talks to an OpenClaw Gateway. This command speaks ACP over stdio for IDEs and forwards prompts to the Gateway over WebSocket. It keeps ACP sessions mapped to Gateway session keys. `openclaw acp` is a Gateway-backed ACP bridge, not a full ACP-native editor runtime. It focuses on session routing, prompt delivery, and basic streaming updates.

## Compatibility Matrix

| ACP area                                                              | Status      | Notes                                                                                                                                                                                                                                            |
| --------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `initialize`, `newSession`, `prompt`, `cancel`                        | Implemented | Core bridge flow over stdio to Gateway chat/send + abort.                                                                                                                                                                                        |
| `listSessions`, slash commands                                        | Implemented | Session list works against Gateway session state; commands are advertised via `available_commands_update`.                                                                                                                                       |
| `loadSession`                                                         | Partial     | Rebinds the ACP session to a Gateway session key and replays stored user/assistant text history. Tool/system history is not reconstructed yet.                                                                                                   |
| Prompt content (`text`, embedded `resource`, images)                  | Partial     | Text/resources are flattened into chat input; images become Gateway attachments.                                                                                                                                                                 |
| Session modes                                                         | Partial     | `session/set_mode` is supported and the bridge exposes initial Gateway-backed session controls for thought level, tool verbosity, reasoning, usage detail, and elevated actions. Broader ACP-native mode/config surfaces are still out of scope. |
| Session info and usage updates                                        | Partial     | The bridge emits `session_info_update` and best-effort `usage_update` notifications from cached Gateway session snapshots. Usage is approximate and only sent when Gateway token totals are marked fresh.                                        |
| Tool streaming                                                        | Partial     | `tool_call` / `tool_call_update` events include raw I/O, text content, and best-effort file locations when Gateway tool args/results expose them. Embedded terminals and richer diff-native output are still not exposed.                        |
| Per-session MCP servers (`mcpServers`)                                | Unsupported | Bridge mode rejects per-session MCP server requests. Configure MCP on the OpenClaw gateway or agent instead.                                                                                                                                     |
| Client filesystem methods (`fs/read_text_file`, `fs/write_text_file`) | Unsupported | The bridge does not call ACP client filesystem methods.                                                                                                                                                                                          |
| Client terminal methods (`terminal/*`)                                | Unsupported | The bridge does not create ACP client terminals or stream terminal ids through tool calls.                                                                                                                                                       |
| Session plans / thought streaming                                     | Unsupported | The bridge currently emits output text and tool status, not ACP plan or thought updates.                                                                                                                                                         |

## Known Limitations

* `loadSession` replays stored user and assistant text history, but it does not reconstruct historic tool calls, system notices, or richer ACP-native event types.
* If multiple ACP clients share the same Gateway session key, event and cancel routing are best-effort rather than strictly isolated per client. Prefer the default isolated `acp:<uuid>` sessions when you need clean editor-local turns.
* Gateway stop states are translated into ACP stop reasons, but that mapping is less expressive than a fully ACP-native runtime.
* Initial session controls currently surface a focused subset of Gateway knobs: thought level, tool verbosity, reasoning, usage detail, and elevated actions. Model selection and exec-host controls are not yet exposed as ACP config options.
* `session_info_update` and `usage_update` are derived from Gateway session snapshots, not live ACP-native runtime accounting. Usage is approximate, carries no cost data, and is only emitted when the Gateway marks total token data as fresh.
* Tool follow-along data is best-effort. The bridge can surface file paths that appear in known tool args/results, but it does not yet emit ACP terminals or structured file diffs.

## Usage

## ACP client (debug)

Use the built-in ACP client to sanity-check the bridge without an IDE. It spawns the ACP bridge and lets you type prompts interactively.

Permission model (client debug mode):

## How to use this

Use ACP when an IDE (or other client) speaks Agent Client Protocol and you want it to drive an OpenClaw Gateway session.

1. Ensure the Gateway is running (local or remote).
2. Configure the Gateway target (config or flags).
3. Point your IDE to run `openclaw acp` over stdio.

Example config (persisted):

Example direct run (no config write):

## Selecting agents

ACP does not pick agents directly. It routes by the Gateway session key. Use agent-scoped session keys to target a specific agent:

Each ACP session maps to a single Gateway session key. One agent can have many sessions; ACP defaults to an isolated `acp:<uuid>` session unless you override the key or label. Per-session `mcpServers` are not supported in bridge mode. If an ACP client sends them during `newSession` or `loadSession`, the bridge returns a clear error instead of silently ignoring them.

## Use from `acpx` (Codex, Claude, other ACP clients)

If you want a coding agent such as Codex or Claude Code to talk to your OpenClaw bot over ACP, use `acpx` with its built-in `openclaw` target. Typical flow:

1. Run the Gateway and make sure the ACP bridge can reach it.
2. Point `acpx openclaw` at `openclaw acp`.
3. Target the OpenClaw session key you want the coding agent to use.

Examples:

If you want `acpx openclaw` to target a specific Gateway and session key every time, override the `openclaw` agent command in `~/.acpx/config.json`:

For a repo-local OpenClaw checkout, use the direct CLI entrypoint instead of the dev runner so the ACP stream stays clean. For example:

This is the easiest way to let Codex, Claude Code, or another ACP-aware client pull contextual information from an OpenClaw agent without scraping a terminal.

## Zed editor setup

Add a custom ACP agent in `~/.config/zed/settings.json` (or use Zed’s Settings UI):

To target a specific Gateway or agent:

In Zed, open the Agent panel and select “OpenClaw ACP” to start a thread.

## Session mapping

By default, ACP sessions get an isolated Gateway session key with an `acp:` prefix. To reuse a known session, pass a session key or label:

If your ACP client supports metadata, you can override per session:

Learn more about session keys at [/concepts/session](https://docs.openclaw.ai/concepts/session).

## Options

Security note:

### `acp client` options

----
url: https://docs.openclaw.ai/install/uninstall
----

# Uninstall - OpenClaw

## [​](#uninstall)Uninstall

Two paths:

* **Easy path** if `openclaw` is still installed.
* **Manual service removal** if the CLI is gone but the service is still running.

## [​](#easy-path-cli-still-installed)Easy path (CLI still installed)

Recommended: use the built-in uninstaller:

```
openclaw uninstall
```

Non-interactive (automation / npx):

```
openclaw uninstall --all --yes --non-interactive
npx -y openclaw uninstall --all --yes --non-interactive
```

Manual steps (same result):

1. Stop the gateway service:

```
openclaw gateway stop
```

2. Uninstall the gateway service (launchd/systemd/schtasks):

```
openclaw gateway uninstall
```

3. Delete state + config:

```
rm -rf "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
```

If you set `OPENCLAW_CONFIG_PATH` to a custom location outside the state dir, delete that file too.

4. Delete your workspace (optional, removes agent files):

```
rm -rf ~/.openclaw/workspace
```

5. Remove the CLI install (pick the one you used):

```
npm rm -g openclaw
pnpm remove -g openclaw
bun remove -g openclaw
```

6. If you installed the macOS app:

```
rm -rf /Applications/OpenClaw.app
```

Notes:

* If you used profiles (`--profile` / `OPENCLAW_PROFILE`), repeat step 3 for each state dir (defaults are `~/.openclaw-<profile>`).
* In remote mode, the state dir lives on the **gateway host**, so run steps 1-4 there too.

## [​](#manual-service-removal-cli-not-installed)Manual service removal (CLI not installed)

Use this if the gateway service keeps running but `openclaw` is missing.

### [​](#macos-launchd)macOS (launchd)

Default label is `ai.openclaw.gateway` (or `ai.openclaw.<profile>`; legacy `com.openclaw.*` may still exist):

```
launchctl bootout gui/$UID/ai.openclaw.gateway
rm -f ~/Library/LaunchAgents/ai.openclaw.gateway.plist
```

If you used a profile, replace the label and plist name with `ai.openclaw.<profile>`. Remove any legacy `com.openclaw.*` plists if present.

### [​](#linux-systemd-user-unit)Linux (systemd user unit)

Default unit name is `openclaw-gateway.service` (or `openclaw-gateway-<profile>.service`):

```
systemctl --user disable --now openclaw-gateway.service
rm -f ~/.config/systemd/user/openclaw-gateway.service
systemctl --user daemon-reload
```

### [​](#windows-scheduled-task)Windows (Scheduled Task)

Default task name is `OpenClaw Gateway` (or `OpenClaw Gateway (<profile>)`). The task script lives under your state dir.

```
schtasks /Delete /F /TN "OpenClaw Gateway"
Remove-Item -Force "$env:USERPROFILE\.openclaw\gateway.cmd"
```

If you used a profile, delete the matching task name and `~\.openclaw-<profile>\gateway.cmd`.

## [​](#normal-install-vs-source-checkout)Normal install vs source checkout

### [​](#normal-install-install-sh-/-npm-/-pnpm-/-bun)Normal install (install.sh / npm / pnpm / bun)

If you used `https://openclaw.ai/install.sh` or `install.ps1`, the CLI was installed with `npm install -g openclaw@latest`. Remove it with `npm rm -g openclaw` (or `pnpm remove -g` / `bun remove -g` if you installed that way).

### [​](#source-checkout-git-clone)Source checkout (git clone)

If you run from a repo checkout (`git clone` + `openclaw ...` / `bun run openclaw ...`):

1. Uninstall the gateway service **before** deleting the repo (use the easy path above or manual service removal).
2. Delete the repo directory.
3. Remove state + workspace as shown above.

----
url: https://docs.openclaw.ai/install/development-channels
----

# Release Channels - OpenClaw

## Development channels

OpenClaw ships three update channels:

We ship builds to **beta**, test them, then **promote a vetted build to `latest`** without changing the version number — dist-tags are the source of truth for npm installs.

## Switching channels

`--channel` persists your choice in config (`update.channel`) and aligns the install method:

Tip: if you want stable + dev in parallel, keep two clones and point your gateway at the stable one.

## One-off version or tag targeting

Use `--tag` to target a specific dist-tag, version, or package spec for a single update **without** changing your persisted channel:

Notes:

## Dry run

Preview what `openclaw update` would do without making changes:

The dry run shows the effective channel, target version, planned actions, and whether a downgrade confirmation would be required.

## Plugins and channels

When you switch channels with `openclaw update`, OpenClaw also syncs plugin sources:

## Checking current status

Shows the active channel, install kind (git or package), current version, and source (config, git tag, git branch, or default).

## Tagging best practices

## macOS app availability

Beta and dev builds may **not** include a macOS app release. That is OK:

----
url: https://docs.openclaw.ai/platforms/mac/webchat
----

# WebChat (macOS) - OpenClaw

* [WebChat (macOS app)](#webchat-macos-app)
* [Launch & debugging](#launch-%26-debugging)
* [How it is wired](#how-it-is-wired)
* [Security surface](#security-surface)
* [Known limitations](#known-limitations)

## [​](#webchat-macos-app)WebChat (macOS app)

The macOS menu bar app embeds the WebChat UI as a native SwiftUI view. It connects to the Gateway and defaults to the **main session** for the selected agent (with a session switcher for other sessions).

* **Local mode**: connects directly to the local Gateway WebSocket.
* **Remote mode**: forwards the Gateway control port over SSH and uses that tunnel as the data plane.

## [​](#launch-&-debugging)Launch & debugging

* Manual: Lobster menu → “Open Chat”.
* Auto‑open for testing:
  ```
  dist/OpenClaw.app/Contents/MacOS/OpenClaw --webchat
  ```
* Logs: `./scripts/clawlog.sh` (subsystem `ai.openclaw`, category `WebChatSwiftUI`).

## [​](#how-it-is-wired)How it is wired

* Data plane: Gateway WS methods `chat.history`, `chat.send`, `chat.abort`, `chat.inject` and events `chat`, `agent`, `presence`, `tick`, `health`.
* Session: defaults to the primary session (`main`, or `global` when scope is global). The UI can switch between sessions.
* Onboarding uses a dedicated session to keep first‑run setup separate.

## [​](#security-surface)Security surface

* Remote mode forwards only the Gateway WebSocket control port over SSH.

## [​](#known-limitations)Known limitations

* The UI is optimized for chat sessions (not a full browser sandbox).

[Voice Overlay](https://docs.openclaw.ai/platforms/mac/voice-overlay)[Canvas](https://docs.openclaw.ai/platforms/mac/canvas)

----
url: https://docs.openclaw.ai/tools/skills
----

# Skills - OpenClaw

OpenClaw uses **[AgentSkills](https://agentskills.io/)-compatible** skill folders to teach the agent how to use tools. Each skill is a directory containing a `SKILL.md` with YAML frontmatter and instructions. OpenClaw loads **bundled skills** plus optional local overrides, and filters them at load time based on environment, config, and binary presence.

## Locations and precedence

Skills are loaded from **three** places:

1. **Bundled skills**: shipped with the install (npm package or OpenClaw\.app)
2. **Managed/local skills**: `~/.openclaw/skills`
3. **Workspace skills**: `<workspace>/skills`

If a skill name conflicts, precedence is: `<workspace>/skills` (highest) → `~/.openclaw/skills` → bundled skills (lowest) Additionally, you can configure extra skill folders (lowest precedence) via `skills.load.extraDirs` in `~/.openclaw/openclaw.json`.

In **multi-agent** setups, each agent has its own workspace. That means:

If the same skill name exists in more than one place, the usual precedence applies: workspace wins, then managed/local, then bundled.

## Plugins + skills

Plugins can ship their own skills by listing `skills` directories in `openclaw.plugin.json` (paths relative to the plugin root). Plugin skills load when the plugin is enabled and participate in the normal skill precedence rules. You can gate them via `metadata.openclaw.requires.config` on the plugin’s config entry. See [Plugins](https://docs.openclaw.ai/tools/plugin) for discovery/config and [Tools](https://docs.openclaw.ai/tools) for the tool surface those skills teach.

## ClawHub (install + sync)

ClawHub is the public skills registry for OpenClaw. Browse at [https://clawhub.com](https://clawhub.com/). Use native `openclaw skills` commands to discover/install/update skills, or the separate `clawhub` CLI when you need publish/sync workflows. Full guide: [ClawHub](https://docs.openclaw.ai/tools/clawhub). Common flows:

Native `openclaw skills install` installs into the active workspace `skills/` directory. The separate `clawhub` CLI also installs into `./skills` under your current working directory (or falls back to the configured OpenClaw workspace). OpenClaw picks that up as `<workspace>/skills` on the next session.

## Security notes

## Format (AgentSkills + Pi-compatible)

`SKILL.md` must include at least:

Notes:

## Gating (load-time filters)

OpenClaw **filters skills at load time** using `metadata` (single-line JSON):

Fields under `metadata.openclaw`:

Note on sandboxing:

Installer example:

```
---
name: gemini
description: Use Gemini CLI for coding assistance and Google search lookups.
metadata:
  {
    "openclaw":
      {
        "emoji": "♊️",
        "requires": { "bins": ["gemini"] },
        "install":
          [
            {
              "id": "brew",
              "kind": "brew",
              "formula": "gemini-cli",
              "bins": ["gemini"],
              "label": "Install Gemini CLI (brew)",
            },
          ],
      },
  }
---
```

Notes:

If no `metadata.openclaw` is present, the skill is always eligible (unless disabled in config or blocked by `skills.allowBundled` for bundled skills).

## Config overrides (`~/.openclaw/openclaw.json`)

Bundled/managed skills can be toggled and supplied with env values:

```
{
  skills: {
    entries: {
      "image-lab": {
        enabled: true,
        apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string
        env: {
          GEMINI_API_KEY: "GEMINI_KEY_HERE",
        },
        config: {
          endpoint: "https://example.invalid",
          model: "nano-pro",
        },
      },
      peekaboo: { enabled: true },
      sag: { enabled: false },
    },
  },
}
```

Note: if the skill name contains hyphens, quote the key (JSON5 allows quoted keys). If you want stock image generation/editing inside OpenClaw itself, use the core `image_generate` tool with `agents.defaults.imageGenerationModel` instead of a bundled skill. Skill examples here are for custom or third-party workflows. For native image analysis, use the `image` tool with `agents.defaults.imageModel`. For native image generation/editing, use `image_generate` with `agents.defaults.imageGenerationModel`. If you pick `openai/*`, `google/*`, `fal/*`, or another provider-specific image model, add that provider’s auth/API key too. Config keys match the **skill name** by default. If a skill defines `metadata.openclaw.skillKey`, use that key under `skills.entries`. Rules:

## Environment injection (per agent run)

When an agent run starts, OpenClaw:

1. Reads skill metadata.
2. Applies any `skills.entries.<key>.env` or `skills.entries.<key>.apiKey` to `process.env`.
3. Builds the system prompt with **eligible** skills.
4. Restores the original environment after the run ends.

This is **scoped to the agent run**, not a global shell environment.

## Session snapshot (performance)

OpenClaw snapshots the eligible skills **when a session starts** and reuses that list for subsequent turns in the same session. Changes to skills or config take effect on the next new session. Skills can also refresh mid-session when the skills watcher is enabled or when a new eligible remote node appears (see below). Think of this as a **hot reload**: the refreshed list is picked up on the next agent turn.

## Remote macOS nodes (Linux gateway)

If the Gateway is running on Linux but a **macOS node** is connected **with `system.run` allowed** (Exec approvals security not set to `deny`), OpenClaw can treat macOS-only skills as eligible when the required binaries are present on that node. The agent should execute those skills via the `nodes` tool (typically `nodes.run`). This relies on the node reporting its command support and on a bin probe via `system.run`. If the macOS node goes offline later, the skills remain visible; invocations may fail until the node reconnects.

## Skills watcher (auto-refresh)

By default, OpenClaw watches skill folders and bumps the skills snapshot when `SKILL.md` files change. Configure this under `skills.load`:

## Token impact (skills list)

When skills are eligible, OpenClaw injects a compact XML list of available skills into the system prompt (via `formatSkillsForPrompt` in `pi-coding-agent`). The cost is deterministic:

Formula (characters):

Notes:

## Managed skills lifecycle

OpenClaw ships a baseline set of skills as **bundled skills** as part of the install (npm package or OpenClaw\.app). `~/.openclaw/skills` exists for local overrides (for example, pinning/patching a skill without changing the bundled copy). Workspace skills are user-owned and override both on name conflicts.

## Config reference

See [Skills config](https://docs.openclaw.ai/tools/skills-config) for the full configuration schema.

## Looking for more skills?

Browse [https://clawhub.com](https://clawhub.com/).

***

----
url: https://docs.openclaw.ai/automation/cron-vs-heartbeat
----

# Cron vs Heartbeat - OpenClaw

## Cron vs Heartbeat: When to Use Each

Both heartbeats and cron jobs let you run tasks on a schedule. This guide helps you choose the right mechanism for your use case.

## Quick Decision Guide

| Use Case                             | Recommended         | Why                                      |
| ------------------------------------ | ------------------- | ---------------------------------------- |
| Check inbox every 30 min             | Heartbeat           | Batches with other checks, context-aware |
| Send daily report at 9am sharp       | Cron (isolated)     | Exact timing needed                      |
| Monitor calendar for upcoming events | Heartbeat           | Natural fit for periodic awareness       |
| Run weekly deep analysis             | Cron (isolated)     | Standalone task, can use different model |
| Remind me in 20 minutes              | Cron (main, `--at`) | One-shot with precise timing             |
| Background project health check      | Heartbeat           | Piggybacks on existing cycle             |

## Heartbeat: Periodic Awareness

Heartbeats run in the **main session** at a regular interval (default: 30 min). They’re designed for the agent to check on things and surface anything important.

### When to use heartbeat

### Heartbeat advantages

### Heartbeat example: HEARTBEAT.md checklist

The agent reads this on each heartbeat and handles all items in one turn.

### Configuring heartbeat

See [Heartbeat](https://docs.openclaw.ai/gateway/heartbeat) for full configuration.

## Cron: Precise Scheduling

Cron jobs run at precise times and can run in isolated sessions without affecting main context. Recurring top-of-hour schedules are automatically spread by a deterministic per-job offset in a 0-5 minute window.

### When to use cron

### Cron advantages

### Cron example: Daily morning briefing

This runs at exactly 7:00 AM New York time, uses Opus for quality, and announces a summary directly to WhatsApp.

### Cron example: One-shot reminder

See [Cron jobs](https://docs.openclaw.ai/automation/cron-jobs) for full CLI reference.

## Decision Flowchart

## Combining Both

The most efficient setup uses **both**:

1. **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
2. **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders.

### Example: Efficient automation setup

**HEARTBEAT.md** (checked every 30 min):

**Cron jobs** (precise timing):

## Lobster: Deterministic workflows with approvals

Lobster is the workflow runtime for **multi-step tool pipelines** that need deterministic execution and explicit approvals. Use it when the task is more than a single agent turn, and you want a resumable workflow with human checkpoints.

### When Lobster fits

### How it pairs with heartbeat and cron

For scheduled workflows, use cron or heartbeat to trigger an agent turn that calls Lobster. For ad-hoc workflows, call Lobster directly.

### Operational notes (from the code)

See [Lobster](https://docs.openclaw.ai/tools/lobster) for full usage and examples.

## Main Session vs Isolated Session

Both heartbeat and cron can interact with the main session, but differently:

|         | Heartbeat                       | Cron (main)              | Cron (isolated)                                 |
| ------- | ------------------------------- | ------------------------ | ----------------------------------------------- |
| Session | Main                            | Main (via system event)  | `cron:<jobId>` or custom session                |
| History | Shared                          | Shared                   | Fresh each run (isolated) / Persistent (custom) |
| Context | Full                            | Full                     | None (isolated) / Cumulative (custom)           |
| Model   | Main session model              | Main session model       | Can override                                    |
| Output  | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Announce summary (default)                      |

### When to use main session cron

Use `--session main` with `--system-event` when you want:

### When to use isolated cron

Use `--session isolated` when you want:

## Cost Considerations

| Mechanism       | Cost Profile                                            |
| --------------- | ------------------------------------------------------- |
| Heartbeat       | One turn every N minutes; scales with HEARTBEAT.md size |
| Cron (main)     | Adds event to next heartbeat (no isolated turn)         |
| Cron (isolated) | Full agent turn per job; can use cheaper model          |

**Tips**:

----
url: https://docs.openclaw.ai/plugins/building-plugins
----

# Building Plugins - OpenClaw

Plugins extend OpenClaw with new capabilities: channels, model providers, speech, image generation, web search, agent tools, or any combination. You do not need to add your plugin to the OpenClaw repository. Publish to [ClawHub](https://docs.openclaw.ai/tools/clawhub) or npm and users install with `openclaw plugins install <package-name>`. OpenClaw tries ClawHub first and falls back to npm automatically.

## Prerequisites

## What kind of plugin?

## Quick start: tool plugin

This walkthrough creates a minimal plugin that registers an agent tool. Channel and provider plugins have dedicated guides linked above.

## Plugin capabilities

A single plugin can register any number of capabilities via the `api` object:

| Capability           | Registration method                           | Detailed guide                                                                                          |
| -------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Text inference (LLM) | `api.registerProvider(...)`                   | [Provider Plugins](https://docs.openclaw.ai/plugins/sdk-provider-plugins)                               |
| Channel / messaging  | `api.registerChannel(...)`                    | [Channel Plugins](https://docs.openclaw.ai/plugins/sdk-channel-plugins)                                 |
| Speech (TTS/STT)     | `api.registerSpeechProvider(...)`             | [Provider Plugins](https://docs.openclaw.ai/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Media understanding  | `api.registerMediaUnderstandingProvider(...)` | [Provider Plugins](https://docs.openclaw.ai/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Image generation     | `api.registerImageGenerationProvider(...)`    | [Provider Plugins](https://docs.openclaw.ai/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Web search           | `api.registerWebSearchProvider(...)`          | [Provider Plugins](https://docs.openclaw.ai/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Agent tools          | `api.registerTool(...)`                       | Below                                                                                                   |
| Custom commands      | `api.registerCommand(...)`                    | [Entry Points](https://docs.openclaw.ai/plugins/sdk-entrypoints)                                        |
| Event hooks          | `api.registerHook(...)`                       | [Entry Points](https://docs.openclaw.ai/plugins/sdk-entrypoints)                                        |
| HTTP routes          | `api.registerHttpRoute(...)`                  | [Internals](https://docs.openclaw.ai/plugins/architecture#gateway-http-routes)                          |
| CLI subcommands      | `api.registerCli(...)`                        | [Entry Points](https://docs.openclaw.ai/plugins/sdk-entrypoints)                                        |

For the full registration API, see [SDK Overview](https://docs.openclaw.ai/plugins/sdk-overview#registration-api).

## Registering agent tools

Tools are typed functions the LLM can call. They can be required (always available) or optional (user opt-in):

```
register(api) {
  // Required tool — always available
  api.registerTool({
    name: "my_tool",
    description: "Do a thing",
    parameters: Type.Object({ input: Type.String() }),
    async execute(_id, params) {
      return { content: [{ type: "text", text: params.input }] };
    },
  });

  // Optional tool — user must add to allowlist
  api.registerTool(
    {
      name: "workflow_tool",
      description: "Run a workflow",
      parameters: Type.Object({ pipeline: Type.String() }),
      async execute(_id, params) {
        return { content: [{ type: "text", text: params.pipeline }] };
      },
    },
    { optional: true },
  );
}
```

Users enable optional tools in config:

## Import conventions

Always import from focused `openclaw/plugin-sdk/<subpath>` paths:

For the full subpath reference, see [SDK Overview](https://docs.openclaw.ai/plugins/sdk-overview). Within your plugin, use local barrel files (`api.ts`, `runtime-api.ts`) for internal imports — never import your own plugin through its SDK path.

## Pre-submission checklist

## Next steps

----
url: https://docs.openclaw.ai/reference/secretref-credential-surface
----

# SecretRef Credential Surface - OpenClaw

## [​](#secretref-credential-surface)SecretRef credential surface

This page defines the canonical SecretRef credential surface. Scope intent:

* In scope: strictly user-supplied credentials that OpenClaw does not mint or rotate.
* Out of scope: runtime-minted or rotating credentials, OAuth refresh material, and session-like artifacts.

## [​](#supported-credentials)Supported credentials

### [​](#openclaw-json-targets-secrets-configure-+-secrets-apply-+-secrets-audit)`openclaw.json` targets (`secrets configure` + `secrets apply` + `secrets audit`)

* `models.providers.*.apiKey`
* `models.providers.*.headers.*`
* `skills.entries.*.apiKey`
* `agents.defaults.memorySearch.remote.apiKey`
* `agents.list[].memorySearch.remote.apiKey`
* `talk.apiKey`
* `talk.providers.*.apiKey`
* `messages.tts.elevenlabs.apiKey`
* `messages.tts.openai.apiKey`
* `tools.web.fetch.firecrawl.apiKey`
* `plugins.entries.brave.config.webSearch.apiKey`
* `plugins.entries.google.config.webSearch.apiKey`
* `plugins.entries.xai.config.webSearch.apiKey`
* `plugins.entries.moonshot.config.webSearch.apiKey`
* `plugins.entries.perplexity.config.webSearch.apiKey`
* `plugins.entries.firecrawl.config.webSearch.apiKey`
* `plugins.entries.tavily.config.webSearch.apiKey`
* `tools.web.search.apiKey`
* `tools.web.search.gemini.apiKey`
* `tools.web.search.grok.apiKey`
* `tools.web.search.kimi.apiKey`
* `tools.web.search.perplexity.apiKey`
* `gateway.auth.password`
* `gateway.auth.token`
* `gateway.remote.token`
* `gateway.remote.password`
* `cron.webhookToken`
* `channels.telegram.botToken`
* `channels.telegram.webhookSecret`
* `channels.telegram.accounts.*.botToken`
* `channels.telegram.accounts.*.webhookSecret`
* `channels.slack.botToken`
* `channels.slack.appToken`
* `channels.slack.userToken`
* `channels.slack.signingSecret`
* `channels.slack.accounts.*.botToken`
* `channels.slack.accounts.*.appToken`
* `channels.slack.accounts.*.userToken`
* `channels.slack.accounts.*.signingSecret`
* `channels.discord.token`
* `channels.discord.pluralkit.token`
* `channels.discord.voice.tts.elevenlabs.apiKey`
* `channels.discord.voice.tts.openai.apiKey`
* `channels.discord.accounts.*.token`
* `channels.discord.accounts.*.pluralkit.token`
* `channels.discord.accounts.*.voice.tts.elevenlabs.apiKey`
* `channels.discord.accounts.*.voice.tts.openai.apiKey`
* `channels.irc.password`
* `channels.irc.nickserv.password`
* `channels.irc.accounts.*.password`
* `channels.irc.accounts.*.nickserv.password`
* `channels.bluebubbles.password`
* `channels.bluebubbles.accounts.*.password`
* `channels.feishu.appSecret`
* `channels.feishu.encryptKey`
* `channels.feishu.verificationToken`
* `channels.feishu.accounts.*.appSecret`
* `channels.feishu.accounts.*.encryptKey`
* `channels.feishu.accounts.*.verificationToken`
* `channels.msteams.appPassword`
* `channels.mattermost.botToken`
* `channels.mattermost.accounts.*.botToken`
* `channels.matrix.password`
* `channels.matrix.accounts.*.password`
* `channels.nextcloud-talk.botSecret`
* `channels.nextcloud-talk.apiPassword`
* `channels.nextcloud-talk.accounts.*.botSecret`
* `channels.nextcloud-talk.accounts.*.apiPassword`
* `channels.zalo.botToken`
* `channels.zalo.webhookSecret`
* `channels.zalo.accounts.*.botToken`
* `channels.zalo.accounts.*.webhookSecret`
* `channels.googlechat.serviceAccount` via sibling `serviceAccountRef` (compatibility exception)
* `channels.googlechat.accounts.*.serviceAccount` via sibling `serviceAccountRef` (compatibility exception)

### [​](#auth-profiles-json-targets-secrets-configure-+-secrets-apply-+-secrets-audit)`auth-profiles.json` targets (`secrets configure` + `secrets apply` + `secrets audit`)

* `profiles.*.keyRef` (`type: "api_key"`)
* `profiles.*.tokenRef` (`type: "token"`)

Notes:

* Auth-profile plan targets require `agentId`.

* Plan entries target `profiles.*.key` / `profiles.*.token` and write sibling refs (`keyRef` / `tokenRef`).

* Auth-profile refs are included in runtime resolution and audit coverage.

* For SecretRef-managed model providers, generated `agents/*/agent/models.json` entries persist non-secret markers (not resolved secret values) for `apiKey`/header surfaces.

* Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values.

* For web search:

  * In explicit provider mode (`tools.web.search.provider` set), only the selected provider key is active.
  * In auto mode (`tools.web.search.provider` unset), only the first provider key that resolves by precedence is active.
  * In auto mode, non-selected provider refs are treated as inactive until selected.
  * Legacy `tools.web.search.*` provider paths still resolve during the compatibility window, but the canonical SecretRef surface is `plugins.entries.<plugin>.config.webSearch.*`.

## [​](#unsupported-credentials)Unsupported credentials

Out-of-scope credentials include:

* `commands.ownerDisplaySecret`
* `channels.matrix.accessToken`
* `channels.matrix.accounts.*.accessToken`
* `hooks.token`
* `hooks.gmail.pushToken`
* `hooks.mappings[].sessionKey`
* `auth-profiles.oauth.*`
* `discord.threadBindings.*.webhookToken`
* `whatsapp.creds.json`

Rationale:

* These credentials are minted, rotated, session-bearing, or OAuth-durable classes that do not fit read-only external SecretRef resolution.

----
url: https://docs.openclaw.ai/platforms/macos
----

# macOS App - OpenClaw

The macOS app is the **menu‑bar companion** for OpenClaw. It owns permissions, manages/attaches to the Gateway locally (launchd or manual), and exposes macOS capabilities to the agent as a node.

## What it does

## Local vs remote mode

## Launchd control

The app manages a per‑user LaunchAgent labeled `ai.openclaw.gateway` (or `ai.openclaw.<profile>` when using `--profile`/`OPENCLAW_PROFILE`; legacy `com.openclaw.*` still unloads).

Replace the label with `ai.openclaw.<profile>` when running a named profile. If the LaunchAgent isn’t installed, enable it from the app or run `openclaw gateway install`.

## Node capabilities (mac)

The macOS app presents itself as a node. Common commands:

The node reports a `permissions` map so agents can decide what’s allowed. Node service + app IPC:

Diagram (SCI):

## Exec approvals (system.run)

`system.run` is controlled by **Exec approvals** in the macOS app (Settings → Exec approvals). Security + ask + allowlist are stored locally on the Mac in:

Example:

Notes:

* `allowlist` entries are glob patterns for resolved binary paths.
* Raw shell command text that contains shell control or expansion syntax (`&&`, `||`, `;`, `|`, `` ` ``, `$`, `<`, `>`, `(`, `)`) is treated as an allowlist miss and requires explicit approval (or allowlisting the shell binary).
* Choosing “Always Allow” in the prompt adds that command to the allowlist.
* `system.run` environment overrides are filtered (drops `PATH`, `DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`, `SHELLOPTS`, `PS4`) and then merged with the app’s environment.
* For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped environment overrides are reduced to a small explicit allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`).
* For allow-always decisions in allowlist mode, known dispatch wrappers (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist inner executable paths instead of wrapper paths. If unwrapping is not safe, no allowlist entry is persisted automatically.

## Deep links

The app registers the `openclaw://` URL scheme for local actions.

### `openclaw://agent`

Triggers a Gateway `agent` request.

Query parameters:

Safety:

## Onboarding flow (typical)

1. Install and launch **OpenClaw\.app**.
2. Complete the permissions checklist (TCC prompts).
3. Ensure **Local** mode is active and the Gateway is running.
4. Install the CLI if you want terminal access.

## State dir placement (macOS)

Avoid putting your OpenClaw state dir in iCloud or other cloud-synced folders. Sync-backed paths can add latency and occasionally cause file-lock/sync races for sessions and credentials. Prefer a local non-synced state path such as:

If `openclaw doctor` detects state under:

it will warn and recommend moving back to a local path.

## Build & dev workflow (native)

## Debug gateway connectivity (macOS CLI)

Use the debug CLI to exercise the same Gateway WebSocket handshake and discovery logic that the macOS app uses, without launching the app.

Connect options:

Discovery options:

Tip: compare against `openclaw gateway discover --json` to see whether the macOS app’s discovery pipeline (NWBrowser + tailnet DNS‑SD fallback) differs from the Node CLI’s `dns-sd` based discovery.

## Remote connection plumbing (SSH tunnels)

When the macOS app runs in **Remote** mode, it opens an SSH tunnel so local UI components can talk to a remote Gateway as if it were on localhost.

### Control tunnel (Gateway WebSocket port)

For setup steps, see [macOS remote access](https://docs.openclaw.ai/platforms/mac/remote). For protocol details, see [Gateway protocol](https://docs.openclaw.ai/gateway/protocol).

----
url: https://docs.openclaw.ai/install/oracle
----

# Oracle Cloud - OpenClaw

## [​](#oracle-cloud)Oracle Cloud

Run a persistent OpenClaw Gateway on Oracle Cloud’s **Always Free** ARM tier (up to 4 OCPU, 24 GB RAM, 200 GB storage) at no cost.

## [​](#prerequisites)Prerequisites

* Oracle Cloud account ([signup](https://www.oracle.com/cloud/free/)) — see [community signup guide](https://gist.github.com/rssnyder/51e3cfedd730e7dd5f4a816143b25dbd) if you hit issues
* Tailscale account (free at [tailscale.com](https://tailscale.com/))
* An SSH key pair
* About 30 minutes

## [​](#setup)Setup

1

[](#)

Create an OCI instance

1. Log into [Oracle Cloud Console](https://cloud.oracle.com/).

2. Navigate to **Compute > Instances > Create Instance**.

3. Configure:

   * **Name:** `openclaw`
   * **Image:** Ubuntu 24.04 (aarch64)
   * **Shape:** `VM.Standard.A1.Flex` (Ampere ARM)
   * **OCPUs:** 2 (or up to 4)
   * **Memory:** 12 GB (or up to 24 GB)
   * **Boot volume:** 50 GB (up to 200 GB free)
   * **SSH key:** Add your public key

4. Click **Create** and note the public IP address.

If instance creation fails with “Out of capacity”, try a different availability domain or retry later. Free tier capacity is limited.

2

[](#)

Connect and update the system

```
ssh ubuntu@YOUR_PUBLIC_IP

sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential
```

`build-essential` is required for ARM compilation of some dependencies.

3

[](#)

Configure user and hostname

```
sudo hostnamectl set-hostname openclaw
sudo passwd ubuntu
sudo loginctl enable-linger ubuntu
```

Enabling linger keeps user services running after logout.

4

[](#)

Install Tailscale

```
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --ssh --hostname=openclaw
```

From now on, connect via Tailscale: `ssh ubuntu@openclaw`.

5

[](#)

Install OpenClaw

```
curl -fsSL https://openclaw.ai/install.sh | bash
source ~/.bashrc
```

When prompted “How do you want to hatch your bot?”, select **Do this later**.

6

[](#)

Configure the gateway

Use token auth with Tailscale Serve for secure remote access.

```
openclaw config set gateway.bind loopback
openclaw config set gateway.auth.mode token
openclaw doctor --generate-gateway-token
openclaw config set gateway.tailscale.mode serve
openclaw config set gateway.trustedProxies '["127.0.0.1"]'

systemctl --user restart openclaw-gateway
```

7

[](#)

Lock down VCN security

Block all traffic except Tailscale at the network edge:

1. Go to **Networking > Virtual Cloud Networks** in the OCI Console.
2. Click your VCN, then **Security Lists > Default Security List**.
3. **Remove** all ingress rules except `0.0.0.0/0 UDP 41641` (Tailscale).
4. Keep default egress rules (allow all outbound).

This blocks SSH on port 22, HTTP, HTTPS, and everything else at the network edge. You can only connect via Tailscale from this point on.

8

[](#)

Verify

```
openclaw --version
systemctl --user status openclaw-gateway
tailscale serve status
curl http://localhost:18789
```

Access the Control UI from any device on your tailnet:

```
https://openclaw.<tailnet-name>.ts.net/
```

Replace `<tailnet-name>` with your tailnet name (visible in `tailscale status`).

## [​](#fallback-ssh-tunnel)Fallback: SSH tunnel

If Tailscale Serve is not working, use an SSH tunnel from your local machine:

```
ssh -L 18789:127.0.0.1:18789 ubuntu@openclaw
```

Then open `http://localhost:18789`.

## [​](#troubleshooting)Troubleshooting

**Instance creation fails (“Out of capacity”)** — Free tier ARM instances are popular. Try a different availability domain or retry during off-peak hours. **Tailscale will not connect** — Run `sudo tailscale up --ssh --hostname=openclaw --reset` to re-authenticate. **Gateway will not start** — Run `openclaw doctor --non-interactive` and check logs with `journalctl --user -u openclaw-gateway -n 50`. **ARM binary issues** — Most npm packages work on ARM64. For native binaries, look for `linux-arm64` or `aarch64` releases. Verify architecture with `uname -m`.

## [​](#next-steps)Next steps

* [Channels](https://docs.openclaw.ai/channels) — connect Telegram, WhatsApp, Discord, and more
* [Gateway configuration](https://docs.openclaw.ai/gateway/configuration) — all config options
* [Updating](https://docs.openclaw.ai/install/updating) — keep OpenClaw up to date

----
url: https://docs.openclaw.ai/cli/status
----

# status - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw status](#openclaw-status)

## [​](#openclaw-status)`openclaw status`

Diagnostics for channels + sessions.

```
openclaw status
openclaw status --all
openclaw status --deep
openclaw status --usage
```

Notes:

* `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Google Chat + Slack + Signal).
* Output includes per-agent session stores when multiple agents are configured.
* Overview includes Gateway + node host service install/runtime status when available.
* Overview includes update channel + git SHA (for source checkouts).
* Update info surfaces in the Overview; if an update is available, status prints a hint to run `openclaw update` (see [Updating](https://docs.openclaw.ai/install/updating)).
* Read-only status surfaces (`status`, `status --json`, `status --all`) resolve supported SecretRefs for their targeted config paths when possible.
* If a supported channel SecretRef is configured but unavailable in the current command path, status stays read-only and reports degraded output instead of crashing. Human output shows warnings such as “configured token unavailable in this command path”, and JSON output includes `secretDiagnostics`.
* When command-local SecretRef resolution succeeds, status prefers the resolved snapshot and clears transient “secret unavailable” channel markers from the final output.
* `status --all` includes a Secrets overview row and a diagnosis section that summarizes secret diagnostics (truncated for readability) without stopping report generation.

[setup](https://docs.openclaw.ai/cli/setup)[uninstall](https://docs.openclaw.ai/cli/uninstall)

----
url: https://docs.openclaw.ai/concepts/streaming
----

# Streaming and Chunking - OpenClaw

OpenClaw has two separate streaming layers:

There is **no true token-delta streaming** to channel messages today. Preview streaming is message-based (send + edits/appends).

## Block streaming (channel messages)

Block streaming sends assistant output in coarse chunks as it becomes available.

Legend:

**Controls:**

**Boundary semantics:**

`message_end` still uses the chunker if the buffered text exceeds `maxChars`, so it can emit multiple chunks at the end.

## Chunking algorithm (low/high bounds)

Block chunking is implemented by `EmbeddedBlockChunker`:

`maxChars` is clamped to the channel `textChunkLimit`, so you can’t exceed per-channel caps.

## Coalescing (merge streamed blocks)

When block streaming is enabled, OpenClaw can **merge consecutive block chunks** before sending them out. This reduces “single-line spam” while still providing progressive output.

## Human-like pacing between blocks

When block streaming is enabled, you can add a **randomized pause** between block replies (after the first block). This makes multi-bubble responses feel more natural.

## ”Stream chunks or everything”

This maps to:

**Channel note:** Block streaming is **off unless** `*.blockStreaming` is explicitly set to `true`. Channels can stream a live preview (`channels.<channel>.streaming`) without block replies. Config location reminder: the `blockStreaming*` defaults live under `agents.defaults`, not the root config.

## Preview streaming modes

Canonical key: `channels.<channel>.streaming` Modes:

### Channel mapping

| Channel  | `off` | `partial` | `block` | `progress`        |
| -------- | ----- | --------- | ------- | ----------------- |
| Telegram | ✅     | ✅         | ✅       | maps to `partial` |
| Discord  | ✅     | ✅         | ✅       | maps to `partial` |
| Slack    | ✅     | ✅         | ✅       | ✅                 |

Slack-only:

Legacy key migration:

### Runtime behavior

Telegram:

Discord:

Slack:

----
url: https://docs.openclaw.ai/gateway/troubleshooting
----

# Troubleshooting - OpenClaw

## Gateway troubleshooting

This page is the deep runbook. Start at [/help/troubleshooting](https://docs.openclaw.ai/help/troubleshooting) if you want the fast triage flow first.

## Command ladder

Run these first, in this order:

Expected healthy signals:

Use this when logs/errors include: `HTTP 429: rate_limit_error: Extra usage is required for long context requests`.

Look for:

Fix options:

1. Disable `context1m` for that model to fall back to the normal context window.
2. Use an Anthropic API key with billing, or enable Anthropic Extra Usage on the subscription account.
3. Configure fallback models so runs continue when Anthropic long-context requests are rejected.

Related:

If channels are up but nothing answers, check routing and policy before reconnecting anything.

Look for:

Common signatures:

Related:

## Dashboard control ui connectivity

When dashboard/control UI will not connect, validate URL, auth mode, and secure context assumptions.

Look for:

Common signatures:

### Auth detail codes quick map

Use `error.details.code` from the failed `connect` response to pick the next action:

| Detail code                  | Meaning                                                  | Recommended action                                                                                                                                                                           |
| ---------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH_TOKEN_MISSING`         | Client did not send a required shared token.             | Paste/set token in the client and retry. For dashboard paths: `openclaw config get gateway.auth.token` then paste into Control UI settings.                                                  |
| `AUTH_TOKEN_MISMATCH`        | Shared token did not match gateway auth token.           | If `canRetryWithDeviceToken=true`, allow one trusted retry. If still failing, run the [token drift recovery checklist](https://docs.openclaw.ai/cli/devices#token-drift-recovery-checklist). |
| `AUTH_DEVICE_TOKEN_MISMATCH` | Cached per-device token is stale or revoked.             | Rotate/re-approve device token using [devices CLI](https://docs.openclaw.ai/cli/devices), then reconnect.                                                                                    |
| `PAIRING_REQUIRED`           | Device identity is known but not approved for this role. | Approve pending request: `openclaw devices list` then `openclaw devices approve <requestId>`.                                                                                                |

Device auth v2 migration check:

If logs show nonce/signature errors, update the connecting client and verify it:

1. waits for `connect.challenge`
2. signs the challenge-bound payload
3. sends `connect.params.device.nonce` with the same challenge nonce

Related:

## Gateway service not running

Use this when service is installed but process does not stay up.

Look for:

Common signatures:

Related:

## Channel connected messages not flowing

If channel state is connected but message flow is dead, focus on policy, permissions, and channel specific delivery rules.

Look for:

Common signatures:

Related:

## Cron and heartbeat delivery

If cron or heartbeat did not run or did not deliver, verify scheduler state first, then delivery target.

Look for:

Common signatures:

Related:

## Node paired tool fails

If a node is paired but tools fail, isolate foreground, permission, and approval state.

Look for:

Common signatures:

Related:

## Browser tool fails

Use this when browser tool actions fail even though the gateway itself is healthy.

Look for:

Common signatures:

Related:

## If you upgraded and something suddenly broke

Most post-upgrade breakage is config drift or stricter defaults now being enforced.

### 1) Auth and URL override behavior changed

What to check:

Common signatures:

### 2) Bind and auth guardrails are stricter

What to check:

Common signatures:

### 3) Pairing and device identity state changed

What to check:

Common signatures:

If the service config and runtime still disagree after checks, reinstall service metadata from the same profile/state directory:

Related:

----
url: https://docs.openclaw.ai/providers/openai
----

# OpenAI - OpenClaw

OpenAI provides developer APIs for GPT models. Codex supports **ChatGPT sign-in** for subscription access or **API key** sign-in for usage-based access. Codex cloud requires ChatGPT sign-in. OpenAI explicitly supports subscription OAuth usage in external tools/workflows like OpenClaw.

## Option A: OpenAI API key (OpenAI Platform)

**Best for:** direct API access and usage-based billing. Get your API key from the OpenAI dashboard.

### CLI setup

### Config snippet

OpenAI’s current API model docs list `gpt-5.4` and `gpt-5.4-pro` for direct OpenAI API usage. OpenClaw forwards both through the `openai/*` Responses path. OpenClaw intentionally suppresses the stale `openai/gpt-5.3-codex-spark` row, because direct OpenAI API calls reject it in live traffic. OpenClaw does **not** expose `openai/gpt-5.3-codex-spark` on the direct OpenAI API path. `pi-ai` still ships a built-in row for that model, but live OpenAI API requests currently reject it. Spark is treated as Codex-only in OpenClaw.

## Option B: OpenAI Code (Codex) subscription

**Best for:** using ChatGPT/Codex subscription access instead of an API key. Codex cloud requires ChatGPT sign-in, while the Codex CLI supports ChatGPT or API key sign-in.

### CLI setup (Codex OAuth)

### Config snippet (Codex subscription)

OpenAI’s current Codex docs list `gpt-5.4` as the current Codex model. OpenClaw maps that to `openai-codex/gpt-5.4` for ChatGPT/Codex OAuth usage. If your Codex account is entitled to Codex Spark, OpenClaw also supports:

OpenClaw treats Codex Spark as Codex-only. It does not expose a direct `openai/gpt-5.3-codex-spark` API-key path. OpenClaw also preserves `openai-codex/gpt-5.3-codex-spark` when `pi-ai` discovers it. Treat it as entitlement-dependent and experimental: Codex Spark is separate from GPT-5.4 `/fast`, and availability depends on the signed-in Codex / ChatGPT account.

### Transport default

OpenClaw uses `pi-ai` for model streaming. For both `openai/*` and `openai-codex/*`, default transport is `"auto"` (WebSocket-first, then SSE fallback). You can set `agents.defaults.models.<provider/model>.params.transport`:

For `openai/*` (Responses API), OpenClaw also enables WebSocket warm-up by default (`openaiWsWarmup: true`) when WebSocket transport is used. Related OpenAI docs:

### OpenAI WebSocket warm-up

OpenAI docs describe warm-up as optional. OpenClaw enables it by default for `openai/*` to reduce first-turn latency when using WebSocket transport.

### Disable warm-up

### Enable warm-up explicitly

### OpenAI priority processing

OpenAI’s API exposes priority processing via `service_tier=priority`. In OpenClaw, set `agents.defaults.models["openai/<model>"].params.serviceTier` to pass that field through on direct `openai/*` Responses requests.

Supported values are `auto`, `default`, `flex`, and `priority`.

### OpenAI fast mode

OpenClaw exposes a shared fast-mode toggle for both `openai/*` and `openai-codex/*` sessions:

When fast mode is enabled, OpenClaw applies a low-latency OpenAI profile:

Example:

Session overrides win over config. Clearing the session override in the Sessions UI returns the session to the configured default.

### OpenAI Responses server-side compaction

For direct OpenAI Responses models (`openai/*` using `api: "openai-responses"` with `baseUrl` on `api.openai.com`), OpenClaw now auto-enables OpenAI server-side compaction payload hints:

By default, `compact_threshold` is `70%` of model `contextWindow` (or `80000` when unavailable).

### Enable server-side compaction explicitly

Use this when you want to force `context_management` injection on compatible Responses models (for example Azure OpenAI Responses):

### Enable with a custom threshold

### Disable server-side compaction

`responsesServerCompaction` only controls `context_management` injection. Direct OpenAI Responses models still force `store: true` unless compat sets `supportsStore: false`.

## Notes

----
url: https://docs.openclaw.ai/channels/mattermost
----

# Mattermost - OpenClaw

## Mattermost (plugin)

Status: supported via plugin (bot token + WebSocket events). Channels, groups, and DMs are supported. Mattermost is a self-hostable team messaging platform; see the official site at [mattermost.com](https://mattermost.com/) for product details and downloads.

## Plugin required

Mattermost ships as a plugin and is not bundled with the core install. Install via CLI (npm registry):

Local checkout (when running from a git repo):

If you choose Mattermost during setup and a git checkout is detected, OpenClaw will offer the local install path automatically. Details: [Plugins](https://docs.openclaw.ai/tools/plugin)

## Quick setup

1. Install the Mattermost plugin.
2. Create a Mattermost bot account and copy the **bot token**.
3. Copy the Mattermost **base URL** (e.g., `https://chat.example.com`).
4. Configure OpenClaw and start the gateway.

Minimal config:

## Native slash commands

Native slash commands are opt-in. When enabled, OpenClaw registers `oc_*` slash commands via the Mattermost API and receives callback POSTs on the gateway HTTP server.

Notes:

## Environment variables (default account)

Set these on the gateway host if you prefer env vars:

Env vars apply only to the **default** account (`default`). Other accounts must use config values.

## Chat modes

Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`:

Config example:

Notes:

## Threading and sessions

Use `channels.mattermost.replyToMode` to control whether channel and group replies stay in the main channel or start a thread under the triggering post.

Config example:

Notes:

## Access control (DMs)

## Channels (groups)

## Targets for outbound delivery

Use these target formats with `openclaw message send` or cron/webhooks:

Bare opaque IDs (like `64ifufp...`) are **ambiguous** in Mattermost (user ID vs channel ID). OpenClaw resolves them **user-first**:

If you need deterministic behavior, always use the explicit prefixes (`user:<id>` / `channel:<id>`).

## DM channel retry

When OpenClaw sends to a Mattermost DM target and needs to resolve the direct channel first, it retries transient direct-channel creation failures by default. Use `channels.mattermost.dmChannelRetry` to tune that behavior globally for the Mattermost plugin, or `channels.mattermost.accounts.<id>.dmChannelRetry` for one account.

Notes:

## Reactions (message tool)

Examples:

Config:

## Interactive buttons (message tool)

Send messages with clickable buttons. When a user clicks a button, the agent receives the selection and can respond. Enable buttons by adding `inlineButtons` to the channel capabilities:

Use `message action=send` with a `buttons` parameter. Buttons are a 2D array (rows of buttons):

Button fields:

When a user clicks a button:

1. All buttons are replaced with a confirmation line (e.g., ”✓ **Yes** selected by @user”).
2. The agent receives the selection as an inbound message and responds.

Notes:

Config:

### Direct API integration (external scripts)

External scripts and webhooks can post buttons directly via the Mattermost REST API instead of going through the agent’s `message` tool. Use `buildButtonAttachments()` from the extension when possible; if posting raw JSON, follow these rules: **Payload structure:**

```
{
  channel_id: "<channelId>",
  message: "Choose an option:",
  props: {
    attachments: [
      {
        actions: [
          {
            id: "mybutton01", // alphanumeric only — see below
            type: "button", // required, or clicks are silently ignored
            name: "Approve", // display label
            style: "primary", // optional: "default", "primary", "danger"
            integration: {
              url: "https://gateway.example.com/mattermost/interactions/default",
              context: {
                action_id: "mybutton01", // must match button id (for name lookup)
                action: "approve",
                // ... any custom fields ...
                _token: "<hmac>", // see HMAC section below
              },
            },
          },
        ],
      },
    ],
  },
}
```

**Critical rules:**

1. Attachments go in `props.attachments`, not top-level `attachments` (silently ignored).
2. Every action needs `type: "button"` — without it, clicks are swallowed silently.
3. Every action needs an `id` field — Mattermost ignores actions without IDs.
4. Action `id` must be **alphanumeric only** (`[a-zA-Z0-9]`). Hyphens and underscores break Mattermost’s server-side action routing (returns 404). Strip them before use.
5. `context.action_id` must match the button’s `id` so the confirmation message shows the button name (e.g., “Approve”) instead of a raw ID.
6. `context.action_id` is required — the interaction handler returns 400 without it.

**HMAC token generation:** The gateway verifies button clicks with HMAC-SHA256. External scripts must generate tokens that match the gateway’s verification logic:

1. Derive the secret from the bot token: `HMAC-SHA256(key="openclaw-mattermost-interactions", data=botToken)`
2. Build the context object with all fields **except** `_token`.
3. Serialize with **sorted keys** and **no spaces** (the gateway uses `JSON.stringify` with sorted keys, which produces compact output).
4. Sign: `HMAC-SHA256(key=secret, data=serializedContext)`
5. Add the resulting hex digest as `_token` in the context.

Python example:

```
import hmac, hashlib, json

secret = hmac.new(
    b"openclaw-mattermost-interactions",
    bot_token.encode(), hashlib.sha256
).hexdigest()

ctx = {"action_id": "mybutton01", "action": "approve"}
payload = json.dumps(ctx, sort_keys=True, separators=(",", ":"))
token = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()

context = {**ctx, "_token": token}
```

Common HMAC pitfalls:

## Directory adapter

The Mattermost plugin includes a directory adapter that resolves channel and user names via the Mattermost API. This enables `#channel-name` and `@username` targets in `openclaw message send` and cron/webhook deliveries. No configuration is needed — the adapter uses the bot token from the account config.

## Multi-account

Mattermost supports multiple accounts under `channels.mattermost.accounts`:

## Troubleshooting

----
url: https://docs.openclaw.ai/cli/browser
----

# browser - OpenClaw

Manage OpenClaw’s browser control server and run browser actions (tabs, snapshots, screenshots, navigation, clicks, typing). Related:

## Common flags

## Quick start (local)

## Profiles

Profiles are named browser routing configs. In practice:

Use a specific profile:

## Tabs

## Snapshot / screenshot / actions

Snapshot:

Screenshot:

Navigate/click/type (ref-based UI automation):

## Existing Chrome via MCP

Use the built-in `user` profile, or create your own `existing-session` profile:

This path is host-only. For Docker, headless servers, Browserless, or other remote setups, use a CDP profile instead.

## Remote browser control (node host proxy)

If the Gateway runs on a different machine than the browser, run a **node host** on the machine that has Chrome/Brave/Edge/Chromium. The Gateway will proxy browser actions to that node (no separate browser control server required). Use `gateway.nodes.browser.mode` to control auto-routing and `gateway.nodes.browser.node` to pin a specific node if multiple are connected. Security + remote setup: [Browser tool](https://docs.openclaw.ai/tools/browser), [Remote access](https://docs.openclaw.ai/gateway/remote), [Tailscale](https://docs.openclaw.ai/gateway/tailscale), [Security](https://docs.openclaw.ai/gateway/security)

----
url: https://docs.openclaw.ai/concepts/architecture
----

# Gateway Architecture - OpenClaw

## Overview

* A single long‑lived **Gateway** owns all messaging surfaces (WhatsApp via Baileys, Telegram via grammY, Slack, Discord, Signal, iMessage, WebChat).
* Control-plane clients (macOS app, CLI, web UI, automations) connect to the Gateway over **WebSocket** on the configured bind host (default `127.0.0.1:18789`).
* **Nodes** (macOS/iOS/Android/headless) also connect over **WebSocket**, but declare `role: node` with explicit caps/commands.
* One Gateway per host; it is the only place that opens a WhatsApp session.
* The **canvas host** is served by the Gateway HTTP server under:

## Components and flows

### Gateway (daemon)

### Clients (mac app / CLI / web admin)

### Nodes (macOS / iOS / Android / headless)

Protocol details:

### WebChat

## Connection lifecycle (single client)

## Wire protocol (summary)

* Transport: WebSocket, text frames with JSON payloads.

* First frame **must** be `connect`.

* After handshake:

  * Requests: `{type:"req", id, method, params}` → `{type:"res", id, ok, payload|error}`
  * Events: `{type:"event", event, payload, seq?, stateVersion?}`

* If `OPENCLAW_GATEWAY_TOKEN` (or `--token`) is set, `connect.params.auth.token` must match or the socket closes.

* Idempotency keys are required for side‑effecting methods (`send`, `agent`) to safely retry; the server keeps a short‑lived dedupe cache.

* Nodes must include `role: "node"` plus caps/commands/permissions in `connect`.

## Pairing + local trust

Details: [Gateway protocol](https://docs.openclaw.ai/gateway/protocol), [Pairing](https://docs.openclaw.ai/channels/pairing), [Security](https://docs.openclaw.ai/gateway/security).

## Protocol typing and codegen

## Remote access

## Operations snapshot

## Invariants

----
url: https://docs.openclaw.ai/providers/moonshot
----

# Moonshot AI - OpenClaw

## Moonshot AI (Kimi)

Moonshot provides the Kimi API with OpenAI-compatible endpoints. Configure the provider and set the default model to `moonshot/kimi-k2.5`, or use Kimi Coding with `kimi-coding/k2p5`. Current Kimi K2 model IDs:

Kimi Coding:

Note: Moonshot and Kimi Coding are separate providers. Keys are not interchangeable, endpoints differ, and model refs differ (Moonshot uses `moonshot/...`, Kimi Coding uses `kimi-coding/...`).

## Config snippet (Moonshot API)

```
{
  env: { MOONSHOT_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "moonshot/kimi-k2.5" },
      models: {
        // moonshot-kimi-k2-aliases:start
        "moonshot/kimi-k2.5": { alias: "Kimi K2.5" },
        "moonshot/kimi-k2-0905-preview": { alias: "Kimi K2" },
        "moonshot/kimi-k2-turbo-preview": { alias: "Kimi K2 Turbo" },
        "moonshot/kimi-k2-thinking": { alias: "Kimi K2 Thinking" },
        "moonshot/kimi-k2-thinking-turbo": { alias: "Kimi K2 Thinking Turbo" },
        // moonshot-kimi-k2-aliases:end
      },
    },
  },
  models: {
    mode: "merge",
    providers: {
      moonshot: {
        baseUrl: "https://api.moonshot.ai/v1",
        apiKey: "${MOONSHOT_API_KEY}",
        api: "openai-completions",
        models: [
          // moonshot-kimi-k2-models:start
          {
            id: "kimi-k2.5",
            name: "Kimi K2.5",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 256000,
            maxTokens: 8192,
          },
          {
            id: "kimi-k2-0905-preview",
            name: "Kimi K2 0905 Preview",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 256000,
            maxTokens: 8192,
          },
          {
            id: "kimi-k2-turbo-preview",
            name: "Kimi K2 Turbo",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 256000,
            maxTokens: 8192,
          },
          {
            id: "kimi-k2-thinking",
            name: "Kimi K2 Thinking",
            reasoning: true,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 256000,
            maxTokens: 8192,
          },
          {
            id: "kimi-k2-thinking-turbo",
            name: "Kimi K2 Thinking Turbo",
            reasoning: true,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 256000,
            maxTokens: 8192,
          },
          // moonshot-kimi-k2-models:end
        ],
      },
    },
  },
}
```

## Kimi Coding

## Notes

## Native thinking mode (Moonshot)

Moonshot Kimi supports binary native thinking:

Configure it per model via `agents.defaults.models.<provider/model>.params`:

OpenClaw also maps runtime `/think` levels for Moonshot:

When Moonshot thinking is enabled, `tool_choice` must be `auto` or `none`. OpenClaw normalizes incompatible `tool_choice` values to `auto` for compatibility.

----
url: https://docs.openclaw.ai/providers/opencode
----

# OpenCode - OpenClaw

## [​](#opencode)OpenCode

OpenCode exposes two hosted catalogs in OpenClaw:

* `opencode/...` for the **Zen** catalog
* `opencode-go/...` for the **Go** catalog

Both catalogs use the same OpenCode API key. OpenClaw keeps the runtime provider ids split so upstream per-model routing stays correct, but onboarding and docs treat them as one OpenCode setup.

## [​](#cli-setup)CLI setup

### [​](#zen-catalog)Zen catalog

```
openclaw onboard --auth-choice opencode-zen
openclaw onboard --opencode-zen-api-key "$OPENCODE_API_KEY"
```

### [​](#go-catalog)Go catalog

```
openclaw onboard --auth-choice opencode-go
openclaw onboard --opencode-go-api-key "$OPENCODE_API_KEY"
```

## [​](#config-snippet)Config snippet

```
{
  env: { OPENCODE_API_KEY: "sk-..." },
  agents: { defaults: { model: { primary: "opencode/claude-opus-4-6" } } },
}
```

## [​](#catalogs)Catalogs

### [​](#zen)Zen

* Runtime provider: `opencode`
* Example models: `opencode/claude-opus-4-6`, `opencode/gpt-5.2`, `opencode/gemini-3-pro`
* Best when you want the curated OpenCode multi-model proxy

### [​](#go)Go

* Runtime provider: `opencode-go`
* Example models: `opencode-go/kimi-k2.5`, `opencode-go/glm-5`, `opencode-go/minimax-m2.5`
* Best when you want the OpenCode-hosted Kimi/GLM/MiniMax lineup

## [​](#notes)Notes

* `OPENCODE_ZEN_API_KEY` is also supported.
* Entering one OpenCode key during setup stores credentials for both runtime providers.
* You sign in to OpenCode, add billing details, and copy your API key.
* Billing and catalog availability are managed from the OpenCode dashboard.

----
url: https://docs.openclaw.ai/concepts/usage-tracking
----

# Usage Tracking - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [Usage tracking](#usage-tracking)
- [What it is](#what-it-is)
- [Where it shows up](#where-it-shows-up)
- [Providers + credentials](#providers-%2B-credentials)

## [​](#usage-tracking)Usage tracking

## [​](#what-it-is)What it is

* Pulls provider usage/quota directly from their usage endpoints.
* No estimated costs; only the provider-reported windows.

## [​](#where-it-shows-up)Where it shows up

* `/status` in chats: emoji‑rich status card with session tokens + estimated cost (API key only). Provider usage shows for the **current model provider** when available.
* `/usage off|tokens|full` in chats: per-response usage footer (OAuth shows tokens only).
* `/usage cost` in chats: local cost summary aggregated from OpenClaw session logs.
* CLI: `openclaw status --usage` prints a full per-provider breakdown.
* CLI: `openclaw channels list` prints the same usage snapshot alongside provider config (use `--no-usage` to skip).
* macOS menu bar: “Usage” section under Context (only if available).

## [​](#providers-+-credentials)Providers + credentials

* **Anthropic (Claude)**: OAuth tokens in auth profiles.
* **GitHub Copilot**: OAuth tokens in auth profiles.
* **Gemini CLI**: OAuth tokens in auth profiles.
* **Antigravity**: OAuth tokens in auth profiles.
* **OpenAI Codex**: OAuth tokens in auth profiles (accountId used when present).
* **MiniMax**: API key (coding plan key; `MINIMAX_CODE_PLAN_KEY` or `MINIMAX_API_KEY`); uses the 5‑hour coding plan window.
* **z.ai**: API key via env/config/auth store.

Usage is hidden if no matching OAuth/API credentials exist.

[Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)[Timezones](https://docs.openclaw.ai/concepts/timezone)

----
url: https://docs.openclaw.ai/plugins/voice-call
----

# Voice Call Plugin - OpenClaw

Voice calls for OpenClaw via a plugin. Supports outbound notifications and multi-turn conversations with inbound policies. Current providers:

Quick mental model:

## Where it runs (local vs remote)

The Voice Call plugin runs **inside the Gateway process**. If you use a remote Gateway, install/configure the plugin on the **machine running the Gateway**, then restart the Gateway to load it.

## Install

### Option A: install from npm (recommended)

Restart the Gateway afterwards.

### Option B: install from a local folder (dev, no copying)

Restart the Gateway afterwards.

## Config

Set config under `plugins.entries.voice-call.config`:

```
{
  plugins: {
    entries: {
      "voice-call": {
        enabled: true,
        config: {
          provider: "twilio", // or "telnyx" | "plivo" | "mock"
          fromNumber: "+15550001234",
          toNumber: "+15550005678",

          twilio: {
            accountSid: "ACxxxxxxxx",
            authToken: "...",
          },

          telnyx: {
            apiKey: "...",
            connectionId: "...",
            // Telnyx webhook public key from the Telnyx Mission Control Portal
            // (Base64 string; can also be set via TELNYX_PUBLIC_KEY).
            publicKey: "...",
          },

          plivo: {
            authId: "MAxxxxxxxxxxxxxxxxxxxx",
            authToken: "...",
          },

          // Webhook server
          serve: {
            port: 3334,
            path: "/voice/webhook",
          },

          // Webhook security (recommended for tunnels/proxies)
          webhookSecurity: {
            allowedHosts: ["voice.example.com"],
            trustedProxyIPs: ["100.64.0.1"],
          },

          // Public exposure (pick one)
          // publicUrl: "https://example.ngrok.app/voice/webhook",
          // tunnel: { provider: "ngrok" },
          // tailscale: { mode: "funnel", path: "/voice/webhook" }

          outbound: {
            defaultMode: "notify", // notify | conversation
          },

          streaming: {
            enabled: true,
            streamPath: "/voice/stream",
            preStartTimeoutMs: 5000,
            maxPendingConnections: 32,
            maxPendingConnectionsPerIp: 4,
            maxConnections: 128,
          },
        },
      },
    },
  },
}
```

Notes:

## Stale call reaper

Use `staleCallReaperSeconds` to end calls that never receive a terminal webhook (for example, notify-mode calls that never complete). The default is `0` (disabled). Recommended ranges:

Example:

## Webhook Security

When a proxy or tunnel sits in front of the Gateway, the plugin reconstructs the public URL for signature verification. These options control which forwarded headers are trusted. `webhookSecurity.allowedHosts` allowlists hosts from forwarding headers. `webhookSecurity.trustForwardingHeaders` trusts forwarded headers without an allowlist. `webhookSecurity.trustedProxyIPs` only trusts forwarded headers when the request remote IP matches the list. Webhook replay protection is enabled for Twilio and Plivo. Replayed valid webhook requests are acknowledged but skipped for side effects. Twilio conversation turns include a per-turn token in `<Gather>` callbacks, so stale/replayed speech callbacks cannot satisfy a newer pending transcript turn. Unauthenticated webhook requests are rejected before body reads when the provider’s required signature headers are missing. The voice-call webhook uses the shared pre-auth body profile (64 KB / 5 seconds) plus a per-IP in-flight cap before signature verification. Example with a stable public host:

## TTS for calls

Voice Call uses the core `messages.tts` configuration for streaming speech on calls. You can override it under the plugin config with the **same shape** — it deep‑merges with `messages.tts`.

Notes:

### More examples

Use core TTS only (no override):

Override to ElevenLabs just for calls (keep core default elsewhere):

```
{
  plugins: {
    entries: {
      "voice-call": {
        config: {
          tts: {
            provider: "elevenlabs",
            elevenlabs: {
              apiKey: "elevenlabs_key",
              voiceId: "pMsXgVXv3BLzUgSXRplE",
              modelId: "eleven_multilingual_v2",
            },
          },
        },
      },
    },
  },
}
```

Override only the OpenAI model for calls (deep‑merge example):

## Inbound calls

Inbound policy defaults to `disabled`. To enable inbound calls, set:

`inboundPolicy: "allowlist"` is a low-assurance caller-ID screen. The plugin normalizes the provider-supplied `From` value and compares it to `allowFrom`. Webhook verification authenticates provider delivery and payload integrity, but it does not prove PSTN/VoIP caller-number ownership. Treat `allowFrom` as caller-ID filtering, not strong caller identity. Auto-responses use the agent system. Tune with:

### Spoken output contract

For auto-responses, Voice Call appends a strict spoken-output contract to the system prompt:

Voice Call then extracts speech text defensively:

This keeps spoken playback focused on caller-facing text and avoids leaking planning text into audio.

### Conversation startup behavior

For outbound `conversation` calls, first-message handling is tied to live playback state:

### Twilio stream disconnect grace

When a Twilio media stream disconnects, Voice Call waits `2000ms` before auto-ending the call:

## CLI

`latency` reads `calls.jsonl` from the default voice-call storage path. Use `--file <path>` to point at a different log and `--last <n>` to limit analysis to the last N records (default 200). Output includes p50/p90/p99 for turn latency and listen-wait times.

## Agent tool

Tool name: `voice_call` Actions:

This repo ships a matching skill doc at `skills/voice-call/SKILL.md`.

## Gateway RPC

----
url: https://docs.openclaw.ai/gateway/bonjour
----

# Bonjour Discovery - OpenClaw

## [​](#bonjour-/-mdns-discovery)Bonjour / mDNS discovery

OpenClaw uses Bonjour (mDNS / DNS‑SD) as a **LAN‑only convenience** to discover an active Gateway (WebSocket endpoint). It is best‑effort and does **not** replace SSH or Tailnet-based connectivity.

## [​](#wide-area-bonjour-unicast-dns-sd-over-tailscale)Wide-area Bonjour (Unicast DNS-SD) over Tailscale

If the node and gateway are on different networks, multicast mDNS won’t cross the boundary. You can keep the same discovery UX by switching to **unicast DNS‑SD** (“Wide‑Area Bonjour”) over Tailscale. High‑level steps:

1. Run a DNS server on the gateway host (reachable over Tailnet).
2. Publish DNS‑SD records for `_openclaw-gw._tcp` under a dedicated zone (example: `openclaw.internal.`).
3. Configure Tailscale **split DNS** so your chosen domain resolves via that DNS server for clients (including iOS).

OpenClaw supports any discovery domain; `openclaw.internal.` is just an example. iOS/Android nodes browse both `local.` and your configured wide‑area domain.

### [​](#gateway-config-recommended)Gateway config (recommended)

```
{
  gateway: { bind: "tailnet" }, // tailnet-only (recommended)
  discovery: { wideArea: { enabled: true } }, // enables wide-area DNS-SD publishing
}
```

### [​](#one-time-dns-server-setup-gateway-host)One-time DNS server setup (gateway host)

```
openclaw dns setup --apply
```

This installs CoreDNS and configures it to:

* listen on port 53 only on the gateway’s Tailscale interfaces
* serve your chosen domain (example: `openclaw.internal.`) from `~/.openclaw/dns/<domain>.db`

Validate from a tailnet‑connected machine:

```
dns-sd -B _openclaw-gw._tcp openclaw.internal.
dig @<TAILNET_IPV4> -p 53 _openclaw-gw._tcp.openclaw.internal PTR +short
```

### [​](#tailscale-dns-settings)Tailscale DNS settings

In the Tailscale admin console:

* Add a nameserver pointing at the gateway’s tailnet IP (UDP/TCP 53).
* Add split DNS so your discovery domain uses that nameserver.

Once clients accept tailnet DNS, iOS nodes can browse `_openclaw-gw._tcp` in your discovery domain without multicast.

### [​](#gateway-listener-security-recommended)Gateway listener security (recommended)

The Gateway WS port (default `18789`) binds to loopback by default. For LAN/tailnet access, bind explicitly and keep auth enabled. For tailnet‑only setups:

* Set `gateway.bind: "tailnet"` in `~/.openclaw/openclaw.json`.
* Restart the Gateway (or restart the macOS menubar app).

## [​](#what-advertises)What advertises

Only the Gateway advertises `_openclaw-gw._tcp`.

## [​](#service-types)Service types

* `_openclaw-gw._tcp` — gateway transport beacon (used by macOS/iOS/Android nodes).

## [​](#txt-keys-non-secret-hints)TXT keys (non-secret hints)

The Gateway advertises small non‑secret hints to make UI flows convenient:

* `role=gateway`
* `displayName=<friendly name>`
* `lanHost=<hostname>.local`
* `gatewayPort=<port>` (Gateway WS + HTTP)
* `gatewayTls=1` (only when TLS is enabled)
* `gatewayTlsSha256=<sha256>` (only when TLS is enabled and fingerprint is available)
* `canvasPort=<port>` (only when the canvas host is enabled; currently the same as `gatewayPort`)
* `sshPort=<port>` (defaults to 22 when not overridden)
* `transport=gateway`
* `cliPath=<path>` (optional; absolute path to a runnable `openclaw` entrypoint)
* `tailnetDns=<magicdns>` (optional hint when Tailnet is available)

Security notes:

* Bonjour/mDNS TXT records are **unauthenticated**. Clients must not treat TXT as authoritative routing.
* Clients should route using the resolved service endpoint (SRV + A/AAAA). Treat `lanHost`, `tailnetDns`, `gatewayPort`, and `gatewayTlsSha256` as hints only.
* TLS pinning must never allow an advertised `gatewayTlsSha256` to override a previously stored pin.
* iOS/Android nodes should treat discovery-based direct connects as **TLS-only** and require explicit user confirmation before trusting a first-time fingerprint.

## [​](#debugging-on-macos)Debugging on macOS

Useful built‑in tools:

* Browse instances:
  ```
  dns-sd -B _openclaw-gw._tcp local.
  ```
* Resolve one instance (replace `<instance>`):
  ```
  dns-sd -L "<instance>" _openclaw-gw._tcp local.
  ```

If browsing works but resolving fails, you’re usually hitting a LAN policy or mDNS resolver issue.

## [​](#debugging-in-gateway-logs)Debugging in Gateway logs

The Gateway writes a rolling log file (printed on startup as `gateway log file: ...`). Look for `bonjour:` lines, especially:

* `bonjour: advertise failed ...`
* `bonjour: ... name conflict resolved` / `hostname conflict resolved`
* `bonjour: watchdog detected non-announced service ...`

## [​](#debugging-on-ios-node)Debugging on iOS node

The iOS node uses `NWBrowser` to discover `_openclaw-gw._tcp`. To capture logs:

* Settings → Gateway → Advanced → **Discovery Debug Logs**
* Settings → Gateway → Advanced → **Discovery Logs** → reproduce → **Copy**

The log includes browser state transitions and result‑set changes.

## [​](#common-failure-modes)Common failure modes

* **Bonjour doesn’t cross networks**: use Tailnet or SSH.
* **Multicast blocked**: some Wi‑Fi networks disable mDNS.
* **Sleep / interface churn**: macOS may temporarily drop mDNS results; retry.
* **Browse works but resolve fails**: keep machine names simple (avoid emojis or punctuation), then restart the Gateway. The service instance name derives from the host name, so overly complex names can confuse some resolvers.

## [​](#escaped-instance-names-\032)Escaped instance names (`\032`)

Bonjour/DNS‑SD often escapes bytes in service instance names as decimal `\DDD` sequences (e.g. spaces become `\032`).

* This is normal at the protocol level.
* UIs should decode for display (iOS uses `BonjourEscapes.decode`).

## [​](#disabling-/-configuration)Disabling / configuration

* `OPENCLAW_DISABLE_BONJOUR=1` disables advertising (legacy: `OPENCLAW_DISABLE_BONJOUR`).
* `gateway.bind` in `~/.openclaw/openclaw.json` controls the Gateway bind mode.
* `OPENCLAW_SSH_PORT` overrides the SSH port advertised in TXT (legacy: `OPENCLAW_SSH_PORT`).
* `OPENCLAW_TAILNET_DNS` publishes a MagicDNS hint in TXT (legacy: `OPENCLAW_TAILNET_DNS`).
* `OPENCLAW_CLI_PATH` overrides the advertised CLI path (legacy: `OPENCLAW_CLI_PATH`).

## [​](#related-docs)Related docs

* Discovery policy and transport selection: [Discovery](https://docs.openclaw.ai/gateway/discovery)
* Node pairing + approvals: [Gateway pairing](https://docs.openclaw.ai/gateway/pairing)

----
url: https://docs.openclaw.ai/plugins/sdk-overview
----

# Plugin SDK Overview - OpenClaw

The plugin SDK is the typed contract between plugins and core. This page is the reference for **what to import** and **what you can register**.

## Import convention

Always import from a specific subpath:

Each subpath is a small, self-contained module. This keeps startup fast and prevents circular dependency issues.

## Subpath reference

The most commonly used subpaths, grouped by purpose. The full list of 100+ subpaths is in `scripts/lib/plugin-sdk-entrypoints.json`.

### Plugin entry

| Subpath                   | Key exports                                                                                                                            |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `plugin-sdk/plugin-entry` | `definePluginEntry`                                                                                                                    |
| `plugin-sdk/core`         | `defineChannelPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase`, `defineSetupPluginEntry`, `buildChannelConfigSchema` |

Channel subpaths

| Subpath                             | Key exports                                                     |
| ----------------------------------- | --------------------------------------------------------------- |
| `plugin-sdk/channel-setup`          | `createOptionalChannelSetupSurface`                             |
| `plugin-sdk/channel-pairing`        | `createChannelPairingController`                                |
| `plugin-sdk/channel-reply-pipeline` | `createChannelReplyPipeline`                                    |
| `plugin-sdk/channel-config-helpers` | `createHybridChannelConfigAdapter`                              |
| `plugin-sdk/channel-config-schema`  | Channel config schema types                                     |
| `plugin-sdk/channel-policy`         | `resolveChannelGroupRequireMention`                             |
| `plugin-sdk/channel-lifecycle`      | `createAccountStatusSink`                                       |
| `plugin-sdk/channel-inbound`        | Debounce, mention matching, envelope helpers                    |
| `plugin-sdk/channel-send-result`    | Reply result types                                              |
| `plugin-sdk/channel-actions`        | `createMessageToolButtonsSchema`, `createMessageToolCardSchema` |
| `plugin-sdk/channel-targets`        | Target parsing/matching helpers                                 |
| `plugin-sdk/channel-contract`       | Channel contract types                                          |
| `plugin-sdk/channel-feedback`       | Feedback/reaction wiring                                        |

Provider subpaths

| Subpath                       | Key exports                                                                                |
| ----------------------------- | ------------------------------------------------------------------------------------------ |
| `plugin-sdk/provider-auth`    | `createProviderApiKeyAuthMethod`, `ensureApiKeyFromOptionEnvOrPrompt`, `upsertAuthProfile` |
| `plugin-sdk/provider-models`  | `normalizeModelCompat`                                                                     |
| `plugin-sdk/provider-catalog` | Catalog type re-exports                                                                    |
| `plugin-sdk/provider-usage`   | `fetchClaudeUsage` and similar                                                             |
| `plugin-sdk/provider-stream`  | Stream wrapper types                                                                       |
| `plugin-sdk/provider-onboard` | Onboarding config patch helpers                                                            |

Auth and security subpaths

| Subpath                      | Key exports                    |
| ---------------------------- | ------------------------------ |
| `plugin-sdk/command-auth`    | `resolveControlCommandGate`    |
| `plugin-sdk/allow-from`      | `formatAllowFromLowercase`     |
| `plugin-sdk/secret-input`    | Secret input parsing helpers   |
| `plugin-sdk/webhook-ingress` | Webhook request/target helpers |

Runtime and storage subpaths

| Subpath                        | Key exports                          |
| ------------------------------ | ------------------------------------ |
| `plugin-sdk/runtime-store`     | `createPluginRuntimeStore`           |
| `plugin-sdk/config-runtime`    | Config load/write helpers            |
| `plugin-sdk/infra-runtime`     | System event/heartbeat helpers       |
| `plugin-sdk/agent-runtime`     | Agent dir/identity/workspace helpers |
| `plugin-sdk/directory-runtime` | Config-backed directory query/dedup  |
| `plugin-sdk/keyed-async-queue` | `KeyedAsyncQueue`                    |

Capability and testing subpaths

| Subpath                          | Key exports                                                 |
| -------------------------------- | ----------------------------------------------------------- |
| `plugin-sdk/image-generation`    | Image generation provider types                             |
| `plugin-sdk/media-understanding` | Media understanding provider types                          |
| `plugin-sdk/speech`              | Speech provider types                                       |
| `plugin-sdk/testing`             | `installCommonResolveTargetErrorCases`, `shouldAckReaction` |

## Registration API

The `register(api)` callback receives an `OpenClawPluginApi` object with these methods:

### Capability registration

| Method                                        | What it registers              |
| --------------------------------------------- | ------------------------------ |
| `api.registerProvider(...)`                   | Text inference (LLM)           |
| `api.registerChannel(...)`                    | Messaging channel              |
| `api.registerSpeechProvider(...)`             | Text-to-speech / STT synthesis |
| `api.registerMediaUnderstandingProvider(...)` | Image/audio/video analysis     |
| `api.registerImageGenerationProvider(...)`    | Image generation               |
| `api.registerWebSearchProvider(...)`          | Web search                     |

### Tools and commands

| Method                          | What it registers                             |
| ------------------------------- | --------------------------------------------- |
| `api.registerTool(tool, opts?)` | Agent tool (required or `{ optional: true }`) |
| `api.registerCommand(def)`      | Custom command (bypasses the LLM)             |

### Infrastructure

| Method                                         | What it registers     |
| ---------------------------------------------- | --------------------- |
| `api.registerHook(events, handler, opts?)`     | Event hook            |
| `api.registerHttpRoute(params)`                | Gateway HTTP endpoint |
| `api.registerGatewayMethod(name, handler)`     | Gateway RPC method    |
| `api.registerCli(registrar, opts?)`            | CLI subcommand        |
| `api.registerService(service)`                 | Background service    |
| `api.registerInteractiveHandler(registration)` | Interactive handler   |

### Exclusive slots

| Method                                     | What it registers                     |
| ------------------------------------------ | ------------------------------------- |
| `api.registerContextEngine(id, factory)`   | Context engine (one active at a time) |
| `api.registerMemoryPromptSection(builder)` | Memory prompt section builder         |

### Events and lifecycle

| Method                                       | What it does                  |
| -------------------------------------------- | ----------------------------- |
| `api.on(hookName, handler, opts?)`           | Typed lifecycle hook          |
| `api.onConversationBindingResolved(handler)` | Conversation binding callback |

### API object fields

| Field                    | Type                      | Description                                                     |
| ------------------------ | ------------------------- | --------------------------------------------------------------- |
| `api.id`                 | `string`                  | Plugin id                                                       |
| `api.name`               | `string`                  | Display name                                                    |
| `api.version`            | `string?`                 | Plugin version (optional)                                       |
| `api.description`        | `string?`                 | Plugin description (optional)                                   |
| `api.source`             | `string`                  | Plugin source path                                              |
| `api.rootDir`            | `string?`                 | Plugin root directory (optional)                                |
| `api.config`             | `OpenClawConfig`          | Current config snapshot                                         |
| `api.pluginConfig`       | `Record<string, unknown>` | Plugin-specific config from `plugins.entries.<id>.config`       |
| `api.runtime`            | `PluginRuntime`           | [Runtime helpers](https://docs.openclaw.ai/plugins/sdk-runtime) |
| `api.logger`             | `PluginLogger`            | Scoped logger (`debug`, `info`, `warn`, `error`)                |
| `api.registrationMode`   | `PluginRegistrationMode`  | `"full"`, `"setup-only"`, or `"setup-runtime"`                  |
| `api.resolvePath(input)` | `(string) => string`      | Resolve path relative to plugin root                            |

## Internal module convention

Within your plugin, use local barrel files for internal imports:

----
url: https://docs.openclaw.ai/tools/kimi-search
----

# Kimi Search - OpenClaw

OpenClaw supports Kimi as a `web_search` provider, using Moonshot web search to produce AI-synthesized answers with citations.

## Get an API key

## Config

```
{
  plugins: {
    entries: {
      moonshot: {
        config: {
          webSearch: {
            apiKey: "sk-...", // optional if KIMI_API_KEY or MOONSHOT_API_KEY is set
          },
        },
      },
    },
  },
  tools: {
    web: {
      search: {
        provider: "kimi",
      },
    },
  },
}
```

**Environment alternative:** set `KIMI_API_KEY` or `MOONSHOT_API_KEY` in the Gateway environment. For a gateway install, put it in `~/.openclaw/.env`.

## How it works

Kimi uses Moonshot web search to synthesize answers with inline citations, similar to Gemini and Grok’s grounded response approach.

## Supported parameters

Kimi search supports the standard `query` and `count` parameters. Provider-specific filters are not currently supported.

----
url: https://docs.openclaw.ai/concepts/session-tool
----

# Session Tools - OpenClaw

Goal: small, hard-to-misuse tool set so agents can list sessions, fetch history, and send to another session.

## Tool Names

## Key Model

`global` and `unknown` are reserved values and are never listed. If `session.scope = "global"`, we alias it to `main` for all tools so callers never see `global`.

## sessions\_list

List sessions as an array of rows. Parameters:

Behavior:

Row shape (JSON):

## sessions\_history

Fetch transcript for one session. Parameters:

Behavior:

## Gateway session history and live transcript APIs

Control UI and gateway clients can use the lower level history and live transcript surfaces directly. HTTP:

WebSocket:

## sessions\_send

Send a message into another session. Parameters:

Behavior:

* `timeoutSeconds = 0`: enqueue and return `{ runId, status: "accepted" }`.
* `timeoutSeconds > 0`: wait up to N seconds for completion, then return `{ runId, status: "ok", reply }`.
* If wait times out: `{ runId, status: "timeout", error }`. Run continues; call `sessions_history` later.
* If the run fails: `{ runId, status: "error", error }`.
* Announce delivery runs after the primary run completes and is best-effort; `status: "ok"` does not guarantee the announce was delivered.
* Waits via gateway `agent.wait` (server-side) so reconnects don’t drop the wait.
* Agent-to-agent message context is injected for the primary run.
* Inter-session messages are persisted with `message.provenance.kind = "inter_session"` so transcript readers can distinguish routed agent instructions from external user input.
* After the primary run completes, OpenClaw runs a **reply-back loop**:
* Once the loop ends, OpenClaw runs the **agent‑to‑agent announce step** (target agent only):

## Channel Field

## Security / Send Policy

Policy-based blocking by channel/chat type (not per session id).

Runtime override (per session entry):

Enforcement points:

## sessions\_spawn

Spawn an isolated delegated session.

Parameters:

Allowlist:

Discovery:

Behavior:

## Sandbox Session Visibility

Session tools can be scoped to reduce cross-session access. Default behavior:

Config:

Notes:

----
url: https://docs.openclaw.ai/install/ansible
----

# Ansible - OpenClaw

## Ansible Installation

Deploy OpenClaw to production servers with **[openclaw-ansible](https://github.com/openclaw/openclaw-ansible)** — an automated installer with security-first architecture.

## Prerequisites

| Requirement | Details                                                   |
| ----------- | --------------------------------------------------------- |
| **OS**      | Debian 11+ or Ubuntu 20.04+                               |
| **Access**  | Root or sudo privileges                                   |
| **Network** | Internet connection for package installation              |
| **Ansible** | 2.14+ (installed automatically by the quick-start script) |

## What You Get

## Quick Start

One-command install:

## What Gets Installed

The Ansible playbook installs and configures:

1. **Tailscale** — mesh VPN for secure remote access
2. **UFW firewall** — SSH + Tailscale ports only
3. **Docker CE + Compose V2** — for agent sandboxes
4. **Node.js 24 + pnpm** — runtime dependencies (Node 22 LTS, currently `22.16+`, remains supported)
5. **OpenClaw** — host-based, not containerized
6. **Systemd service** — auto-start with security hardening

## Post-Install Setup

### Quick Commands

## Security Architecture

The deployment uses a 4-layer defense model:

1. **Firewall (UFW)** — only SSH (22) + Tailscale (41641/udp) exposed publicly
2. **VPN (Tailscale)** — gateway accessible only via VPN mesh
3. **Docker isolation** — DOCKER-USER iptables chain prevents external port exposure
4. **Systemd hardening** — NoNewPrivileges, PrivateTmp, unprivileged user

To verify your external attack surface:

Only port 22 (SSH) should be open. All other services (gateway, Docker) are locked down. Docker is installed for agent sandboxes (isolated tool execution), not for running the gateway itself. See [Multi-Agent Sandbox and Tools](https://docs.openclaw.ai/tools/multi-agent-sandbox-tools) for sandbox configuration.

## Manual Installation

If you prefer manual control over the automation:

## Updating

The Ansible installer sets up OpenClaw for manual updates. See [Updating](https://docs.openclaw.ai/install/updating) for the standard update flow. To re-run the Ansible playbook (for example, for configuration changes):

This is idempotent and safe to run multiple times.

## Troubleshooting

## Advanced Configuration

For detailed security architecture and troubleshooting, see the openclaw-ansible repo:

----
url: https://docs.openclaw.ai/cli/onboard
----

# onboard - OpenClaw

## [​](#openclaw-onboard)`openclaw onboard`

Interactive onboarding for local or remote Gateway setup.

## [​](#related-guides)Related guides

* CLI onboarding hub: [Onboarding (CLI)](https://docs.openclaw.ai/start/wizard)
* Onboarding overview: [Onboarding Overview](https://docs.openclaw.ai/start/onboarding-overview)
* CLI onboarding reference: [CLI Setup Reference](https://docs.openclaw.ai/start/wizard-cli-reference)
* CLI automation: [CLI Automation](https://docs.openclaw.ai/start/wizard-cli-automation)
* macOS onboarding: [Onboarding (macOS App)](https://docs.openclaw.ai/start/onboarding)

## [​](#examples)Examples

```
openclaw onboard
openclaw onboard --flow quickstart
openclaw onboard --flow manual
openclaw onboard --mode remote --remote-url wss://gateway-host:18789
```

For plaintext private-network `ws://` targets (trusted networks only), set `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` in the onboarding process environment. Non-interactive custom provider:

```
openclaw onboard --non-interactive \
  --auth-choice custom-api-key \
  --custom-base-url "https://llm.example.com/v1" \
  --custom-model-id "foo-large" \
  --custom-api-key "$CUSTOM_API_KEY" \
  --secret-input-mode plaintext \
  --custom-compatibility openai
```

`--custom-api-key` is optional in non-interactive mode. If omitted, onboarding checks `CUSTOM_API_KEY`. Non-interactive Ollama:

```
openclaw onboard --non-interactive \
  --auth-choice ollama \
  --custom-base-url "http://ollama-host:11434" \
  --custom-model-id "qwen3.5:27b" \
  --accept-risk
```

`--custom-base-url` defaults to `http://127.0.0.1:11434`. `--custom-model-id` is optional; if omitted, onboarding uses Ollama’s suggested defaults. Cloud model IDs such as `kimi-k2.5:cloud` also work here. Store provider keys as refs instead of plaintext:

```
openclaw onboard --non-interactive \
  --auth-choice openai-api-key \
  --secret-input-mode ref \
  --accept-risk
```

With `--secret-input-mode ref`, onboarding writes env-backed refs instead of plaintext key values. For auth-profile backed providers this writes `keyRef` entries; for custom providers this writes `models.providers.<id>.apiKey` as an env ref (for example `{ source: "env", provider: "default", id: "CUSTOM_API_KEY" }`). Non-interactive `ref` mode contract:

* Set the provider env var in the onboarding process environment (for example `OPENAI_API_KEY`).
* Do not pass inline key flags (for example `--openai-api-key`) unless that env var is also set.
* If an inline key flag is passed without the required env var, onboarding fails fast with guidance.

Gateway token options in non-interactive mode:

* `--gateway-auth token --gateway-token <token>` stores a plaintext token.
* `--gateway-auth token --gateway-token-ref-env <name>` stores `gateway.auth.token` as an env SecretRef.
* `--gateway-token` and `--gateway-token-ref-env` are mutually exclusive.
* `--gateway-token-ref-env` requires a non-empty env var in the onboarding process environment.
* With `--install-daemon`, when token auth requires a token, SecretRef-managed gateway tokens are validated but not persisted as resolved plaintext in supervisor service environment metadata.
* With `--install-daemon`, if token mode requires a token and the configured token SecretRef is unresolved, onboarding fails closed with remediation guidance.
* With `--install-daemon`, if both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, onboarding blocks install until mode is set explicitly.

Example:

```
export OPENCLAW_GATEWAY_TOKEN="your-token"
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice skip \
  --gateway-auth token \
  --gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN \
  --accept-risk
```

Non-interactive local gateway health:

* Unless you pass `--skip-health`, onboarding waits for a reachable local gateway before it exits successfully.
* `--install-daemon` starts the managed gateway install path first. Without it, you must already have a local gateway running, for example `openclaw gateway run`.
* If you only want config/workspace/bootstrap writes in automation, use `--skip-health`.
* On native Windows, `--install-daemon` tries Scheduled Tasks first and falls back to a per-user Startup-folder login item if task creation is denied.

Interactive onboarding behavior with reference mode:

* Choose **Use secret reference** when prompted.

* Then choose either:

  * Environment variable
  * Configured secret provider (`file` or `exec`)

* Onboarding performs a fast preflight validation before saving the ref.
  * If validation fails, onboarding shows the error and lets you retry.

Non-interactive Z.AI endpoint choices: Note: `--auth-choice zai-api-key` now auto-detects the best Z.AI endpoint for your key (prefers the general API with `zai/glm-5`). If you specifically want the GLM Coding Plan endpoints, pick `zai-coding-global` or `zai-coding-cn`.

```
# Promptless endpoint selection
openclaw onboard --non-interactive \
  --auth-choice zai-coding-global \
  --zai-api-key "$ZAI_API_KEY"

# Other Z.AI endpoint choices:
# --auth-choice zai-coding-cn
# --auth-choice zai-global
# --auth-choice zai-cn
```

Non-interactive Mistral example:

```
openclaw onboard --non-interactive \
  --auth-choice mistral-api-key \
  --mistral-api-key "$MISTRAL_API_KEY"
```

Flow notes:

* `quickstart`: minimal prompts, auto-generates a gateway token.
* `manual`: full prompts for port/bind/auth (alias of `advanced`).
* Local onboarding DM scope behavior: [CLI Setup Reference](https://docs.openclaw.ai/start/wizard-cli-reference#outputs-and-internals).
* Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
* Custom Provider: connect any OpenAI or Anthropic compatible endpoint, including hosted providers not listed. Use Unknown to auto-detect.

## [​](#common-follow-up-commands)Common follow-up commands

```
openclaw configure
openclaw agents add <name>
```

`--json` does not imply non-interactive mode. Use `--non-interactive` for scripts.

----
url: https://docs.openclaw.ai/plugins/community
----

# Community Plugins - OpenClaw

Community plugins are third-party packages that extend OpenClaw with new channels, tools, providers, or other capabilities. They are built and maintained by the community, published on [ClawHub](https://docs.openclaw.ai/tools/clawhub) or npm, and installable with a single command.

OpenClaw checks ClawHub first and falls back to npm automatically.

## Listed plugins

### Codex App Server Bridge

Independent OpenClaw bridge for Codex App Server conversations. Bind a chat to a Codex thread, talk to it with plain text, and control it with chat-native commands for resume, planning, review, model selection, compaction, and more.

### DingTalk

Enterprise robot integration using Stream mode. Supports text, images, and file messages via any DingTalk client.

### Lossless Claw (LCM)

Lossless Context Management plugin for OpenClaw. DAG-based conversation summarization with incremental compaction — preserves full context fidelity while reducing token usage.

### Opik

Official plugin that exports agent traces to Opik. Monitor agent behavior, cost, tokens, errors, and more.

### QQbot

Connect OpenClaw to QQ via the QQ Bot API. Supports private chats, group mentions, channel messages, and rich media including voice, images, videos, and files.

### wecom

OpenClaw Enterprise WeCom Channel Plugin. A bot plugin powered by WeCom AI Bot WebSocket persistent connections, supports direct messages & group chats, streaming replies, and proactive messaging.

## Submit your plugin

We welcome community plugins that are useful, documented, and safe to operate.

## Quality bar

| Requirement                 | Why                                           |
| --------------------------- | --------------------------------------------- |
| Published on ClawHub or npm | Users need `openclaw plugins install` to work |
| Public GitHub repo          | Source review, issue tracking, transparency   |
| Setup and usage docs        | Users need to know how to configure it        |
| Active maintenance          | Recent updates or responsive issue handling   |

Low-effort wrappers, unclear ownership, or unmaintained packages may be declined.

----
url: https://docs.openclaw.ai/tools/subagents
----

# Sub-Agents - OpenClaw

Sub-agents are background agent runs spawned from an existing agent run. They run in their own session (`agent:<agentId>:subagent:<uuid>`) and, when finished, **announce** their result back to the requester chat channel.

## Slash command

Use `/subagents` to inspect or control sub-agent runs for the **current session**:

Thread binding controls: These commands work on channels that support persistent thread bindings. See **Thread supporting channels** below.

`/subagents info` shows run metadata (status, timestamps, session id, transcript path, cleanup).

### Spawn behavior

`/subagents spawn` starts a background sub-agent as a user command, not an internal relay, and it sends one final completion update back to the requester chat when the run finishes.

Primary goals:

Cost note: each sub-agent has its **own** context and token usage. For heavy or repetitive tasks, set a cheaper model for sub-agents and keep your main agent on a higher-quality model. You can configure this via `agents.defaults.subagents.model` or per-agent overrides.

## Tool

Use `sessions_spawn`:

Tool params:

* `task` (required)
* `label?` (optional)
* `agentId?` (optional; spawn under another agent id if allowed)
* `model?` (optional; overrides the sub-agent model; invalid values are skipped and the sub-agent runs on the default model with a warning in the tool result)
* `thinking?` (optional; overrides thinking level for the sub-agent run)
* `runTimeoutSeconds?` (defaults to `agents.defaults.subagents.runTimeoutSeconds` when set, otherwise `0`; when set, the sub-agent run is aborted after N seconds)
* `thread?` (default `false`; when `true`, requests channel thread binding for this sub-agent session)
* `mode?` (`run|session`)
* `cleanup?` (`delete|keep`, default `keep`)
* `sandbox?` (`inherit|require`, default `inherit`; `require` rejects spawn unless target child runtime is sandboxed)
* `sessions_spawn` does **not** accept channel-delivery params (`target`, `channel`, `to`, `threadId`, `replyTo`, `transport`). For delivery, use `message`/`sessions_send` from the spawned run.

## Thread-bound sessions

When thread bindings are enabled for a channel, a sub-agent can stay bound to a thread so follow-up user messages in that thread keep routing to the same sub-agent session.

### Thread supporting channels

Quick flow:

1. Spawn with `sessions_spawn` using `thread: true` (and optionally `mode: "session"`).
2. OpenClaw creates or binds a thread to that session target in the active channel.
3. Replies and follow-up messages in that thread route to the bound session.
4. Use `/session idle` to inspect/update inactivity auto-unfocus and `/session max-age` to control the hard cap.
5. Use `/unfocus` to detach manually.

Manual controls:

Config switches:

See [Configuration Reference](https://docs.openclaw.ai/gateway/configuration-reference) and [Slash commands](https://docs.openclaw.ai/tools/slash-commands) for current adapter details. Allowlist:

Discovery:

Auto-archive:

## Nested Sub-Agents

By default, sub-agents cannot spawn their own sub-agents (`maxSpawnDepth: 1`). You can enable one level of nesting by setting `maxSpawnDepth: 2`, which allows the **orchestrator pattern**: main → orchestrator sub-agent → worker sub-sub-agents.

### How to enable

### Depth levels

| Depth | Session key shape                            | Role                                          | Can spawn?                   |
| ----- | -------------------------------------------- | --------------------------------------------- | ---------------------------- |
| 0     | `agent:<id>:main`                            | Main agent                                    | Always                       |
| 1     | `agent:<id>:subagent:<uuid>`                 | Sub-agent (orchestrator when depth 2 allowed) | Only if `maxSpawnDepth >= 2` |
| 2     | `agent:<id>:subagent:<uuid>:subagent:<uuid>` | Sub-sub-agent (leaf worker)                   | Never                        |

### Announce chain

Results flow back up the chain:

1. Depth-2 worker finishes → announces to its parent (depth-1 orchestrator)
2. Depth-1 orchestrator receives the announce, synthesizes results, finishes → announces to main
3. Main agent receives the announce and delivers to the user

Each level only sees announces from its direct children.

### Tool policy by depth

### Per-agent spawn limit

Each agent session (at any depth) can have at most `maxChildrenPerAgent` (default: 5) active children at a time. This prevents runaway fan-out from a single orchestrator.

### Cascade stop

Stopping a depth-1 orchestrator automatically stops all its depth-2 children:

## Authentication

Sub-agent auth is resolved by **agent id**, not by session type:

Note: the merge is additive, so main profiles are always available as fallbacks. Fully isolated auth per agent is not supported yet.

## Announce

Sub-agents report back via an announce step:

Announce payloads include a stats line at the end (even when wrapped):

## Tool Policy (sub-agent tools)

By default, sub-agents get **all tools except session tools** and system tools:

When `maxSpawnDepth >= 2`, depth-1 orchestrator sub-agents additionally receive `sessions_spawn`, `subagents`, `sessions_list`, and `sessions_history` so they can manage their children. Override via config:

```
{
  agents: {
    defaults: {
      subagents: {
        maxConcurrent: 1,
      },
    },
  },
  tools: {
    subagents: {
      tools: {
        // deny wins
        deny: ["gateway", "cron"],
        // if allow is set, it becomes allow-only (deny still wins)
        // allow: ["read", "exec", "process"]
      },
    },
  },
}
```

## Concurrency

Sub-agents use a dedicated in-process queue lane:

## Stopping

## Limitations

----
url: https://docs.openclaw.ai/reference/transcript-hygiene
----

# Transcript Hygiene - OpenClaw

## Transcript Hygiene (Provider Fixups)

This document describes **provider-specific fixes** applied to transcripts before a run (building model context). These are **in-memory** adjustments used to satisfy strict provider requirements. These hygiene steps do **not** rewrite the stored JSONL transcript on disk; however, a separate session-file repair pass may rewrite malformed JSONL files by dropping invalid lines before the session is loaded. When a repair occurs, the original file is backed up alongside the session file. Scope includes:

If you need transcript storage details, see:

***

## Where this runs

All transcript hygiene is centralized in the embedded runner:

The policy uses `provider`, `modelApi`, and `modelId` to decide what to apply. Separate from transcript hygiene, session files are repaired (if needed) before load:

***

## Global rule: image sanitization

Image payloads are always sanitized to prevent provider-side rejection due to size limits (downscale/recompress oversized base64 images). This also helps control image-driven token pressure for vision-capable models. Lower max dimensions generally reduce token usage; higher dimensions preserve detail. Implementation:

***

## Global rule: malformed tool calls

Assistant tool-call blocks that are missing both `input` and `arguments` are dropped before model context is built. This prevents provider rejections from partially persisted tool calls (for example, after a rate limit failure). Implementation:

***

## Global rule: inter-session input provenance

When an agent sends a prompt into another session via `sessions_send` (including agent-to-agent reply/announce steps), OpenClaw persists the created user turn with:

This metadata is written at transcript append time and does not change role (`role: "user"` remains for provider compatibility). Transcript readers can use this to avoid treating routed internal prompts as end-user-authored instructions. During context rebuild, OpenClaw also prepends a short `[Inter-session message]` marker to those user turns in-memory so the model can distinguish them from external end-user instructions.

***

## Provider matrix (current behavior)

**OpenAI / OpenAI Codex**

**Google (Generative AI / Gemini CLI / Antigravity)**

**Anthropic / Minimax (Anthropic-compatible)**

**Mistral (including model-id based detection)**

**OpenRouter Gemini**

**Everything else**

***

## Historical behavior (pre-2026.1.22)

Before the 2026.1.22 release, OpenClaw applied multiple layers of transcript hygiene:

This complexity caused cross-provider regressions (notably `openai-responses` `call_id|fc_id` pairing). The 2026.1.22 cleanup removed the extension, centralized logic in the runner, and made OpenAI **no-touch** beyond image sanitization.

----
url: https://docs.openclaw.ai/concepts/models
----

# Models CLI - OpenClaw

See [/concepts/model-failover](https://docs.openclaw.ai/concepts/model-failover) for auth profile rotation, cooldowns, and how that interacts with fallbacks. Quick provider overview + examples: [/concepts/model-providers](https://docs.openclaw.ai/concepts/model-providers).

## How model selection works

OpenClaw selects models in this order:

1. **Primary** model (`agents.defaults.model.primary` or `agents.defaults.model`).
2. **Fallbacks** in `agents.defaults.model.fallbacks` (in order).
3. **Provider auth failover** happens inside a provider before moving to the next model.

Related:

## Quick model policy

## Onboarding (recommended)

If you don’t want to hand-edit config, run onboarding:

It can set up model + auth for common providers, including **OpenAI Code (Codex) subscription** (OAuth) and **Anthropic** (API key or `claude setup-token`).

## Config keys (overview)

Model refs are normalized to lowercase. Provider aliases like `z.ai/*` normalize to `zai/*`. Provider configuration examples (including OpenCode) live in [/providers/opencode](https://docs.openclaw.ai/providers/opencode).

## ”Model is not allowed” (and why replies stop)

If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for session overrides. When a user selects a model that isn’t in that allowlist, OpenClaw returns:

This happens **before** a normal reply is generated, so the message can feel like it “didn’t respond.” The fix is to either:

Example allowlist config:

## Switching models in chat (`/model`)

You can switch models for the current session without restarting:

Notes:

Full command behavior/config: [Slash commands](https://docs.openclaw.ai/tools/slash-commands).

## CLI commands

`openclaw models` (no subcommand) is a shortcut for `models status`.

### `models list`

Shows configured models by default. Useful flags:

### `models status`

Shows the resolved primary model, fallbacks, image model, and an auth overview of configured providers. It also surfaces OAuth expiry status for profiles found in the auth store (warns within 24h by default). `--plain` prints only the resolved primary model. OAuth status is always shown (and included in `--json` output). If a configured provider has no credentials, `models status` prints a **Missing auth** section. JSON includes `auth.oauth` (warn window + profiles) and `auth.providers` (effective auth per provider). Use `--check` for automation (exit `1` when missing/expired, `2` when expiring). Auth choice is provider/account dependent. For always-on gateway hosts, API keys are usually the most predictable; subscription token flows are also supported. Example (Anthropic setup-token):

## Scanning (OpenRouter free models)

`openclaw models scan` inspects OpenRouter’s **free model catalog** and can optionally probe models for tool and image support. Key flags:

Probing requires an OpenRouter API key (from auth profiles or `OPENROUTER_API_KEY`). Without a key, use `--no-probe` to list candidates only. Scan results are ranked by:

1. Image support
2. Tool latency
3. Context size
4. Parameter count

Input

When run in a TTY, you can select fallbacks interactively. In non‑interactive mode, pass `--yes` to accept defaults.

## Models registry (`models.json`)

Custom providers in `models.providers` are written into `models.json` under the agent directory (default `~/.openclaw/agents/<agentId>/agent/models.json`). This file is merged by default unless `models.mode` is set to `replace`. Merge mode precedence for matching provider IDs:

Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values. This applies whenever OpenClaw regenerates `models.json`, including command-driven paths like `openclaw agent`.

----
url: https://docs.openclaw.ai/cli
----

# CLI Reference - OpenClaw

This page describes the current CLI behavior. If commands change, update this doc.

## Command pages

## Global flags

## Output styling

## Color palette

OpenClaw uses a lobster palette for CLI output.

Palette source of truth: `src/terminal/palette.ts` (the “lobster palette”).

## Command tree

Note: plugins can add additional top-level commands (for example `openclaw voicecall`).

## Security

## Secrets

## Plugins

Manage extensions and their config:

Most plugin changes require a gateway restart. See [/plugin](https://docs.openclaw.ai/tools/plugin).

## Memory

Vector search over `MEMORY.md` + `memory/*.md`:

## Chat slash commands

Chat messages support `/...` commands (text and native). See [/tools/slash-commands](https://docs.openclaw.ai/tools/slash-commands). Highlights:

## Setup + onboarding

### `setup`

Initialize config + workspace. Options:

Onboarding auto-runs when any onboarding flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`).

### `onboard`

Interactive onboarding for gateway, workspace, and skills. Options:

### `configure`

Interactive configuration wizard (models, channels, skills, gateway).

### `config`

Non-interactive config helpers (get/set/unset/file/validate). Running `openclaw config` with no subcommand launches the wizard. Subcommands:

### `doctor`

Health checks + quick fixes (config + gateway + legacy services). Options:

## Channel helpers

### `channels`

Manage chat channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/Microsoft Teams). Subcommands:

Common options:

`channels login` options:

`channels logout` options:

`channels list` options:

`channels logs` options:

More detail: [/concepts/oauth](https://docs.openclaw.ai/concepts/oauth) Examples:

### `skills`

List and inspect available skills plus readiness info. Subcommands:

Options:

Tip: use `openclaw skills search`, `openclaw skills install`, and `openclaw skills update` for ClawHub-backed skills.

### `pairing`

Approve DM pairing requests across channels. Subcommands:

### `devices`

Manage gateway device pairing entries and per-role device tokens. Subcommands:

### `webhooks gmail`

Gmail Pub/Sub hook setup + runner. See [/automation/gmail-pubsub](https://docs.openclaw.ai/automation/gmail-pubsub). Subcommands:

* `webhooks gmail setup` (requires `--account <email>`; supports `--project`, `--topic`, `--subscription`, `--label`, `--hook-url`, `--hook-token`, `--push-token`, `--bind`, `--port`, `--path`, `--include-body`, `--max-bytes`, `--renew-minutes`, `--tailscale`, `--tailscale-path`, `--tailscale-target`, `--push-endpoint`, `--json`)
* `webhooks gmail run` (runtime overrides for the same flags)

### `dns setup`

Wide-area discovery DNS helper (CoreDNS + Tailscale). See [/gateway/discovery](https://docs.openclaw.ai/gateway/discovery). Options:

## Messaging + agent

### `message`

Unified outbound messaging + channel actions. See: [/cli/message](https://docs.openclaw.ai/cli/message) Subcommands:

Examples:

### `agent`

Run one agent turn via the Gateway (or `--local` embedded). Required:

Options:

### `agents`

Manage isolated agents (workspaces + auth + routing).

#### `agents list`

List configured agents. Options:

#### `agents add [name]`

Add a new isolated agent. Runs the guided wizard unless flags (or `--non-interactive`) are passed; `--workspace` is required in non-interactive mode. Options:

Binding specs use `channel[:accountId]`. When `accountId` is omitted, OpenClaw may resolve account scope via channel defaults/plugin hooks; otherwise it is a channel binding without explicit account scope.

#### `agents bindings`

List routing bindings. Options:

#### `agents bind`

Add routing bindings for an agent. Options:

#### `agents unbind`

Remove routing bindings for an agent. Options:

#### `agents delete <id>`

Delete an agent and prune its workspace + state. Options:

### `acp`

Run the ACP bridge that connects IDEs to the Gateway. See [`acp`](https://docs.openclaw.ai/cli/acp) for full options and examples.

### `status`

Show linked session health and recent recipients. Options:

Notes:

### Usage tracking

OpenClaw can surface provider usage/quota when OAuth/API creds are available. Surfaces:

Notes:

### `health`

Fetch health from the running Gateway. Options:

### `sessions`

List stored conversation sessions. Options:

## Reset / Uninstall

### `reset`

Reset local config/state (keeps the CLI installed). Options:

Notes:

### `uninstall`

Uninstall the gateway service + local data (CLI remains). Options:

Notes:

## Gateway

### `gateway`

Run the WebSocket Gateway. Options:

### `gateway service`

Manage the Gateway service (launchd/systemd/schtasks). Subcommands:

Notes:

* `gateway status` probes the Gateway RPC by default using the service’s resolved port/config (override with `--url/--token/--password`).
* `gateway status` supports `--no-probe`, `--deep`, `--require-rpc`, and `--json` for scripting.
* `gateway status` also surfaces legacy or extra gateway services when it can detect them (`--deep` adds system-level scans). Profile-named OpenClaw services are treated as first-class and aren’t flagged as “extra”.
* `gateway status` prints which config path the CLI uses vs which config the service likely uses (service env), plus the resolved probe target URL.
* If gateway auth SecretRefs are unresolved in the current command path, `gateway status --json` reports `rpc.authWarning` only when probe connectivity/auth fails (warnings are suppressed when probe succeeds).
* On Linux systemd installs, status token-drift checks include both `Environment=` and `EnvironmentFile=` unit sources.
* `gateway install|uninstall|start|stop|restart` support `--json` for scripting (default output stays human-friendly).
* `gateway install` defaults to Node runtime; bun is **not recommended** (WhatsApp/Telegram bugs).
* `gateway install` options: `--port`, `--runtime`, `--token`, `--force`, `--json`.

### `logs`

Tail Gateway file logs via RPC. Notes:

Examples:

### `gateway <subcommand>`

Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands). When you pass `--url`, the CLI does not auto-apply config or environment credentials. Include `--token` or `--password` explicitly. Missing explicit credentials is an error. Subcommands:

Common RPCs:

Tip: when calling `config.set`/`config.apply`/`config.patch` directly, pass `baseHash` from `config.get` if a config already exists.

## Models

See [/concepts/models](https://docs.openclaw.ai/concepts/models) for fallback behavior and scanning strategy. Anthropic setup-token (supported):

Policy note: this is technical compatibility. Anthropic has blocked some subscription usage outside Claude Code in the past; verify current Anthropic terms before relying on setup-token in production.

### `models` (root)

`openclaw models` is an alias for `models status`. Root options:

### `models list`

Options:

### `models status`

Options:

Always includes the auth overview and OAuth expiry status for profiles in the auth store. `--probe` runs live requests (may consume tokens and trigger rate limits).

### `models set <model>`

Set `agents.defaults.model.primary`.

### `models set-image <model>`

Set `agents.defaults.imageModel.primary`.

### `models aliases list|add|remove`

Options:

### `models fallbacks list|add|remove|clear`

Options:

### `models image-fallbacks list|add|remove|clear`

Options:

### `models scan`

Options:

### `models auth add|setup-token|paste-token`

Options:

### `models auth order get|set|clear`

Options:

## System

### `system event`

Enqueue a system event and optionally trigger a heartbeat (Gateway RPC). Required:

Options:

### `system heartbeat last|enable|disable`

Heartbeat controls (Gateway RPC). Options:

### `system presence`

List system presence entries (Gateway RPC). Options:

## Cron

Manage scheduled jobs (Gateway RPC). See [/automation/cron-jobs](https://docs.openclaw.ai/automation/cron-jobs). Subcommands:

All `cron` commands accept `--url`, `--token`, `--timeout`, `--expect-final`.

## Node host

`node` runs a **headless node host** or manages it as a background service. See [`openclaw node`](https://docs.openclaw.ai/cli/node). Subcommands:

Auth notes:

## Nodes

`nodes` talks to the Gateway and targets paired nodes. See [/nodes](https://docs.openclaw.ai/nodes). Common options:

Subcommands:

Camera:

Canvas + screen:

Location:

## Browser

Browser control CLI (dedicated Chrome/Brave/Edge/Chromium). See [`openclaw browser`](https://docs.openclaw.ai/cli/browser) and the [Browser tool](https://docs.openclaw.ai/tools/browser). Common options:

Manage:

Inspect:

Actions:

## Docs search

### `docs [query...]`

Search the live docs index.

## TUI

### `tui`

Open the terminal UI connected to the Gateway. Options:

----
url: https://docs.openclaw.ai/providers/kilocode
----

# Kilo Gateway - OpenClaw

## [​](#kilo-gateway)Kilo Gateway

Kilo Gateway provides a **unified API** that routes requests to many models behind a single endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switching the base URL.

## [​](#getting-an-api-key)Getting an API key

1. Go to [app.kilo.ai](https://app.kilo.ai/)
2. Sign in or create an account
3. Navigate to API Keys and generate a new key

## [​](#cli-setup)CLI setup

```
openclaw onboard --kilocode-api-key <key>
```

Or set the environment variable:

```
export KILOCODE_API_KEY="<your-kilocode-api-key>" # pragma: allowlist secret
```

## [​](#config-snippet)Config snippet

```
{
  env: { KILOCODE_API_KEY: "<your-kilocode-api-key>" }, // pragma: allowlist secret
  agents: {
    defaults: {
      model: { primary: "kilocode/kilo/auto" },
    },
  },
}
```

## [​](#default-model)Default model

The default model is `kilocode/kilo/auto`, a smart routing model that automatically selects the best underlying model based on the task:

* Planning, debugging, and orchestration tasks route to Claude Opus
* Code writing and exploration tasks route to Claude Sonnet

## [​](#available-models)Available models

OpenClaw dynamically discovers available models from the Kilo Gateway at startup. Use `/models kilocode` to see the full list of models available with your account. Any model available on the gateway can be used with the `kilocode/` prefix:

```
kilocode/kilo/auto              (default - smart routing)
kilocode/anthropic/claude-sonnet-4
kilocode/openai/gpt-5.2
kilocode/google/gemini-3-pro-preview
...and many more
```

## [​](#notes)Notes

* Model refs are `kilocode/<model-id>` (e.g., `kilocode/anthropic/claude-sonnet-4`).
* Default model: `kilocode/kilo/auto`
* Base URL: `https://api.kilo.ai/api/gateway/`
* For more model/provider options, see [/concepts/model-providers](https://docs.openclaw.ai/concepts/model-providers).
* Kilo Gateway uses a Bearer token with your API key under the hood.

----
url: https://docs.openclaw.ai/tools/thinking
----

# Thinking Levels - OpenClaw

## [​](#thinking-levels-/think-directives)Thinking Levels (/think directives)

## [​](#what-it-does)What it does

* Inline directive in any inbound body: `/t <level>`, `/think:<level>`, or `/thinking <level>`.

* Levels (aliases): `off | minimal | low | medium | high | xhigh | adaptive`

  * minimal → “think”
  * low → “think hard”
  * medium → “think harder”
  * high → “ultrathink” (max budget)
  * xhigh → “ultrathink+” (GPT-5.2 + Codex models only)
  * adaptive → provider-managed adaptive reasoning budget (supported for Anthropic Claude 4.6 model family)
  * `x-high`, `x_high`, `extra-high`, `extra high`, and `extra_high` map to `xhigh`.
  * `highest`, `max` map to `high`.

* Provider notes:

  * Anthropic Claude 4.6 models default to `adaptive` when no explicit thinking level is set.
  * Z.AI (`zai/*`) only supports binary thinking (`on`/`off`). Any non-`off` level is treated as `on` (mapped to `low`).
  * Moonshot (`moonshot/*`) maps `/think off` to `thinking: { type: "disabled" }` and any non-`off` level to `thinking: { type: "enabled" }`. When thinking is enabled, Moonshot only accepts `tool_choice` `auto|none`; OpenClaw normalizes incompatible values to `auto`.

## [​](#resolution-order)Resolution order

1. Inline directive on the message (applies only to that message).
2. Session override (set by sending a directive-only message).
3. Per-agent default (`agents.list[].thinkingDefault` in config).
4. Global default (`agents.defaults.thinkingDefault` in config).
5. Fallback: `adaptive` for Anthropic Claude 4.6 models, `low` for other reasoning-capable models, `off` otherwise.

## [​](#setting-a-session-default)Setting a session default

* Send a message that is **only** the directive (whitespace allowed), e.g. `/think:medium` or `/t high`.
* That sticks for the current session (per-sender by default); cleared by `/think:off` or session idle reset.
* Confirmation reply is sent (`Thinking level set to high.` / `Thinking disabled.`). If the level is invalid (e.g. `/thinking big`), the command is rejected with a hint and the session state is left unchanged.
* Send `/think` (or `/think:`) with no argument to see the current thinking level.

## [​](#application-by-agent)Application by agent

* **Embedded Pi**: the resolved level is passed to the in-process Pi agent runtime.

## [​](#fast-mode-/fast)Fast mode (/fast)

* Levels: `on|off`.

* Directive-only message toggles a session fast-mode override and replies `Fast mode enabled.` / `Fast mode disabled.`.

* Send `/fast` (or `/fast status`) with no mode to see the current effective fast-mode state.

* OpenClaw resolves fast mode in this order:

  1. Inline/directive-only `/fast on|off`
  2. Session override
  3. Per-agent default (`agents.list[].fastModeDefault`)
  4. Per-model config: `agents.defaults.models["<provider>/<model>"].params.fastMode`
  5. Fallback: `off`

* For `openai/*`, fast mode applies the OpenAI fast profile: `service_tier=priority` when supported, plus low reasoning effort and low text verbosity.

* For `openai-codex/*`, fast mode applies the same low-latency profile on Codex Responses. OpenClaw keeps one shared `/fast` toggle across both auth paths.

* For direct `anthropic/*` API-key requests, fast mode maps to Anthropic service tiers: `/fast on` sets `service_tier=auto`, `/fast off` sets `service_tier=standard_only`.

* Anthropic fast mode is API-key only. OpenClaw skips Anthropic service-tier injection for Claude setup-token / OAuth auth and for non-Anthropic proxy base URLs.

## [​](#verbose-directives-/verbose-or-/v)Verbose directives (/verbose or /v)

* Levels: `on` (minimal) | `full` | `off` (default).
* Directive-only message toggles session verbose and replies `Verbose logging enabled.` / `Verbose logging disabled.`; invalid levels return a hint without changing state.
* `/verbose off` stores an explicit session override; clear it via the Sessions UI by choosing `inherit`.
* Inline directive affects only that message; session/global defaults apply otherwise.
* Send `/verbose` (or `/verbose:`) with no argument to see the current verbose level.
* When verbose is on, agents that emit structured tool results (Pi, other JSON agents) send each tool call back as its own metadata-only message, prefixed with `<emoji> <tool-name>: <arg>` when available (path/command). These tool summaries are sent as soon as each tool starts (separate bubbles), not as streaming deltas.
* Tool failure summaries remain visible in normal mode, but raw error detail suffixes are hidden unless verbose is `on` or `full`.
* When verbose is `full`, tool outputs are also forwarded after completion (separate bubble, truncated to a safe length). If you toggle `/verbose on|full|off` while a run is in-flight, subsequent tool bubbles honor the new setting.

## [​](#reasoning-visibility-/reasoning)Reasoning visibility (/reasoning)

* Levels: `on|off|stream`.
* Directive-only message toggles whether thinking blocks are shown in replies.
* When enabled, reasoning is sent as a **separate message** prefixed with `Reasoning:`.
* `stream` (Telegram only): streams reasoning into the Telegram draft bubble while the reply is generating, then sends the final answer without reasoning.
* Alias: `/reason`.
* Send `/reasoning` (or `/reasoning:`) with no argument to see the current reasoning level.
* Resolution order: inline directive, then session override, then per-agent default (`agents.list[].reasoningDefault`), then fallback (`off`).

## [​](#related)Related

* Elevated mode docs live in [Elevated mode](https://docs.openclaw.ai/tools/elevated).

## [​](#heartbeats)Heartbeats

* Heartbeat probe body is the configured heartbeat prompt (default: `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`). Inline directives in a heartbeat message apply as usual (but avoid changing session defaults from heartbeats).
* Heartbeat delivery defaults to the final payload only. To also send the separate `Reasoning:` message (when available), set `agents.defaults.heartbeat.includeReasoning: true` or per-agent `agents.list[].heartbeat.includeReasoning: true`.

## [​](#web-chat-ui)Web chat UI

* The web chat thinking selector mirrors the session’s stored level from the inbound session store/config when the page loads.
* Picking another level applies only to the next message (`thinkingOnce`); after sending, the selector snaps back to the stored session level.
* To change the session default, send a `/think:<level>` directive (as before); the selector will reflect it after the next reload.

----
url: https://docs.openclaw.ai/concepts/agent
----

# Agent Runtime - OpenClaw

OpenClaw runs a single embedded agent runtime.

## Workspace (required)

OpenClaw uses a single agent workspace directory (`agents.defaults.workspace`) as the agent’s **only** working directory (`cwd`) for tools and context. Recommended: use `openclaw setup` to create `~/.openclaw/openclaw.json` if missing and initialize the workspace files. Full workspace layout + backup guide: [Agent workspace](https://docs.openclaw.ai/concepts/agent-workspace) If `agents.defaults.sandbox` is enabled, non-main sessions can override this with per-session workspaces under `agents.defaults.sandbox.workspaceRoot` (see [Gateway configuration](https://docs.openclaw.ai/gateway/configuration)).

## Bootstrap files (injected)

Inside `agents.defaults.workspace`, OpenClaw expects these user-editable files:

On the first turn of a new session, OpenClaw injects the contents of these files directly into the agent context. Blank files are skipped. Large files are trimmed and truncated with a marker so prompts stay lean (read the file for full content). If a file is missing, OpenClaw injects a single “missing file” marker line (and `openclaw setup` will create a safe default template). `BOOTSTRAP.md` is only created for a **brand new workspace** (no other bootstrap files present). If you delete it after completing the ritual, it should not be recreated on later restarts. To disable bootstrap file creation entirely (for pre-seeded workspaces), set:

## Built-in tools

Core tools (read/exec/edit/write and related system tools) are always available, subject to tool policy. `apply_patch` is optional and gated by `tools.exec.applyPatch`. `TOOLS.md` does **not** control which tools exist; it’s guidance for how *you* want them used.

## Skills

OpenClaw loads skills from three locations (workspace wins on name conflict):

Skills can be gated by config/env (see `skills` in [Gateway configuration](https://docs.openclaw.ai/gateway/configuration)).

## Runtime boundaries

The embedded agent runtime is built on the Pi agent core (models, tools, and prompt pipeline). Session management, discovery, tool wiring, and channel delivery are OpenClaw-owned layers on top of that core.

## Sessions

Session transcripts are stored as JSONL at:

The session ID is stable and chosen by OpenClaw. Legacy session folders from other tools are not read.

## Steering while streaming

When queue mode is `steer`, inbound messages are injected into the current run. Queued steering is delivered **after the current assistant turn finishes executing its tool calls**, before the next LLM call. Steering no longer skips remaining tool calls from the current assistant message; it injects the queued message at the next model boundary instead. When queue mode is `followup` or `collect`, inbound messages are held until the current turn ends, then a new agent turn starts with the queued payloads. See [Queue](https://docs.openclaw.ai/concepts/queue) for mode + debounce/cap behavior. Block streaming sends completed assistant blocks as soon as they finish; it is **off by default** (`agents.defaults.blockStreamingDefault: "off"`). Tune the boundary via `agents.defaults.blockStreamingBreak` (`text_end` vs `message_end`; defaults to text\_end). Control soft block chunking with `agents.defaults.blockStreamingChunk` (defaults to 800–1200 chars; prefers paragraph breaks, then newlines; sentences last). Coalesce streamed chunks with `agents.defaults.blockStreamingCoalesce` to reduce single-line spam (idle-based merging before send). Non-Telegram channels require explicit `*.blockStreaming: true` to enable block replies. Verbose tool summaries are emitted at tool start (no debounce); Control UI streams tool output via agent events when available. More details: [Streaming + chunking](https://docs.openclaw.ai/concepts/streaming).

## Model refs

Model refs in config (for example `agents.defaults.model` and `agents.defaults.models`) are parsed by splitting on the **first** `/`.

## Configuration (minimal)

At minimum, set:

***

*Next: [Group Chats](https://docs.openclaw.ai/channels/group-messages)* 🦞

----
url: https://docs.openclaw.ai/cli/hooks
----

# hooks - OpenClaw

Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, and gateway startup). Related:

## List All Hooks

List all discovered hooks from workspace, managed, extra, and bundled directories. **Options:**

**Example output:**

**Example (verbose):**

Shows missing requirements for ineligible hooks. **Example (JSON):**

Returns structured JSON for programmatic use.

## Get Hook Information

Show detailed information about a specific hook. **Arguments:**

**Options:**

**Example:**

**Output:**

## Check Hooks Eligibility

Show summary of hook eligibility status (how many are ready vs. not ready). **Options:**

**Example output:**

## Enable a Hook

Enable a specific hook by adding it to your config (`~/.openclaw/config.json`). **Note:** Workspace hooks are disabled by default until enabled here or in config. Hooks managed by plugins show `plugin:<id>` in `openclaw hooks list` and can’t be enabled/disabled here. Enable/disable the plugin instead. **Arguments:**

**Example:**

**Output:**

**What it does:**

If the hook came from `<workspace>/hooks/`, this opt-in step is required before the Gateway will load it. **After enabling:**

## Disable a Hook

Disable a specific hook by updating your config. **Arguments:**

**Example:**

**Output:**

**After disabling:**

## Install Hook Packs

Install hook packs through the unified plugins installer. `openclaw hooks install` still works as a compatibility alias, but it prints a deprecation warning and forwards to `openclaw plugins install`. Npm specs are **registry-only** (package name + optional **exact version** or **dist-tag**). Git/URL/file specs and semver ranges are rejected. Dependency installs run with `--ignore-scripts` for safety. Bare specs and `@latest` stay on the stable track. If npm resolves either of those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a prerelease tag such as `@beta`/`@rc` or an exact prerelease version. **What it does:**

**Options:**

**Supported archives:** `.zip`, `.tgz`, `.tar.gz`, `.tar` **Examples:**

Linked hook packs are treated as managed hooks from an operator-configured directory, not as workspace hooks.

## Update Hook Packs

Update tracked npm-based hook packs through the unified plugins updater. `openclaw hooks update` still works as a compatibility alias, but it prints a deprecation warning and forwards to `openclaw plugins update`. **Options:**

When a stored integrity hash exists and the fetched artifact hash changes, OpenClaw prints a warning and asks for confirmation before proceeding. Use global `--yes` to bypass prompts in CI/non-interactive runs.

## Bundled Hooks

### session-memory

Saves session context to memory when you issue `/new` or `/reset`. **Enable:**

**Output:** `~/.openclaw/workspace/memory/YYYY-MM-DD-slug.md` **See:** [session-memory documentation](https://docs.openclaw.ai/automation/hooks#session-memory)

Injects additional bootstrap files (for example monorepo-local `AGENTS.md` / `TOOLS.md`) during `agent:bootstrap`. **Enable:**

**See:** [bootstrap-extra-files documentation](https://docs.openclaw.ai/automation/hooks#bootstrap-extra-files)

### command-logger

Logs all command events to a centralized audit file. **Enable:**

**Output:** `~/.openclaw/logs/commands.log` **View logs:**

**See:** [command-logger documentation](https://docs.openclaw.ai/automation/hooks#command-logger)

### boot-md

Runs `BOOT.md` when the gateway starts (after channels start). **Events**: `gateway:startup` **Enable**:

**See:** [boot-md documentation](https://docs.openclaw.ai/automation/hooks#boot-md)

----
url: https://docs.openclaw.ai/platforms/mac/peekaboo
----

# Peekaboo Bridge - OpenClaw

## [​](#peekaboo-bridge-macos-ui-automation)Peekaboo Bridge (macOS UI automation)

OpenClaw can host **PeekabooBridge** as a local, permission‑aware UI automation broker. This lets the `peekaboo` CLI drive UI automation while reusing the macOS app’s TCC permissions.

## [​](#what-this-is-and-is-not)What this is (and is not)

* **Host**: OpenClaw\.app can act as a PeekabooBridge host.
* **Client**: use the `peekaboo` CLI (no separate `openclaw ui ...` surface).
* **UI**: visual overlays stay in Peekaboo.app; OpenClaw is a thin broker host.

## [​](#enable-the-bridge)Enable the bridge

In the macOS app:

* Settings → **Enable Peekaboo Bridge**

When enabled, OpenClaw starts a local UNIX socket server. If disabled, the host is stopped and `peekaboo` will fall back to other available hosts.

## [​](#client-discovery-order)Client discovery order

Peekaboo clients typically try hosts in this order:

1. Peekaboo.app (full UX)
2. Claude.app (if installed)
3. OpenClaw\.app (thin broker)

Use `peekaboo bridge status --verbose` to see which host is active and which socket path is in use. You can override with:

```
export PEEKABOO_BRIDGE_SOCKET=/path/to/bridge.sock
```

## [​](#security-&-permissions)Security & permissions

* The bridge validates **caller code signatures**; an allowlist of TeamIDs is enforced (Peekaboo host TeamID + OpenClaw app TeamID).
* Requests time out after \~10 seconds.
* If required permissions are missing, the bridge returns a clear error message rather than launching System Settings.

## [​](#snapshot-behavior-automation)Snapshot behavior (automation)

Snapshots are stored in memory and expire automatically after a short window. If you need longer retention, re‑capture from the client.

## [​](#troubleshooting)Troubleshooting

* If `peekaboo` reports “bridge client is not authorized”, ensure the client is properly signed or run the host with `PEEKABOO_ALLOW_UNSIGNED_SOCKET_CLIENTS=1` in **debug** mode only.
* If no hosts are found, open one of the host apps (Peekaboo.app or OpenClaw\.app) and confirm permissions are granted.

----
url: https://docs.openclaw.ai/install/digitalocean
----

# DigitalOcean - OpenClaw

## [​](#digitalocean)DigitalOcean

Run a persistent OpenClaw Gateway on a DigitalOcean Droplet.

## [​](#prerequisites)Prerequisites

* DigitalOcean account ([signup](https://cloud.digitalocean.com/registrations/new))
* SSH key pair (or willingness to use password auth)
* About 20 minutes

## [​](#setup)Setup

1

[](#)

Create a Droplet

Use a clean base image (Ubuntu 24.04 LTS). Avoid third-party Marketplace 1-click images unless you have reviewed their startup scripts and firewall defaults.

1. Log into [DigitalOcean](https://cloud.digitalocean.com/).

2. Click **Create > Droplets**.

3. Choose:

   * **Region:** Closest to you
   * **Image:** Ubuntu 24.04 LTS
   * **Size:** Basic, Regular, 1 vCPU / 1 GB RAM / 25 GB SSD
   * **Authentication:** SSH key (recommended) or password

4. Click **Create Droplet** and note the IP address.

2

[](#)

Connect and install

```
ssh root@YOUR_DROPLET_IP

apt update && apt upgrade -y

# Install Node.js 24
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
apt install -y nodejs

# Install OpenClaw
curl -fsSL https://openclaw.ai/install.sh | bash
openclaw --version
```

3

[](#)

Run onboarding

```
openclaw onboard --install-daemon
```

The wizard walks you through model auth, channel setup, gateway token generation, and daemon installation (systemd).

4

[](#)

Add swap (recommended for 1 GB Droplets)

```
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
```

5

[](#)

Verify the gateway

```
openclaw status
systemctl --user status openclaw-gateway.service
journalctl --user -u openclaw-gateway.service -f
```

6

[](#)

Access the Control UI

The gateway binds to loopback by default. Pick one of these options.**Option A: SSH tunnel (simplest)**

```
# From your local machine
ssh -L 18789:localhost:18789 root@YOUR_DROPLET_IP
```

Then open `http://localhost:18789`.**Option B: Tailscale Serve**

```
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up
openclaw config set gateway.tailscale.mode serve
openclaw gateway restart
```

Then open `https://<magicdns>/` from any device on your tailnet.**Option C: Tailnet bind (no Serve)**

```
openclaw config set gateway.bind tailnet
openclaw gateway restart
```

Then open `http://<tailscale-ip>:18789` (token required).

## [​](#troubleshooting)Troubleshooting

**Gateway will not start** — Run `openclaw doctor --non-interactive` and check logs with `journalctl --user -u openclaw-gateway.service -n 50`. **Port already in use** — Run `lsof -i :18789` to find the process, then stop it. **Out of memory** — Verify swap is active with `free -h`. If still hitting OOM, use API-based models (Claude, GPT) rather than local models, or upgrade to a 2 GB Droplet.

## [​](#next-steps)Next steps

* [Channels](https://docs.openclaw.ai/channels) — connect Telegram, WhatsApp, Discord, and more
* [Gateway configuration](https://docs.openclaw.ai/gateway/configuration) — all config options
* [Updating](https://docs.openclaw.ai/install/updating) — keep OpenClaw up to date

----
url: https://docs.openclaw.ai/tools/pdf
----

# PDF Tool - OpenClaw

## [​](#pdf-tool)PDF tool

`pdf` analyzes one or more PDF documents and returns text. Quick behavior:

* Native provider mode for Anthropic and Google model providers.
* Extraction fallback mode for other providers (extract text first, then page images when needed).
* Supports single (`pdf`) or multi (`pdfs`) input, max 10 PDFs per call.

## [​](#availability)Availability

The tool is only registered when OpenClaw can resolve a PDF-capable model config for the agent:

1. `agents.defaults.pdfModel`
2. fallback to `agents.defaults.imageModel`
3. fallback to best effort provider defaults based on available auth

If no usable model can be resolved, the `pdf` tool is not exposed.

## [​](#input-reference)Input reference

* `pdf` (`string`): one PDF path or URL
* `pdfs` (`string[]`): multiple PDF paths or URLs, up to 10 total
* `prompt` (`string`): analysis prompt, default `Analyze this PDF document.`
* `pages` (`string`): page filter like `1-5` or `1,3,7-9`
* `model` (`string`): optional model override (`provider/model`)
* `maxBytesMb` (`number`): per-PDF size cap in MB

Input notes:

* `pdf` and `pdfs` are merged and deduplicated before loading.
* If no PDF input is provided, the tool errors.
* `pages` is parsed as 1-based page numbers, deduped, sorted, and clamped to the configured max pages.
* `maxBytesMb` defaults to `agents.defaults.pdfMaxBytesMb` or `10`.

## [​](#supported-pdf-references)Supported PDF references

* local file path (including `~` expansion)
* `file://` URL
* `http://` and `https://` URL

Reference notes:

* Other URI schemes (for example `ftp://`) are rejected with `unsupported_pdf_reference`.
* In sandbox mode, remote `http(s)` URLs are rejected.
* With workspace-only file policy enabled, local file paths outside allowed roots are rejected.

## [​](#execution-modes)Execution modes

### [​](#native-provider-mode)Native provider mode

Native mode is used for provider `anthropic` and `google`. The tool sends raw PDF bytes directly to provider APIs. Native mode limits:

* `pages` is not supported. If set, the tool returns an error.

### [​](#extraction-fallback-mode)Extraction fallback mode

Fallback mode is used for non-native providers. Flow:

1. Extract text from selected pages (up to `agents.defaults.pdfMaxPages`, default `20`).
2. If extracted text length is below `200` chars, render selected pages to PNG images and include them.
3. Send extracted content plus prompt to the selected model.

Fallback details:

* Page image extraction uses a pixel budget of `4,000,000`.
* If the target model does not support image input and there is no extractable text, the tool errors.
* Extraction fallback requires `pdfjs-dist` (and `@napi-rs/canvas` for image rendering).

## [​](#config)Config

```
{
  agents: {
    defaults: {
      pdfModel: {
        primary: "anthropic/claude-opus-4-6",
        fallbacks: ["openai/gpt-5-mini"],
      },
      pdfMaxBytesMb: 10,
      pdfMaxPages: 20,
    },
  },
}
```

See [Configuration Reference](https://docs.openclaw.ai/gateway/configuration-reference) for full field details.

## [​](#output-details)Output details

The tool returns text in `content[0].text` and structured metadata in `details`. Common `details` fields:

* `model`: resolved model ref (`provider/model`)
* `native`: `true` for native provider mode, `false` for fallback
* `attempts`: fallback attempts that failed before success

Path fields:

* single PDF input: `details.pdf`
* multiple PDF inputs: `details.pdfs[]` with `pdf` entries
* sandbox path rewrite metadata (when applicable): `rewrittenFrom`

## [​](#error-behavior)Error behavior

* Missing PDF input: throws `pdf required: provide a path or URL to a PDF document`
* Too many PDFs: returns structured error in `details.error = "too_many_pdfs"`
* Unsupported reference scheme: returns `details.error = "unsupported_pdf_reference"`
* Native mode with `pages`: throws clear `pages is not supported with native PDF providers` error

## [​](#examples)Examples

Single PDF:

```
{
  "pdf": "/tmp/report.pdf",
  "prompt": "Summarize this report in 5 bullets"
}
```

Multiple PDFs:

```
{
  "pdfs": ["/tmp/q1.pdf", "/tmp/q2.pdf"],
  "prompt": "Compare risks and timeline changes across both documents"
}
```

Page-filtered fallback model:

```
{
  "pdf": "https://example.com/report.pdf",
  "pages": "1-3,7",
  "model": "openai/gpt-5-mini",
  "prompt": "Extract only customer-impacting incidents"
}
```

----
url: https://docs.openclaw.ai/concepts/delegate-architecture
----

# Delegate Architecture - OpenClaw

Goal: run OpenClaw as a **named delegate** — an agent with its own identity that acts “on behalf of” people in an organization. The agent never impersonates a human. It sends, reads, and schedules under its own account with explicit delegation permissions. This extends [Multi-Agent Routing](https://docs.openclaw.ai/concepts/multi-agent) from personal use into organizational deployments.

## What is a delegate?

A **delegate** is an OpenClaw agent that:

The delegate model maps directly to how executive assistants work: they have their own credentials, send mail “on behalf of” their principal, and follow a defined scope of authority.

## Why delegates?

OpenClaw’s default mode is a **personal assistant** — one human, one agent. Delegates extend this to organizations:

| Personal mode               | Delegate mode                                  |
| --------------------------- | ---------------------------------------------- |
| Agent uses your credentials | Agent has its own credentials                  |
| Replies come from you       | Replies come from the delegate, on your behalf |
| One principal               | One or many principals                         |
| Trust boundary = you        | Trust boundary = organization policy           |

Delegates solve two problems:

1. **Accountability**: messages sent by the agent are clearly from the agent, not a human.
2. **Scope control**: the identity provider enforces what the delegate can access, independent of OpenClaw’s own tool policy.

## Capability tiers

Start with the lowest tier that meets your needs. Escalate only when the use case demands it.

### Tier 1: Read-Only + Draft

The delegate can **read** organizational data and **draft** messages for human review. Nothing is sent without approval.

This tier requires only read permissions from the identity provider. The agent does not write to any mailbox or calendar — drafts and proposals are delivered via chat for the human to act on.

### Tier 2: Send on Behalf

The delegate can **send** messages and **create** calendar events under its own identity. Recipients see “Delegate Name on behalf of Principal Name.”

This tier requires send-on-behalf (or delegate) permissions.

### Tier 3: Proactive

The delegate operates **autonomously** on a schedule, executing standing orders without per-action human approval. Humans review output asynchronously.

This tier combines Tier 2 permissions with [Cron Jobs](https://docs.openclaw.ai/automation/cron-jobs) and [Standing Orders](https://docs.openclaw.ai/automation/standing-orders).

> **Security warning**: Tier 3 requires careful configuration of hard blocks — actions the agent must never take regardless of instruction. Complete the prerequisites below before granting any identity provider permissions.

## Prerequisites: isolation and hardening

> **Do this first.** Before you grant any credentials or identity provider access, lock down the delegate’s boundaries. The steps in this section define what the agent **cannot** do — establish these constraints before giving it the ability to do anything.

### Hard blocks (non-negotiable)

Define these in the delegate’s `SOUL.md` and `AGENTS.md` before connecting any external accounts:

These rules load every session. They are the last line of defense regardless of what instructions the agent receives.

### Tool restrictions

Use per-agent tool policy (v2026.1.6+) to enforce boundaries at the Gateway level. This operates independently of the agent’s personality files — even if the agent is instructed to bypass its rules, the Gateway blocks the tool call:

```
{
  id: "delegate",
  workspace: "~/.openclaw/workspace-delegate",
  tools: {
    allow: ["read", "exec", "message", "cron"],
    deny: ["write", "edit", "apply_patch", "browser", "canvas"],
  },
}
```

### Sandbox isolation

For high-security deployments, sandbox the delegate agent so it cannot access the host filesystem or network beyond its allowed tools:

See [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing) and [Multi-Agent Sandbox & Tools](https://docs.openclaw.ai/tools/multi-agent-sandbox-tools).

### Audit trail

Configure logging before the delegate handles any real data:

All delegate actions flow through OpenClaw’s session store. For compliance, ensure these logs are retained and reviewed.

## Setting up a delegate

With hardening in place, proceed to grant the delegate its identity and permissions.

### 1. Create the delegate agent

Use the multi-agent wizard to create an isolated agent for the delegate:

This creates:

Configure the delegate’s personality in its workspace files:

### 2. Configure identity provider delegation

The delegate needs its own account in your identity provider with explicit delegation permissions. **Apply the principle of least privilege** — start with Tier 1 (read-only) and escalate only when the use case demands it.

#### Microsoft 365

Create a dedicated user account for the delegate (e.g., `delegate@[organization].org`). **Send on Behalf** (Tier 2):

**Read access** (Graph API with application permissions): Register an Azure AD application with `Mail.Read` and `Calendars.Read` application permissions. **Before using the application**, scope access with an [application access policy](https://learn.microsoft.com/graph/auth-limit-mailbox-access) to restrict the app to only the delegate and principal mailboxes:

> **Security warning**: without an application access policy, `Mail.Read` application permission grants access to **every mailbox in the tenant**. Always create the access policy before the application reads any mail. Test by confirming the app returns `403` for mailboxes outside the security group.

#### Google Workspace

Create a service account and enable domain-wide delegation in the Admin Console. Delegate only the scopes you need:

The service account impersonates the delegate user (not the principal), preserving the “on behalf of” model.

> **Security warning**: domain-wide delegation allows the service account to impersonate **any user in the entire domain**. Restrict the scopes to the minimum required, and limit the service account’s client ID to only the scopes listed above in the Admin Console (Security > API controls > Domain-wide delegation). A leaked service account key with broad scopes grants full access to every mailbox and calendar in the organization. Rotate keys on a schedule and monitor the Admin Console audit log for unexpected impersonation events.

### 3. Bind the delegate to channels

Route inbound messages to the delegate agent using [Multi-Agent Routing](https://docs.openclaw.ai/concepts/multi-agent) bindings:

```
{
  agents: {
    list: [
      { id: "main", workspace: "~/.openclaw/workspace" },
      {
        id: "delegate",
        workspace: "~/.openclaw/workspace-delegate",
        tools: {
          deny: ["browser", "canvas"],
        },
      },
    ],
  },
  bindings: [
    // Route a specific channel account to the delegate
    {
      agentId: "delegate",
      match: { channel: "whatsapp", accountId: "org" },
    },
    // Route a Discord guild to the delegate
    {
      agentId: "delegate",
      match: { channel: "discord", guildId: "123456789012345678" },
    },
    // Everything else goes to the main personal agent
    { agentId: "main", match: { channel: "whatsapp" } },
  ],
}
```

### 4. Add credentials to the delegate agent

Copy or create auth profiles for the delegate’s `agentDir`:

Never share the main agent’s `agentDir` with the delegate. See [Multi-Agent Routing](https://docs.openclaw.ai/concepts/multi-agent) for auth isolation details.

## Example: organizational assistant

A complete delegate configuration for an organizational assistant that handles email, calendar, and social media:

```
{
  agents: {
    list: [
      { id: "main", default: true, workspace: "~/.openclaw/workspace" },
      {
        id: "org-assistant",
        name: "[Organization] Assistant",
        workspace: "~/.openclaw/workspace-org",
        agentDir: "~/.openclaw/agents/org-assistant/agent",
        identity: { name: "[Organization] Assistant" },
        tools: {
          allow: ["read", "exec", "message", "cron", "sessions_list", "sessions_history"],
          deny: ["write", "edit", "apply_patch", "browser", "canvas"],
        },
      },
    ],
  },
  bindings: [
    {
      agentId: "org-assistant",
      match: { channel: "signal", peer: { kind: "group", id: "[group-id]" } },
    },
    { agentId: "org-assistant", match: { channel: "whatsapp", accountId: "org" } },
    { agentId: "main", match: { channel: "whatsapp" } },
    { agentId: "main", match: { channel: "signal" } },
  ],
}
```

The delegate’s `AGENTS.md` defines its autonomous authority — what it may do without asking, what requires approval, and what is forbidden. [Cron Jobs](https://docs.openclaw.ai/automation/cron-jobs) drive its daily schedule.

## Scaling pattern

The delegate model works for any small organization:

1. **Create one delegate agent** per organization.
2. **Harden first** — tool restrictions, sandbox, hard blocks, audit trail.
3. **Grant scoped permissions** via the identity provider (least privilege).
4. **Define [standing orders](https://docs.openclaw.ai/automation/standing-orders)** for autonomous operations.
5. **Schedule cron jobs** for recurring tasks.
6. **Review and adjust** the capability tier as trust builds.

Multiple organizations can share one Gateway server using multi-agent routing — each org gets its own isolated agent, workspace, and credentials.

----
url: https://docs.openclaw.ai/gateway/bridge-protocol
----

# Bridge Protocol - OpenClaw

## [​](#bridge-protocol-legacy-node-transport)Bridge protocol (legacy node transport)

The Bridge protocol is a **legacy** node transport (TCP JSONL). New node clients should use the unified Gateway WebSocket protocol instead. If you are building an operator or node client, use the [Gateway protocol](https://docs.openclaw.ai/gateway/protocol). **Note:** Current OpenClaw builds no longer ship the TCP bridge listener; this document is kept for historical reference. Legacy `bridge.*` config keys are no longer part of the config schema.

## [​](#why-we-have-both)Why we have both

* **Security boundary**: the bridge exposes a small allowlist instead of the full gateway API surface.
* **Pairing + node identity**: node admission is owned by the gateway and tied to a per-node token.
* **Discovery UX**: nodes can discover gateways via Bonjour on LAN, or connect directly over a tailnet.
* **Loopback WS**: the full WS control plane stays local unless tunneled via SSH.

## [​](#transport)Transport

* TCP, one JSON object per line (JSONL).
* Optional TLS (when `bridge.tls.enabled` is true).
* Legacy default listener port was `18790` (current builds do not start a TCP bridge).

When TLS is enabled, discovery TXT records include `bridgeTls=1` plus `bridgeTlsSha256` as a non-secret hint. Note that Bonjour/mDNS TXT records are unauthenticated; clients must not treat the advertised fingerprint as an authoritative pin without explicit user intent or other out-of-band verification.

## [​](#handshake-+-pairing)Handshake + pairing

1. Client sends `hello` with node metadata + token (if already paired).
2. If not paired, gateway replies `error` (`NOT_PAIRED`/`UNAUTHORIZED`).
3. Client sends `pair-request`.
4. Gateway waits for approval, then sends `pair-ok` and `hello-ok`.

`hello-ok` returns `serverName` and may include `canvasHostUrl`.

## [​](#frames)Frames

Client → Gateway:

* `req` / `res`: scoped gateway RPC (chat, sessions, config, health, voicewake, skills.bins)
* `event`: node signals (voice transcript, agent request, chat subscribe, exec lifecycle)

Gateway → Client:

* `invoke` / `invoke-res`: node commands (`canvas.*`, `camera.*`, `screen.record`, `location.get`, `sms.send`)
* `event`: chat updates for subscribed sessions
* `ping` / `pong`: keepalive

Legacy allowlist enforcement lived in `src/gateway/server-bridge.ts` (removed).

## [​](#exec-lifecycle-events)Exec lifecycle events

Nodes can emit `exec.finished` or `exec.denied` events to surface system.run activity. These are mapped to system events in the gateway. (Legacy nodes may still emit `exec.started`.) Payload fields (all optional unless noted):

* `sessionKey` (required): agent session to receive the system event.
* `runId`: unique exec id for grouping.
* `command`: raw or formatted command string.
* `exitCode`, `timedOut`, `success`, `output`: completion details (finished only).
* `reason`: denial reason (denied only).

## [​](#tailnet-usage)Tailnet usage

* Bind the bridge to a tailnet IP: `bridge.bind: "tailnet"` in `~/.openclaw/openclaw.json`.
* Clients connect via MagicDNS name or tailnet IP.
* Bonjour does **not** cross networks; use manual host/port or wide-area DNS‑SD when needed.

## [​](#versioning)Versioning

Bridge is currently **implicit v1** (no min/max negotiation). Backward‑compat is expected; add a bridge protocol version field before any breaking changes.

----
url: https://docs.openclaw.ai/providers/groq
----

# Groq - OpenClaw

## [​](#groq)Groq

[Groq](https://groq.com/) provides ultra-fast inference on open-source models (Llama, Gemma, Mistral, and more) using custom LPU hardware. OpenClaw connects to Groq through its OpenAI-compatible API.

* Provider: `groq`
* Auth: `GROQ_API_KEY`
* API: OpenAI-compatible

## [​](#quick-start)Quick start

1. Get an API key from [console.groq.com/keys](https://console.groq.com/keys).
2. Set the API key:

```
export GROQ_API_KEY="gsk_..."
```

3. Set a default model:

```
{
  agents: {
    defaults: {
      model: { primary: "groq/llama-3.3-70b-versatile" },
    },
  },
}
```

## [​](#config-file-example)Config file example

```
{
  env: { GROQ_API_KEY: "gsk_..." },
  agents: {
    defaults: {
      model: { primary: "groq/llama-3.3-70b-versatile" },
    },
  },
}
```

## [​](#audio-transcription)Audio transcription

Groq also provides fast Whisper-based audio transcription. When configured as a media-understanding provider, OpenClaw uses Groq’s `whisper-large-v3-turbo` model to transcribe voice messages.

```
{
  media: {
    understanding: {
      audio: {
        models: [{ provider: "groq" }],
      },
    },
  },
}
```

## [​](#environment-note)Environment note

If the Gateway runs as a daemon (launchd/systemd), make sure `GROQ_API_KEY` is available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).

## [​](#available-models)Available models

Groq’s model catalog changes frequently. Run `openclaw models list | grep groq` to see currently available models, or check [console.groq.com/docs/models](https://console.groq.com/docs/models). Popular choices include:

* **Llama 3.3 70B Versatile** - general-purpose, large context
* **Llama 3.1 8B Instant** - fast, lightweight
* **Gemma 2 9B** - compact, efficient
* **Mixtral 8x7B** - MoE architecture, strong reasoning

## [​](#links)Links

* [Groq Console](https://console.groq.com/)
* [API Documentation](https://console.groq.com/docs)
* [Model List](https://console.groq.com/docs/models)
* [Pricing](https://groq.com/pricing)

----
url: https://docs.openclaw.ai/concepts/model-providers
----

# Model Providers - OpenClaw

This page covers **LLM/model providers** (not chat channels like WhatsApp/Telegram). For model selection rules, see [/concepts/models](https://docs.openclaw.ai/concepts/models).

## Quick rules

* Model refs use `provider/model` (example: `opencode/claude-opus-4-6`).
* If you set `agents.defaults.models`, it becomes the allowlist.
* CLI helpers: `openclaw onboard`, `openclaw models list`, `openclaw models set <provider/model>`.
* Provider plugins can inject model catalogs via `registerProvider({ catalog })`; OpenClaw merges that output into `models.providers` before writing `models.json`.
* Provider manifests can declare `providerAuthEnvVars` so generic env-based auth probes do not need to load plugin runtime. The remaining core env-var map is now just for non-plugin/core providers and a few generic-precedence cases such as Anthropic API-key-first onboarding.
* Provider plugins can also own provider runtime behavior via `resolveDynamicModel`, `prepareDynamicModel`, `normalizeResolvedModel`, `capabilities`, `prepareExtraParams`, `wrapStreamFn`, `formatApiKey`, `refreshOAuth`, `buildAuthDoctorHint`, `isCacheTtlEligible`, `buildMissingAuthMessage`, `suppressBuiltInModel`, `augmentModelCatalog`, `isBinaryThinking`, `supportsXHighThinking`, `resolveDefaultThinkingLevel`, `isModernModelRef`, `prepareRuntimeAuth`, `resolveUsageAuth`, and `fetchUsageSnapshot`.
* Note: provider runtime `capabilities` is shared runner metadata (provider family, transcript/tooling quirks, transport/cache hints). It is not the same as the [public capability model](https://docs.openclaw.ai/plugins/architecture#public-capability-model) which describes what a plugin registers (text inference, speech, etc.).

## Plugin-owned provider behavior

Provider plugins can now own most provider-specific logic while OpenClaw keeps the generic inference loop. Typical split:

Current bundled examples:

* `anthropic`: Claude 4.6 forward-compat fallback, auth repair hints, usage endpoint fetching, and cache-TTL/provider-family metadata
* `openrouter`: pass-through model ids, request wrappers, provider capability hints, and cache-TTL policy
* `github-copilot`: onboarding/device login, forward-compat model fallback, Claude-thinking transcript hints, runtime token exchange, and usage endpoint fetching
* `openai`: GPT-5.4 forward-compat fallback, direct OpenAI transport normalization, Codex-aware missing-auth hints, Spark suppression, synthetic OpenAI/Codex catalog rows, thinking/live-model policy, and provider-family metadata
* `google` and `google-gemini-cli`: Gemini 3.1 forward-compat fallback and modern-model matching; Gemini CLI OAuth also owns auth-profile token formatting, usage-token parsing, and quota endpoint fetching for usage surfaces
* `moonshot`: shared transport, plugin-owned thinking payload normalization
* `kilocode`: shared transport, plugin-owned request headers, reasoning payload normalization, Gemini transcript hints, and cache-TTL policy
* `zai`: GLM-5 forward-compat fallback, `tool_stream` defaults, cache-TTL policy, binary-thinking/live-model policy, and usage auth + quota fetching
* `mistral`, `opencode`, and `opencode-go`: plugin-owned capability metadata
* `byteplus`, `cloudflare-ai-gateway`, `huggingface`, `kimi-coding`, `modelstudio`, `nvidia`, `qianfan`, `synthetic`, `together`, `venice`, `vercel-ai-gateway`, and `volcengine`: plugin-owned catalogs only
* `qwen-portal`: plugin-owned catalog, OAuth login, and OAuth refresh
* `minimax` and `xiaomi`: plugin-owned catalogs plus usage auth/snapshot logic

The bundled `openai` plugin now owns both provider ids: `openai` and `openai-codex`. That covers providers that still fit OpenClaw’s normal transports. A provider that needs a totally custom request executor is a separate, deeper extension surface.

## API key rotation

## Built-in providers (pi-ai catalog)

OpenClaw ships with the pi‑ai catalog. These providers require **no** `models.providers` config; just set auth + pick a model.

### OpenAI

### Anthropic

### OpenAI Code (Codex)

### OpenCode

### Google Gemini (API key)

### Google Vertex and Gemini CLI

### Z.AI (GLM)

### Vercel AI Gateway

### Kilo Gateway

See [/providers/kilocode](https://docs.openclaw.ai/providers/kilocode) for setup details.

### Other bundled provider plugins

## Providers via `models.providers` (custom/base URL)

Use `models.providers` (or `models.json`) to add **custom** providers or OpenAI/Anthropic‑compatible proxies. Many of the bundled provider plugins below already publish a default catalog. Use explicit `models.providers.<id>` entries only when you want to override the default base URL, headers, or model list.

### Moonshot AI (Kimi)

Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:

Kimi K2 model IDs:

```
{
  agents: {
    defaults: { model: { primary: "moonshot/kimi-k2.5" } },
  },
  models: {
    mode: "merge",
    providers: {
      moonshot: {
        baseUrl: "https://api.moonshot.ai/v1",
        apiKey: "${MOONSHOT_API_KEY}",
        api: "openai-completions",
        models: [{ id: "kimi-k2.5", name: "Kimi K2.5" }],
      },
    },
  },
}
```

### Kimi Coding

Kimi Coding uses Moonshot AI’s Anthropic-compatible endpoint:

### Qwen OAuth (free tier)

Qwen provides OAuth access to Qwen Coder + Vision via a device-code flow. The bundled provider plugin is enabled by default, so just log in:

Model refs:

See [/providers/qwen](https://docs.openclaw.ai/providers/qwen) for setup details and notes.

### Volcano Engine (Doubao)

Volcano Engine (火山引擎) provides access to Doubao and other models in China.

Available models:

Coding models (`volcengine-plan`):

### BytePlus (International)

BytePlus ARK provides access to the same models as Volcano Engine for international users.

Available models:

Coding models (`byteplus-plan`):

### Synthetic

Synthetic provides Anthropic-compatible models behind the `synthetic` provider:

```
{
  agents: {
    defaults: { model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" } },
  },
  models: {
    mode: "merge",
    providers: {
      synthetic: {
        baseUrl: "https://api.synthetic.new/anthropic",
        apiKey: "${SYNTHETIC_API_KEY}",
        api: "anthropic-messages",
        models: [{ id: "hf:MiniMaxAI/MiniMax-M2.5", name: "MiniMax M2.5" }],
      },
    },
  },
}
```

### MiniMax

MiniMax is configured via `models.providers` because it uses custom endpoints:

See [/providers/minimax](https://docs.openclaw.ai/providers/minimax) for setup details, model options, and config snippets.

### Ollama

Ollama ships as a bundled provider plugin and uses Ollama’s native API:

Ollama is detected locally at `http://127.0.0.1:11434` when you opt in with `OLLAMA_API_KEY`, and the bundled provider plugin adds Ollama directly to `openclaw onboard` and the model picker. See [/providers/ollama](https://docs.openclaw.ai/providers/ollama) for onboarding, cloud/local mode, and custom configuration.

### vLLM

vLLM ships as a bundled provider plugin for local/self-hosted OpenAI-compatible servers:

To opt in to auto-discovery locally (any value works if your server doesn’t enforce auth):

Then set a model (replace with one of the IDs returned by `/v1/models`):

See [/providers/vllm](https://docs.openclaw.ai/providers/vllm) for details.

### SGLang

SGLang ships as a bundled provider plugin for fast self-hosted OpenAI-compatible servers:

To opt in to auto-discovery locally (any value works if your server does not enforce auth):

Then set a model (replace with one of the IDs returned by `/v1/models`):

See [/providers/sglang](https://docs.openclaw.ai/providers/sglang) for details.

### Local proxies (LM Studio, vLLM, LiteLLM, etc.)

Example (OpenAI‑compatible):

```
{
  agents: {
    defaults: {
      model: { primary: "lmstudio/minimax-m2.5-gs32" },
      models: { "lmstudio/minimax-m2.5-gs32": { alias: "Minimax" } },
    },
  },
  models: {
    providers: {
      lmstudio: {
        baseUrl: "http://localhost:1234/v1",
        apiKey: "LMSTUDIO_KEY",
        api: "openai-completions",
        models: [
          {
            id: "minimax-m2.5-gs32",
            name: "MiniMax M2.5",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 200000,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

Notes:

## CLI examples

See also: [/gateway/configuration](https://docs.openclaw.ai/gateway/configuration) for full configuration examples.

----
url: https://docs.openclaw.ai/help/scripts
----

# Scripts - OpenClaw

## [​](#scripts)Scripts

The `scripts/` directory contains helper scripts for local workflows and ops tasks. Use these when a task is clearly tied to a script; otherwise prefer the CLI.

## [​](#conventions)Conventions

* Scripts are **optional** unless referenced in docs or release checklists.
* Prefer CLI surfaces when they exist (example: auth monitoring uses `openclaw models status --check`).
* Assume scripts are host‑specific; read them before running on a new machine.

## [​](#auth-monitoring-scripts)Auth monitoring scripts

Auth monitoring scripts are documented here: [/automation/auth-monitoring](https://docs.openclaw.ai/automation/auth-monitoring)

## [​](#when-adding-scripts)When adding scripts

* Keep scripts focused and documented.
* Add a short entry in the relevant doc (or create one if missing).

----
url: https://docs.openclaw.ai/tools/plugin
----

# Plugins - OpenClaw

Plugins extend OpenClaw with new capabilities: channels, model providers, tools, skills, speech, image generation, and more. Some plugins are **core** (shipped with OpenClaw), others are **external** (published on npm by the community).

## Quick start

If you prefer chat-native control, enable `commands.plugins: true` and use:

The install path uses the same resolver as the CLI: local path/archive, explicit `clawhub:<pkg>`, or bare package spec (ClawHub first, then npm fallback).

## Plugin types

OpenClaw recognizes two plugin formats:

| Format     | How it works                                                       | Examples                                               |
| ---------- | ------------------------------------------------------------------ | ------------------------------------------------------ |
| **Native** | `openclaw.plugin.json` + runtime module; executes in-process       | Official plugins, community npm packages               |
| **Bundle** | Codex/Claude/Cursor-compatible layout; mapped to OpenClaw features | `.codex-plugin/`, `.claude-plugin/`, `.cursor-plugin/` |

Both show up under `openclaw plugins list`. See [Plugin Bundles](https://docs.openclaw.ai/plugins/bundles) for bundle details. If you are writing a native plugin, start with [Building Plugins](https://docs.openclaw.ai/plugins/building-plugins) and the [Plugin SDK Overview](https://docs.openclaw.ai/plugins/sdk-overview).

## Official plugins

### Installable (npm)

| Plugin          | Package                | Docs                                                         |
| --------------- | ---------------------- | ------------------------------------------------------------ |
| Matrix          | `@openclaw/matrix`     | [Matrix](https://docs.openclaw.ai/channels/matrix)           |
| Microsoft Teams | `@openclaw/msteams`    | [Microsoft Teams](https://docs.openclaw.ai/channels/msteams) |
| Nostr           | `@openclaw/nostr`      | [Nostr](https://docs.openclaw.ai/channels/nostr)             |
| Voice Call      | `@openclaw/voice-call` | [Voice Call](https://docs.openclaw.ai/plugins/voice-call)    |
| Zalo            | `@openclaw/zalo`       | [Zalo](https://docs.openclaw.ai/channels/zalo)               |
| Zalo Personal   | `@openclaw/zalouser`   | [Zalo Personal](https://docs.openclaw.ai/plugins/zalouser)   |

### Core (shipped with OpenClaw)

Looking for third-party plugins? See [Community Plugins](https://docs.openclaw.ai/plugins/community).

## Configuration

| Field            | Description                                               |
| ---------------- | --------------------------------------------------------- |
| `enabled`        | Master toggle (default: `true`)                           |
| `allow`          | Plugin allowlist (optional)                               |
| `deny`           | Plugin denylist (optional; deny wins)                     |
| `load.paths`     | Extra plugin files/directories                            |
| `slots`          | Exclusive slot selectors (e.g. `memory`, `contextEngine`) |
| `entries.\<id\>` | Per-plugin toggles + config                               |

Config changes **require a gateway restart**. If the Gateway is running with config watch + in-process restart enabled (the default `openclaw gateway` path), that restart is usually performed automatically a moment after the config write lands.

Plugin states: disabled vs missing vs invalid

## Discovery and precedence

OpenClaw scans for plugins in this order (first match wins):

### Enablement rules

## Plugin slots (exclusive categories)

Some categories are exclusive (only one active at a time):

| Slot            | What it controls      | Default             |
| --------------- | --------------------- | ------------------- |
| `memory`        | Active memory plugin  | `memory-core`       |
| `contextEngine` | Active context engine | `legacy` (built-in) |

## CLI reference

See [`openclaw plugins` CLI reference](https://docs.openclaw.ai/cli/plugins) for full details.

## Plugin API overview

Plugins export either a function or an object with `register(api)`:

Common registration methods:

| Method                               | What it registers    |
| ------------------------------------ | -------------------- |
| `registerProvider`                   | Model provider (LLM) |
| `registerChannel`                    | Chat channel         |
| `registerTool`                       | Agent tool           |
| `registerHook` / `on(...)`           | Lifecycle hooks      |
| `registerSpeechProvider`             | Text-to-speech / STT |
| `registerMediaUnderstandingProvider` | Image/audio analysis |
| `registerImageGenerationProvider`    | Image generation     |
| `registerWebSearchProvider`          | Web search           |
| `registerHttpRoute`                  | HTTP endpoint        |
| `registerCommand` / `registerCli`    | CLI commands         |
| `registerContextEngine`              | Context engine       |
| `registerService`                    | Background service   |

----
url: https://docs.openclaw.ai/gateway/tailscale
----

# Tailscale - OpenClaw

## Tailscale (Gateway dashboard)

OpenClaw can auto-configure Tailscale **Serve** (tailnet) or **Funnel** (public) for the Gateway dashboard and WebSocket port. This keeps the Gateway bound to loopback while Tailscale provides HTTPS, routing, and (for Serve) identity headers.

## Modes

## Auth

Set `gateway.auth.mode` to control the handshake:

When `tailscale.mode = "serve"` and `gateway.auth.allowTailscale` is `true`, Control UI/WebSocket auth can use Tailscale identity headers (`tailscale-user-login`) without supplying a token/password. OpenClaw verifies the identity by resolving the `x-forwarded-for` address via the local Tailscale daemon (`tailscale whois`) and matching it to the header before accepting it. OpenClaw only treats a request as Serve when it arrives from loopback with Tailscale’s `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host` headers. HTTP API endpoints (for example `/v1/*`, `/tools/invoke`, and `/api/channels/*`) still require token/password auth. This tokenless flow assumes the gateway host is trusted. If untrusted local code may run on the same host, disable `gateway.auth.allowTailscale` and require token/password auth instead. To require explicit credentials, set `gateway.auth.allowTailscale: false` or force `gateway.auth.mode: "password"`.

## Config examples

### Tailnet-only (Serve)

Open: `https://<magicdns>/` (or your configured `gateway.controlUi.basePath`)

### Tailnet-only (bind to Tailnet IP)

Use this when you want the Gateway to listen directly on the Tailnet IP (no Serve/Funnel).

Connect from another Tailnet device:

Note: loopback (`http://127.0.0.1:18789`) will **not** work in this mode.

### Public internet (Funnel + shared password)

Prefer `OPENCLAW_GATEWAY_PASSWORD` over committing a password to disk.

## CLI examples

## Notes

## Browser control (remote Gateway + local browser)

If you run the Gateway on one machine but want to drive a browser on another machine, run a **node host** on the browser machine and keep both on the same tailnet. The Gateway will proxy browser actions to the node; no separate control server or Serve URL needed. Avoid Funnel for browser control; treat node pairing like operator access.

## Tailscale prerequisites + limits

## Learn more

----
url: https://docs.openclaw.ai/web/tui
----

# TUI - OpenClaw

## [​](#tui-terminal-ui)TUI (Terminal UI)

## [​](#quick-start)Quick start

1. Start the Gateway.

```
openclaw gateway
```

2. Open the TUI.

```
openclaw tui
```

3. Type a message and press Enter.

Remote Gateway:

```
openclaw tui --url ws://<host>:<port> --token <gateway-token>
```

Use `--password` if your Gateway uses password auth.

## [​](#what-you-see)What you see

* Header: connection URL, current agent, current session.
* Chat log: user messages, assistant replies, system notices, tool cards.
* Status line: connection/run state (connecting, running, streaming, idle, error).
* Footer: connection state + agent + session + model + think/fast/verbose/reasoning + token counts + deliver.
* Input: text editor with autocomplete.

## [​](#mental-model-agents-+-sessions)Mental model: agents + sessions

* Agents are unique slugs (e.g. `main`, `research`). The Gateway exposes the list.

* Sessions belong to the current agent.

* Session keys are stored as `agent:<agentId>:<sessionKey>`.

  * If you type `/session main`, the TUI expands it to `agent:<currentAgent>:main`.
  * If you type `/session agent:other:main`, you switch to that agent session explicitly.

* Session scope:

  * `per-sender` (default): each agent has many sessions.
  * `global`: the TUI always uses the `global` session (the picker may be empty).

* The current agent + session are always visible in the footer.

## [​](#sending-+-delivery)Sending + delivery

* Messages are sent to the Gateway; delivery to providers is off by default.

* Turn delivery on:

  * `/deliver on`
  * or the Settings panel
  * or start with `openclaw tui --deliver`

## [​](#pickers-+-overlays)Pickers + overlays

* Model picker: list available models and set the session override.
* Agent picker: choose a different agent.
* Session picker: shows only sessions for the current agent.
* Settings: toggle deliver, tool output expansion, and thinking visibility.

## [​](#keyboard-shortcuts)Keyboard shortcuts

* Enter: send message
* Esc: abort active run
* Ctrl+C: clear input (press twice to exit)
* Ctrl+D: exit
* Ctrl+L: model picker
* Ctrl+G: agent picker
* Ctrl+P: session picker
* Ctrl+O: toggle tool output expansion
* Ctrl+T: toggle thinking visibility (reloads history)

## [​](#slash-commands)Slash commands

Core:

* `/help`
* `/status`
* `/agent <id>` (or `/agents`)
* `/session <key>` (or `/sessions`)
* `/model <provider/model>` (or `/models`)

Session controls:

* `/think <off|minimal|low|medium|high>`
* `/fast <status|on|off>`
* `/verbose <on|full|off>`
* `/reasoning <on|off|stream>`
* `/usage <off|tokens|full>`
* `/elevated <on|off|ask|full>` (alias: `/elev`)
* `/activation <mention|always>`
* `/deliver <on|off>`

Session lifecycle:

* `/new` or `/reset` (reset the session)
* `/abort` (abort the active run)
* `/settings`
* `/exit`

Other Gateway slash commands (for example, `/context`) are forwarded to the Gateway and shown as system output. See [Slash commands](https://docs.openclaw.ai/tools/slash-commands).

## [​](#local-shell-commands)Local shell commands

* Prefix a line with `!` to run a local shell command on the TUI host.
* The TUI prompts once per session to allow local execution; declining keeps `!` disabled for the session.
* Commands run in a fresh, non-interactive shell in the TUI working directory (no persistent `cd`/env).
* Local shell commands receive `OPENCLAW_SHELL=tui-local` in their environment.
* A lone `!` is sent as a normal message; leading spaces do not trigger local exec.

## [​](#tool-output)Tool output

* Tool calls show as cards with args + results.
* Ctrl+O toggles between collapsed/expanded views.
* While tools run, partial updates stream into the same card.

## [​](#terminal-colors)Terminal colors

* The TUI keeps assistant body text in your terminal’s default foreground so dark and light terminals both stay readable.
* If your terminal uses a light background and auto-detection is wrong, set `OPENCLAW_THEME=light` before launching `openclaw tui`.
* To force the original dark palette instead, set `OPENCLAW_THEME=dark`.

## [​](#history-+-streaming)History + streaming

* On connect, the TUI loads the latest history (default 200 messages).
* Streaming responses update in place until finalized.
* The TUI also listens to agent tool events for richer tool cards.

## [​](#connection-details)Connection details

* The TUI registers with the Gateway as `mode: "tui"`.
* Reconnects show a system message; event gaps are surfaced in the log.

## [​](#options)Options

* `--url <url>`: Gateway WebSocket URL (defaults to config or `ws://127.0.0.1:<port>`)
* `--token <token>`: Gateway token (if required)
* `--password <password>`: Gateway password (if required)
* `--session <key>`: Session key (default: `main`, or `global` when scope is global)
* `--deliver`: Deliver assistant replies to the provider (default off)
* `--thinking <level>`: Override thinking level for sends
* `--timeout-ms <ms>`: Agent timeout in ms (defaults to `agents.defaults.timeoutSeconds`)

Note: when you set `--url`, the TUI does not fall back to config or environment credentials. Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.

## [​](#troubleshooting)Troubleshooting

No output after sending a message:

* Run `/status` in the TUI to confirm the Gateway is connected and idle/busy.
* Check the Gateway logs: `openclaw logs --follow`.
* Confirm the agent can run: `openclaw status` and `openclaw models status`.
* If you expect messages in a chat channel, enable delivery (`/deliver on` or `--deliver`).
* `--history-limit <n>`: History entries to load (default 200)

## [​](#connection-troubleshooting)Connection troubleshooting

* `disconnected`: ensure the Gateway is running and your `--url/--token/--password` are correct.
* No agents in picker: check `openclaw agents list` and your routing config.
* Empty session picker: you might be in global scope or have no sessions yet.

----
url: https://docs.openclaw.ai/nodes/camera
----

# Camera Capture - OpenClaw

## [​](#camera-capture-agent)Camera capture (agent)

OpenClaw supports **camera capture** for agent workflows:

* **iOS node** (paired via Gateway): capture a **photo** (`jpg`) or **short video clip** (`mp4`, with optional audio) via `node.invoke`.
* **Android node** (paired via Gateway): capture a **photo** (`jpg`) or **short video clip** (`mp4`, with optional audio) via `node.invoke`.
* **macOS app** (node via Gateway): capture a **photo** (`jpg`) or **short video clip** (`mp4`, with optional audio) via `node.invoke`.

All camera access is gated behind **user-controlled settings**.

## [​](#ios-node)iOS node

### [​](#user-setting-default-on)User setting (default on)

* iOS Settings tab → **Camera** → **Allow Camera** (`camera.enabled`)

  * Default: **on** (missing key is treated as enabled).
  * When off: `camera.*` commands return `CAMERA_DISABLED`.

### [​](#commands-via-gateway-node-invoke)Commands (via Gateway `node.invoke`)

* `camera.list`
  * Response payload:
    * `devices`: array of `{ id, name, position, deviceType }`

* `camera.snap`

  * Params:

    * `facing`: `front|back` (default: `front`)
    * `maxWidth`: number (optional; default `1600` on the iOS node)
    * `quality`: `0..1` (optional; default `0.9`)
    * `format`: currently `jpg`
    * `delayMs`: number (optional; default `0`)
    * `deviceId`: string (optional; from `camera.list`)

  * Response payload:

    * `format: "jpg"`
    * `base64: "<...>"`
    * `width`, `height`

  * Payload guard: photos are recompressed to keep the base64 payload under 5 MB.

* `camera.clip`

  * Params:

    * `facing`: `front|back` (default: `front`)
    * `durationMs`: number (default `3000`, clamped to a max of `60000`)
    * `includeAudio`: boolean (default `true`)
    * `format`: currently `mp4`
    * `deviceId`: string (optional; from `camera.list`)

  * Response payload:

    * `format: "mp4"`
    * `base64: "<...>"`
    * `durationMs`
    * `hasAudio`

### [​](#foreground-requirement)Foreground requirement

Like `canvas.*`, the iOS node only allows `camera.*` commands in the **foreground**. Background invocations return `NODE_BACKGROUND_UNAVAILABLE`.

### [​](#cli-helper-temp-files-+-media)CLI helper (temp files + MEDIA)

The easiest way to get attachments is via the CLI helper, which writes decoded media to a temp file and prints `MEDIA:<path>`. Examples:

```
openclaw nodes camera snap --node <id>               # default: both front + back (2 MEDIA lines)
openclaw nodes camera snap --node <id> --facing front
openclaw nodes camera clip --node <id> --duration 3000
openclaw nodes camera clip --node <id> --no-audio
```

Notes:

* `nodes camera snap` defaults to **both** facings to give the agent both views.
* Output files are temporary (in the OS temp directory) unless you build your own wrapper.

## [​](#android-node)Android node

### [​](#android-user-setting-default-on)Android user setting (default on)

* Android Settings sheet → **Camera** → **Allow Camera** (`camera.enabled`)

  * Default: **on** (missing key is treated as enabled).
  * When off: `camera.*` commands return `CAMERA_DISABLED`.

### [​](#permissions)Permissions

* Android requires runtime permissions:

  * `CAMERA` for both `camera.snap` and `camera.clip`.
  * `RECORD_AUDIO` for `camera.clip` when `includeAudio=true`.

If permissions are missing, the app will prompt when possible; if denied, `camera.*` requests fail with a `*_PERMISSION_REQUIRED` error.

### [​](#android-foreground-requirement)Android foreground requirement

Like `canvas.*`, the Android node only allows `camera.*` commands in the **foreground**. Background invocations return `NODE_BACKGROUND_UNAVAILABLE`.

### [​](#android-commands-via-gateway-node-invoke)Android commands (via Gateway `node.invoke`)

* `camera.list`
  * Response payload:
    * `devices`: array of `{ id, name, position, deviceType }`

### [​](#payload-guard)Payload guard

Photos are recompressed to keep the base64 payload under 5 MB.

## [​](#macos-app)macOS app

### [​](#user-setting-default-off)User setting (default off)

The macOS companion app exposes a checkbox:

* **Settings → General → Allow Camera** (`openclaw.cameraEnabled`)

  * Default: **off**
  * When off: camera requests return “Camera disabled by user”.

### [​](#cli-helper-node-invoke)CLI helper (node invoke)

Use the main `openclaw` CLI to invoke camera commands on the macOS node. Examples:

```
openclaw nodes camera list --node <id>            # list camera ids
openclaw nodes camera snap --node <id>            # prints MEDIA:<path>
openclaw nodes camera snap --node <id> --max-width 1280
openclaw nodes camera snap --node <id> --delay-ms 2000
openclaw nodes camera snap --node <id> --device-id <id>
openclaw nodes camera clip --node <id> --duration 10s          # prints MEDIA:<path>
openclaw nodes camera clip --node <id> --duration-ms 3000      # prints MEDIA:<path> (legacy flag)
openclaw nodes camera clip --node <id> --device-id <id>
openclaw nodes camera clip --node <id> --no-audio
```

Notes:

* `openclaw nodes camera snap` defaults to `maxWidth=1600` unless overridden.
* On macOS, `camera.snap` waits `delayMs` (default 2000ms) after warm-up/exposure settle before capturing.
* Photo payloads are recompressed to keep base64 under 5 MB.

## [​](#safety-+-practical-limits)Safety + practical limits

* Camera and microphone access trigger the usual OS permission prompts (and require usage strings in Info.plist).
* Video clips are capped (currently `<= 60s`) to avoid oversized node payloads (base64 overhead + message limits).

## [​](#macos-screen-video-os-level)macOS screen video (OS-level)

For *screen* video (not camera), use the macOS companion:

```
openclaw nodes screen record --node <id> --duration 10s --fps 15   # prints MEDIA:<path>
```

Notes:

* Requires macOS **Screen Recording** permission (TCC).

----
url: https://docs.openclaw.ai/tools/exec
----

# Exec Tool - OpenClaw

Run shell commands in the workspace. Supports foreground + background execution via `process`. If `process` is disallowed, `exec` runs synchronously and ignores `yieldMs`/`background`. Background sessions are scoped per agent; `process` only sees sessions from the same agent.

## Parameters

Notes:

* `host` defaults to `sandbox`.
* `elevated` is ignored when sandboxing is off (exec already runs on the host).
* `gateway`/`node` approvals are controlled by `~/.openclaw/exec-approvals.json`.
* `node` requires a paired node (companion app or headless node host).
* If multiple nodes are available, set `exec.node` or `tools.exec.node` to select one.
* On non-Windows hosts, exec uses `SHELL` when set; if `SHELL` is `fish`, it prefers `bash` (or `sh`) from `PATH` to avoid fish-incompatible scripts, then falls back to `SHELL` if neither exists.
* On Windows hosts, exec prefers PowerShell 7 (`pwsh`) discovery (Program Files, ProgramW6432, then PATH), then falls back to Windows PowerShell 5.1.
* Host execution (`gateway`/`node`) rejects `env.PATH` and loader overrides (`LD_*`/`DYLD_*`) to prevent binary hijacking or injected code.
* OpenClaw sets `OPENCLAW_SHELL=exec` in the spawned command environment (including PTY and sandbox execution) so shell/profile rules can detect exec-tool context.
* Important: sandboxing is **off by default**. If sandboxing is off and `host=sandbox` is explicitly configured/requested, exec now fails closed instead of silently running on the gateway host. Enable sandboxing or use `host=gateway` with approvals.
* Script preflight checks (for common Python/Node shell-syntax mistakes) only inspect files inside the effective `workdir` boundary. If a script path resolves outside `workdir`, preflight is skipped for that file.

## Config

* `tools.exec.notifyOnExit` (default: true): when true, backgrounded exec sessions enqueue a system event and request a heartbeat on exit.
* `tools.exec.approvalRunningNoticeMs` (default: 10000): emit a single “running” notice when an approval-gated exec runs longer than this (0 disables).
* `tools.exec.host` (default: `sandbox`)
* `tools.exec.security` (default: `deny` for sandbox, `allowlist` for gateway + node when unset)
* `tools.exec.ask` (default: `on-miss`)
* `tools.exec.node` (default: unset)
* `tools.exec.strictInlineEval` (default: false): when true, inline interpreter eval forms such as `python -c`, `node -e`, `ruby -e`, `perl -e`, `php -r`, `lua -e`, and `osascript -e` always require explicit approval and are never persisted by `allow-always`.
* `tools.exec.pathPrepend`: list of directories to prepend to `PATH` for exec runs (gateway + sandbox only).
* `tools.exec.safeBins`: stdin-only safe binaries that can run without explicit allowlist entries. For behavior details, see [Safe bins](https://docs.openclaw.ai/tools/exec-approvals#safe-bins-stdin-only).
* `tools.exec.safeBinTrustedDirs`: additional explicit directories trusted for `safeBins` path checks. `PATH` entries are never auto-trusted. Built-in defaults are `/bin` and `/usr/bin`.
* `tools.exec.safeBinProfiles`: optional custom argv policy per safe bin (`minPositional`, `maxPositional`, `allowedValueFlags`, `deniedFlags`).

Example:

### PATH handling

Per-agent node binding (use the agent list index in config):

Control UI: the Nodes tab includes a small “Exec node binding” panel for the same settings.

## Session overrides (`/exec`)

Use `/exec` to set **per-session** defaults for `host`, `security`, `ask`, and `node`. Send `/exec` with no arguments to show the current values. Example:

`/exec` is only honored for **authorized senders** (channel allowlists/pairing plus `commands.useAccessGroups`). It updates **session state only** and does not write config. To hard-disable exec, deny it via tool policy (`tools.deny: ["exec"]` or per-agent). Host approvals still apply unless you explicitly set `security=full` and `ask=off`.

## Exec approvals (companion app / node host)

Sandboxed agents can require per-request approval before `exec` runs on the gateway or node host. See [Exec approvals](https://docs.openclaw.ai/tools/exec-approvals) for the policy, allowlist, and UI flow. When approvals are required, the exec tool returns immediately with `status: "approval-pending"` and an approval id. Once approved (or denied / timed out), the Gateway emits system events (`Exec finished` / `Exec denied`). If the command is still running after `tools.exec.approvalRunningNoticeMs`, a single `Exec running` notice is emitted.

## Allowlist + safe bins

Manual allowlist enforcement matches **resolved binary paths only** (no basename matches). When `security=allowlist`, shell commands are auto-allowed only if every pipeline segment is allowlisted or a safe bin. Chaining (`;`, `&&`, `||`) and redirections are rejected in allowlist mode unless every top-level segment satisfies the allowlist (including safe bins). Redirections remain unsupported. `autoAllowSkills` is a separate convenience path in exec approvals. It is not the same as manual path allowlist entries. For strict explicit trust, keep `autoAllowSkills` disabled. Use the two controls for different jobs:

Do not treat `safeBins` as a generic allowlist, and do not add interpreter/runtime binaries (for example `python3`, `node`, `ruby`, `bash`). If you need those, use explicit allowlist entries and keep approval prompts enabled. `openclaw security audit` warns when interpreter/runtime `safeBins` entries are missing explicit profiles, and `openclaw doctor --fix` can scaffold missing custom `safeBinProfiles` entries. `openclaw security audit` and `openclaw doctor` also warn when you explicitly add broad-behavior bins such as `jq` back into `safeBins`. If you explicitly allowlist interpreters, enable `tools.exec.strictInlineEval` so inline code-eval forms still require a fresh approval. For full policy details and examples, see [Exec approvals](https://docs.openclaw.ai/tools/exec-approvals#safe-bins-stdin-only) and [Safe bins versus allowlist](https://docs.openclaw.ai/tools/exec-approvals#safe-bins-versus-allowlist).

## Examples

Foreground:

Background + poll:

Send keys (tmux-style):

```
{"tool":"process","action":"send-keys","sessionId":"<id>","keys":["Enter"]}
{"tool":"process","action":"send-keys","sessionId":"<id>","keys":["C-c"]}
{"tool":"process","action":"send-keys","sessionId":"<id>","keys":["Up","Up","Enter"]}
```

Submit (send CR only):

Paste (bracketed by default):

## apply\_patch (experimental)

`apply_patch` is a subtool of `exec` for structured multi-file edits. Enable it explicitly:

Notes:

----
url: https://docs.openclaw.ai/reference/test
----

# Tests - OpenClaw

## Local PR gate

For local PR land/gate checks, run:

If `pnpm test` flakes on a loaded host, rerun once before treating it as a regression, then isolate with `pnpm vitest run <path/to/test>`. For memory-constrained hosts, use:

## Model latency bench (local keys)

Script: [`scripts/bench-model.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/bench-model.ts) Usage:

Last run (2025-12-31, 20 runs):

## CLI startup bench

Script: [`scripts/bench-cli-startup.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/bench-cli-startup.ts) Usage:

This benchmarks these commands:

Output includes avg, p50, p95, min/max, and exit-code/signal distribution for each command.

## Onboarding E2E (Docker)

Docker is optional; this is only needed for containerized onboarding smoke tests. Full cold-start flow in a clean Linux container:

This script drives the interactive wizard via a pseudo-tty, verifies config/workspace/session files, then starts the gateway and runs `openclaw health`.

## QR import smoke (Docker)

Ensures `qrcode-terminal` loads under the supported Docker Node runtimes (Node 24 default, Node 22 compatible):

----
url: https://docs.openclaw.ai/gateway/pairing
----

# Gateway-Owned Pairing - OpenClaw

## [​](#gateway-owned-pairing-option-b)Gateway-owned pairing (Option B)

In Gateway-owned pairing, the **Gateway** is the source of truth for which nodes are allowed to join. UIs (macOS app, future clients) are just frontends that approve or reject pending requests. **Important:** WS nodes use **device pairing** (role `node`) during `connect`. `node.pair.*` is a separate pairing store and does **not** gate the WS handshake. Only clients that explicitly call `node.pair.*` use this flow.

## [​](#concepts)Concepts

* **Pending request**: a node asked to join; requires approval.
* **Paired node**: approved node with an issued auth token.
* **Transport**: the Gateway WS endpoint forwards requests but does not decide membership. (Legacy TCP bridge support is deprecated/removed.)

## [​](#how-pairing-works)How pairing works

1. A node connects to the Gateway WS and requests pairing.
2. The Gateway stores a **pending request** and emits `node.pair.requested`.
3. You approve or reject the request (CLI or UI).
4. On approval, the Gateway issues a **new token** (tokens are rotated on re‑pair).
5. The node reconnects using the token and is now “paired”.

Pending requests expire automatically after **5 minutes**.

## [​](#cli-workflow-headless-friendly)CLI workflow (headless friendly)

```
openclaw nodes pending
openclaw nodes approve <requestId>
openclaw nodes reject <requestId>
openclaw nodes status
openclaw nodes rename --node <id|name|ip> --name "Living Room iPad"
```

`nodes status` shows paired/connected nodes and their capabilities.

## [​](#api-surface-gateway-protocol)API surface (gateway protocol)

Events:

* `node.pair.requested` — emitted when a new pending request is created.
* `node.pair.resolved` — emitted when a request is approved/rejected/expired.

Methods:

* `node.pair.request` — create or reuse a pending request.
* `node.pair.list` — list pending + paired nodes.
* `node.pair.approve` — approve a pending request (issues token).
* `node.pair.reject` — reject a pending request.
* `node.pair.verify` — verify `{ nodeId, token }`.

Notes:

* `node.pair.request` is idempotent per node: repeated calls return the same pending request.
* Approval **always** generates a fresh token; no token is ever returned from `node.pair.request`.
* Requests may include `silent: true` as a hint for auto-approval flows.

## [​](#auto-approval-macos-app)Auto-approval (macOS app)

The macOS app can optionally attempt a **silent approval** when:

* the request is marked `silent`, and
* the app can verify an SSH connection to the gateway host using the same user.

If silent approval fails, it falls back to the normal “Approve/Reject” prompt.

## [​](#storage-local-private)Storage (local, private)

Pairing state is stored under the Gateway state directory (default `~/.openclaw`):

* `~/.openclaw/nodes/paired.json`
* `~/.openclaw/nodes/pending.json`

If you override `OPENCLAW_STATE_DIR`, the `nodes/` folder moves with it. Security notes:

* Tokens are secrets; treat `paired.json` as sensitive.
* Rotating a token requires re-approval (or deleting the node entry).

## [​](#transport-behavior)Transport behavior

* The transport is **stateless**; it does not store membership.
* If the Gateway is offline or pairing is disabled, nodes cannot pair.
* If the Gateway is in remote mode, pairing still happens against the remote Gateway’s store.

----
url: https://docs.openclaw.ai/plugins/sdk-provider-plugins
----

# Building Provider Plugins - OpenClaw

This guide walks through building a provider plugin that adds a model provider (LLM) to OpenClaw. By the end you will have a provider with a model catalog, API key auth, and dynamic model resolution.

## Walkthrough

Add runtime hooks (as needed)

Most providers only need `catalog` + `resolveDynamicModel`. Add hooks incrementally as your provider requires them.

All available provider hooks

OpenClaw calls hooks in this order. Most providers only use 2-3:

| #  | Hook                          | When to use                                      |
| -- | ----------------------------- | ------------------------------------------------ |
| 1  | `catalog`                     | Model catalog or base URL defaults               |
| 2  | `resolveDynamicModel`         | Accept arbitrary upstream model IDs              |
| 3  | `prepareDynamicModel`         | Async metadata fetch before resolving            |
| 4  | `normalizeResolvedModel`      | Transport rewrites before the runner             |
| 5  | `capabilities`                | Transcript/tooling metadata (data, not callable) |
| 6  | `prepareExtraParams`          | Default request params                           |
| 7  | `wrapStreamFn`                | Custom headers/body wrappers                     |
| 8  | `formatApiKey`                | Custom runtime token shape                       |
| 9  | `refreshOAuth`                | Custom OAuth refresh                             |
| 10 | `buildAuthDoctorHint`         | Auth repair guidance                             |
| 11 | `isCacheTtlEligible`          | Prompt cache TTL gating                          |
| 12 | `buildMissingAuthMessage`     | Custom missing-auth hint                         |
| 13 | `suppressBuiltInModel`        | Hide stale upstream rows                         |
| 14 | `augmentModelCatalog`         | Synthetic forward-compat rows                    |
| 15 | `isBinaryThinking`            | Binary thinking on/off                           |
| 16 | `supportsXHighThinking`       | `xhigh` reasoning support                        |
| 17 | `resolveDefaultThinkingLevel` | Default `/think` policy                          |
| 18 | `isModernModelRef`            | Live/smoke model matching                        |
| 19 | `prepareRuntimeAuth`          | Token exchange before inference                  |
| 20 | `resolveUsageAuth`            | Custom usage credential parsing                  |
| 21 | `fetchUsageSnapshot`          | Custom usage endpoint                            |
| 22 | `onModelSelected`             | Post-selection callback (e.g. telemetry)         |

For detailed descriptions and real-world examples, see [Internals: Provider Runtime Hooks](https://docs.openclaw.ai/plugins/architecture#provider-runtime-hooks).

## File structure

## Catalog order reference

`catalog.order` controls when your catalog merges relative to built-in providers:

| Order     | When          | Use case                                        |
| --------- | ------------- | ----------------------------------------------- |
| `simple`  | First pass    | Plain API-key providers                         |
| `profile` | After simple  | Providers gated on auth profiles                |
| `paired`  | After profile | Synthesize multiple related entries             |
| `late`    | Last pass     | Override existing providers (wins on collision) |

## Next steps

----
url: https://docs.openclaw.ai/automation/auth-monitoring
----

# Auth Monitoring - OpenClaw

## [​](#auth-monitoring)Auth monitoring

OpenClaw exposes OAuth expiry health via `openclaw models status`. Use that for automation and alerting; scripts are optional extras for phone workflows.

## [​](#preferred-cli-check-portable)Preferred: CLI check (portable)

```
openclaw models status --check
```

Exit codes:

* `0`: OK
* `1`: expired or missing credentials
* `2`: expiring soon (within 24h)

This works in cron/systemd and requires no extra scripts.

## [​](#optional-scripts-ops-/-phone-workflows)Optional scripts (ops / phone workflows)

These live under `scripts/` and are **optional**. They assume SSH access to the gateway host and are tuned for systemd + Termux.

* `scripts/claude-auth-status.sh` now uses `openclaw models status --json` as the source of truth (falling back to direct file reads if the CLI is unavailable), so keep `openclaw` on `PATH` for timers.
* `scripts/auth-monitor.sh`: cron/systemd timer target; sends alerts (ntfy or phone).
* `scripts/systemd/openclaw-auth-monitor.{service,timer}`: systemd user timer.
* `scripts/claude-auth-status.sh`: Claude Code + OpenClaw auth checker (full/json/simple).
* `scripts/mobile-reauth.sh`: guided re‑auth flow over SSH.
* `scripts/termux-quick-auth.sh`: one‑tap widget status + open auth URL.
* `scripts/termux-auth-widget.sh`: full guided widget flow.
* `scripts/termux-sync-widget.sh`: sync Claude Code creds → OpenClaw.

If you don’t need phone automation or systemd timers, skip these scripts.

----
url: https://docs.openclaw.ai/tools/loop-detection
----

# Tool-loop detection - OpenClaw

OpenClaw can keep agents from getting stuck in repeated tool-call patterns. The guard is **disabled by default**. Enable it only where needed, because it can block legitimate repeated calls with strict settings.

## Why this exists

## Configuration block

Global defaults:

```
{
  tools: {
    loopDetection: {
      enabled: false,
      historySize: 30,
      warningThreshold: 10,
      criticalThreshold: 20,
      globalCircuitBreakerThreshold: 30,
      detectors: {
        genericRepeat: true,
        knownPollNoProgress: true,
        pingPong: true,
      },
    },
  },
}
```

Per-agent override (optional):

### Field behavior

## Recommended setup

## Logs and expected behavior

When a loop is detected, OpenClaw reports a loop event and blocks or dampens the next tool-cycle depending on severity. This protects users from runaway token spend and lockups while preserving normal tool access.

## Notes

----
url: https://docs.openclaw.ai/start/wizard
----

# Onboarding (CLI) - OpenClaw

CLI onboarding is the **recommended** way to set up OpenClaw on macOS, Linux, or Windows (via WSL2; strongly recommended). It configures a local Gateway or a remote Gateway connection, plus channels, skills, and workspace defaults in one guided flow.

To reconfigure later:

## QuickStart vs Advanced

Onboarding starts with **QuickStart** (defaults) vs **Advanced** (full control).

## What onboarding configures

**Local mode (default)** walks you through these steps:

1. **Model/Auth** — choose any supported provider/auth flow (API key, OAuth, or setup-token), including Custom Provider (OpenAI-compatible, Anthropic-compatible, or Unknown auto-detect). Pick a default model. Security note: if this agent will run tools or process webhook/hooks content, prefer the strongest latest-generation model available and keep tool policy strict. Weaker/older tiers are easier to prompt-inject. For non-interactive runs, `--secret-input-mode ref` stores env-backed refs in auth profiles instead of plaintext API key values. In non-interactive `ref` mode, the provider env var must be set; passing inline key flags without that env var fails fast. In interactive runs, choosing secret reference mode lets you point at either an environment variable or a configured provider ref (`file` or `exec`), with a fast preflight validation before saving.
2. **Workspace** — Location for agent files (default `~/.openclaw/workspace`). Seeds bootstrap files.
3. **Gateway** — Port, bind address, auth mode, Tailscale exposure. In interactive token mode, choose default plaintext token storage or opt into SecretRef. Non-interactive token SecretRef path: `--gateway-token-ref-env <ENV_VAR>`.
4. **Channels** — WhatsApp, Telegram, Discord, Google Chat, Mattermost, Signal, BlueBubbles, or iMessage.
5. **Daemon** — Installs a LaunchAgent (macOS) or systemd user unit (Linux/WSL2). If token auth requires a token and `gateway.auth.token` is SecretRef-managed, daemon install validates it but does not persist the resolved token into supervisor service environment metadata. If token auth requires a token and the configured token SecretRef is unresolved, daemon install is blocked with actionable guidance. If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, daemon install is blocked until mode is set explicitly.
6. **Health check** — Starts the Gateway and verifies it’s running.
7. **Skills** — Installs recommended skills and optional dependencies.

**Remote mode** only configures the local client to connect to a Gateway elsewhere. It does **not** install or change anything on the remote host.

## Add another agent

Use `openclaw agents add <name>` to create a separate agent with its own workspace, sessions, and auth profiles. Running without `--workspace` launches onboarding. What it sets:

Notes:

## Full reference

For detailed step-by-step breakdowns and config outputs, see [CLI Setup Reference](https://docs.openclaw.ai/start/wizard-cli-reference). For non-interactive examples, see [CLI Automation](https://docs.openclaw.ai/start/wizard-cli-automation). For the deeper technical reference, including RPC details, see [Onboarding Reference](https://docs.openclaw.ai/reference/wizard).

----
url: https://docs.openclaw.ai/install/raspberry-pi
----

# Raspberry Pi - OpenClaw

## [​](#raspberry-pi)Raspberry Pi

Run a persistent, always-on OpenClaw Gateway on a Raspberry Pi. Since the Pi is just the gateway (models run in the cloud via API), even a modest Pi handles the workload well.

## [​](#prerequisites)Prerequisites

* Raspberry Pi 4 or 5 with 2 GB+ RAM (4 GB recommended)
* MicroSD card (16 GB+) or USB SSD (better performance)
* Official Pi power supply
* Network connection (Ethernet or WiFi)
* 64-bit Raspberry Pi OS (required — do not use 32-bit)
* About 30 minutes

## [​](#setup)Setup

1

[](#)

Flash the OS

Use **Raspberry Pi OS Lite (64-bit)** — no desktop needed for a headless server.

1. Download [Raspberry Pi Imager](https://www.raspberrypi.com/software/).

2. Choose OS: **Raspberry Pi OS Lite (64-bit)**.

3. In the settings dialog, pre-configure:

   * Hostname: `gateway-host`
   * Enable SSH
   * Set username and password
   * Configure WiFi (if not using Ethernet)

4. Flash to your SD card or USB drive, insert it, and boot the Pi.

2

[](#)

Connect via SSH

```
ssh user@gateway-host
```

3

[](#)

Update the system

```
sudo apt update && sudo apt upgrade -y
sudo apt install -y git curl build-essential

# Set timezone (important for cron and reminders)
sudo timedatectl set-timezone America/Chicago
```

4

[](#)

Install Node.js 24

```
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --version
```

5

[](#)

Add swap (important for 2 GB or less)

```
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# Reduce swappiness for low-RAM devices
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
```

6

[](#)

Install OpenClaw

```
curl -fsSL https://openclaw.ai/install.sh | bash
```

7

[](#)

Run onboarding

```
openclaw onboard --install-daemon
```

Follow the wizard. API keys are recommended over OAuth for headless devices. Telegram is the easiest channel to start with.

8

[](#)

Verify

```
openclaw status
sudo systemctl status openclaw
journalctl -u openclaw -f
```

9

[](#)

Access the Control UI

On your computer, get a dashboard URL from the Pi:

```
ssh user@gateway-host 'openclaw dashboard --no-open'
```

Then create an SSH tunnel in another terminal:

```
ssh -N -L 18789:127.0.0.1:18789 user@gateway-host
```

Open the printed URL in your local browser. For always-on remote access, see [Tailscale integration](https://docs.openclaw.ai/gateway/tailscale).

## [​](#performance-tips)Performance tips

**Use a USB SSD** — SD cards are slow and wear out. A USB SSD dramatically improves performance. See the [Pi USB boot guide](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#usb-mass-storage-boot). **Enable module compile cache** — Speeds up repeated CLI invocations on lower-power Pi hosts:

```
grep -q 'NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache' ~/.bashrc || cat >> ~/.bashrc <<'EOF' # pragma: allowlist secret
export NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache
mkdir -p /var/tmp/openclaw-compile-cache
export OPENCLAW_NO_RESPAWN=1
EOF
source ~/.bashrc
```

**Reduce memory usage** — For headless setups, free GPU memory and disable unused services:

```
echo 'gpu_mem=16' | sudo tee -a /boot/config.txt
sudo systemctl disable bluetooth
```

## [​](#troubleshooting)Troubleshooting

**Out of memory** — Verify swap is active with `free -h`. Disable unused services (`sudo systemctl disable cups bluetooth avahi-daemon`). Use API-based models only. **Slow performance** — Use a USB SSD instead of an SD card. Check for CPU throttling with `vcgencmd get_throttled` (should return `0x0`). **Service will not start** — Check logs with `journalctl -u openclaw --no-pager -n 100` and run `openclaw doctor --non-interactive`. **ARM binary issues** — If a skill fails with “exec format error”, check whether the binary has an ARM64 build. Verify architecture with `uname -m` (should show `aarch64`). **WiFi drops** — Disable WiFi power management: `sudo iwconfig wlan0 power off`.

## [​](#next-steps)Next steps

* [Channels](https://docs.openclaw.ai/channels) — connect Telegram, WhatsApp, Discord, and more
* [Gateway configuration](https://docs.openclaw.ai/gateway/configuration) — all config options
* [Updating](https://docs.openclaw.ai/install/updating) — keep OpenClaw up to date

----
url: https://docs.openclaw.ai/concepts/oauth
----

# OAuth - OpenClaw

OpenClaw supports “subscription auth” via OAuth for providers that offer it (notably **OpenAI Codex (ChatGPT OAuth)**). For Anthropic subscriptions, use the **setup-token** flow. Anthropic subscription use outside Claude Code has been restricted for some users in the past, so treat it as a user-choice risk and verify current Anthropic policy yourself. OpenAI Codex OAuth is explicitly supported for use in external tools like OpenClaw. This page explains: For Anthropic in production, API key auth is the safer recommended path over subscription setup-token auth.

OpenClaw also supports **provider plugins** that ship their own OAuth or API‑key flows. Run them via:

## The token sink (why it exists)

OAuth providers commonly mint a **new refresh token** during login/refresh flows. Some providers (or OAuth clients) can invalidate older refresh tokens when a new one is issued for the same user/app. Practical symptom:

To reduce that, OpenClaw treats `auth-profiles.json` as a **token sink**:

## Storage (where tokens live)

Secrets are stored **per-agent**:

Legacy import-only file (still supported, but not the main store):

All of the above also respect `$OPENCLAW_STATE_DIR` (state dir override). Full reference: [/gateway/configuration](https://docs.openclaw.ai/gateway/configuration-reference#auth-storage) For static secret refs and runtime snapshot activation behavior, see [Secrets Management](https://docs.openclaw.ai/gateway/secrets).

## Anthropic setup-token (subscription auth)

Run `claude setup-token` on any machine, then paste it into OpenClaw:

If you generated the token elsewhere, paste it manually:

Verify:

## OAuth exchange (how login works)

OpenClaw’s interactive login flows are implemented in `@mariozechner/pi-ai` and wired into the wizards/commands.

### Anthropic setup-token

Flow shape:

1. run `claude setup-token`
2. paste the token into OpenClaw
3. store as a token auth profile (no refresh)

The wizard path is `openclaw onboard` → auth choice `setup-token` (Anthropic).

### OpenAI Codex (ChatGPT OAuth)

OpenAI Codex OAuth is explicitly supported for use outside the Codex CLI, including OpenClaw workflows. Flow shape (PKCE):

1. generate PKCE verifier/challenge + random `state`
2. open `https://auth.openai.com/oauth/authorize?...`
3. try to capture callback on `http://127.0.0.1:1455/auth/callback`
4. if callback can’t bind (or you’re remote/headless), paste the redirect URL/code
5. exchange at `https://auth.openai.com/oauth/token`
6. extract `accountId` from the access token and store `{ access, refresh, expires, accountId }`

Wizard path is `openclaw onboard` → auth choice `openai-codex`.

## Refresh + expiry

Profiles store an `expires` timestamp. At runtime:

The refresh flow is automatic; you generally don’t need to manage tokens manually.

## Multiple accounts (profiles) + routing

Two patterns:

### 1) Preferred: separate agents

If you want “personal” and “work” to never interact, use isolated agents (separate sessions + credentials + workspace):

Then configure auth per-agent (wizard) and route chats to the right agent.

### 2) Advanced: multiple profiles in one agent

`auth-profiles.json` supports multiple profile IDs for the same provider. Pick which profile is used:

Example (session override):

How to see what profile IDs exist:

Related docs:

----
url: https://docs.openclaw.ai/providers/opencode-go
----

# OpenCode Go - OpenClaw

## [​](#opencode-go)OpenCode Go

OpenCode Go is the Go catalog within [OpenCode](https://docs.openclaw.ai/providers/opencode). It uses the same `OPENCODE_API_KEY` as the Zen catalog, but keeps the runtime provider id `opencode-go` so upstream per-model routing stays correct.

## [​](#supported-models)Supported models

* `opencode-go/kimi-k2.5`
* `opencode-go/glm-5`
* `opencode-go/minimax-m2.5`

## [​](#cli-setup)CLI setup

```
openclaw onboard --auth-choice opencode-go
# or non-interactive
openclaw onboard --opencode-go-api-key "$OPENCODE_API_KEY"
```

## [​](#config-snippet)Config snippet

```
{
  env: { OPENCODE_API_KEY: "YOUR_API_KEY_HERE" }, // pragma: allowlist secret
  agents: { defaults: { model: { primary: "opencode-go/kimi-k2.5" } } },
}
```

## [​](#routing-behavior)Routing behavior

OpenClaw handles per-model routing automatically when the model ref uses `opencode-go/...`.

## [​](#notes)Notes

* Use [OpenCode](https://docs.openclaw.ai/providers/opencode) for the shared onboarding and catalog overview.
* Runtime refs stay explicit: `opencode/...` for Zen, `opencode-go/...` for Go.

----
url: https://docs.openclaw.ai/cli/reset
----

# reset - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw reset](#openclaw-reset)

## [​](#openclaw-reset)`openclaw reset`

Reset local config/state (keeps the CLI installed).

```
openclaw backup create
openclaw reset
openclaw reset --dry-run
openclaw reset --scope config+creds+sessions --yes --non-interactive
```

Run `openclaw backup create` first if you want a restorable snapshot before removing local state.

[onboard](https://docs.openclaw.ai/cli/onboard)[secrets](https://docs.openclaw.ai/cli/secrets)

----
url: https://docs.openclaw.ai/cli/config
----

# config - OpenClaw

Config helpers for non-interactive edits in `openclaw.json`: get/set/unset/validate values by path and print the active config file. Run without a subcommand to open the configure wizard (same as `openclaw configure`).

## Examples

## Paths

Paths use dot or bracket notation:

Use the agent list index to target a specific agent:

## Values

Values are parsed as JSON5 when possible; otherwise they are treated as strings. Use `--strict-json` to require JSON5 parsing. `--json` remains supported as a legacy alias.

## `config set` modes

`openclaw config set` supports four assignment styles:

1. Value mode: `openclaw config set <path> <value>`
2. SecretRef builder mode:

3) Provider builder mode (`secrets.providers.<alias>` path only):

4. Batch mode (`--batch-json` or `--batch-file`):

Batch parsing always uses the batch payload (`--batch-json`/`--batch-file`) as the source of truth. `--strict-json` / `--json` do not change batch parsing behavior. JSON path/value mode remains supported for both SecretRefs and providers:

## Provider Builder Flags

Provider builder targets must use `secrets.providers.<alias>` as the path. Common flags:

Env provider (`--provider-source env`):

File provider (`--provider-source file`):

Exec provider (`--provider-source exec`):

Hardened exec provider example:

## Dry run

Use `--dry-run` to validate changes without writing `openclaw.json`.

Dry-run behavior:

`--dry-run --json` prints a machine-readable report:

### JSON Output Shape

```
{
  ok: boolean,
  operations: number,
  configPath: string,
  inputModes: ["value" | "json" | "builder", ...],
  checks: {
    schema: boolean,
    resolvability: boolean,
    resolvabilityComplete: boolean,
  },
  refsChecked: number,
  skippedExecRefs: number,
  errors?: [
    {
      kind: "schema" | "resolvability",
      message: string,
      ref?: string, // present for resolvability errors
    },
  ],
}
```

Success example:

Failure example:

```
{
  "ok": false,
  "operations": 1,
  "configPath": "~/.openclaw/openclaw.json",
  "inputModes": ["builder"],
  "checks": {
    "schema": false,
    "resolvability": true,
    "resolvabilityComplete": true
  },
  "refsChecked": 1,
  "skippedExecRefs": 0,
  "errors": [
    {
      "kind": "resolvability",
      "message": "Error: Environment variable \"MISSING_TEST_SECRET\" is not set.",
      "ref": "env:default:MISSING_TEST_SECRET"
    }
  ]
}
```

If dry-run fails:

## Subcommands

Restart the gateway after edits.

## Validate

Validate the current config against the active schema without starting the gateway.

----
url: https://docs.openclaw.ai/channels/msteams
----

# Microsoft Teams - OpenClaw

## Microsoft Teams (plugin)

> “Abandon all hope, ye who enter here.”

Updated: 2026-01-21 Status: text + DM attachments are supported; channel/group file sending requires `sharePointSiteId` + Graph permissions (see [Sending files in group chats](#sending-files-in-group-chats)). Polls are sent via Adaptive Cards.

## Plugin required

Microsoft Teams ships as a plugin and is not bundled with the core install. **Breaking change (2026.1.15):** Microsoft Teams moved out of core. If you use it, you must install the plugin. Explainable: keeps core installs lighter and lets Microsoft Teams dependencies update independently. Install via CLI (npm registry):

Local checkout (when running from a git repo):

If you choose Teams during setup and a git checkout is detected, OpenClaw will offer the local install path automatically. Details: [Plugins](https://docs.openclaw.ai/tools/plugin)

## Quick setup (beginner)

1. Install the Microsoft Teams plugin.
2. Create an **Azure Bot** (App ID + client secret + tenant ID).
3. Configure OpenClaw with those credentials.
4. Expose `/api/messages` (port 3978 by default) via a public URL or tunnel.
5. Install the Teams app package and start the gateway.

Minimal config:

Note: group chats are blocked by default (`channels.msteams.groupPolicy: "allowlist"`). To allow group replies, set `channels.msteams.groupAllowFrom` (or use `groupPolicy: "open"` to allow any member, mention-gated).

## Goals

## Config writes

By default, Microsoft Teams is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`). Disable with:

## Access control (DMs + groups)

**DM access**

**Group access**

Example:

**Teams + channel allowlist**

Example:

## How it works

1. Install the Microsoft Teams plugin.
2. Create an **Azure Bot** (App ID + secret + tenant ID).
3. Build a **Teams app package** that references the bot and includes the RSC permissions below.
4. Upload/install the Teams app into a team (or personal scope for DMs).
5. Configure `msteams` in `~/.openclaw/openclaw.json` (or env vars) and start the gateway.
6. The gateway listens for Bot Framework webhook traffic on `/api/messages` by default.

## Azure Bot Setup (Prerequisites)

Before configuring OpenClaw, you need to create an Azure Bot resource.

### Step 1: Create Azure Bot

1. Go to [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot)

2. Fill in the **Basics** tab:

   | Field              | Value                                                    |
   | ------------------ | -------------------------------------------------------- |
   | **Bot handle**     | Your bot name, e.g., `openclaw-msteams` (must be unique) |
   | **Subscription**   | Select your Azure subscription                           |
   | **Resource group** | Create new or use existing                               |
   | **Pricing tier**   | **Free** for dev/testing                                 |
   | **Type of App**    | **Single Tenant** (recommended - see note below)         |
   | **Creation type**  | **Create new Microsoft App ID**                          |

> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.

3. Click **Review + create** → **Create** (wait \~1-2 minutes)

### Step 2: Get Credentials

1. Go to your Azure Bot resource → **Configuration**
2. Copy **Microsoft App ID** → this is your `appId`
3. Click **Manage Password** → go to the App Registration
4. Under **Certificates & secrets** → **New client secret** → copy the **Value** → this is your `appPassword`
5. Go to **Overview** → copy **Directory (tenant) ID** → this is your `tenantId`

### Step 3: Configure Messaging Endpoint

1. In Azure Bot → **Configuration**
2. Set **Messaging endpoint** to your webhook URL:

### Step 4: Enable Teams Channel

1. In Azure Bot → **Channels**
2. Click **Microsoft Teams** → Configure → Save
3. Accept the Terms of Service

## Local Development (Tunneling)

Teams can’t reach `localhost`. Use a tunnel for local development: **Option A: ngrok**

**Option B: Tailscale Funnel**

## Teams Developer Portal (Alternative)

Instead of manually creating a manifest ZIP, you can use the [Teams Developer Portal](https://dev.teams.microsoft.com/apps):

1. Click **+ New app**
2. Fill in basic info (name, description, developer info)
3. Go to **App features** → **Bot**
4. Select **Enter a bot ID manually** and paste your Azure Bot App ID
5. Check scopes: **Personal**, **Team**, **Group Chat**
6. Click **Distribute** → **Download app package**
7. In Teams: **Apps** → **Manage your apps** → **Upload a custom app** → select the ZIP

This is often easier than hand-editing JSON manifests.

## Testing the Bot

**Option A: Azure Web Chat (verify webhook first)**

1. In Azure Portal → your Azure Bot resource → **Test in Web Chat**
2. Send a message - you should see a response
3. This confirms your webhook endpoint works before Teams setup

**Option B: Teams (after app installation)**

1. Install the Teams app (sideload or org catalog)
2. Find the bot in Teams and send a DM
3. Check gateway logs for incoming activity

## Setup (minimal text-only)

1. **Install the Microsoft Teams plugin**
2. **Bot registration**
3. **Teams app manifest**
4. **Configure OpenClaw** You can also use environment variables instead of config keys:
5. **Bot endpoint**
6. **Run the gateway**

## History context

## Current Teams RSC Permissions (Manifest)

These are the **existing resourceSpecific permissions** in our Teams app manifest. They only apply inside the team/chat where the app is installed. **For channels (team scope):**

**For group chats:**

## Example Teams Manifest (redacted)

Minimal, valid example with the required fields. Replace IDs and URLs.

```
{
  $schema: "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json",
  manifestVersion: "1.23",
  version: "1.0.0",
  id: "00000000-0000-0000-0000-000000000000",
  name: { short: "OpenClaw" },
  developer: {
    name: "Your Org",
    websiteUrl: "https://example.com",
    privacyUrl: "https://example.com/privacy",
    termsOfUseUrl: "https://example.com/terms",
  },
  description: { short: "OpenClaw in Teams", full: "OpenClaw in Teams" },
  icons: { outline: "outline.png", color: "color.png" },
  accentColor: "#5B6DEF",
  bots: [
    {
      botId: "11111111-1111-1111-1111-111111111111",
      scopes: ["personal", "team", "groupChat"],
      isNotificationOnly: false,
      supportsCalling: false,
      supportsVideo: false,
      supportsFiles: true,
    },
  ],
  webApplicationInfo: {
    id: "11111111-1111-1111-1111-111111111111",
  },
  authorization: {
    permissions: {
      resourceSpecific: [
        { name: "ChannelMessage.Read.Group", type: "Application" },
        { name: "ChannelMessage.Send.Group", type: "Application" },
        { name: "Member.Read.Group", type: "Application" },
        { name: "Owner.Read.Group", type: "Application" },
        { name: "ChannelSettings.Read.Group", type: "Application" },
        { name: "TeamMember.Read.Group", type: "Application" },
        { name: "TeamSettings.Read.Group", type: "Application" },
        { name: "ChatMessage.Read.Chat", type: "Application" },
      ],
    },
  },
}
```

### Manifest caveats (must-have fields)

### Updating an existing app

To update an already-installed Teams app (e.g., to add RSC permissions):

1. Update your `manifest.json` with the new settings
2. **Increment the `version` field** (e.g., `1.0.0` → `1.1.0`)
3. **Re-zip** the manifest with icons (`manifest.json`, `outline.png`, `color.png`)
4. Upload the new zip:
5. **For team channels:** Reinstall the app in each team for new permissions to take effect
6. **Fully quit and relaunch Teams** (not just close the window) to clear cached app metadata

## Capabilities: RSC only vs Graph

### With **Teams RSC only** (app installed, no Graph API permissions)

Works:

Does NOT work:

### With **Teams RSC + Microsoft Graph Application permissions**

Adds:

### RSC vs Graph API

| Capability              | RSC Permissions      | Graph API                           |
| ----------------------- | -------------------- | ----------------------------------- |
| **Real-time messages**  | Yes (via webhook)    | No (polling only)                   |
| **Historical messages** | No                   | Yes (can query history)             |
| **Setup complexity**    | App manifest only    | Requires admin consent + token flow |
| **Works offline**       | No (must be running) | Yes (query anytime)                 |

**Bottom line:** RSC is for real-time listening; Graph API is for historical access. For catching up on missed messages while offline, you need Graph API with `ChannelMessage.Read.All` (requires admin consent).

## Graph-enabled media + history (required for channels)

If you need images/files in **channels** or want to fetch **message history**, you must enable Microsoft Graph permissions and grant admin consent.

1. In Entra ID (Azure AD) **App Registration**, add Microsoft Graph **Application permissions**:
2. **Grant admin consent** for the tenant.
3. Bump the Teams app **manifest version**, re-upload, and **reinstall the app in Teams**.
4. **Fully quit and relaunch Teams** to clear cached app metadata.

**Additional permission for user mentions:** User @mentions work out of the box for users in the conversation. However, if you want to dynamically search and mention users who are **not in the current conversation**, add `User.Read.All` (Application) permission and grant admin consent.

## Known Limitations

### Webhook timeouts

Teams delivers messages via HTTP webhook. If processing takes too long (e.g., slow LLM responses), you may see:

OpenClaw handles this by returning quickly and sending replies proactively, but very slow responses may still cause issues.

### Formatting

Teams markdown is more limited than Slack or Discord:

## Configuration

Key settings (see `/gateway/configuration` for shared channel patterns):

## Routing & Sessions

## Reply Style: Threads vs Posts

Teams recently introduced two channel UI styles over the same underlying data model:

| Style                    | Description                                               | Recommended `replyStyle` |
| ------------------------ | --------------------------------------------------------- | ------------------------ |
| **Posts** (classic)      | Messages appear as cards with threaded replies underneath | `thread` (default)       |
| **Threads** (Slack-like) | Messages flow linearly, more like Slack                   | `top-level`              |

**The problem:** The Teams API does not expose which UI style a channel uses. If you use the wrong `replyStyle`:

**Solution:** Configure `replyStyle` per-channel based on how the channel is set up:

## Attachments & Images

**Current limitations:**

Without Graph permissions, channel messages with images will be received as text-only (the image content is not accessible to the bot). By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Override with `channels.msteams.mediaAllowHosts` (use `["*"]` to allow any host). Authorization headers are only attached for hosts in `channels.msteams.mediaAuthAllowHosts` (defaults to Graph + Bot Framework hosts). Keep this list strict (avoid multi-tenant suffixes).

## Sending files in group chats

Bots can send files in DMs using the FileConsentCard flow (built-in). However, **sending files in group chats/channels** requires additional setup:

| Context                  | How files are sent                           | Setup needed                                    |
| ------------------------ | -------------------------------------------- | ----------------------------------------------- |
| **DMs**                  | FileConsentCard → user accepts → bot uploads | Works out of the box                            |
| **Group chats/channels** | Upload to SharePoint → share link            | Requires `sharePointSiteId` + Graph permissions |
| **Images (any context)** | Base64-encoded inline                        | Works out of the box                            |

### Why group chats need SharePoint

Bots don’t have a personal OneDrive drive (the `/me/drive` Graph API endpoint doesn’t work for application identities). To send files in group chats/channels, the bot uploads to a **SharePoint site** and creates a sharing link.

### Setup

1. **Add Graph API permissions** in Entra ID (Azure AD) → App Registration:
2. **Grant admin consent** for the tenant.
3. **Get your SharePoint site ID:**
4. **Configure OpenClaw:**

### Sharing behavior

| Permission                              | Sharing behavior                                          |
| --------------------------------------- | --------------------------------------------------------- |
| `Sites.ReadWrite.All` only              | Organization-wide sharing link (anyone in org can access) |
| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access)      |

Per-user sharing is more secure as only the chat participants can access the file. If `Chat.Read.All` permission is missing, the bot falls back to organization-wide sharing.

### Fallback behavior

| Scenario                                          | Result                                             |
| ------------------------------------------------- | -------------------------------------------------- |
| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link            |
| Group chat + file + no `sharePointSiteId`         | Attempt OneDrive upload (may fail), send text only |
| Personal chat + file                              | FileConsentCard flow (works without SharePoint)    |
| Any context + image                               | Base64-encoded inline (works without SharePoint)   |

### Files stored location

Uploaded files are stored in a `/OpenClawShared/` folder in the configured SharePoint site’s default document library.

## Polls (Adaptive Cards)

OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API).

## Adaptive Cards (arbitrary)

Send any Adaptive Card JSON to Teams users or conversations using the `message` tool or CLI. The `card` parameter accepts an Adaptive Card JSON object. When `card` is provided, the message text is optional. **Agent tool:**

**CLI:**

See [Adaptive Cards documentation](https://adaptivecards.io/) for card schema and examples. For target format details, see [Target formats](#target-formats) below.

## Target formats

MSTeams targets use prefixes to distinguish between users and conversations:

| Target type         | Format                           | Example                                             |
| ------------------- | -------------------------------- | --------------------------------------------------- |
| User (by ID)        | `user:<aad-object-id>`           | `user:40a1a0ed-4ff2-4164-a219-55518990c197`         |
| User (by name)      | `user:<display-name>`            | `user:John Smith` (requires Graph API)              |
| Group/channel       | `conversation:<conversation-id>` | `conversation:19:abc123...@thread.tacv2`            |
| Group/channel (raw) | `<conversation-id>`              | `19:abc123...@thread.tacv2` (if contains `@thread`) |

**CLI examples:**

**Agent tool examples:**

Note: Without the `user:` prefix, names default to group/team resolution. Always use `user:` when targeting people by display name.

## Proactive messaging

## Team and Channel IDs (Common Gotcha)

The `groupId` query parameter in Teams URLs is **NOT** the team ID used for configuration. Extract IDs from the URL path instead: **Team URL:**

**Channel URL:**

**For config:**

## Private Channels

Bots have limited support in private channels:

| Feature                      | Standard Channels | Private Channels       |
| ---------------------------- | ----------------- | ---------------------- |
| Bot installation             | Yes               | Limited                |
| Real-time messages (webhook) | Yes               | May not work           |
| RSC permissions              | Yes               | May behave differently |
| @mentions                    | Yes               | If bot is accessible   |
| Graph API history            | Yes               | Yes (with permissions) |

**Workarounds if private channels don’t work:**

1. Use standard channels for bot interactions
2. Use DMs - users can always message the bot directly
3. Use Graph API for historical access (requires `ChannelMessage.Read.All`)

## Troubleshooting

### Common issues

### Manifest upload errors

### RSC permissions not working

1. Verify `webApplicationInfo.id` matches your bot’s App ID exactly
2. Re-upload the app and reinstall in the team/chat
3. Check if your org admin has blocked RSC permissions
4. Confirm you’re using the right scope: `ChannelMessage.Read.Group` for teams, `ChatMessage.Read.Chat` for group chats

## References

----
url: https://docs.openclaw.ai/gateway/discovery
----

# Discovery and Transports - OpenClaw

OpenClaw has two distinct problems that look similar on the surface:

1. **Operator remote control**: the macOS menu bar app controlling a gateway running elsewhere.
2. **Node pairing**: iOS/Android (and future nodes) finding a gateway and pairing securely.

The design goal is to keep all network discovery/advertising in the **Node Gateway** (`openclaw gateway`) and keep clients (mac app, iOS) as consumers.

## Terms

Protocol details:

## Why we keep both “direct” and SSH

## Discovery inputs (how clients learn where the gateway is)

### 1) Bonjour / mDNS (LAN only)

Bonjour is best-effort and does not cross networks. It is only used for “same LAN” convenience. Target direction:

Troubleshooting and beacon details: [Bonjour](https://docs.openclaw.ai/gateway/bonjour).

#### Service beacon details

Security notes:

Disable/override:

### 2) Tailnet (cross-network)

For London/Vienna style setups, Bonjour won’t help. The recommended “direct” target is:

If the gateway can detect it is running under Tailscale, it publishes `tailnetDns` as an optional hint for clients (including wide-area beacons).

### 3) Manual / SSH target

When there is no direct route (or direct is disabled), clients can always connect via SSH by forwarding the loopback gateway port. See [Remote access](https://docs.openclaw.ai/gateway/remote).

## Transport selection (client policy)

Recommended client behavior:

1. If a paired direct endpoint is configured and reachable, use it.
2. Else, if Bonjour finds a gateway on LAN, offer a one-tap “Use this gateway” choice and save it as the direct endpoint.
3. Else, if a tailnet DNS/IP is configured, try direct.
4. Else, fall back to SSH.

## Pairing + auth (direct transport)

The gateway is the source of truth for node/client admission.

## Responsibilities by component

----
url: https://docs.openclaw.ai/plugins/zalouser
----

# Zalo Personal Plugin - OpenClaw

## [​](#zalo-personal-plugin)Zalo Personal (plugin)

Zalo Personal support for OpenClaw via a plugin, using native `zca-js` to automate a normal Zalo user account.

> **Warning:** Unofficial automation may lead to account suspension/ban. Use at your own risk.

## [​](#naming)Naming

Channel id is `zalouser` to make it explicit this automates a **personal Zalo user account** (unofficial). We keep `zalo` reserved for a potential future official Zalo API integration.

## [​](#where-it-runs)Where it runs

This plugin runs **inside the Gateway process**. If you use a remote Gateway, install/configure it on the **machine running the Gateway**, then restart the Gateway. No external `zca`/`openzca` CLI binary is required.

## [​](#install)Install

### [​](#option-a-install-from-npm)Option A: install from npm

```
openclaw plugins install @openclaw/zalouser
```

Restart the Gateway afterwards.

### [​](#option-b-install-from-a-local-folder-dev)Option B: install from a local folder (dev)

```
openclaw plugins install ./extensions/zalouser
cd ./extensions/zalouser && pnpm install
```

Restart the Gateway afterwards.

## [​](#config)Config

Channel config lives under `channels.zalouser` (not `plugins.entries.*`):

```
{
  channels: {
    zalouser: {
      enabled: true,
      dmPolicy: "pairing",
    },
  },
}
```

## [​](#cli)CLI

```
openclaw channels login --channel zalouser
openclaw channels logout --channel zalouser
openclaw channels status --probe
openclaw message send --channel zalouser --target <threadId> --message "Hello from OpenClaw"
openclaw directory peers list --channel zalouser --query "name"
```

## [​](#agent-tool)Agent tool

Tool name: `zalouser` Actions: `send`, `image`, `link`, `friends`, `groups`, `me`, `status` Channel message actions also support `react` for message reactions.

----
url: https://docs.openclaw.ai/tools/skills-config
----

# Skills Config - OpenClaw

All skills-related configuration lives under `skills` in `~/.openclaw/openclaw.json`.

```
{
  skills: {
    allowBundled: ["gemini", "peekaboo"],
    load: {
      extraDirs: ["~/Projects/agent-scripts/skills", "~/Projects/oss/some-skill-pack/skills"],
      watch: true,
      watchDebounceMs: 250,
    },
    install: {
      preferBrew: true,
      nodeManager: "npm", // npm | pnpm | yarn | bun (Gateway runtime still Node; bun not recommended)
    },
    entries: {
      "image-lab": {
        enabled: true,
        apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string
        env: {
          GEMINI_API_KEY: "GEMINI_KEY_HERE",
        },
      },
      peekaboo: { enabled: true },
      sag: { enabled: false },
    },
  },
}
```

For built-in image generation/editing, prefer `agents.defaults.imageGenerationModel` plus the core `image_generate` tool. `skills.entries.*` is only for custom or third-party skill workflows. If you select a specific image provider/model, also configure that provider’s auth/API key. Typical examples: `GEMINI_API_KEY` or `GOOGLE_API_KEY` for `google/*`, `OPENAI_API_KEY` for `openai/*`, and `FAL_KEY` for `fal/*`. Examples:

## Fields

Per-skill fields:

## Notes

### Sandboxed skills + env vars

When a session is **sandboxed**, skill processes run inside Docker. The sandbox does **not** inherit the host `process.env`. Use one of:

Global `env` and `skills.entries.<skill>.env/apiKey` apply to **host** runs only.

----
url: https://docs.openclaw.ai/platforms/mac/skills
----

# Skills (macOS) - OpenClaw

## [​](#skills-macos)Skills (macOS)

The macOS app surfaces OpenClaw skills via the gateway; it does not parse skills locally.

## [​](#data-source)Data source

* `skills.status` (gateway) returns all skills plus eligibility and missing requirements (including allowlist blocks for bundled skills).
* Requirements are derived from `metadata.openclaw.requires` in each `SKILL.md`.

## [​](#install-actions)Install actions

* `metadata.openclaw.install` defines install options (brew/node/go/uv).
* The app calls `skills.install` to run installers on the gateway host.
* The gateway surfaces only one preferred installer when multiple are provided (brew when available, otherwise node manager from `skills.install`, default npm).

## [​](#env/api-keys)Env/API keys

* The app stores keys in `~/.openclaw/openclaw.json` under `skills.entries.<skillKey>`.
* `skills.update` patches `enabled`, `apiKey`, and `env`.

## [​](#remote-mode)Remote mode

* Install + config updates happen on the gateway host (not the local Mac).

----
url: https://docs.openclaw.ai/tools/apply-patch
----

# apply_patch Tool - OpenClaw

* [apply\_patch tool](#apply_patch-tool)
* [Parameters](#parameters)
* [Notes](#notes)
* [Example](#example)

## [​](#apply_patch-tool)apply\_patch tool

Apply file changes using a structured patch format. This is ideal for multi-file or multi-hunk edits where a single `edit` call would be brittle. The tool accepts a single `input` string that wraps one or more file operations:

```
*** Begin Patch
*** Add File: path/to/file.txt
+line 1
+line 2
*** Update File: src/app.ts
@@
-old line
+new line
*** Delete File: obsolete.txt
*** End Patch
```

## [​](#parameters)Parameters

* `input` (required): Full patch contents including `*** Begin Patch` and `*** End Patch`.

## [​](#notes)Notes

* Patch paths support relative paths (from the workspace directory) and absolute paths.
* `tools.exec.applyPatch.workspaceOnly` defaults to `true` (workspace-contained). Set it to `false` only if you intentionally want `apply_patch` to write/delete outside the workspace directory.
* Use `*** Move to:` within an `*** Update File:` hunk to rename files.
* `*** End of File` marks an EOF-only insert when needed.
* Experimental and disabled by default. Enable with `tools.exec.applyPatch.enabled`.
* OpenAI-only (including OpenAI Codex). Optionally gate by model via `tools.exec.applyPatch.allowModels`.
* Config is only under `tools.exec`.

## [​](#example)Example

```
{
  "tool": "apply_patch",
  "input": "*** Begin Patch\n*** Update File: src/index.ts\n@@\n-const foo = 1\n+const foo = 2\n*** End Patch"
}
```

[Auth Monitoring](https://docs.openclaw.ai/automation/auth-monitoring)[Browser (OpenClaw-managed)](https://docs.openclaw.ai/tools/browser)

----
url: https://docs.openclaw.ai/reference/templates/BOOT
----

[Skip to main content](#content-area)

[OpenClaw home page](/)

[Get started](/)[Install](/install)[Channels](/channels)[Agents](/concepts/architecture)[Tools & Plugins](/tools)[Models](/providers)[Platforms](/platforms)[Gateway & Ops](/gateway)[Reference](/cli)[Help](/help)

##### CLI commands

* [CLI Reference](/cli)
*
*
*
*
*
*
*
*

##### RPC and API

* [RPC Adapters](/reference/rpc)
* [Device Model Database](/reference/device-models)

##### Templates

* [Default AGENTS.md](/reference/AGENTS.default)
* [AGENTS.md Template](/reference/templates/AGENTS)
* [BOOT.md Template](/reference/templates/BOOT)
* [BOOTSTRAP.md Template](/reference/templates/BOOTSTRAP)
* [HEARTBEAT.md Template](/reference/templates/HEARTBEAT)
* [IDENTITY Template](/reference/templates/IDENTITY)
* [SOUL.md Template](/reference/templates/SOUL)
* [TOOLS.md Template](/reference/templates/TOOLS)
* [USER Template](/reference/templates/USER)

##### Technical reference

* [Pi Integration Architecture](/pi)
* [Onboarding Reference](/reference/wizard)
* [Token Use and Costs](/reference/token-use)
* [SecretRef Credential Surface](/reference/secretref-credential-surface)
* [Prompt Caching](/reference/prompt-caching)
* [API Usage and Costs](/reference/api-usage-costs)
* [Transcript Hygiene](/reference/transcript-hygiene)
* [Memory configuration reference](/reference/memory-config)
* [Date and Time](/date-time)

##### Concept internals

* [TypeBox](/concepts/typebox)
* [Markdown Formatting](/concepts/markdown-formatting)
* [Typing Indicators](/concepts/typing-indicators)
* [Usage Tracking](/concepts/usage-tracking)
* [Timezones](/concepts/timezone)

##### Project

* [Credits](/reference/credits)

##### Release policy

* [Release Policy](/reference/RELEASING)
* [Tests](/reference/test)

- [BOOT.md](#boot-md)

# [​](#boot-md)BOOT.md

Add short, explicit instructions for what OpenClaw should do on startup (enable `hooks.internal.enabled`). If the task sends a message, use the message tool and then reply with NO\_REPLY.

[AGENTS.md Template](/reference/templates/AGENTS)[BOOTSTRAP.md Template](/reference/templates/BOOTSTRAP)

⌘I

----
url: https://docs.openclaw.ai/channels/tlon
----

# Tlon - OpenClaw

## Tlon (plugin)

Tlon is a decentralized messenger built on Urbit. OpenClaw connects to your Urbit ship and can respond to DMs and group chat messages. Group replies require an @ mention by default and can be further restricted via allowlists. Status: supported via plugin. DMs, group mentions, thread replies, rich text formatting, and image uploads are supported. Reactions and polls are not yet supported.

## Plugin required

Tlon ships as a plugin and is not bundled with the core install. Install via CLI (npm registry):

Local checkout (when running from a git repo):

Details: [Plugins](https://docs.openclaw.ai/tools/plugin)

## Setup

1. Install the Tlon plugin.
2. Gather your ship URL and login code.
3. Configure `channels.tlon`.
4. Restart the gateway.
5. DM the bot or mention it in a group channel.

Minimal config (single account):

## Private/LAN ships

By default, OpenClaw blocks private/internal hostnames and IP ranges for SSRF protection. If your ship is running on a private network (localhost, LAN IP, or internal hostname), you must explicitly opt in:

This applies to URLs like:

⚠️ Only enable this if you trust your local network. This setting disables SSRF protections for requests to your ship URL.

## Group channels

Auto-discovery is enabled by default. You can also pin channels manually:

Disable auto-discovery:

## Access control

DM allowlist (empty = no DMs allowed, use `ownerShip` for approval flow):

Group authorization (restricted by default):

```
{
  channels: {
    tlon: {
      defaultAuthorizedShips: ["~zod"],
      authorization: {
        channelRules: {
          "chat/~host-ship/general": {
            mode: "restricted",
            allowedShips: ["~zod", "~nec"],
          },
          "chat/~host-ship/announcements": {
            mode: "open",
          },
        },
      },
    },
  },
}
```

## Owner and approval system

Set an owner ship to receive approval requests when unauthorized users try to interact:

The owner ship is **automatically authorized everywhere** — DM invites are auto-accepted and channel messages are always allowed. You don’t need to add the owner to `dmAllowlist` or `defaultAuthorizedShips`. When set, the owner receives DM notifications for:

## Auto-accept settings

Auto-accept DM invites (for ships in dmAllowlist):

Auto-accept group invites:

## Delivery targets (CLI/cron)

Use these with `openclaw message send` or cron delivery:

## Bundled skill

The Tlon plugin includes a bundled skill ([`@tloncorp/tlon-skill`](https://github.com/tloncorp/tlon-skill)) that provides CLI access to Tlon operations:

The skill is automatically available when the plugin is installed.

## Capabilities

| Feature         | Status                                 |
| --------------- | -------------------------------------- |
| Direct messages | ✅ Supported                            |
| Groups/channels | ✅ Supported (mention-gated by default) |
| Threads         | ✅ Supported (auto-replies in thread)   |
| Rich text       | ✅ Markdown converted to Tlon format    |
| Images          | ✅ Uploaded to Tlon storage             |
| Reactions       | ✅ Via [bundled skill](#bundled-skill)  |
| Polls           | ❌ Not yet supported                    |
| Native commands | ✅ Supported (owner-only by default)    |

## Troubleshooting

Run this ladder first:

Common failures:

## Configuration reference

Full configuration: [Configuration](https://docs.openclaw.ai/gateway/configuration) Provider options:

## Notes

----
url: https://docs.openclaw.ai/channels/synology-chat
----

# Synology Chat - OpenClaw

## Synology Chat (plugin)

Status: supported via plugin as a direct-message channel using Synology Chat webhooks. The plugin accepts inbound messages from Synology Chat outgoing webhooks and sends replies through a Synology Chat incoming webhook.

## Plugin required

Synology Chat is plugin-based and not part of the default core channel install. Install from a local checkout:

Details: [Plugins](https://docs.openclaw.ai/tools/plugin)

## Quick setup

1. Install and enable the Synology Chat plugin.
2. In Synology Chat integrations:
3. Point the outgoing webhook URL to your OpenClaw gateway:
4. Finish setup in OpenClaw.
5. Restart gateway and send a DM to the Synology Chat bot.

Minimal config:

```
{
  channels: {
    "synology-chat": {
      enabled: true,
      token: "synology-outgoing-token",
      incomingUrl: "https://nas.example.com/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=...",
      webhookPath: "/webhook/synology",
      dmPolicy: "allowlist",
      allowedUserIds: ["123456"],
      rateLimitPerMinute: 30,
      allowInsecureSsl: false,
    },
  },
}
```

## Environment variables

For the default account, you can use env vars:

Config values override env vars.

## DM policy and access control

## Outbound delivery

Use numeric Synology Chat user IDs as targets. Examples:

Media sends are supported by URL-based file delivery.

## Multi-account

Multiple Synology Chat accounts are supported under `channels.synology-chat.accounts`. Each account can override token, incoming URL, webhook path, DM policy, and limits. Direct-message sessions are isolated per account and user, so the same numeric `user_id` on two different Synology accounts does not share transcript state. Give each enabled account a distinct `webhookPath`. OpenClaw now rejects duplicate exact paths and refuses to start named accounts that only inherit a shared webhook path in multi-account setups. If you intentionally need legacy inheritance for a named account, set `dangerouslyAllowInheritedWebhookPath: true` on that account or at `channels.synology-chat`, but duplicate exact paths are still rejected fail-closed. Prefer explicit per-account paths.

```
{
  channels: {
    "synology-chat": {
      enabled: true,
      accounts: {
        default: {
          token: "token-a",
          incomingUrl: "https://nas-a.example.com/...token=...",
        },
        alerts: {
          token: "token-b",
          incomingUrl: "https://nas-b.example.com/...token=...",
          webhookPath: "/webhook/synology-alerts",
          dmPolicy: "allowlist",
          allowedUserIds: ["987654"],
        },
      },
    },
  },
}
```

## Security notes

----
url: https://docs.openclaw.ai/channels/groups
----

# Groups - OpenClaw

OpenClaw treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, Slack, Signal, iMessage, Microsoft Teams, Zalo.

## Beginner intro (2 minutes)

OpenClaw “lives” on your own messaging accounts. There is no separate WhatsApp bot user. If **you** are in a group, OpenClaw can see that group and respond there. Default behavior:

Translation: allowlisted senders can trigger OpenClaw by mentioning it.

> TL;DR

Quick flow (what happens to a group message):

If you want…

| Goal                                         | What to set                                                |
| -------------------------------------------- | ---------------------------------------------------------- |
| Allow all groups but only reply on @mentions | `groups: { "*": { requireMention: true } }`                |
| Disable all group replies                    | `groupPolicy: "disabled"`                                  |
| Only specific groups                         | `groups: { "<group-id>": { ... } }` (no `"*"` key)         |
| Only you can trigger in groups               | `groupPolicy: "allowlist"`, `groupAllowFrom: ["+1555..."]` |

## Session keys

## Pattern: personal DMs + public groups (single agent)

Yes — this works well if your “personal” traffic is **DMs** and your “public” traffic is **groups**. Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in Docker while your main DM session stays on-host. This gives you one agent “brain” (shared workspace + memory), but two execution postures:

> If you need truly separate workspaces/personas (“personal” and “public” must never mix), use a second agent + bindings. See [Multi-Agent Routing](https://docs.openclaw.ai/concepts/multi-agent).

Example (DMs on host, groups sandboxed + messaging-only tools):

```
{
  agents: {
    defaults: {
      sandbox: {
        mode: "non-main", // groups/channels are non-main -> sandboxed
        scope: "session", // strongest isolation (one container per group/channel)
        workspaceAccess: "none",
      },
    },
  },
  tools: {
    sandbox: {
      tools: {
        // If allow is non-empty, everything else is blocked (deny still wins).
        allow: ["group:messaging", "group:sessions"],
        deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"],
      },
    },
  },
}
```

Want “groups can only see folder X” instead of “no host access”? Keep `workspaceAccess: "none"` and mount only allowlisted paths into the sandbox:

Related:

## Display labels

## Group policy

Control how group/room messages are handled per channel:

```
{
  channels: {
    whatsapp: {
      groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
      groupAllowFrom: ["+15551234567"],
    },
    telegram: {
      groupPolicy: "disabled",
      groupAllowFrom: ["123456789"], // numeric Telegram user id (wizard can resolve @username)
    },
    signal: {
      groupPolicy: "disabled",
      groupAllowFrom: ["+15551234567"],
    },
    imessage: {
      groupPolicy: "disabled",
      groupAllowFrom: ["chat_id:123"],
    },
    msteams: {
      groupPolicy: "disabled",
      groupAllowFrom: ["user@org.com"],
    },
    discord: {
      groupPolicy: "allowlist",
      guilds: {
        GUILD_ID: { channels: { help: { allow: true } } },
      },
    },
    slack: {
      groupPolicy: "allowlist",
      channels: { "#general": { allow: true } },
    },
    matrix: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["@owner:example.org"],
      groups: {
        "!roomId:example.org": { allow: true },
        "#alias:example.org": { allow: true },
      },
    },
  },
}
```

| Policy        | Behavior                                                     |
| ------------- | ------------------------------------------------------------ |
| `"open"`      | Groups bypass allowlists; mention-gating still applies.      |
| `"disabled"`  | Block all group messages entirely.                           |
| `"allowlist"` | Only allow groups/rooms that match the configured allowlist. |

Notes:

Quick mental model (evaluation order for group messages):

1. `groupPolicy` (open/disabled/allowlist)
2. group allowlists (`*.groups`, `*.groupAllowFrom`, channel-specific allowlist)
3. mention gating (`requireMention`, `/activation`)

## Mention gating (default)

Group messages require a mention unless overridden per group. Defaults live per subsystem under `*.groups."*"`. Replying to a bot message counts as an implicit mention (when the channel supports reply metadata). This applies to Telegram, WhatsApp, Slack, Discord, and Microsoft Teams.

```
{
  channels: {
    whatsapp: {
      groups: {
        "*": { requireMention: true },
        "123@g.us": { requireMention: false },
      },
    },
    telegram: {
      groups: {
        "*": { requireMention: true },
        "123456789": { requireMention: false },
      },
    },
    imessage: {
      groups: {
        "*": { requireMention: true },
        "123": { requireMention: false },
      },
    },
  },
  agents: {
    list: [
      {
        id: "main",
        groupChat: {
          mentionPatterns: ["@openclaw", "openclaw", "\\+15555550123"],
          historyLimit: 50,
        },
      },
    ],
  },
}
```

Notes:

## Group/channel tool restrictions (optional)

Some channel configs support restricting which tools are available **inside a specific group/room/channel**.

Resolution order (most specific wins):

1. group/channel `toolsBySender` match
2. group/channel `tools`
3. default (`"*"`) `toolsBySender` match
4. default (`"*"`) `tools`

Example (Telegram):

```
{
  channels: {
    telegram: {
      groups: {
        "*": { tools: { deny: ["exec"] } },
        "-1001234567890": {
          tools: { deny: ["exec", "read", "write"] },
          toolsBySender: {
            "id:123456789": { alsoAllow: ["exec"] },
          },
        },
      },
    },
  },
}
```

Notes:

## Group allowlists

When `channels.whatsapp.groups`, `channels.telegram.groups`, or `channels.imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior. Common intents (copy/paste):

1. Disable all group replies

2) Allow only specific groups (WhatsApp)

3. Allow all groups but require mention (explicit)

4) Only the owner can trigger in groups (WhatsApp)

## Activation (owner-only)

Group owners can toggle per-group activation:

Owner is determined by `channels.whatsapp.allowFrom` (or the bot’s self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.

## Context fields

Group inbound payloads set:

The agent system prompt includes a group intro on the first turn of a new group session. It reminds the model to respond like a human, avoid Markdown tables, and avoid typing literal `\n` sequences.

## iMessage specifics

## WhatsApp specifics

See [Group messages](https://docs.openclaw.ai/channels/group-messages) for WhatsApp-only behavior (history injection, mention handling details).

----
url: https://docs.openclaw.ai/gateway/configuration-reference
----

# Configuration Reference - OpenClaw

Every field available in `~/.openclaw/openclaw.json`. For a task-oriented overview, see [Configuration](https://docs.openclaw.ai/gateway/configuration). Config format is **JSON5** (comments + trailing commas allowed). All fields are optional — OpenClaw uses safe defaults when omitted.

***

## Channels

Each channel starts automatically when its config section exists (unless `enabled: false`).

### DM and group access

All channels support DM policies and group policies:

| DM policy           | Behavior                                                        |
| ------------------- | --------------------------------------------------------------- |
| `pairing` (default) | Unknown senders get a one-time pairing code; owner must approve |
| `allowlist`         | Only senders in `allowFrom` (or paired allow store)             |
| `open`              | Allow all inbound DMs (requires `allowFrom: ["*"]`)             |
| `disabled`          | Ignore all inbound DMs                                          |

| Group policy          | Behavior                                               |
| --------------------- | ------------------------------------------------------ |
| `allowlist` (default) | Only groups matching the configured allowlist          |
| `open`                | Bypass group allowlists (mention-gating still applies) |
| `disabled`            | Block all group/room messages                          |

### Channel model overrides

Use `channels.modelByChannel` to pin specific channel IDs to a model. Values accept `provider/model` or configured model aliases. The channel mapping applies when a session does not already have a model override (for example, set via `/model`).

### Channel defaults and heartbeat

Use `channels.defaults` for shared group-policy and heartbeat behavior across providers:

### WhatsApp

WhatsApp runs through the gateway’s web channel (Baileys Web). It starts automatically when a linked session exists.

```
{
  channels: {
    whatsapp: {
      dmPolicy: "pairing", // pairing | allowlist | open | disabled
      allowFrom: ["+15555550123", "+447700900123"],
      textChunkLimit: 4000,
      chunkMode: "length", // length | newline
      mediaMaxMb: 50,
      sendReadReceipts: true, // blue ticks (false in self-chat mode)
      groups: {
        "*": { requireMention: true },
      },
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15551234567"],
    },
  },
  web: {
    enabled: true,
    heartbeatSeconds: 60,
    reconnect: {
      initialMs: 2000,
      maxMs: 120000,
      factor: 1.4,
      jitter: 0.2,
      maxAttempts: 0,
    },
  },
}
```

Multi-account WhatsApp

### Telegram

```
{
  channels: {
    telegram: {
      enabled: true,
      botToken: "your-bot-token",
      dmPolicy: "pairing",
      allowFrom: ["tg:123456789"],
      groups: {
        "*": { requireMention: true },
        "-1001234567890": {
          allowFrom: ["@admin"],
          systemPrompt: "Keep answers brief.",
          topics: {
            "99": {
              requireMention: false,
              skills: ["search"],
              systemPrompt: "Stay on topic.",
            },
          },
        },
      },
      customCommands: [
        { command: "backup", description: "Git backup" },
        { command: "generate", description: "Create an image" },
      ],
      historyLimit: 50,
      replyToMode: "first", // off | first | all
      linkPreview: true,
      streaming: "partial", // off | partial | block | progress (default: off; opt in explicitly to avoid preview-edit rate limits)
      actions: { reactions: true, sendMessage: true },
      reactionNotifications: "own", // off | own | all
      mediaMaxMb: 100,
      retry: {
        attempts: 3,
        minDelayMs: 400,
        maxDelayMs: 30000,
        jitter: 0.1,
      },
      network: {
        autoSelectFamily: true,
        dnsResultOrder: "ipv4first",
      },
      proxy: "socks5://localhost:9050",
      webhookUrl: "https://example.com/telegram-webhook",
      webhookSecret: "secret",
      webhookPath: "/telegram-webhook",
    },
  },
}
```

### Discord

```
{
  channels: {
    discord: {
      enabled: true,
      token: "your-bot-token",
      mediaMaxMb: 8,
      allowBots: false,
      actions: {
        reactions: true,
        stickers: true,
        polls: true,
        permissions: true,
        messages: true,
        threads: true,
        pins: true,
        search: true,
        memberInfo: true,
        roleInfo: true,
        roles: false,
        channelInfo: true,
        voiceStatus: true,
        events: true,
        moderation: false,
      },
      replyToMode: "off", // off | first | all
      dmPolicy: "pairing",
      allowFrom: ["1234567890", "123456789012345678"],
      dm: { enabled: true, groupEnabled: false, groupChannels: ["openclaw-dm"] },
      guilds: {
        "123456789012345678": {
          slug: "friends-of-openclaw",
          requireMention: false,
          ignoreOtherMentions: true,
          reactionNotifications: "own",
          users: ["987654321098765432"],
          channels: {
            general: { allow: true },
            help: {
              allow: true,
              requireMention: true,
              users: ["987654321098765432"],
              skills: ["docs"],
              systemPrompt: "Short answers only.",
            },
          },
        },
      },
      historyLimit: 20,
      textChunkLimit: 2000,
      chunkMode: "length", // length | newline
      streaming: "off", // off | partial | block | progress (progress maps to partial on Discord)
      maxLinesPerMessage: 17,
      ui: {
        components: {
          accentColor: "#5865F2",
        },
      },
      threadBindings: {
        enabled: true,
        idleHours: 24,
        maxAgeHours: 0,
        spawnSubagentSessions: false, // opt-in for sessions_spawn({ thread: true })
      },
      voice: {
        enabled: true,
        autoJoin: [
          {
            guildId: "123456789012345678",
            channelId: "234567890123456789",
          },
        ],
        daveEncryption: true,
        decryptionFailureTolerance: 24,
        tts: {
          provider: "openai",
          openai: { voice: "alloy" },
        },
      },
      retry: {
        attempts: 3,
        minDelayMs: 500,
        maxDelayMs: 30000,
        jitter: 0.1,
      },
    },
  },
}
```

**Reaction notification modes:** `off` (none), `own` (bot’s messages, default), `all` (all messages), `allowlist` (from `guilds.<id>.users` on all messages).

### Google Chat

```
{
  channels: {
    googlechat: {
      enabled: true,
      serviceAccountFile: "/path/to/service-account.json",
      audienceType: "app-url", // app-url | project-number
      audience: "https://gateway.example.com/googlechat",
      webhookPath: "/googlechat",
      botUser: "users/1234567890",
      dm: {
        enabled: true,
        policy: "pairing",
        allowFrom: ["users/1234567890"],
      },
      groupPolicy: "allowlist",
      groups: {
        "spaces/AAAA": { allow: true, requireMention: true },
      },
      actions: { reactions: true },
      typingIndicator: "message",
      mediaMaxMb: 20,
    },
  },
}
```

### Slack

```
{
  channels: {
    slack: {
      enabled: true,
      botToken: "xoxb-...",
      appToken: "xapp-...",
      dmPolicy: "pairing",
      allowFrom: ["U123", "U456", "*"],
      dm: { enabled: true, groupEnabled: false, groupChannels: ["G123"] },
      channels: {
        C123: { allow: true, requireMention: true, allowBots: false },
        "#general": {
          allow: true,
          requireMention: true,
          allowBots: false,
          users: ["U123"],
          skills: ["docs"],
          systemPrompt: "Short answers only.",
        },
      },
      historyLimit: 50,
      allowBots: false,
      reactionNotifications: "own",
      reactionAllowlist: ["U123"],
      replyToMode: "off", // off | first | all
      thread: {
        historyScope: "thread", // thread | channel
        inheritParent: false,
      },
      actions: {
        reactions: true,
        messages: true,
        pins: true,
        memberInfo: true,
        emojiList: true,
      },
      slashCommand: {
        enabled: true,
        name: "openclaw",
        sessionPrefix: "slack:slash",
        ephemeral: true,
      },
      typingReaction: "hourglass_flowing_sand",
      textChunkLimit: 4000,
      chunkMode: "length",
      streaming: "partial", // off | partial | block | progress (preview mode)
      nativeStreaming: true, // use Slack native streaming API when streaming=partial
      mediaMaxMb: 20,
    },
  },
}
```

**Reaction notification modes:** `off`, `own` (default), `all`, `allowlist` (from `reactionAllowlist`). **Thread session isolation:** `thread.historyScope` is per-thread (default) or shared across channel. `thread.inheritParent` copies parent channel transcript to new threads.

| Action group | Default | Notes                  |
| ------------ | ------- | ---------------------- |
| reactions    | enabled | React + list reactions |
| messages     | enabled | Read/send/edit/delete  |
| pins         | enabled | Pin/unpin/list         |
| memberInfo   | enabled | Member info            |
| emojiList    | enabled | Custom emoji list      |

### Mattermost

Mattermost ships as a plugin: `openclaw plugins install @openclaw/mattermost`.

```
{
  channels: {
    mattermost: {
      enabled: true,
      botToken: "mm-token",
      baseUrl: "https://chat.example.com",
      dmPolicy: "pairing",
      chatmode: "oncall", // oncall | onmessage | onchar
      oncharPrefixes: [">", "!"],
      commands: {
        native: true, // opt-in
        nativeSkills: true,
        callbackPath: "/api/channels/mattermost/command",
        // Optional explicit URL for reverse-proxy/public deployments
        callbackUrl: "https://gateway.example.com/api/channels/mattermost/command",
      },
      textChunkLimit: 4000,
      chunkMode: "length",
    },
  },
}
```

Chat modes: `oncall` (respond on @-mention, default), `onmessage` (every message), `onchar` (messages starting with trigger prefix). When Mattermost native commands are enabled:

### Signal

```
{
  channels: {
    signal: {
      enabled: true,
      account: "+15555550123", // optional account binding
      dmPolicy: "pairing",
      allowFrom: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"],
      configWrites: true,
      reactionNotifications: "own", // off | own | all | allowlist
      reactionAllowlist: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"],
      historyLimit: 50,
    },
  },
}
```

**Reaction notification modes:** `off`, `own` (default), `all`, `allowlist` (from `reactionAllowlist`).

### BlueBubbles

BlueBubbles is the recommended iMessage path (plugin-backed, configured under `channels.bluebubbles`).

### iMessage

OpenClaw spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required.

```
{
  channels: {
    imessage: {
      enabled: true,
      cliPath: "imsg",
      dbPath: "~/Library/Messages/chat.db",
      remoteHost: "user@gateway-host",
      dmPolicy: "pairing",
      allowFrom: ["+15555550123", "user@example.com", "chat_id:123"],
      historyLimit: 50,
      includeAttachments: false,
      attachmentRoots: ["/Users/*/Library/Messages/Attachments"],
      remoteAttachmentRoots: ["/Users/*/Library/Messages/Attachments"],
      mediaMaxMb: 16,
      service: "auto",
      region: "US",
    },
  },
}
```

iMessage SSH wrapper example

### Microsoft Teams

Microsoft Teams is extension-backed and configured under `channels.msteams`.

### IRC

IRC is extension-backed and configured under `channels.irc`.

```
{
  channels: {
    irc: {
      enabled: true,
      dmPolicy: "pairing",
      configWrites: true,
      nickserv: {
        enabled: true,
        service: "NickServ",
        password: "${IRC_NICKSERV_PASSWORD}",
        register: false,
        registerEmail: "bot@example.com",
      },
    },
  },
}
```

### Multi-account (all channels)

Run multiple accounts per channel (each with its own `accountId`):

### Other extension channels

Many extension channels are configured as `channels.<id>` and documented in their dedicated channel pages (for example Feishu, Matrix, LINE, Nostr, Zalo, Nextcloud Talk, Synology Chat, and Twitch). See the full channel index: [Channels](https://docs.openclaw.ai/channels).

### Group chat mention gating

Group messages default to **require mention** (metadata mention or safe regex patterns). Applies to WhatsApp, Telegram, Discord, Google Chat, and iMessage group chats. **Mention types:**

`messages.groupChat.historyLimit` sets the global default. Channels can override with `channels.<channel>.historyLimit` (or per-account). Set `0` to disable.

#### DM history limits

Resolution: per-DM override → provider default → no limit (all retained). Supported: `telegram`, `whatsapp`, `discord`, `slack`, `signal`, `imessage`, `msteams`.

#### Self-chat mode

Include your own number in `allowFrom` to enable self-chat mode (ignores native @-mentions, only responds to text patterns):

```
{
  channels: {
    whatsapp: {
      allowFrom: ["+15555550123"],
      groups: { "*": { requireMention: true } },
    },
  },
  agents: {
    list: [
      {
        id: "main",
        groupChat: { mentionPatterns: ["reisponde", "@openclaw"] },
      },
    ],
  },
}
```

### Commands (chat command handling)

```
{
  commands: {
    native: "auto", // register native commands when supported
    text: true, // parse /commands in chat messages
    bash: false, // allow ! (alias: /bash)
    bashForegroundMs: 2000,
    config: false, // allow /config
    debug: false, // allow /debug
    restart: false, // allow /restart + gateway restart tool
    allowFrom: {
      "*": ["user1"],
      discord: ["user:123"],
    },
    useAccessGroups: true,
  },
}
```

Command details

***

## Agent defaults

### `agents.defaults.workspace`

Default: `~/.openclaw/workspace`.

### `agents.defaults.repoRoot`

Optional repository root shown in the system prompt’s Runtime line. If unset, OpenClaw auto-detects by walking upward from the workspace.

### `agents.defaults.skipBootstrap`

Disables automatic creation of workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`).

### `agents.defaults.bootstrapMaxChars`

Max characters per workspace bootstrap file before truncation. Default: `20000`.

### `agents.defaults.bootstrapTotalMaxChars`

Max total characters injected across all workspace bootstrap files. Default: `150000`.

### `agents.defaults.bootstrapPromptTruncationWarning`

Controls agent-visible warning text when bootstrap context is truncated. Default: `"once"`.

### `agents.defaults.imageMaxDimensionPx`

Max pixel size for the longest image side in transcript/tool image blocks before provider calls. Default: `1200`. Lower values usually reduce vision-token usage and request payload size for screenshot-heavy runs. Higher values preserve more visual detail.

### `agents.defaults.userTimezone`

Timezone for system prompt context (not message timestamps). Falls back to host timezone.

### `agents.defaults.timeFormat`

Time format in system prompt. Default: `auto` (OS preference).

### `agents.defaults.model`

```
{
  agents: {
    defaults: {
      models: {
        "anthropic/claude-opus-4-6": { alias: "opus" },
        "minimax/MiniMax-M2.7": { alias: "minimax" },
      },
      model: {
        primary: "anthropic/claude-opus-4-6",
        fallbacks: ["minimax/MiniMax-M2.7"],
      },
      imageModel: {
        primary: "openrouter/qwen/qwen-2.5-vl-72b-instruct:free",
        fallbacks: ["openrouter/google/gemini-2.0-flash-vision:free"],
      },
      imageGenerationModel: {
        primary: "openai/gpt-image-1",
        fallbacks: ["google/gemini-3.1-flash-image-preview"],
      },
      pdfModel: {
        primary: "anthropic/claude-opus-4-6",
        fallbacks: ["openai/gpt-5-mini"],
      },
      pdfMaxBytesMb: 10,
      pdfMaxPages: 20,
      thinkingDefault: "low",
      verboseDefault: "off",
      elevatedDefault: "on",
      timeoutSeconds: 600,
      mediaMaxMb: 5,
      contextTokens: 200000,
      maxConcurrent: 3,
    },
  },
}
```

* `model`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
* `imageModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
* `imageGenerationModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
* `pdfModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
* `pdfMaxBytesMb`: default PDF size limit for the `pdf` tool when `maxBytesMb` is not passed at call time.
* `pdfMaxPages`: default maximum pages considered by extraction fallback mode in the `pdf` tool.
* `model.primary`: format `provider/model` (e.g. `anthropic/claude-opus-4-6`). If you omit the provider, OpenClaw assumes `anthropic` (deprecated).
* `models`: the configured model catalog and allowlist for `/model`. Each entry can include `alias` (shortcut) and `params` (provider-specific, for example `temperature`, `maxTokens`, `cacheRetention`, `context1m`).
* `params` merge precedence (config): `agents.defaults.models["provider/model"].params` is the base, then `agents.list[].params` (matching agent id) overrides by key.
* Config writers that mutate these fields (for example `/models set`, `/models set-image`, and fallback add/remove commands) save canonical object form and preserve existing fallback lists when possible.
* `maxConcurrent`: max parallel agent runs across sessions (each session still serialized). Default: 1.

**Built-in alias shorthands** (only apply when the model is in `agents.defaults.models`):

| Alias               | Model                                  |
| ------------------- | -------------------------------------- |
| `opus`              | `anthropic/claude-opus-4-6`            |
| `sonnet`            | `anthropic/claude-sonnet-4-6`          |
| `gpt`               | `openai/gpt-5.4`                       |
| `gpt-mini`          | `openai/gpt-5-mini`                    |
| `gemini`            | `google/gemini-3.1-pro-preview`        |
| `gemini-flash`      | `google/gemini-3-flash-preview`        |
| `gemini-flash-lite` | `google/gemini-3.1-flash-lite-preview` |

Your configured aliases always win over defaults. Z.AI GLM-4.x models automatically enable thinking mode unless you set `--thinking off` or define `agents.defaults.models["zai/<model>"].params.thinking` yourself. Z.AI models enable `tool_stream` by default for tool call streaming. Set `agents.defaults.models["zai/<model>"].params.tool_stream` to `false` to disable it. Anthropic Claude 4.6 models default to `adaptive` thinking when no explicit thinking level is set.

### `agents.defaults.cliBackends`

Optional CLI backends for text-only fallback runs (no tool calls). Useful as a backup when API providers fail.

```
{
  agents: {
    defaults: {
      cliBackends: {
        "claude-cli": {
          command: "/opt/homebrew/bin/claude",
        },
        "my-cli": {
          command: "my-cli",
          args: ["--json"],
          output: "json",
          modelArg: "--model",
          sessionArg: "--session",
          sessionMode: "existing",
          systemPromptArg: "--system",
          systemPromptWhen: "first",
          imageArg: "--image",
          imageMode: "repeat",
        },
      },
    },
  },
}
```

### `agents.defaults.heartbeat`

Periodic heartbeat runs.

```
{
  agents: {
    defaults: {
      heartbeat: {
        every: "30m", // 0m disables
        model: "openai/gpt-5.2-mini",
        includeReasoning: false,
        lightContext: false, // default: false; true keeps only HEARTBEAT.md from workspace bootstrap files
        isolatedSession: false, // default: false; true runs each heartbeat in a fresh session (no conversation history)
        session: "main",
        to: "+15555550123",
        directPolicy: "allow", // allow (default) | block
        target: "none", // default: none | options: last | whatsapp | telegram | discord | ...
        prompt: "Read HEARTBEAT.md if it exists...",
        ackMaxChars: 300,
        suppressToolErrorWarnings: false,
      },
    },
  },
}
```

### `agents.defaults.compaction`

```
{
  agents: {
    defaults: {
      compaction: {
        mode: "safeguard", // default | safeguard
        timeoutSeconds: 900,
        reserveTokensFloor: 24000,
        identifierPolicy: "strict", // strict | off | custom
        identifierInstructions: "Preserve deployment IDs, ticket IDs, and host:port pairs exactly.", // used when identifierPolicy=custom
        postCompactionSections: ["Session Startup", "Red Lines"], // [] disables reinjection
        model: "openrouter/anthropic/claude-sonnet-4-6", // optional compaction-only model override
        memoryFlush: {
          enabled: true,
          softThresholdTokens: 6000,
          systemPrompt: "Session nearing compaction. Store durable memories now.",
          prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.",
        },
      },
    },
  },
}
```

### `agents.defaults.contextPruning`

Prunes **old tool results** from in-memory context before sending to the LLM. Does **not** modify session history on disk.

```
{
  agents: {
    defaults: {
      contextPruning: {
        mode: "cache-ttl", // off | cache-ttl
        ttl: "1h", // duration (ms/s/m/h), default unit: minutes
        keepLastAssistants: 3,
        softTrimRatio: 0.3,
        hardClearRatio: 0.5,
        minPrunableToolChars: 50000,
        softTrim: { maxChars: 4000, headChars: 1500, tailChars: 1500 },
        hardClear: { enabled: true, placeholder: "[Old tool result content cleared]" },
        tools: { deny: ["browser", "canvas"] },
      },
    },
  },
}
```

cache-ttl mode behavior

See [Session Pruning](https://docs.openclaw.ai/concepts/session-pruning) for behavior details.

### Block streaming

See [Streaming](https://docs.openclaw.ai/concepts/streaming) for behavior + chunking details.

### Typing indicators

See [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators).

### `agents.defaults.sandbox`

Optional sandboxing for the embedded agent. See [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing) for the full guide.

```
{
  agents: {
    defaults: {
      sandbox: {
        mode: "non-main", // off | non-main | all
        backend: "docker", // docker | ssh | openshell
        scope: "agent", // session | agent | shared
        workspaceAccess: "none", // none | ro | rw
        workspaceRoot: "~/.openclaw/sandboxes",
        docker: {
          image: "openclaw-sandbox:bookworm-slim",
          containerPrefix: "openclaw-sbx-",
          workdir: "/workspace",
          readOnlyRoot: true,
          tmpfs: ["/tmp", "/var/tmp", "/run"],
          network: "none",
          user: "1000:1000",
          capDrop: ["ALL"],
          env: { LANG: "C.UTF-8" },
          setupCommand: "apt-get update && apt-get install -y git curl jq",
          pidsLimit: 256,
          memory: "1g",
          memorySwap: "2g",
          cpus: 1,
          ulimits: {
            nofile: { soft: 1024, hard: 2048 },
            nproc: 256,
          },
          seccompProfile: "/path/to/seccomp.json",
          apparmorProfile: "openclaw-sandbox",
          dns: ["1.1.1.1", "8.8.8.8"],
          extraHosts: ["internal.service:10.0.0.5"],
          binds: ["/home/user/source:/source:rw"],
        },
        ssh: {
          target: "user@gateway-host:22",
          command: "ssh",
          workspaceRoot: "/tmp/openclaw-sandboxes",
          strictHostKeyChecking: true,
          updateHostKeys: true,
          identityFile: "~/.ssh/id_ed25519",
          certificateFile: "~/.ssh/id_ed25519-cert.pub",
          knownHostsFile: "~/.ssh/known_hosts",
          // SecretRefs / inline contents also supported:
          // identityData: { source: "env", provider: "default", id: "SSH_IDENTITY" },
          // certificateData: { source: "env", provider: "default", id: "SSH_CERTIFICATE" },
          // knownHostsData: { source: "env", provider: "default", id: "SSH_KNOWN_HOSTS" },
        },
        browser: {
          enabled: false,
          image: "openclaw-sandbox-browser:bookworm-slim",
          network: "openclaw-sandbox-browser",
          cdpPort: 9222,
          cdpSourceRange: "172.21.0.1/32",
          vncPort: 5900,
          noVncPort: 6080,
          headless: false,
          enableNoVnc: true,
          allowHostControl: false,
          autoStart: true,
          autoStartTimeoutMs: 12000,
        },
        prune: {
          idleHours: 24,
          maxAgeDays: 7,
        },
      },
    },
  },
  tools: {
    sandbox: {
      tools: {
        allow: [
          "exec",
          "process",
          "read",
          "write",
          "edit",
          "apply_patch",
          "sessions_list",
          "sessions_history",
          "sessions_send",
          "sessions_spawn",
          "session_status",
        ],
        deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"],
      },
    },
  },
}
```

Sandbox details

**Backend:**

When `backend: "openshell"` is selected, runtime-specific settings move to `plugins.entries.openshell.config`.**SSH backend config:**

**SSH auth precedence:**

**SSH backend behavior:**

**Workspace access:**

**Scope:**

**OpenShell plugin config:**

```
{
  plugins: {
    entries: {
      openshell: {
        enabled: true,
        config: {
          mode: "mirror", // mirror | remote
          from: "openclaw",
          remoteWorkspaceDir: "/sandbox",
          remoteAgentWorkspaceDir: "/agent",
          gateway: "lab", // optional
          gatewayEndpoint: "https://lab.example", // optional
          policy: "strict", // optional OpenShell policy id
          providers: ["openai"], // optional
          autoProviders: true,
          timeoutSeconds: 120,
        },
      },
    },
  },
}
```

**OpenShell mode:**

In `remote` mode, host-local edits made outside OpenClaw are not synced into the sandbox automatically after the seed step. Transport is SSH into the OpenShell sandbox, but the plugin owns sandbox lifecycle and optional mirror sync.**`setupCommand`** runs once after container creation (via `sh -lc`). Needs network egress, writable root, root user.**Containers default to `network: "none"`** — set to `"bridge"` (or a custom bridge network) if the agent needs outbound access. `"host"` is blocked. `"container:<id>"` is blocked by default unless you explicitly set `sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true` (break-glass).**Inbound attachments** are staged into `media/inbound/*` in the active workspace.**`docker.binds`** mounts additional host directories; global and per-agent binds are merged.**Sandboxed browser** (`sandbox.browser.enabled`): Chromium + CDP in a container. noVNC URL injected into system prompt. Does not require `browser.enabled` in `openclaw.json`. noVNC observer access uses VNC auth by default and OpenClaw emits a short-lived token URL (instead of exposing the password in the shared URL).

Browser sandboxing and `sandbox.docker.binds` are currently Docker-only. Build images:

### `agents.list` (per-agent overrides)

```
{
  agents: {
    list: [
      {
        id: "main",
        default: true,
        name: "Main Agent",
        workspace: "~/.openclaw/workspace",
        agentDir: "~/.openclaw/agents/main/agent",
        model: "anthropic/claude-opus-4-6", // or { primary, fallbacks }
        thinkingDefault: "high", // per-agent thinking level override
        reasoningDefault: "on", // per-agent reasoning visibility override
        fastModeDefault: false, // per-agent fast mode override
        params: { cacheRetention: "none" }, // overrides matching defaults.models params by key
        identity: {
          name: "Samantha",
          theme: "helpful sloth",
          emoji: "🦥",
          avatar: "avatars/samantha.png",
        },
        groupChat: { mentionPatterns: ["@openclaw"] },
        sandbox: { mode: "off" },
        runtime: {
          type: "acp",
          acp: {
            agent: "codex",
            backend: "acpx",
            mode: "persistent",
            cwd: "/workspace/openclaw",
          },
        },
        subagents: { allowAgents: ["*"] },
        tools: {
          profile: "coding",
          allow: ["browser"],
          deny: ["canvas"],
          elevated: { enabled: true },
        },
      },
    ],
  },
}
```

* `id`: stable agent id (required).
* `default`: when multiple are set, first wins (warning logged). If none set, first list entry is default.
* `model`: string form overrides `primary` only; object form `{ primary, fallbacks }` overrides both (`[]` disables global fallbacks). Cron jobs that only override `primary` still inherit default fallbacks unless you set `fallbacks: []`.
* `params`: per-agent stream params merged over the selected model entry in `agents.defaults.models`. Use this for agent-specific overrides like `cacheRetention`, `temperature`, or `maxTokens` without duplicating the whole model catalog.
* `thinkingDefault`: optional per-agent default thinking level (`off | minimal | low | medium | high | xhigh | adaptive`). Overrides `agents.defaults.thinkingDefault` for this agent when no per-message or session override is set.
* `reasoningDefault`: optional per-agent default reasoning visibility (`on | off | stream`). Applies when no per-message or session reasoning override is set.
* `fastModeDefault`: optional per-agent default for fast mode (`true | false`). Applies when no per-message or session fast-mode override is set.
* `runtime`: optional per-agent runtime descriptor. Use `type: "acp"` with `runtime.acp` defaults (`agent`, `backend`, `mode`, `cwd`) when the agent should default to ACP harness sessions.
* `identity.avatar`: workspace-relative path, `http(s)` URL, or `data:` URI.
* `identity` derives defaults: `ackReaction` from `emoji`, `mentionPatterns` from `name`/`emoji`.
* `subagents.allowAgents`: allowlist of agent ids for `sessions_spawn` (`["*"]` = any; default: same agent only).
* Sandbox inheritance guard: if the requester session is sandboxed, `sessions_spawn` rejects targets that would run unsandboxed.

***

## Multi-agent routing

Run multiple isolated agents inside one Gateway. See [Multi-Agent](https://docs.openclaw.ai/concepts/multi-agent).

```
{
  agents: {
    list: [
      { id: "home", default: true, workspace: "~/.openclaw/workspace-home" },
      { id: "work", workspace: "~/.openclaw/workspace-work" },
    ],
  },
  bindings: [
    { agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
    { agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
  ],
}
```

### Binding match fields

**Deterministic match order:**

1. `match.peer`
2. `match.guildId`
3. `match.teamId`
4. `match.accountId` (exact, no peer/guild/team)
5. `match.accountId: "*"` (channel-wide)
6. Default agent

Within each tier, the first matching `bindings` entry wins. For `type: "acp"` entries, OpenClaw resolves by exact conversation identity (`match.channel` + account + `match.peer.id`) and does not use the route binding tier order above.

### Per-agent access profiles

Full access (no sandbox)

Read-only tools + workspace

```
{
  agents: {
    list: [
      {
        id: "family",
        workspace: "~/.openclaw/workspace-family",
        sandbox: { mode: "all", scope: "agent", workspaceAccess: "ro" },
        tools: {
          allow: [
            "read",
            "sessions_list",
            "sessions_history",
            "sessions_send",
            "sessions_spawn",
            "session_status",
          ],
          deny: ["write", "edit", "apply_patch", "exec", "process", "browser"],
        },
      },
    ],
  },
}
```

No filesystem access (messaging only)

```
{
  agents: {
    list: [
      {
        id: "public",
        workspace: "~/.openclaw/workspace-public",
        sandbox: { mode: "all", scope: "agent", workspaceAccess: "none" },
        tools: {
          allow: [
            "sessions_list",
            "sessions_history",
            "sessions_send",
            "sessions_spawn",
            "session_status",
            "whatsapp",
            "telegram",
            "slack",
            "discord",
            "gateway",
          ],
          deny: [
            "read",
            "write",
            "edit",
            "apply_patch",
            "exec",
            "process",
            "browser",
            "canvas",
            "nodes",
            "cron",
            "gateway",
            "image",
          ],
        },
      },
    ],
  },
}
```

See [Multi-Agent Sandbox & Tools](https://docs.openclaw.ai/tools/multi-agent-sandbox-tools) for precedence details.

***

## Session

```
{
  session: {
    scope: "per-sender",
    dmScope: "main", // main | per-peer | per-channel-peer | per-account-channel-peer
    identityLinks: {
      alice: ["telegram:123456789", "discord:987654321012345678"],
    },
    reset: {
      mode: "daily", // daily | idle
      atHour: 4,
      idleMinutes: 60,
    },
    resetByType: {
      thread: { mode: "daily", atHour: 4 },
      direct: { mode: "idle", idleMinutes: 240 },
      group: { mode: "idle", idleMinutes: 120 },
    },
    resetTriggers: ["/new", "/reset"],
    store: "~/.openclaw/agents/{agentId}/sessions/sessions.json",
    parentForkMaxTokens: 100000, // skip parent-thread fork above this token count (0 disables)
    maintenance: {
      mode: "warn", // warn | enforce
      pruneAfter: "30d",
      maxEntries: 500,
      rotateBytes: "10mb",
      resetArchiveRetention: "30d", // duration or false
      maxDiskBytes: "500mb", // optional hard budget
      highWaterBytes: "400mb", // optional cleanup target
    },
    threadBindings: {
      enabled: true,
      idleHours: 24, // default inactivity auto-unfocus in hours (`0` disables)
      maxAgeHours: 0, // default hard max age in hours (`0` disables)
    },
    mainKey: "main", // legacy (runtime always uses "main")
    agentToAgent: { maxPingPongTurns: 5 },
    sendPolicy: {
      rules: [{ action: "deny", match: { channel: "discord", chatType: "group" } }],
      default: "allow",
    },
  },
}
```

Session field details

***

## Messages

```
{
  messages: {
    responsePrefix: "🦞", // or "auto"
    ackReaction: "👀",
    ackReactionScope: "group-mentions", // group-mentions | group-all | direct | all
    removeAckAfterReply: false,
    queue: {
      mode: "collect", // steer | followup | collect | steer-backlog | steer+backlog | queue | interrupt
      debounceMs: 1000,
      cap: 20,
      drop: "summarize", // old | new | summarize
      byChannel: {
        whatsapp: "collect",
        telegram: "collect",
      },
    },
    inbound: {
      debounceMs: 2000, // 0 disables
      byChannel: {
        whatsapp: 5000,
        slack: 1500,
      },
    },
  },
}
```

### Response prefix

Per-channel/account overrides: `channels.<channel>.responsePrefix`, `channels.<channel>.accounts.<id>.responsePrefix`. Resolution (most specific wins): account → channel → global. `""` disables and stops cascade. `"auto"` derives `[{identity.name}]`. **Template variables:**

| Variable          | Description            | Example                     |
| ----------------- | ---------------------- | --------------------------- |
| `{model}`         | Short model name       | `claude-opus-4-6`           |
| `{modelFull}`     | Full model identifier  | `anthropic/claude-opus-4-6` |
| `{provider}`      | Provider name          | `anthropic`                 |
| `{thinkingLevel}` | Current thinking level | `high`, `low`, `off`        |
| `{identity.name}` | Agent identity name    | (same as `"auto"`)          |

Variables are case-insensitive. `{think}` is an alias for `{thinkingLevel}`.

### Ack reaction

### Inbound debounce

Batches rapid text-only messages from the same sender into a single agent turn. Media/attachments flush immediately. Control commands bypass debouncing.

### TTS (text-to-speech)

```
{
  messages: {
    tts: {
      auto: "always", // off | always | inbound | tagged
      mode: "final", // final | all
      provider: "elevenlabs",
      summaryModel: "openai/gpt-4.1-mini",
      modelOverrides: { enabled: true },
      maxTextLength: 4000,
      timeoutMs: 30000,
      prefsPath: "~/.openclaw/settings/tts.json",
      elevenlabs: {
        apiKey: "elevenlabs_api_key",
        baseUrl: "https://api.elevenlabs.io",
        voiceId: "voice_id",
        modelId: "eleven_multilingual_v2",
        seed: 42,
        applyTextNormalization: "auto",
        languageCode: "en",
        voiceSettings: {
          stability: 0.5,
          similarityBoost: 0.75,
          style: 0.0,
          useSpeakerBoost: true,
          speed: 1.0,
        },
      },
      openai: {
        apiKey: "openai_api_key",
        baseUrl: "https://api.openai.com/v1",
        model: "gpt-4o-mini-tts",
        voice: "alloy",
      },
    },
  },
}
```

***

## Talk

Defaults for Talk mode (macOS/iOS/Android).

```
{
  talk: {
    voiceId: "elevenlabs_voice_id",
    voiceAliases: {
      Clawd: "EXAVITQu4vr4xnSDxMaL",
      Roger: "CwhRBWXzGAHq8TQ4Fs17",
    },
    modelId: "eleven_v3",
    outputFormat: "mp3_44100_128",
    apiKey: "elevenlabs_api_key",
    silenceTimeoutMs: 1500,
    interruptOnSpeech: true,
  },
}
```

***

## Tools

### Tool profiles

`tools.profile` sets a base allowlist before `tools.allow`/`tools.deny`: Local onboarding defaults new local configs to `tools.profile: "coding"` when unset (existing explicit profiles are preserved).

| Profile     | Includes                                                                                  |
| ----------- | ----------------------------------------------------------------------------------------- |
| `minimal`   | `session_status` only                                                                     |
| `coding`    | `group:fs`, `group:runtime`, `group:sessions`, `group:memory`, `image`                    |
| `messaging` | `group:messaging`, `sessions_list`, `sessions_history`, `sessions_send`, `session_status` |
| `full`      | No restriction (same as unset)                                                            |

### Tool groups

| Group              | Tools                                                                                    |
| ------------------ | ---------------------------------------------------------------------------------------- |
| `group:runtime`    | `exec`, `process` (`bash` is accepted as an alias for `exec`)                            |
| `group:fs`         | `read`, `write`, `edit`, `apply_patch`                                                   |
| `group:sessions`   | `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` |
| `group:memory`     | `memory_search`, `memory_get`                                                            |
| `group:web`        | `web_search`, `web_fetch`                                                                |
| `group:ui`         | `browser`, `canvas`                                                                      |
| `group:automation` | `cron`, `gateway`                                                                        |
| `group:messaging`  | `message`                                                                                |
| `group:nodes`      | `nodes`                                                                                  |
| `group:openclaw`   | All built-in tools (excludes provider plugins)                                           |

### `tools.allow` / `tools.deny`

Global tool allow/deny policy (deny wins). Case-insensitive, supports `*` wildcards. Applied even when Docker sandbox is off.

### `tools.byProvider`

Further restrict tools for specific providers or models. Order: base profile → provider profile → allow/deny.

### `tools.elevated`

Controls elevated (host) exec access:

### `tools.exec`

```
{
  tools: {
    exec: {
      backgroundMs: 10000,
      timeoutSec: 1800,
      cleanupMs: 1800000,
      notifyOnExit: true,
      notifyOnExitEmptySuccess: false,
      applyPatch: {
        enabled: false,
        allowModels: ["gpt-5.2"],
      },
    },
  },
}
```

### `tools.loopDetection`

Tool-loop safety checks are **disabled by default**. Set `enabled: true` to activate detection. Settings can be defined globally in `tools.loopDetection` and overridden per-agent at `agents.list[].tools.loopDetection`.

```
{
  tools: {
    loopDetection: {
      enabled: true,
      historySize: 30,
      warningThreshold: 10,
      criticalThreshold: 20,
      globalCircuitBreakerThreshold: 30,
      detectors: {
        genericRepeat: true,
        knownPollNoProgress: true,
        pingPong: true,
      },
    },
  },
}
```

### `tools.web`

```
{
  tools: {
    web: {
      search: {
        enabled: true,
        apiKey: "brave_api_key", // or BRAVE_API_KEY env
        maxResults: 5,
        timeoutSeconds: 30,
        cacheTtlMinutes: 15,
      },
      fetch: {
        enabled: true,
        maxChars: 50000,
        maxCharsCap: 50000,
        timeoutSeconds: 30,
        cacheTtlMinutes: 15,
        userAgent: "custom-ua",
      },
    },
  },
}
```

### `tools.media`

Configures inbound media understanding (image/audio/video):

```
{
  tools: {
    media: {
      concurrency: 2,
      audio: {
        enabled: true,
        maxBytes: 20971520,
        scope: {
          default: "deny",
          rules: [{ action: "allow", match: { chatType: "direct" } }],
        },
        models: [
          { provider: "openai", model: "gpt-4o-mini-transcribe" },
          { type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] },
        ],
      },
      video: {
        enabled: true,
        maxBytes: 52428800,
        models: [{ provider: "google", model: "gemini-3-flash-preview" }],
      },
    },
  },
}
```

Media model entry fields

### `tools.agentToAgent`

### `tools.sessions`

Controls which sessions can be targeted by the session tools (`sessions_list`, `sessions_history`, `sessions_send`). Default: `tree` (current session + sessions spawned by it, such as subagents).

Notes:

### `tools.sessions_spawn`

Controls inline attachment support for `sessions_spawn`.

Notes:

### `tools.subagents`

***

## Custom providers and base URLs

OpenClaw uses the pi-coding-agent model catalog. Add custom providers via `models.providers` in config or `~/.openclaw/agents/<agentId>/agent/models.json`.

```
{
  models: {
    mode: "merge", // merge (default) | replace
    providers: {
      "custom-proxy": {
        baseUrl: "http://localhost:4000/v1",
        apiKey: "LITELLM_KEY",
        api: "openai-completions", // openai-completions | openai-responses | anthropic-messages | google-generative-ai
        models: [
          {
            id: "llama-3.1-8b",
            name: "Llama 3.1 8B",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 128000,
            maxTokens: 32000,
          },
        ],
      },
    },
  },
}
```

### Provider field details

### Provider examples

Cerebras (GLM 4.6 / 4.7)

```
{
  env: { CEREBRAS_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: {
        primary: "cerebras/zai-glm-4.7",
        fallbacks: ["cerebras/zai-glm-4.6"],
      },
      models: {
        "cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" },
        "cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" },
      },
    },
  },
  models: {
    mode: "merge",
    providers: {
      cerebras: {
        baseUrl: "https://api.cerebras.ai/v1",
        apiKey: "${CEREBRAS_API_KEY}",
        api: "openai-completions",
        models: [
          { id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" },
          { id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" },
        ],
      },
    },
  },
}
```

Use `cerebras/zai-glm-4.7` for Cerebras; `zai/glm-4.7` for Z.AI direct.

OpenCode

Z.AI (GLM-4.7)

Moonshot AI (Kimi)

```
{
  env: { MOONSHOT_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "moonshot/kimi-k2.5" },
      models: { "moonshot/kimi-k2.5": { alias: "Kimi K2.5" } },
    },
  },
  models: {
    mode: "merge",
    providers: {
      moonshot: {
        baseUrl: "https://api.moonshot.ai/v1",
        apiKey: "${MOONSHOT_API_KEY}",
        api: "openai-completions",
        models: [
          {
            id: "kimi-k2.5",
            name: "Kimi K2.5",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 256000,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

For the China endpoint: `baseUrl: "https://api.moonshot.cn/v1"` or `openclaw onboard --auth-choice moonshot-api-key-cn`.

Kimi Coding

Synthetic (Anthropic-compatible)

```
{
  env: { SYNTHETIC_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" },
      models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax M2.5" } },
    },
  },
  models: {
    mode: "merge",
    providers: {
      synthetic: {
        baseUrl: "https://api.synthetic.new/anthropic",
        apiKey: "${SYNTHETIC_API_KEY}",
        api: "anthropic-messages",
        models: [
          {
            id: "hf:MiniMaxAI/MiniMax-M2.5",
            name: "MiniMax M2.5",
            reasoning: true,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 192000,
            maxTokens: 65536,
          },
        ],
      },
    },
  },
}
```

Base URL should omit `/v1` (Anthropic client appends it). Shortcut: `openclaw onboard --auth-choice synthetic-api-key`.

MiniMax M2.7 (direct)

```
{
  agents: {
    defaults: {
      model: { primary: "minimax/MiniMax-M2.7" },
      models: {
        "minimax/MiniMax-M2.7": { alias: "Minimax" },
      },
    },
  },
  models: {
    mode: "merge",
    providers: {
      minimax: {
        baseUrl: "https://api.minimax.io/anthropic",
        apiKey: "${MINIMAX_API_KEY}",
        api: "anthropic-messages",
        models: [
          {
            id: "MiniMax-M2.7",
            name: "MiniMax M2.7",
            reasoning: true,
            input: ["text"],
            cost: { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0.12 },
            contextWindow: 200000,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

Set `MINIMAX_API_KEY`. Shortcut: `openclaw onboard --auth-choice minimax-api`. `MiniMax-M2.5` and `MiniMax-M2.5-highspeed` remain available if you prefer the older text models.

Local models (LM Studio)

See [Local Models](https://docs.openclaw.ai/gateway/local-models). TL;DR: run MiniMax M2.5 via LM Studio Responses API on serious hardware; keep hosted models merged for fallback.

***

## Skills

```
{
  skills: {
    allowBundled: ["gemini", "peekaboo"],
    load: {
      extraDirs: ["~/Projects/agent-scripts/skills"],
    },
    install: {
      preferBrew: true,
      nodeManager: "npm", // npm | pnpm | yarn
    },
    entries: {
      "image-lab": {
        apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string
        env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
      },
      peekaboo: { enabled: true },
      sag: { enabled: false },
    },
  },
}
```

***

## Plugins

```
{
  plugins: {
    enabled: true,
    allow: ["voice-call"],
    deny: [],
    load: {
      paths: ["~/Projects/oss/voice-call-extension"],
    },
    entries: {
      "voice-call": {
        enabled: true,
        hooks: {
          allowPromptInjection: false,
        },
        config: { provider: "twilio" },
      },
    },
  },
}
```

* Loaded from `~/.openclaw/extensions`, `<workspace>/.openclaw/extensions`, plus `plugins.load.paths`.

* Discovery accepts native OpenClaw plugins plus compatible Codex bundles and Claude bundles, including manifestless Claude default-layout bundles.

* **Config changes require a gateway restart.**

* `allow`: optional allowlist (only listed plugins load). `deny` wins.

* `plugins.entries.<id>.apiKey`: plugin-level API key convenience field (when supported by the plugin).

* `plugins.entries.<id>.env`: plugin-scoped env var map.

* `plugins.entries.<id>.hooks.allowPromptInjection`: when `false`, core blocks `before_prompt_build` and ignores prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride`. Applies to native plugin hooks and supported bundle-provided hook directories.

* `plugins.entries.<id>.subagent.allowModelOverride`: explicitly trust this plugin to request per-run `provider` and `model` overrides for background subagent runs.

* `plugins.entries.<id>.subagent.allowedModels`: optional allowlist of canonical `provider/model` targets for trusted subagent overrides. Use `"*"` only when you intentionally want to allow any model.

* `plugins.entries.<id>.config`: plugin-defined config object (validated by native OpenClaw plugin schema when available).

* Enabled Claude bundle plugins can also contribute embedded Pi defaults from `settings.json`; OpenClaw applies those as sanitized agent settings, not as raw OpenClaw config patches.

* `plugins.slots.memory`: pick the active memory plugin id, or `"none"` to disable memory plugins.

* `plugins.slots.contextEngine`: pick the active context engine plugin id; defaults to `"legacy"` unless you install and select another engine.

* `plugins.installs`: CLI-managed install metadata used by `openclaw plugins update`.

  * Includes `source`, `spec`, `sourcePath`, `installPath`, `version`, `resolvedName`, `resolvedVersion`, `resolvedSpec`, `integrity`, `shasum`, `resolvedAt`, `installedAt`.
  * Treat `plugins.installs.*` as managed state; prefer CLI commands over manual edits.

See [Plugins](https://docs.openclaw.ai/tools/plugin).

***

## Browser

```
{
  browser: {
    enabled: true,
    evaluateEnabled: true,
    defaultProfile: "user",
    ssrfPolicy: {
      dangerouslyAllowPrivateNetwork: true, // default trusted-network mode
      // allowPrivateNetwork: true, // legacy alias
      // hostnameAllowlist: ["*.example.com", "example.com"],
      // allowedHostnames: ["localhost"],
    },
    profiles: {
      openclaw: { cdpPort: 18800, color: "#FF4500" },
      work: { cdpPort: 18801, color: "#0066CC" },
      user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
      brave: {
        driver: "existing-session",
        attachOnly: true,
        userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
        color: "#FB542B",
      },
      remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" },
    },
    color: "#FF4500",
    // headless: false,
    // noSandbox: false,
    // extraArgs: [],
    // executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
    // attachOnly: false,
  },
}
```

***

## UI

***

## Gateway

```
{
  gateway: {
    mode: "local", // local | remote
    port: 18789,
    bind: "loopback",
    auth: {
      mode: "token", // none | token | password | trusted-proxy
      token: "your-token",
      // password: "your-password", // or OPENCLAW_GATEWAY_PASSWORD
      // trustedProxy: { userHeader: "x-forwarded-user" }, // for mode=trusted-proxy; see /gateway/trusted-proxy-auth
      allowTailscale: true,
      rateLimit: {
        maxAttempts: 10,
        windowMs: 60000,
        lockoutMs: 300000,
        exemptLoopback: true,
      },
    },
    tailscale: {
      mode: "off", // off | serve | funnel
      resetOnExit: false,
    },
    controlUi: {
      enabled: true,
      basePath: "/openclaw",
      // root: "dist/control-ui",
      // allowedOrigins: ["https://control.example.com"], // required for non-loopback Control UI
      // dangerouslyAllowHostHeaderOriginFallback: false, // dangerous Host-header origin fallback mode
      // allowInsecureAuth: false,
      // dangerouslyDisableDeviceAuth: false,
    },
    remote: {
      url: "ws://gateway.tailnet:18789",
      transport: "ssh", // ssh | direct
      token: "your-token",
      // password: "your-password",
    },
    trustedProxies: ["10.0.0.1"],
    // Optional. Default false.
    allowRealIpFallback: false,
    tools: {
      // Additional /tools/invoke HTTP denies
      deny: ["browser"],
      // Remove tools from the default HTTP deny list
      allow: ["gateway"],
    },
    push: {
      apns: {
        relay: {
          baseUrl: "https://relay.example.com",
          timeoutMs: 10000,
        },
      },
    },
  },
}
```

Gateway field details

* `mode`: `local` (run gateway) or `remote` (connect to remote gateway). Gateway refuses to start unless `local`.
* `port`: single multiplexed port for WS + HTTP. Precedence: `--port` > `OPENCLAW_GATEWAY_PORT` > `gateway.port` > `18789`.
* `bind`: `auto`, `loopback` (default), `lan` (`0.0.0.0`), `tailnet` (Tailscale IP only), or `custom`.
* **Legacy bind aliases**: use bind mode values in `gateway.bind` (`auto`, `loopback`, `lan`, `tailnet`, `custom`), not host aliases (`0.0.0.0`, `127.0.0.1`, `localhost`, `::`, `::1`).
* **Docker note**: the default `loopback` bind listens on `127.0.0.1` inside the container. With Docker bridge networking (`-p 18789:18789`), traffic arrives on `eth0`, so the gateway is unreachable. Use `--network host`, or set `bind: "lan"` (or `bind: "custom"` with `customBindHost: "0.0.0.0"`) to listen on all interfaces.
* **Auth**: required by default. Non-loopback binds require a shared token/password. Onboarding wizard generates a token by default.
* If both `gateway.auth.token` and `gateway.auth.password` are configured (including SecretRefs), set `gateway.auth.mode` explicitly to `token` or `password`. Startup and service install/repair flows fail when both are configured and mode is unset.
* `gateway.auth.mode: "none"`: explicit no-auth mode. Use only for trusted local loopback setups; this is intentionally not offered by onboarding prompts.
* `gateway.auth.mode: "trusted-proxy"`: delegate auth to an identity-aware reverse proxy and trust identity headers from `gateway.trustedProxies` (see [Trusted Proxy Auth](https://docs.openclaw.ai/gateway/trusted-proxy-auth)).
* `gateway.auth.allowTailscale`: when `true`, Tailscale Serve identity headers can satisfy Control UI/WebSocket auth (verified via `tailscale whois`); HTTP API endpoints still require token/password auth. This tokenless flow assumes the gateway host is trusted. Defaults to `true` when `tailscale.mode = "serve"`.
* `gateway.auth.rateLimit`: optional failed-auth limiter. Applies per client IP and per auth scope (shared-secret and device-token are tracked independently). Blocked attempts return `429` + `Retry-After`.
* Browser-origin WS auth attempts are always throttled with loopback exemption disabled (defense-in-depth against browser-based localhost brute force).
* `tailscale.mode`: `serve` (tailnet only, loopback bind) or `funnel` (public, requires auth).
* `controlUi.allowedOrigins`: explicit browser-origin allowlist for Gateway WebSocket connects. Required when browser clients are expected from non-loopback origins.
* `controlUi.dangerouslyAllowHostHeaderOriginFallback`: dangerous mode that enables Host-header origin fallback for deployments that intentionally rely on Host-header origin policy.
* `remote.transport`: `ssh` (default) or `direct` (ws/wss). For `direct`, `remote.url` must be `ws://` or `wss://`.
* `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`: client-side break-glass override that allows plaintext `ws://` to trusted private-network IPs; default remains loopback-only for plaintext.
* `gateway.remote.token` / `.password` are remote-client credential fields. They do not configure gateway auth by themselves.
* `gateway.push.apns.relay.baseUrl`: base HTTPS URL for the external APNs relay used by official/TestFlight iOS builds after they publish relay-backed registrations to the gateway. This URL must match the relay URL compiled into the iOS build.
* `gateway.push.apns.relay.timeoutMs`: gateway-to-relay send timeout in milliseconds. Defaults to `10000`.
* Relay-backed registrations are delegated to a specific gateway identity. The paired iOS app fetches `gateway.identity.get`, includes that identity in the relay registration, and forwards a registration-scoped send grant to the gateway. Another gateway cannot reuse that stored registration.
* `OPENCLAW_APNS_RELAY_BASE_URL` / `OPENCLAW_APNS_RELAY_TIMEOUT_MS`: temporary env overrides for the relay config above.
* `OPENCLAW_APNS_RELAY_ALLOW_HTTP=true`: development-only escape hatch for loopback HTTP relay URLs. Production relay URLs should stay on HTTPS.
* `gateway.channelHealthCheckMinutes`: channel health-monitor interval in minutes. Set `0` to disable health-monitor restarts globally. Default: `5`.
* `gateway.channelStaleEventThresholdMinutes`: stale-socket threshold in minutes. Keep this greater than or equal to `gateway.channelHealthCheckMinutes`. Default: `30`.
* `gateway.channelMaxRestartsPerHour`: maximum health-monitor restarts per channel/account in a rolling hour. Default: `10`.
* `channels.<provider>.healthMonitor.enabled`: per-channel opt-out for health-monitor restarts while keeping the global monitor enabled.
* `channels.<provider>.accounts.<accountId>.healthMonitor.enabled`: per-account override for multi-account channels. When set, it takes precedence over the channel-level override.
* Local gateway call paths can use `gateway.remote.*` as fallback only when `gateway.auth.*` is unset.
* If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking).
* `trustedProxies`: reverse proxy IPs that terminate TLS. Only list proxies you control.
* `allowRealIpFallback`: when `true`, the gateway accepts `X-Real-IP` if `X-Forwarded-For` is missing. Default `false` for fail-closed behavior.
* `gateway.tools.deny`: extra tool names blocked for HTTP `POST /tools/invoke` (extends default deny list).
* `gateway.tools.allow`: remove tool names from the default HTTP deny list.

### OpenAI-compatible endpoints

### Multi-instance isolation

Run multiple gateways on one host with unique ports and state dirs:

Convenience flags: `--dev` (uses `~/.openclaw-dev` + port `19001`), `--profile <name>` (uses `~/.openclaw-<name>`). See [Multiple Gateways](https://docs.openclaw.ai/gateway/multiple-gateways).

***

## Hooks

```
{
  hooks: {
    enabled: true,
    token: "shared-secret",
    path: "/hooks",
    maxBodyBytes: 262144,
    defaultSessionKey: "hook:ingress",
    allowRequestSessionKey: false,
    allowedSessionKeyPrefixes: ["hook:"],
    allowedAgentIds: ["hooks", "main"],
    presets: ["gmail"],
    transformsDir: "~/.openclaw/hooks/transforms",
    mappings: [
      {
        match: { path: "gmail" },
        action: "agent",
        agentId: "hooks",
        wakeMode: "now",
        name: "Gmail",
        sessionKey: "hook:gmail:{{messages[0].id}}",
        messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}",
        deliver: true,
        channel: "last",
        model: "openai/gpt-5.2-mini",
      },
    ],
  },
}
```

Auth: `Authorization: Bearer <token>` or `x-openclaw-token: <token>`. **Endpoints:**

* `POST /hooks/wake` → `{ text, mode?: "now"|"next-heartbeat" }`
* `POST /hooks/agent` → `{ message, name?, agentId?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }`
* `POST /hooks/<name>` → resolved via `hooks.mappings`

Mapping details

### Gmail integration

```
{
  hooks: {
    gmail: {
      account: "openclaw@gmail.com",
      topic: "projects/<project-id>/topics/gog-gmail-watch",
      subscription: "gog-gmail-watch-push",
      pushToken: "shared-push-token",
      hookUrl: "http://127.0.0.1:18789/hooks/gmail",
      includeBody: true,
      maxBytes: 20000,
      renewEveryMinutes: 720,
      serve: { bind: "127.0.0.1", port: 8788, path: "/" },
      tailscale: { mode: "funnel", path: "/gmail-pubsub" },
      model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
      thinking: "off",
    },
  },
}
```

***

## Canvas host

***

## Discovery

### mDNS (Bonjour)

### Wide-area (DNS-SD)

Writes a unicast DNS-SD zone under `~/.openclaw/dns/`. For cross-network discovery, pair with a DNS server (CoreDNS recommended) + Tailscale split DNS. Setup: `openclaw dns setup --apply`.

***

## Environment

### `env` (inline env vars)

### Env var substitution

Reference env vars in any config string with `${VAR_NAME}`:

***

## Secrets

Secret refs are additive: plaintext values still work.

### `SecretRef`

Use one object shape:

Validation:

### Supported credential surface

### Secret providers config

```
{
  secrets: {
    providers: {
      default: { source: "env" }, // optional explicit env provider
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json",
        timeoutMs: 5000,
      },
      vault: {
        source: "exec",
        command: "/usr/local/bin/openclaw-vault-resolver",
        passEnv: ["PATH", "VAULT_ADDR"],
      },
    },
    defaults: {
      env: "default",
      file: "filemain",
      exec: "vault",
    },
  },
}
```

Notes:

***

## Auth storage

```
{
  auth: {
    profiles: {
      "anthropic:me@example.com": { provider: "anthropic", mode: "oauth", email: "me@example.com" },
      "anthropic:work": { provider: "anthropic", mode: "api_key" },
    },
    order: {
      anthropic: ["anthropic:me@example.com", "anthropic:work"],
    },
  },
}
```

***

## Logging

***

## CLI

***

## Wizard

Metadata written by CLI guided setup flows (`onboard`, `configure`, `doctor`):

***

## Identity

Written by the macOS onboarding assistant. Derives defaults:

***

## Bridge (legacy, removed)

Current builds no longer include the TCP bridge. Nodes connect over the Gateway WebSocket. `bridge.*` keys are no longer part of the config schema (validation fails until removed; `openclaw doctor --fix` can strip unknown keys).

Legacy bridge config (historical reference)

***

## Cron

See [Cron Jobs](https://docs.openclaw.ai/automation/cron-jobs).

***

Template placeholders expanded in `tools.media.models[].args`:

| Variable           | Description                                       |
| ------------------ | ------------------------------------------------- |
| `{{Body}}`         | Full inbound message body                         |
| `{{RawBody}}`      | Raw body (no history/sender wrappers)             |
| `{{BodyStripped}}` | Body with group mentions stripped                 |
| `{{From}}`         | Sender identifier                                 |
| `{{To}}`           | Destination identifier                            |
| `{{MessageSid}}`   | Channel message id                                |
| `{{SessionId}}`    | Current session UUID                              |
| `{{IsNewSession}}` | `"true"` when new session created                 |
| `{{MediaUrl}}`     | Inbound media pseudo-URL                          |
| `{{MediaPath}}`    | Local media path                                  |
| `{{MediaType}}`    | Media type (image/audio/document/…)               |
| `{{Transcript}}`   | Audio transcript                                  |
| `{{Prompt}}`       | Resolved media prompt for CLI entries             |
| `{{MaxChars}}`     | Resolved max output chars for CLI entries         |
| `{{ChatType}}`     | `"direct"` or `"group"`                           |
| `{{GroupSubject}}` | Group subject (best effort)                       |
| `{{GroupMembers}}` | Group members preview (best effort)               |
| `{{SenderName}}`   | Sender display name (best effort)                 |
| `{{SenderE164}}`   | Sender phone number (best effort)                 |
| `{{Provider}}`     | Provider hint (whatsapp, telegram, discord, etc.) |

***

## Config includes (`$include`)

Split config into multiple files:

**Merge behavior:**

***

*Related: [Configuration](https://docs.openclaw.ai/gateway/configuration) · [Configuration Examples](https://docs.openclaw.ai/gateway/configuration-examples) · [Doctor](https://docs.openclaw.ai/gateway/doctor)*

----
url: https://docs.openclaw.ai/channels/nostr
----

# Nostr - OpenClaw

**Status:** Optional plugin (disabled by default). Nostr is a decentralized protocol for social networking. This channel enables OpenClaw to receive and respond to encrypted direct messages (DMs) via NIP-04.

## Install (on demand)

### Onboarding (recommended)

Install defaults:

You can always override the choice in the prompt.

### Manual install

Use a local checkout (dev workflows):

Restart the Gateway after installing or enabling plugins.

### Non-interactive setup

Use `--use-env` to keep `NOSTR_PRIVATE_KEY` in the environment instead of storing the key in config.

## Quick setup

1. Generate a Nostr keypair (if needed):

2) Add to config:

3. Export the key:

4) Restart the Gateway.

## Configuration reference

| Key          | Type      | Default                                     | Description                         |
| ------------ | --------- | ------------------------------------------- | ----------------------------------- |
| `privateKey` | string    | required                                    | Private key in `nsec` or hex format |
| `relays`     | string\[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket)              |
| `dmPolicy`   | string    | `pairing`                                   | DM access policy                    |
| `allowFrom`  | string\[] | `[]`                                        | Allowed sender pubkeys              |
| `enabled`    | boolean   | `true`                                      | Enable/disable channel              |
| `name`       | string    | -                                           | Display name                        |
| `profile`    | object    | -                                           | NIP-01 profile metadata             |

Profile data is published as a NIP-01 `kind:0` event. You can manage it from the Control UI (Channels -> Nostr -> Profile) or set it directly in config. Example:

```
{
  channels: {
    nostr: {
      privateKey: "${NOSTR_PRIVATE_KEY}",
      profile: {
        name: "openclaw",
        displayName: "OpenClaw",
        about: "Personal assistant DM bot",
        picture: "https://example.com/avatar.png",
        banner: "https://example.com/banner.png",
        website: "https://example.com",
        nip05: "openclaw@example.com",
        lud16: "openclaw@example.com",
      },
    },
  },
}
```

Notes:

## Access control

### DM policies

Enforcement notes:

### Allowlist example

## Key formats

Accepted formats:

## Relays

Defaults: `relay.damus.io` and `nos.lol`.

Tips:

## Protocol support

| NIP    | Status    | Description                           |
| ------ | --------- | ------------------------------------- |
| NIP-01 | Supported | Basic event format + profile metadata |
| NIP-04 | Supported | Encrypted DMs (`kind:4`)              |
| NIP-17 | Planned   | Gift-wrapped DMs                      |
| NIP-44 | Planned   | Versioned encryption                  |

## Testing

### Local relay

### Manual test

1. Note the bot pubkey (npub) from logs.
2. Open a Nostr client (Damus, Amethyst, etc.).
3. DM the bot pubkey.
4. Verify the response.

## Troubleshooting

### Not receiving messages

### Not sending responses

### Duplicate responses

## Security

## Limitations (MVP)

----
url: https://docs.openclaw.ai/cli/nodes
----

# nodes - OpenClaw

## [​](#openclaw-nodes)`openclaw nodes`

Manage paired nodes (devices) and invoke node capabilities. Related:

* Nodes overview: [Nodes](https://docs.openclaw.ai/nodes)
* Camera: [Camera nodes](https://docs.openclaw.ai/nodes/camera)
* Images: [Image nodes](https://docs.openclaw.ai/nodes/images)

Common options:

* `--url`, `--token`, `--timeout`, `--json`

## [​](#common-commands)Common commands

```
openclaw nodes list
openclaw nodes list --connected
openclaw nodes list --last-connected 24h
openclaw nodes pending
openclaw nodes approve <requestId>
openclaw nodes status
openclaw nodes status --connected
openclaw nodes status --last-connected 24h
```

`nodes list` prints pending/paired tables. Paired rows include the most recent connect age (Last Connect). Use `--connected` to only show currently-connected nodes. Use `--last-connected <duration>` to filter to nodes that connected within a duration (e.g. `24h`, `7d`).

## [​](#invoke-/-run)Invoke / run

```
openclaw nodes invoke --node <id|name|ip> --command <command> --params <json>
openclaw nodes run --node <id|name|ip> <command...>
openclaw nodes run --raw "git status"
openclaw nodes run --agent main --node <id|name|ip> --raw "git status"
```

Invoke flags:

* `--params <json>`: JSON object string (default `{}`).
* `--invoke-timeout <ms>`: node invoke timeout (default `15000`).
* `--idempotency-key <key>`: optional idempotency key.

### [​](#exec-style-defaults)Exec-style defaults

`nodes run` mirrors the model’s exec behavior (defaults + approvals):

* Reads `tools.exec.*` (plus `agents.list[].tools.exec.*` overrides).
* Uses exec approvals (`exec.approval.request`) before invoking `system.run`.
* `--node` can be omitted when `tools.exec.node` is set.
* Requires a node that advertises `system.run` (macOS companion app or headless node host).

Flags:

* `--cwd <path>`: working directory.
* `--env <key=val>`: env override (repeatable). Note: node hosts ignore `PATH` overrides (and `tools.exec.pathPrepend` is not applied to node hosts).
* `--command-timeout <ms>`: command timeout.
* `--invoke-timeout <ms>`: node invoke timeout (default `30000`).
* `--needs-screen-recording`: require screen recording permission.
* `--raw <command>`: run a shell string (`/bin/sh -lc` or `cmd.exe /c`). In allowlist mode on Windows node hosts, `cmd.exe /c` shell-wrapper runs require approval (allowlist entry alone does not auto-allow the wrapper form).
* `--agent <id>`: agent-scoped approvals/allowlists (defaults to configured agent).
* `--ask <off|on-miss|always>`, `--security <deny|allowlist|full>`: overrides.

----
url: https://docs.openclaw.ai/help/debugging
----

# Debugging - OpenClaw

This page covers debugging helpers for streaming output, especially when a provider mixes reasoning into normal text.

## Runtime debug overrides

Use `/debug` in chat to set **runtime-only** config overrides (memory, not disk). `/debug` is disabled by default; enable with `commands.debug: true`. This is handy when you need to toggle obscure settings without editing `openclaw.json`. Examples:

`/debug reset` clears all overrides and returns to the on-disk config.

## Gateway watch mode

For fast iteration, run the gateway under the file watcher:

This maps to:

The watcher restarts on build-relevant files under `src/`, extension source files, extension `package.json` and `openclaw.plugin.json` metadata, `tsconfig.json`, `package.json`, and `tsdown.config.ts`. Extension metadata changes restart the gateway without forcing a `tsdown` rebuild; source and config changes still rebuild `dist` first. Add any gateway CLI flags after `gateway:watch` and they will be passed through on each restart.

## Dev profile + dev gateway (—dev)

Use the dev profile to isolate state and spin up a safe, disposable setup for debugging. There are **two** `--dev` flags:

Recommended flow (dev profile + dev bootstrap):

If you don’t have a global install yet, run the CLI via `pnpm openclaw ...`. What this does:

1. **Profile isolation** (global `--dev`)
2. **Dev bootstrap** (`gateway --dev`)

Reset flow (fresh start):

Note: `--dev` is a **global** profile flag and gets eaten by some runners. If you need to spell it out, use the env var form:

`--reset` wipes config, credentials, sessions, and the dev workspace (using `trash`, not `rm`), then recreates the default dev setup. Tip: if a non‑dev gateway is already running (launchd/systemd), stop it first:

## Raw stream logging (OpenClaw)

OpenClaw can log the **raw assistant stream** before any filtering/formatting. This is the best way to see whether reasoning is arriving as plain text deltas (or as separate thinking blocks). Enable it via CLI:

Optional path override:

Equivalent env vars:

Default file: `~/.openclaw/logs/raw-stream.jsonl`

## Raw chunk logging (pi-mono)

To capture **raw OpenAI-compat chunks** before they are parsed into blocks, pi-mono exposes a separate logger:

Optional path:

Default file: `~/.pi-mono/logs/raw-openai-completions.jsonl`

> Note: this is only emitted by processes using pi-mono’s `openai-completions` provider.

## Safety notes

----
url: https://docs.openclaw.ai/plugins/sdk-channel-plugins
----

# Building Channel Plugins - OpenClaw

This guide walks through building a channel plugin that connects OpenClaw to a messaging platform. By the end you will have a working channel with DM security, pairing, reply threading, and outbound messaging.

## How channel plugins work

Channel plugins do not need their own send/edit/react tools. OpenClaw keeps one shared `message` tool in core. Your plugin owns:

Core owns the shared message tool, prompt wiring, session bookkeeping, and dispatch.

## Walkthrough

Build the channel plugin object

The `ChannelPlugin` interface has many optional adapter surfaces. Start with the minimum — `id` and `setup` — and add adapters as you need them.Create `src/channel.ts`:

```
import {
  createChatChannelPlugin,
  createChannelPluginBase,
} from "openclaw/plugin-sdk/core";
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
import { acmeChatApi } from "./client.js"; // your platform API client

type ResolvedAccount = {
  accountId: string | null;
  token: string;
  allowFrom: string[];
  dmPolicy: string | undefined;
};

function resolveAccount(
  cfg: OpenClawConfig,
  accountId?: string | null,
): ResolvedAccount {
  const section = (cfg.channels as Record<string, any>)?.["acme-chat"];
  const token = section?.token;
  if (!token) throw new Error("acme-chat: token is required");
  return {
    accountId: accountId ?? null,
    token,
    allowFrom: section?.allowFrom ?? [],
    dmPolicy: section?.dmSecurity,
  };
}

export const acmeChatPlugin = createChatChannelPlugin<ResolvedAccount>({
  base: createChannelPluginBase({
    id: "acme-chat",
    setup: {
      resolveAccount,
      inspectAccount(cfg, accountId) {
        const section =
          (cfg.channels as Record<string, any>)?.["acme-chat"];
        return {
          enabled: Boolean(section?.token),
          configured: Boolean(section?.token),
          tokenStatus: section?.token ? "available" : "missing",
        };
      },
    },
  }),

  // DM security: who can message the bot
  security: {
    dm: {
      channelKey: "acme-chat",
      resolvePolicy: (account) => account.dmPolicy,
      resolveAllowFrom: (account) => account.allowFrom,
      defaultPolicy: "allowlist",
    },
  },

  // Pairing: approval flow for new DM contacts
  pairing: {
    text: {
      idLabel: "Acme Chat username",
      message: "Send this code to verify your identity:",
      notify: async ({ target, code }) => {
        await acmeChatApi.sendDm(target, `Pairing code: ${code}`);
      },
    },
  },

  // Threading: how replies are delivered
  threading: { topLevelReplyToMode: "reply" },

  // Outbound: send messages to the platform
  outbound: {
    attachedResults: {
      sendText: async (params) => {
        const result = await acmeChatApi.sendMessage(
          params.to,
          params.text,
        );
        return { messageId: result.id };
      },
    },
    base: {
      sendMedia: async (params) => {
        await acmeChatApi.sendFile(params.to, params.filePath);
      },
    },
  },
});
```

What createChatChannelPlugin does for you

Instead of implementing low-level adapter interfaces manually, you pass declarative options and the builder composes them:

| Option                     | What it wires                                             |
| -------------------------- | --------------------------------------------------------- |
| `security.dm`              | Scoped DM security resolver from config fields            |
| `pairing.text`             | Text-based DM pairing flow with code exchange             |
| `threading`                | Reply-to-mode resolver (fixed, account-scoped, or custom) |
| `outbound.attachedResults` | Send functions that return result metadata (message IDs)  |

You can also pass raw adapter objects instead of the declarative options if you need full control.

## File structure

## Advanced topics

## Next steps

----
url: https://docs.openclaw.ai/web
----

# Web - OpenClaw

## [​](#web-gateway)Web (Gateway)

The Gateway serves a small **browser Control UI** (Vite + Lit) from the same port as the Gateway WebSocket:

* default: `http://<host>:18789/`
* optional prefix: set `gateway.controlUi.basePath` (e.g. `/openclaw`)

Capabilities live in [Control UI](https://docs.openclaw.ai/web/control-ui). This page focuses on bind modes, security, and web-facing surfaces.

## [​](#webhooks)Webhooks

When `hooks.enabled=true`, the Gateway also exposes a small webhook endpoint on the same HTTP server. See [Gateway configuration](https://docs.openclaw.ai/gateway/configuration) → `hooks` for auth + payloads.

## [​](#config-default-on)Config (default-on)

The Control UI is **enabled by default** when assets are present (`dist/control-ui`). You can control it via config:

```
{
  gateway: {
    controlUi: { enabled: true, basePath: "/openclaw" }, // basePath optional
  },
}
```

## [​](#tailscale-access)Tailscale access

### [​](#integrated-serve-recommended)Integrated Serve (recommended)

Keep the Gateway on loopback and let Tailscale Serve proxy it:

```
{
  gateway: {
    bind: "loopback",
    tailscale: { mode: "serve" },
  },
}
```

Then start the gateway:

```
openclaw gateway
```

Open:

* `https://<magicdns>/` (or your configured `gateway.controlUi.basePath`)

### [​](#tailnet-bind-+-token)Tailnet bind + token

```
{
  gateway: {
    bind: "tailnet",
    controlUi: { enabled: true },
    auth: { mode: "token", token: "your-token" },
  },
}
```

Then start the gateway (token required for non-loopback binds):

```
openclaw gateway
```

Open:

* `http://<tailscale-ip>:18789/` (or your configured `gateway.controlUi.basePath`)

### [​](#public-internet-funnel)Public internet (Funnel)

```
{
  gateway: {
    bind: "loopback",
    tailscale: { mode: "funnel" },
    auth: { mode: "password" }, // or OPENCLAW_GATEWAY_PASSWORD
  },
}
```

## [​](#security-notes)Security notes

* Gateway auth is required by default (token/password or Tailscale identity headers).
* Non-loopback binds still **require** a shared token/password (`gateway.auth` or env).
* The wizard generates a gateway token by default (even on loopback).
* The UI sends `connect.params.auth.token` or `connect.params.auth.password`.
* For non-loopback Control UI deployments, set `gateway.controlUi.allowedOrigins` explicitly (full origins). Without it, gateway startup is refused by default.
* `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true` enables Host-header origin fallback mode, but is a dangerous security downgrade.
* With Serve, Tailscale identity headers can satisfy Control UI/WebSocket auth when `gateway.auth.allowTailscale` is `true` (no token/password required). HTTP API endpoints still require token/password. Set `gateway.auth.allowTailscale: false` to require explicit credentials. See [Tailscale](https://docs.openclaw.ai/gateway/tailscale) and [Security](https://docs.openclaw.ai/gateway/security). This tokenless flow assumes the gateway host is trusted.
* `gateway.tailscale.mode: "funnel"` requires `gateway.auth.mode: "password"` (shared password).

## [​](#building-the-ui)Building the UI

The Gateway serves static files from `dist/control-ui`. Build them with:

```
pnpm ui:build # auto-installs UI deps on first run
```

----
url: https://docs.openclaw.ai/concepts/agent-workspace
----

# Agent Workspace - OpenClaw

The workspace is the agent’s home. It is the only working directory used for file tools and for workspace context. Keep it private and treat it as memory. This is separate from `~/.openclaw/`, which stores config, credentials, and sessions. **Important:** the workspace is the **default cwd**, not a hard sandbox. Tools resolve relative paths against the workspace, but absolute paths can still reach elsewhere on the host unless sandboxing is enabled. If you need isolation, use [`agents.defaults.sandbox`](https://docs.openclaw.ai/gateway/sandboxing) (and/or per‑agent sandbox config). When sandboxing is enabled and `workspaceAccess` is not `"rw"`, tools operate inside a sandbox workspace under `~/.openclaw/sandboxes`, not your host workspace.

## Default location

`openclaw onboard`, `openclaw configure`, or `openclaw setup` will create the workspace and seed the bootstrap files if they are missing. Sandbox seed copies only accept regular in-workspace files; symlink/hardlink aliases that resolve outside the source workspace are ignored. If you already manage the workspace files yourself, you can disable bootstrap file creation:

Older installs may have created `~/openclaw`. Keeping multiple workspace directories around can cause confusing auth or state drift, because only one workspace is active at a time. **Recommendation:** keep a single active workspace. If you no longer use the extra folders, archive or move them to Trash (for example `trash ~/openclaw`). If you intentionally keep multiple workspaces, make sure `agents.defaults.workspace` points to the active one. `openclaw doctor` warns when it detects extra workspace directories.

## Workspace file map (what each file means)

These are the standard files OpenClaw expects inside the workspace:

See [Memory](https://docs.openclaw.ai/concepts/memory) for the workflow and automatic memory flush.

If any bootstrap file is missing, OpenClaw injects a “missing file” marker into the session and continues. Large bootstrap files are truncated when injected; adjust limits with `agents.defaults.bootstrapMaxChars` (default: 20000) and `agents.defaults.bootstrapTotalMaxChars` (default: 150000). `openclaw setup` can recreate missing defaults without overwriting existing files.

## What is NOT in the workspace

These live under `~/.openclaw/` and should NOT be committed to the workspace repo:

If you need to migrate sessions or config, copy them separately and keep them out of version control.

## Git backup (recommended, private)

Treat the workspace as private memory. Put it in a **private** git repo so it is backed up and recoverable. Run these steps on the machine where the Gateway runs (that is where the workspace lives).

### 1) Initialize the repo

If git is installed, brand-new workspaces are initialized automatically. If this workspace is not already a repo, run:

### 2) Add a private remote (beginner-friendly options)

Option A: GitHub web UI

1. Create a new **private** repository on GitHub.
2. Do not initialize with a README (avoids merge conflicts).
3. Copy the HTTPS remote URL.
4. Add the remote and push:

Option B: GitHub CLI (`gh`)

Option C: GitLab web UI

1. Create a new **private** repository on GitLab.
2. Do not initialize with a README (avoids merge conflicts).
3. Copy the HTTPS remote URL.
4. Add the remote and push:

### 3) Ongoing updates

## Do not commit secrets

Even in a private repo, avoid storing secrets in the workspace:

If you must store sensitive references, use placeholders and keep the real secret elsewhere (password manager, environment variables, or `~/.openclaw/`). Suggested `.gitignore` starter:

## Moving the workspace to a new machine

1. Clone the repo to the desired path (default `~/.openclaw/workspace`).
2. Set `agents.defaults.workspace` to that path in `~/.openclaw/openclaw.json`.
3. Run `openclaw setup --workspace <path>` to seed any missing files.
4. If you need sessions, copy `~/.openclaw/agents/<agentId>/sessions/` from the old machine separately.

## Advanced notes

----
url: https://docs.openclaw.ai/nodes/images
----

# Image and Media Support - OpenClaw

## [​](#image-&-media-support-2025-12-05)Image & Media Support (2025-12-05)

The WhatsApp channel runs via **Baileys Web**. This document captures the current media handling rules for send, gateway, and agent replies.

## [​](#goals)Goals

* Send media with optional captions via `openclaw message send --media`.
* Allow auto-replies from the web inbox to include media alongside text.
* Keep per-type limits sane and predictable.

## [​](#cli-surface)CLI Surface

* `openclaw message send --media <path-or-url> [--message <caption>]`

  * `--media` optional; caption can be empty for media-only sends.
  * `--dry-run` prints the resolved payload; `--json` emits `{ channel, to, messageId, mediaUrl, caption }`.

## [​](#whatsapp-web-channel-behavior)WhatsApp Web channel behavior

* Input: local file path **or** HTTP(S) URL.

* Flow: load into a Buffer, detect media kind, and build the correct payload:

  * **Images:** resize & recompress to JPEG (max side 2048px) targeting `channels.whatsapp.mediaMaxMb` (default: 50 MB).
  * **Audio/Voice/Video:** pass-through up to 16 MB; audio is sent as a voice note (`ptt: true`).
  * **Documents:** anything else, up to 100 MB, with filename preserved when available.

* WhatsApp GIF-style playback: send an MP4 with `gifPlayback: true` (CLI: `--gif-playback`) so mobile clients loop inline.

* MIME detection prefers magic bytes, then headers, then file extension.

* Caption comes from `--message` or `reply.text`; empty caption is allowed.

* Logging: non-verbose shows `↩️`/`✅`; verbose includes size and source path/URL.

## [​](#auto-reply-pipeline)Auto-Reply Pipeline

* `getReplyFromConfig` returns `{ text?, mediaUrl?, mediaUrls? }`.
* When media is present, the web sender resolves local paths or URLs using the same pipeline as `openclaw message send`.
* Multiple media entries are sent sequentially if provided.

## [​](#inbound-media-to-commands-pi)Inbound Media to Commands (Pi)

* When inbound web messages include media, OpenClaw downloads to a temp file and exposes templating variables:

  * `{{MediaUrl}}` pseudo-URL for the inbound media.
  * `{{MediaPath}}` local temp path written before running the command.

* When a per-session Docker sandbox is enabled, inbound media is copied into the sandbox workspace and `MediaPath`/`MediaUrl` are rewritten to a relative path like `media/inbound/<filename>`.

* Media understanding (if configured via `tools.media.*` or shared `tools.media.models`) runs before templating and can insert `[Image]`, `[Audio]`, and `[Video]` blocks into `Body`.

  * Audio sets `{{Transcript}}` and uses the transcript for command parsing so slash commands still work.
  * Video and image descriptions preserve any caption text for command parsing.

* By default only the first matching image/audio/video attachment is processed; set `tools.media.<cap>.attachments` to process multiple attachments.

## [​](#limits-&-errors)Limits & Errors

**Outbound send caps (WhatsApp web send)**

* Images: up to `channels.whatsapp.mediaMaxMb` (default: 50 MB) after recompression.
* Audio/voice/video: 16 MB cap; documents: 100 MB cap.
* Oversize or unreadable media → clear error in logs and the reply is skipped.

**Media understanding caps (transcription/description)**

* Image default: 10 MB (`tools.media.image.maxBytes`).
* Audio default: 20 MB (`tools.media.audio.maxBytes`).
* Video default: 50 MB (`tools.media.video.maxBytes`).
* Oversize media skips understanding, but replies still go through with the original body.

## [​](#notes-for-tests)Notes for Tests

* Cover send + reply flows for image/audio/document cases.
* Validate recompression for images (size bound) and voice-note flag for audio.
* Ensure multi-media replies fan out as sequential sends.

----
url: https://docs.openclaw.ai/install/railway
----

# Railway - OpenClaw

Deploy OpenClaw on Railway with a one-click template and access it through the web Control UI. This is the easiest “no terminal on the server” path: Railway runs the Gateway for you.

## [​](#quick-checklist-new-users)Quick checklist (new users)

1. Click **Deploy on Railway** (below).
2. Add a **Volume** mounted at `/data`.
3. Set the required **Variables** (at least `OPENCLAW_GATEWAY_PORT` and `OPENCLAW_GATEWAY_TOKEN`).
4. Enable **HTTP Proxy** on port `8080`.
5. Open `https://<your-railway-domain>/openclaw` and connect using your `OPENCLAW_GATEWAY_TOKEN`.

## [​](#one-click-deploy)One-click deploy

[Deploy on Railway](https://railway.com/deploy/clawdbot-railway-template) After deploy, find your public URL in **Railway → your service → Settings → Domains**. Railway will either:

* give you a generated domain (often `https://<something>.up.railway.app`), or
* use your custom domain if you attached one.

Then open:

* `https://<your-railway-domain>/openclaw` — Control UI

## [​](#what-you-get)What you get

* Hosted OpenClaw Gateway + Control UI
* Persistent storage via Railway Volume (`/data`) so config/credentials/workspace survive redeploys

## [​](#required-railway-settings)Required Railway settings

### [​](#public-networking)Public Networking

Enable **HTTP Proxy** for the service.

* Port: `8080`

### [​](#volume-required)Volume (required)

Attach a volume mounted at:

* `/data`

### [​](#variables)Variables

Set these variables on the service:

* `OPENCLAW_GATEWAY_PORT=8080` (required — must match the port in Public Networking)
* `OPENCLAW_GATEWAY_TOKEN` (required; treat as an admin secret)
* `OPENCLAW_STATE_DIR=/data/.openclaw` (recommended)
* `OPENCLAW_WORKSPACE_DIR=/data/workspace` (recommended)

## [​](#connect-a-channel)Connect a channel

Use the Control UI at `/openclaw` or run `openclaw onboard` via Railway’s shell for channel setup instructions:

* [Telegram](https://docs.openclaw.ai/channels/telegram) (fastest — just a bot token)
* [Discord](https://docs.openclaw.ai/channels/discord)
* [All channels](https://docs.openclaw.ai/channels)

## [​](#backups-&-migration)Backups & migration

Export your configuration and workspace:

```
openclaw backup create
```

This creates a portable backup archive you can restore on any OpenClaw host. See [Backup](https://docs.openclaw.ai/cli/backup) for details.

## [​](#next-steps)Next steps

* Set up messaging channels: [Channels](https://docs.openclaw.ai/channels)
* Configure the Gateway: [Gateway configuration](https://docs.openclaw.ai/gateway/configuration)
* Keep OpenClaw up to date: [Updating](https://docs.openclaw.ai/install/updating)

----
url: https://docs.openclaw.ai/reference/templates/HEARTBEAT
----

# HEARTBEAT.md Template - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [HEARTBEAT.md Template](#heartbeat-md-template)

## [​](#heartbeat-md-template)HEARTBEAT.md Template

```
# Keep this file empty (or with only comments) to skip heartbeat API calls.

# Add tasks below when you want the agent to check something periodically.
```

[BOOTSTRAP.md Template](https://docs.openclaw.ai/reference/templates/BOOTSTRAP)[IDENTITY Template](https://docs.openclaw.ai/reference/templates/IDENTITY)

----
url: https://docs.openclaw.ai/channels/slack
----

# Slack - OpenClaw

Status: production-ready for DMs + channels via Slack app integrations. Default mode is Socket Mode; HTTP Events API mode is also supported.

## Quick setup

## Token model

## Access control and routing

## Commands and slash behavior

Slack can render agent-authored interactive reply controls, but this feature is disabled by default. Enable it globally:

Or enable it for one Slack account only:

When enabled, agents can emit Slack-only reply directives:

These directives compile into Slack Block Kit and route clicks or selections back through the existing Slack interaction event path. Notes:

Default slash command settings:

Slash sessions use isolated keys:

and still route command execution against the target conversation session (`CommandTargetSessionKey`).

Reply threading controls:

Manual reply tags are supported:

Note: `replyToMode="off"` disables **all** reply threading in Slack, including explicit `[[reply_to_*]]` tags. This differs from Telegram, where explicit tags are still honored in `"off"` mode. The difference reflects the platform threading models: Slack threads hide messages from the channel, while Telegram replies remain visible in the main chat flow.

## Actions and gates

Slack actions are controlled by `channels.slack.actions.*`. Available action groups in current Slack tooling:

| Group      | Default |
| ---------- | ------- |
| messages   | enabled |
| reactions  | enabled |
| pins       | enabled |
| memberInfo | enabled |
| emojiList  | enabled |

## Events and operational behavior

## Ack reactions

`ackReaction` sends an acknowledgement emoji while OpenClaw is processing an inbound message. Resolution order:

Notes:

## Typing reaction fallback

`typingReaction` adds a temporary reaction to the inbound Slack message while OpenClaw is processing a reply, then removes it when the run finishes. This is a useful fallback when Slack native assistant typing is unavailable, especially in DMs. Resolution order:

Notes:

## Manifest and scope checklist

## Troubleshooting

## Text streaming

OpenClaw supports Slack native text streaming via the Agents and AI Apps API. `channels.slack.streaming` controls live preview behavior:

`channels.slack.nativeStreaming` controls Slack’s native streaming API (`chat.startStream` / `chat.appendStream` / `chat.stopStream`) when `streaming` is `partial` (default: `true`). Disable native Slack streaming (keep draft preview behavior):

Legacy keys:

### Requirements

1. Enable **Agents and AI Apps** in your Slack app settings.
2. Ensure the app has the `assistant:write` scope.
3. A reply thread must be available for that message. Thread selection still follows `replyToMode`.

### Behavior

## Configuration reference pointers

Primary reference:

* [Configuration reference - Slack](https://docs.openclaw.ai/gateway/configuration-reference#slack) High-signal Slack fields:

  * mode/auth: `mode`, `botToken`, `appToken`, `signingSecret`, `webhookPath`, `accounts.*`
  * DM access: `dm.enabled`, `dmPolicy`, `allowFrom` (legacy: `dm.policy`, `dm.allowFrom`), `dm.groupEnabled`, `dm.groupChannels`
  * compatibility toggle: `dangerouslyAllowNameMatching` (break-glass; keep off unless needed)
  * channel access: `groupPolicy`, `channels.*`, `channels.*.users`, `channels.*.requireMention`
  * threading/history: `replyToMode`, `replyToModeByChatType`, `thread.*`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`
  * delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `streaming`, `nativeStreaming`
  * ops/features: `configWrites`, `commands.native`, `slashCommand.*`, `actions.*`, `userToken`, `userTokenReadOnly`

----
url: https://docs.openclaw.ai/start/onboarding-overview
----

# Onboarding Overview - OpenClaw

OpenClaw has two onboarding paths. Both configure auth, the Gateway, and optional channels — they just differ in how you interact with the setup.

## Which path should I use?

|                | CLI onboarding                         | macOS app onboarding      |
| -------------- | -------------------------------------- | ------------------------- |
| **Platforms**  | macOS, Linux, Windows (native or WSL2) | macOS only                |
| **Interface**  | Terminal wizard                        | Guided UI in the app      |
| **Best for**   | Servers, headless, full control        | Desktop Mac, visual setup |
| **Automation** | `--non-interactive` for scripts        | Manual only               |
| **Command**    | `openclaw onboard`                     | Launch the app            |

Most users should start with **CLI onboarding** — it works everywhere and gives you the most control.

## What onboarding configures

Regardless of which path you choose, onboarding sets up:

1. **Model provider and auth** — API key, OAuth, or setup token for your chosen provider
2. **Workspace** — directory for agent files, bootstrap templates, and memory
3. **Gateway** — port, bind address, auth mode
4. **Channels** (optional) — WhatsApp, Telegram, Discord, and more
5. **Daemon** (optional) — background service so the Gateway starts automatically

## CLI onboarding

Run in any terminal:

Add `--install-daemon` to also install the background service in one step. Full reference: [Onboarding (CLI)](https://docs.openclaw.ai/start/wizard) CLI command docs: [`openclaw onboard`](https://docs.openclaw.ai/cli/onboard)

## macOS app onboarding

Open the OpenClaw app. The first-run wizard walks you through the same steps with a visual interface. Full reference: [Onboarding (macOS App)](https://docs.openclaw.ai/start/onboarding)

## Custom or unlisted providers

If your provider is not listed in onboarding, choose **Custom Provider** and enter:

Multiple custom endpoints can coexist — each gets its own endpoint ID.

----
url: https://docs.openclaw.ai/cli/dns
----

# dns - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw dns](#openclaw-dns)
- [Setup](#setup)

## [​](#openclaw-dns)`openclaw dns`

DNS helpers for wide-area discovery (Tailscale + CoreDNS). Currently focused on macOS + Homebrew CoreDNS. Related:

* Gateway discovery: [Discovery](https://docs.openclaw.ai/gateway/discovery)
* Wide-area discovery config: [Configuration](https://docs.openclaw.ai/gateway/configuration)

## [​](#setup)Setup

```
openclaw dns setup
openclaw dns setup --apply
```

[completion](https://docs.openclaw.ai/cli/completion)[docs](https://docs.openclaw.ai/cli/docs)

----
url: https://docs.openclaw.ai/concepts/messages
----

# Messages - OpenClaw

This page ties together how OpenClaw handles inbound messages, sessions, queueing, streaming, and reasoning visibility.

## Message flow (high level)

Key knobs live in configuration:

See [Configuration](https://docs.openclaw.ai/gateway/configuration) for full schema.

## Inbound dedupe

Channels can redeliver the same message after reconnects. OpenClaw keeps a short-lived cache keyed by channel/account/peer/session/message id so duplicate deliveries do not trigger another agent run.

## Inbound debouncing

Rapid consecutive messages from the **same sender** can be batched into a single agent turn via `messages.inbound`. Debouncing is scoped per channel + conversation and uses the most recent message for reply threading/IDs. Config (global default + per-channel overrides):

Notes:

## Sessions and devices

Sessions are owned by the gateway, not by clients.

Multiple devices/channels can map to the same session, but history is not fully synced back to every client. Recommendation: use one primary device for long conversations to avoid divergent context. The Control UI and TUI always show the gateway-backed session transcript, so they are the source of truth. Details: [Session management](https://docs.openclaw.ai/concepts/session).

## Inbound bodies and history context

OpenClaw separates the **prompt body** from the **command body**:

When a channel supplies history, it uses a shared wrapper:

For **non-direct chats** (groups/channels/rooms), the **current message body** is prefixed with the sender label (same style used for history entries). This keeps real-time and queued/history messages consistent in the agent prompt. History buffers are **pending-only**: they include group messages that did *not* trigger a run (for example, mention-gated messages) and **exclude** messages already in the session transcript. Directive stripping only applies to the **current message** section so history remains intact. Channels that wrap history should set `CommandBody` (or `RawBody`) to the original message text and keep `Body` as the combined prompt. History buffers are configurable via `messages.groupChat.historyLimit` (global default) and per-channel overrides like `channels.slack.historyLimit` or `channels.telegram.accounts.<id>.historyLimit` (set `0` to disable).

## Queueing and followups

If a run is already active, inbound messages can be queued, steered into the current run, or collected for a followup turn.

Details: [Queueing](https://docs.openclaw.ai/concepts/queue).

## Streaming, chunking, and batching

Block streaming sends partial replies as the model produces text blocks. Chunking respects channel text limits and avoids splitting fenced code. Key settings:

Details: [Streaming + chunking](https://docs.openclaw.ai/concepts/streaming).

## Reasoning visibility and tokens

OpenClaw can expose or hide model reasoning:

Details: [Thinking + reasoning directives](https://docs.openclaw.ai/tools/thinking) and [Token use](https://docs.openclaw.ai/reference/token-use).

## Prefixes, threading, and replies

Outbound message formatting is centralized in `messages`:

Details: [Configuration](https://docs.openclaw.ai/gateway/configuration-reference#messages) and channel docs.

----
url: https://docs.openclaw.ai/help/testing
----

# Testing - OpenClaw

OpenClaw has three Vitest suites (unit/integration, e2e, live) and a small set of Docker runners. This doc is a “how we test” guide:

## Quick start

Most days:

When you touch tests or want extra confidence:

When debugging real providers/models (requires real creds):

Tip: when you only need one failing case, prefer narrowing live tests via the allowlist env vars described below.

## Test suites (what runs where)

Think of the suites as “increasing realism” (and increasing flakiness/cost):

### Unit / integration (default)

### E2E (gateway smoke)

### E2E: OpenShell backend smoke

### Live (real providers + real models)

## Which suite should I run?

Use this decision table:

## Live: Android node capability sweep

## Live: model smoke (profile keys)

Live tests are split into two layers so we can isolate failures:

### Layer 1: Direct model completion (no gateway)

### Layer 2: Gateway + dev agent smoke (what “@openclaw” actually does)

Tip: to see what you can test on your machine (and the exact `provider/model` ids), run:

## Live: Anthropic setup-token smoke

Setup example:

## Live: CLI backend smoke (Claude Code CLI or other local CLIs)

Example:

### Recommended live recipes

Narrow, explicit allowlists are fastest and least flaky:

Notes:

## Live: model matrix (what we cover)

There is no fixed “CI model list” (live is opt-in), but these are the **recommended** models to cover regularly on a dev machine with keys.

### Modern smoke set (tool calling + image)

This is the “common models” run we expect to keep working:

Run gateway smoke with tools + image: `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.2,openai-codex/gpt-5.4,anthropic/claude-opus-4-6,google/gemini-3.1-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`

### Baseline: tool calling (Read + optional Exec)

Pick at least one per provider family:

Optional additional coverage (nice to have):

### Vision: image send (attachment → multimodal message)

Include at least one image-capable model in `OPENCLAW_LIVE_GATEWAY_MODELS` (Claude/Gemini/OpenAI vision-capable variants, etc.) to exercise the image probe.

### Aggregators / alternate gateways

If you have keys enabled, we also support testing via:

More providers you can include in the live matrix (if you have creds/config):

* Built-in: `openai`, `openai-codex`, `anthropic`, `google`, `google-vertex`, `google-antigravity`, `google-gemini-cli`, `zai`, `openrouter`, `opencode`, `opencode-go`, `xai`, `groq`, `cerebras`, `mistral`, `github-copilot`
* Via `models.providers` (custom endpoints): `minimax` (cloud/API), plus any OpenAI/Anthropic-compatible proxy (LM Studio, vLLM, LiteLLM, etc.)

Tip: don’t try to hardcode “all models” in docs. The authoritative list is whatever `discoverModels(...)` returns on your machine + whatever keys are available.

## Credentials (never commit)

Live tests discover credentials the same way the CLI does. Practical implications:

If you want to rely on env keys (e.g. exported in your `~/.profile`), run local tests after `source ~/.profile`, or use the Docker runners below (they can mount `~/.profile` into the container).

## Deepgram live (audio transcription)

## BytePlus coding plan live

## Image generation live

## Docker runners (optional “works in Linux” checks)

These run `pnpm test:live` inside the repo Docker image, mounting your local config dir and workspace (and sourcing `~/.profile` if mounted). They also bind-mount only the needed CLI auth homes (or all supported ones when the run is not narrowed), then copy them into the container home before the run so external-CLI OAuth can refresh tokens without mutating the host auth store:

The live-model Docker runners also bind-mount the current checkout read-only and stage it into a temporary workdir inside the container. This keeps the runtime image slim while still running Vitest against your exact local source/config. They also set `OPENCLAW_SKIP_CHANNELS=1` so gateway live probes do not start real Telegram/Discord/etc. channel workers inside the container. `test:docker:live-models` still runs `pnpm test:live`, so pass through `OPENCLAW_LIVE_GATEWAY_*` as well when you need to narrow or exclude gateway live coverage from that Docker lane. Manual ACP plain-language thread smoke (not CI):

Useful env vars:

## Docs sanity

Run docs checks after doc edits: `pnpm docs:list`.

## Offline regression (CI-safe)

These are “real pipeline” regressions without real providers:

## Agent reliability evals (skills)

We already have a few CI-safe tests that behave like “agent reliability evals”:

What’s still missing for skills (see [Skills](https://docs.openclaw.ai/tools/skills)):

Future evals should stay deterministic first:

## Contract tests (plugin and channel shape)

Contract tests verify that every registered plugin and channel conforms to its interface contract. They iterate over all discovered plugins and run a suite of shape and behavior assertions.

### Commands

### Channel contracts

Located in `src/channels/plugins/contracts/*.contract.test.ts`:

### Provider contracts

Located in `src/plugins/contracts/*.contract.test.ts`:

### When to run

Contract tests run in CI and do not require real API keys.

## Adding regressions (guidance)

When you fix a provider/model issue discovered in live:

----
url: https://docs.openclaw.ai/tools
----

# Tools and Plugins - OpenClaw

Everything the agent does beyond generating text happens through **tools**. Tools are how the agent reads files, runs commands, browses the web, sends messages, and interacts with devices.

## Tools, skills, and plugins

OpenClaw has three layers that work together:

## Built-in tools

These tools ship with OpenClaw and are available without installing any plugins:

| Tool                         | What it does                                             | Page                                                      |
| ---------------------------- | -------------------------------------------------------- | --------------------------------------------------------- |
| `exec` / `process`           | Run shell commands, manage background processes          | [Exec](https://docs.openclaw.ai/tools/exec)               |
| `browser`                    | Control a Chromium browser (navigate, click, screenshot) | [Browser](https://docs.openclaw.ai/tools/browser)         |
| `web_search` / `web_fetch`   | Search the web, fetch page content                       | [Web](https://docs.openclaw.ai/tools/web)                 |
| `read` / `write` / `edit`    | File I/O in the workspace                                |                                                           |
| `apply_patch`                | Multi-hunk file patches                                  | [Apply Patch](https://docs.openclaw.ai/tools/apply-patch) |
| `message`                    | Send messages across all channels                        | [Agent Send](https://docs.openclaw.ai/tools/agent-send)   |
| `canvas`                     | Drive node Canvas (present, eval, snapshot)              |                                                           |
| `nodes`                      | Discover and target paired devices                       |                                                           |
| `cron` / `gateway`           | Manage scheduled jobs, restart gateway                   |                                                           |
| `image` / `image_generate`   | Analyze or generate images                               |                                                           |
| `sessions_*` / `agents_list` | Session management, sub-agents                           | [Sub-agents](https://docs.openclaw.ai/tools/subagents)    |

For image work, use `image` for analysis and `image_generate` for generation or editing. If you target `openai/*`, `google/*`, `fal/*`, or another non-default image provider, configure that provider’s auth/API key first.

### Plugin-provided tools

Plugins can register additional tools. Some examples:

## Tool configuration

### Allow and deny lists

Control which tools the agent can call via `tools.allow` / `tools.deny` in config. Deny always wins over allow.

### Tool profiles

`tools.profile` sets a base allowlist before `allow`/`deny` is applied. Per-agent override: `agents.list[].tools.profile`.

| Profile     | What it includes                            |
| ----------- | ------------------------------------------- |
| `full`      | All tools (default)                         |
| `coding`    | File I/O, runtime, sessions, memory, image  |
| `messaging` | Messaging, session list/history/send/status |
| `minimal`   | `session_status` only                       |

### Tool groups

Use `group:*` shorthands in allow/deny lists:

| Group              | Tools                                                                               |
| ------------------ | ----------------------------------------------------------------------------------- |
| `group:runtime`    | exec, bash, process                                                                 |
| `group:fs`         | read, write, edit, apply\_patch                                                     |
| `group:sessions`   | sessions\_list, sessions\_history, sessions\_send, sessions\_spawn, session\_status |
| `group:memory`     | memory\_search, memory\_get                                                         |
| `group:web`        | web\_search, web\_fetch                                                             |
| `group:ui`         | browser, canvas                                                                     |
| `group:automation` | cron, gateway                                                                       |
| `group:messaging`  | message                                                                             |
| `group:nodes`      | nodes                                                                               |
| `group:openclaw`   | All built-in OpenClaw tools (excludes plugin tools)                                 |

### Provider-specific restrictions

Use `tools.byProvider` to restrict tools for specific providers without changing global defaults:

----
url: https://docs.openclaw.ai/providers/venice
----

# Venice AI - OpenClaw

## Venice AI (Venice highlight)

**Venice** is our highlight Venice setup for privacy-first inference with optional anonymized access to proprietary models. Venice AI provides privacy-focused AI inference with support for uncensored models and access to major proprietary models through their anonymized proxy. All inference is private by default—no training on your data, no logging.

## Why Venice in OpenClaw

## Privacy Modes

Venice offers two privacy levels — understanding this is key to choosing your model:

| Mode           | Description                                                                                                                       | Models                                                        |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| **Private**    | Fully private. Prompts/responses are **never stored or logged**. Ephemeral.                                                       | Llama, Qwen, DeepSeek, Kimi, MiniMax, Venice Uncensored, etc. |
| **Anonymized** | Proxied through Venice with metadata stripped. The underlying provider (OpenAI, Anthropic, Google, xAI) sees anonymized requests. | Claude, GPT, Gemini, Grok                                     |

## Features

## Setup

### 1. Get API Key

1. Sign up at [venice.ai](https://venice.ai/)
2. Go to **Settings → API Keys → Create new key**
3. Copy your API key (format: `vapi_xxxxxxxxxxxx`)

### 2. Configure OpenClaw

**Option A: Environment Variable**

**Option B: Interactive Setup (Recommended)**

This will:

1. Prompt for your API key (or use existing `VENICE_API_KEY`)
2. Show all available Venice models
3. Let you pick your default model
4. Configure the provider automatically

**Option C: Non-interactive**

### 3. Verify Setup

## Model Selection

After setup, OpenClaw shows all available Venice models. Pick based on your needs:

Change your default model anytime:

List all available models:

## Configure via `openclaw configure`

1. Run `openclaw configure`
2. Select **Model/auth**
3. Choose **Venice AI**

## Which Model Should I Use?

| Use Case                   | Recommended Model                | Why                                          |
| -------------------------- | -------------------------------- | -------------------------------------------- |
| **General chat (default)** | `kimi-k2-5`                      | Strong private reasoning plus vision         |
| **Best overall quality**   | `claude-opus-4-6`                | Strongest anonymized Venice option           |
| **Privacy + coding**       | `qwen3-coder-480b-a35b-instruct` | Private coding model with large context      |
| **Private vision**         | `kimi-k2-5`                      | Vision support without leaving private mode  |
| **Fast + cheap**           | `qwen3-4b`                       | Lightweight reasoning model                  |
| **Complex private tasks**  | `deepseek-v3.2`                  | Strong reasoning, but no Venice tool support |
| **Uncensored**             | `venice-uncensored`              | No content restrictions                      |

## Available Models (41 Total)

### Private Models (26) - Fully Private, No Logging

| Model ID                               | Name                                | Context | Features                   |
| -------------------------------------- | ----------------------------------- | ------- | -------------------------- |
| `kimi-k2-5`                            | Kimi K2.5                           | 256k    | Default, reasoning, vision |
| `kimi-k2-thinking`                     | Kimi K2 Thinking                    | 256k    | Reasoning                  |
| `llama-3.3-70b`                        | Llama 3.3 70B                       | 128k    | General                    |
| `llama-3.2-3b`                         | Llama 3.2 3B                        | 128k    | General                    |
| `hermes-3-llama-3.1-405b`              | Hermes 3 Llama 3.1 405B             | 128k    | General, tools disabled    |
| `qwen3-235b-a22b-thinking-2507`        | Qwen3 235B Thinking                 | 128k    | Reasoning                  |
| `qwen3-235b-a22b-instruct-2507`        | Qwen3 235B Instruct                 | 128k    | General                    |
| `qwen3-coder-480b-a35b-instruct`       | Qwen3 Coder 480B                    | 256k    | Coding                     |
| `qwen3-coder-480b-a35b-instruct-turbo` | Qwen3 Coder 480B Turbo              | 256k    | Coding                     |
| `qwen3-5-35b-a3b`                      | Qwen3.5 35B A3B                     | 256k    | Reasoning, vision          |
| `qwen3-next-80b`                       | Qwen3 Next 80B                      | 256k    | General                    |
| `qwen3-vl-235b-a22b`                   | Qwen3 VL 235B (Vision)              | 256k    | Vision                     |
| `qwen3-4b`                             | Venice Small (Qwen3 4B)             | 32k     | Fast, reasoning            |
| `deepseek-v3.2`                        | DeepSeek V3.2                       | 160k    | Reasoning, tools disabled  |
| `venice-uncensored`                    | Venice Uncensored (Dolphin-Mistral) | 32k     | Uncensored, tools disabled |
| `mistral-31-24b`                       | Venice Medium (Mistral)             | 128k    | Vision                     |
| `google-gemma-3-27b-it`                | Google Gemma 3 27B Instruct         | 198k    | Vision                     |
| `openai-gpt-oss-120b`                  | OpenAI GPT OSS 120B                 | 128k    | General                    |
| `nvidia-nemotron-3-nano-30b-a3b`       | NVIDIA Nemotron 3 Nano 30B          | 128k    | General                    |
| `olafangensan-glm-4.7-flash-heretic`   | GLM 4.7 Flash Heretic               | 128k    | Reasoning                  |
| `zai-org-glm-4.6`                      | GLM 4.6                             | 198k    | General                    |
| `zai-org-glm-4.7`                      | GLM 4.7                             | 198k    | Reasoning                  |
| `zai-org-glm-4.7-flash`                | GLM 4.7 Flash                       | 128k    | Reasoning                  |
| `zai-org-glm-5`                        | GLM 5                               | 198k    | Reasoning                  |
| `minimax-m21`                          | MiniMax M2.1                        | 198k    | Reasoning                  |
| `minimax-m25`                          | MiniMax M2.5                        | 198k    | Reasoning                  |

### Anonymized Models (15) - Via Venice Proxy

| Model ID                        | Name                           | Context | Features                  |
| ------------------------------- | ------------------------------ | ------- | ------------------------- |
| `claude-opus-4-6`               | Claude Opus 4.6 (via Venice)   | 1M      | Reasoning, vision         |
| `claude-opus-4-5`               | Claude Opus 4.5 (via Venice)   | 198k    | Reasoning, vision         |
| `claude-sonnet-4-6`             | Claude Sonnet 4.6 (via Venice) | 1M      | Reasoning, vision         |
| `claude-sonnet-4-5`             | Claude Sonnet 4.5 (via Venice) | 198k    | Reasoning, vision         |
| `openai-gpt-54`                 | GPT-5.4 (via Venice)           | 1M      | Reasoning, vision         |
| `openai-gpt-53-codex`           | GPT-5.3 Codex (via Venice)     | 400k    | Reasoning, vision, coding |
| `openai-gpt-52`                 | GPT-5.2 (via Venice)           | 256k    | Reasoning                 |
| `openai-gpt-52-codex`           | GPT-5.2 Codex (via Venice)     | 256k    | Reasoning, vision, coding |
| `openai-gpt-4o-2024-11-20`      | GPT-4o (via Venice)            | 128k    | Vision                    |
| `openai-gpt-4o-mini-2024-07-18` | GPT-4o Mini (via Venice)       | 128k    | Vision                    |
| `gemini-3-1-pro-preview`        | Gemini 3.1 Pro (via Venice)    | 1M      | Reasoning, vision         |
| `gemini-3-pro-preview`          | Gemini 3 Pro (via Venice)      | 198k    | Reasoning, vision         |
| `gemini-3-flash-preview`        | Gemini 3 Flash (via Venice)    | 256k    | Reasoning, vision         |
| `grok-41-fast`                  | Grok 4.1 Fast (via Venice)     | 1M      | Reasoning, vision         |
| `grok-code-fast-1`              | Grok Code Fast 1 (via Venice)  | 256k    | Reasoning, coding         |

## Model Discovery

OpenClaw automatically discovers models from the Venice API when `VENICE_API_KEY` is set. If the API is unreachable, it falls back to a static catalog. The `/models` endpoint is public (no auth needed for listing), but inference requires a valid API key.

## Streaming & Tool Support

| Feature              | Support                                                |
| -------------------- | ------------------------------------------------------ |
| **Streaming**        | ✅ All models                                           |
| **Function calling** | ✅ Most models (check `supportsFunctionCalling` in API) |
| **Vision/Images**    | ✅ Models marked with “Vision” feature                  |
| **JSON mode**        | ✅ Supported via `response_format`                      |

## Pricing

Venice uses a credit-based system. Check [venice.ai/pricing](https://venice.ai/pricing) for current rates:

## Comparison: Venice vs Direct API

| Aspect       | Venice (Anonymized)           | Direct API          |
| ------------ | ----------------------------- | ------------------- |
| **Privacy**  | Metadata stripped, anonymized | Your account linked |
| **Latency**  | +10-50ms (proxy)              | Direct              |
| **Features** | Most features supported       | Full features       |
| **Billing**  | Venice credits                | Provider billing    |

## Usage Examples

## Troubleshooting

### API key not recognized

Ensure the key starts with `vapi_`.

### Model not available

The Venice model catalog updates dynamically. Run `openclaw models list` to see currently available models. Some models may be temporarily offline.

### Connection issues

Venice API is at `https://api.venice.ai/api/v1`. Ensure your network allows HTTPS connections.

## Config file example

```
{
  env: { VENICE_API_KEY: "vapi_..." },
  agents: { defaults: { model: { primary: "venice/kimi-k2-5" } } },
  models: {
    mode: "merge",
    providers: {
      venice: {
        baseUrl: "https://api.venice.ai/api/v1",
        apiKey: "${VENICE_API_KEY}",
        api: "openai-completions",
        models: [
          {
            id: "kimi-k2-5",
            name: "Kimi K2.5",
            reasoning: true,
            input: ["text", "image"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 256000,
            maxTokens: 65536,
          },
        ],
      },
    },
  },
}
```

## Links

----
url: https://docs.openclaw.ai/channels/matrix
----

# Matrix - OpenClaw

## Matrix (plugin)

Matrix is the Matrix channel plugin for OpenClaw. It uses the official `matrix-js-sdk` and supports DMs, rooms, threads, media, reactions, polls, location, and E2EE.

## Plugin required

Matrix is a plugin and is not bundled with core OpenClaw. Install from npm:

Install from a local checkout:

See [Plugins](https://docs.openclaw.ai/tools/plugin) for plugin behavior and install rules.

## Setup

1. Install the plugin.
2. Create a Matrix account on your homeserver.
3. Configure `channels.matrix` with either:
4. Restart the gateway.
5. Start a DM with the bot or invite it to a room.

Interactive setup paths:

What the Matrix wizard actually asks for:

Wizard behavior that matters:

Minimal token-based setup:

Password-based setup (token is cached after login):

Matrix stores cached credentials in `~/.openclaw/credentials/matrix/`. The default account uses `credentials.json`; named accounts use `credentials-<account>.json`. Environment variable equivalents (used when the config key is not set):

For non-default accounts, use account-scoped env vars:

Example for account `ops`:

For normalized account ID `ops-bot`, use:

The interactive wizard only offers the env-var shortcut when those auth env vars are already present and the selected account does not already have Matrix auth saved in config.

## Configuration example

This is a practical baseline config with DM pairing, room allowlist, and E2EE enabled:

```
{
  channels: {
    matrix: {
      enabled: true,
      homeserver: "https://matrix.example.org",
      accessToken: "syt_xxx",
      encryption: true,

      dm: {
        policy: "pairing",
      },

      groupPolicy: "allowlist",
      groupAllowFrom: ["@admin:example.org"],
      groups: {
        "!roomid:example.org": {
          requireMention: true,
        },
      },

      autoJoin: "allowlist",
      autoJoinAllowlist: ["!roomid:example.org"],
      threadReplies: "inbound",
      replyToMode: "off",
    },
  },
}
```

## E2EE setup

## Bot to bot rooms

By default, Matrix messages from other configured OpenClaw Matrix accounts are ignored. Use `allowBots` when you intentionally want inter-agent Matrix traffic:

Use strict room allowlists and mention requirements when enabling bot-to-bot traffic in shared rooms. Enable encryption:

Check verification status:

Verbose status (full diagnostics):

Include the stored recovery key in machine-readable output:

Bootstrap cross-signing and verification state:

Multi-account support: use `channels.matrix.accounts` with per-account credentials and optional `name`. See [Configuration reference](https://docs.openclaw.ai/gateway/configuration-reference#multi-account-all-channels) for the shared pattern. Verbose bootstrap diagnostics:

Force a fresh cross-signing identity reset before bootstrapping:

Verify this device with a recovery key:

Verbose device verification details:

Check room-key backup health:

Verbose backup health diagnostics:

Restore room keys from server backup:

Verbose restore diagnostics:

Delete the current server backup and create a fresh backup baseline:

All `verify` commands are concise by default (including quiet internal SDK logging) and show detailed diagnostics only with `--verbose`. Use `--json` for full machine-readable output when scripting. In multi-account setups, Matrix CLI commands use the implicit Matrix default account unless you pass `--account <id>`. If you configure multiple named accounts, set `channels.matrix.defaultAccount` first or those implicit CLI operations will stop and ask you to choose an account explicitly. Use `--account` whenever you want verification or device operations to target a named account explicitly:

When encryption is disabled or unavailable for a named account, Matrix warnings and verification errors point at that account’s config key, for example `channels.matrix.accounts.assistant.encryption`.

### What “verified” means

OpenClaw treats this Matrix device as verified only when it is verified by your own cross-signing identity. In practice, `openclaw matrix verify status --verbose` exposes three trust signals:

`Verified by owner` becomes `yes` only when cross-signing verification or owner-signing is present. Local trust by itself is not enough for OpenClaw to treat the device as fully verified.

### What bootstrap does

`openclaw matrix verify bootstrap` is the repair and setup command for encrypted Matrix accounts. It does all of the following in order:

If the homeserver requires interactive auth to upload cross-signing keys, OpenClaw tries the upload without auth first, then with `m.login.dummy`, then with `m.login.password` when `channels.matrix.password` is configured. Use `--force-reset-cross-signing` only when you intentionally want to discard the current cross-signing identity and create a new one. If you intentionally want to discard the current room-key backup and start a new backup baseline for future messages, use `openclaw matrix verify backup reset --yes`. Do this only when you accept that unrecoverable old encrypted history will stay unavailable.

### Fresh backup baseline

If you want to keep future encrypted messages working and accept losing unrecoverable old history, run these commands in order:

Add `--account <id>` to each command when you want to target a named Matrix account explicitly.

### Startup behavior

When `encryption: true`, Matrix defaults `startupVerification` to `"if-unverified"`. On startup, if this device is still unverified, Matrix will request self-verification in another Matrix client, skip duplicate requests while one is already pending, and apply a local cooldown before retrying after restarts. Failed request attempts retry sooner than successful request creation by default. Set `startupVerification: "off"` to disable automatic startup requests, or tune `startupVerificationCooldownHours` if you want a shorter or longer retry window. Startup also performs a conservative crypto bootstrap pass automatically. That pass tries to reuse the current secret storage and cross-signing identity first, and avoids resetting cross-signing unless you run an explicit bootstrap repair flow. If startup finds broken bootstrap state and `channels.matrix.password` is configured, OpenClaw can attempt a stricter repair path. If the current device is already owner-signed, OpenClaw preserves that identity instead of resetting it automatically. Upgrading from the previous public Matrix plugin:

* OpenClaw automatically reuses the same Matrix account, access token, and device identity when possible.
* Before any actionable Matrix migration changes run, OpenClaw creates or reuses a recovery snapshot under `~/Backups/openclaw-migrations/`.
* If you use multiple Matrix accounts, set `channels.matrix.defaultAccount` before upgrading from the old flat-store layout so OpenClaw knows which account should receive that shared legacy state.
* If the previous plugin stored a Matrix room-key backup decryption key locally, startup or `openclaw doctor --fix` will import it into the new recovery-key flow automatically.
* If the Matrix access token changed after migration was prepared, startup now scans sibling token-hash storage roots for pending legacy restore state before giving up on the automatic backup restore.
* If the Matrix access token changes later for the same account, homeserver, and user, OpenClaw now prefers reusing the most complete existing token-hash storage root instead of starting from an empty Matrix state directory.
* On the next gateway start, backed-up room keys are restored automatically into the new crypto store.
* If the old plugin had local-only room keys that were never backed up, OpenClaw will warn clearly. Those keys cannot be exported automatically from the previous rust crypto store, so some old encrypted history may remain unavailable until recovered manually.
* See [Matrix migration](https://docs.openclaw.ai/install/migrating-matrix) for the full upgrade flow, limits, recovery commands, and common migration messages.

Encrypted runtime state is organized under per-account, per-user token-hash roots in `~/.openclaw/matrix/accounts/<account>/<homeserver>__<user>/<token-hash>/`. That directory contains the sync store (`bot-storage.json`), crypto store (`crypto/`), recovery key file (`recovery-key.json`), IndexedDB snapshot (`crypto-idb-snapshot.json`), thread bindings (`thread-bindings.json`), and startup verification state (`startup-verification.json`) when those features are in use. When the token changes but the account identity stays the same, OpenClaw reuses the best existing root for that account/homeserver/user tuple so prior sync state, crypto state, thread bindings, and startup verification state remain visible.

### Node crypto store model

Matrix E2EE in this plugin uses the official `matrix-js-sdk` Rust crypto path in Node. That path expects IndexedDB-backed persistence when you want crypto state to survive restarts. OpenClaw currently provides that in Node by:

This is compatibility/storage plumbing, not a custom crypto implementation. The snapshot file is sensitive runtime state and is stored with restrictive file permissions. Under OpenClaw’s security model, the gateway host and local OpenClaw state directory are already inside the trusted operator boundary, so this is primarily an operational durability concern rather than a separate remote trust boundary. Planned improvement:

## Automatic verification notices

Matrix now posts verification lifecycle notices directly into the strict DM verification room as `m.notice` messages. That includes:

Incoming verification requests from another Matrix client are tracked and auto-accepted by OpenClaw. For self-verification flows, OpenClaw also starts the SAS flow automatically when emoji verification becomes available and confirms its own side. For verification requests from another Matrix user/device, OpenClaw auto-accepts the request and then waits for the SAS flow to proceed normally. You still need to compare the emoji or decimal SAS in your Matrix client and confirm “They match” there to complete the verification. OpenClaw does not auto-accept self-initiated duplicate flows blindly. Startup skips creating a new request when a self-verification request is already pending. Verification protocol/system notices are not forwarded to the agent chat pipeline, so they do not produce `NO_REPLY`.

### Device hygiene

Old OpenClaw-managed Matrix devices can accumulate on the account and make encrypted-room trust harder to reason about. List them with:

Remove stale OpenClaw-managed devices with:

### Direct Room Repair

If direct-message state gets out of sync, OpenClaw can end up with stale `m.direct` mappings that point at old solo rooms instead of the live DM. Inspect the current mapping for a peer with:

Repair it with:

Repair keeps the Matrix-specific logic inside the plugin:

The repair flow does not delete old rooms automatically. It only picks the healthy DM and updates the mapping so new Matrix sends, verification notices, and other direct-message flows target the right room again.

## Threads

Matrix supports native Matrix threads for both automatic replies and message-tool sends.

### Thread Binding Config

Matrix inherits global defaults from `session.threadBindings`, and also supports per-channel overrides:

Matrix thread-bound spawn flags are opt-in:

## Reactions

Matrix supports outbound reaction actions, inbound reaction notifications, and inbound ack reactions.

Ack reactions use the standard OpenClaw resolution order:

Ack reaction scope resolves in this order:

Reaction notification mode resolves in this order:

Current behavior:

## DM and room policy example

```
{
  channels: {
    matrix: {
      dm: {
        policy: "allowlist",
        allowFrom: ["@admin:example.org"],
      },
      groupPolicy: "allowlist",
      groupAllowFrom: ["@admin:example.org"],
      groups: {
        "!roomid:example.org": {
          requireMention: true,
        },
      },
    },
  },
}
```

See [Groups](https://docs.openclaw.ai/channels/groups) for mention-gating and allowlist behavior. Pairing example for Matrix DMs:

If an unapproved Matrix user keeps messaging you before approval, OpenClaw reuses the same pending pairing code and may send a reminder reply again after a short cooldown instead of minting a new code. See [Pairing](https://docs.openclaw.ai/channels/pairing) for the shared DM pairing flow and storage layout.

## Multi-account example

```
{
  channels: {
    matrix: {
      enabled: true,
      defaultAccount: "assistant",
      dm: { policy: "pairing" },
      accounts: {
        assistant: {
          homeserver: "https://matrix.example.org",
          accessToken: "syt_assistant_xxx",
          encryption: true,
        },
        alerts: {
          homeserver: "https://matrix.example.org",
          accessToken: "syt_alerts_xxx",
          dm: {
            policy: "allowlist",
            allowFrom: ["@ops:example.org"],
          },
        },
      },
    },
  },
}
```

Top-level `channels.matrix` values act as defaults for named accounts unless an account overrides them. Set `defaultAccount` when you want OpenClaw to prefer one named Matrix account for implicit routing, probing, and CLI operations. If you configure multiple named accounts, set `defaultAccount` or pass `--account <id>` for CLI commands that rely on implicit account selection. Pass `--account <id>` to `openclaw matrix verify ...` and `openclaw matrix devices ...` when you want to override that implicit selection for one command.

## Private/LAN homeservers

By default, OpenClaw blocks private/internal Matrix homeservers for SSRF protection unless you explicitly opt in per account. If your homeserver runs on localhost, a LAN/Tailscale IP, or an internal hostname, enable `allowPrivateNetwork` for that Matrix account:

CLI setup example:

This opt-in only allows trusted private/internal targets. Public cleartext homeservers such as `http://matrix.example.org:8008` remain blocked. Prefer `https://` whenever possible.

## Target resolution

Matrix accepts these target forms anywhere OpenClaw asks you for a room or user target:

Live directory lookup uses the logged-in Matrix account:

## Configuration reference

* `enabled`: enable or disable the channel.
* `name`: optional label for the account.
* `defaultAccount`: preferred account ID when multiple Matrix accounts are configured.
* `homeserver`: homeserver URL, for example `https://matrix.example.org`.
* `allowPrivateNetwork`: allow this Matrix account to connect to private/internal homeservers. Enable this when the homeserver resolves to `localhost`, a LAN/Tailscale IP, or an internal host such as `matrix-synapse`.
* `userId`: full Matrix user ID, for example `@bot:example.org`.
* `accessToken`: access token for token-based auth.
* `password`: password for password-based login.
* `deviceId`: explicit Matrix device ID.
* `deviceName`: device display name for password login.
* `avatarUrl`: stored self-avatar URL for profile sync and `set-profile` updates.
* `initialSyncLimit`: startup sync event limit.
* `encryption`: enable E2EE.
* `allowlistOnly`: force allowlist-only behavior for DMs and rooms.
* `groupPolicy`: `open`, `allowlist`, or `disabled`.
* `groupAllowFrom`: allowlist of user IDs for room traffic.
* `groupAllowFrom` entries should be full Matrix user IDs. Unresolved names are ignored at runtime.
* `replyToMode`: `off`, `first`, or `all`.
* `threadReplies`: `off`, `inbound`, or `always`.
* `threadBindings`: per-channel overrides for thread-bound session routing and lifecycle.
* `startupVerification`: automatic self-verification request mode on startup (`if-unverified`, `off`).
* `startupVerificationCooldownHours`: cooldown before retrying automatic startup verification requests.
* `textChunkLimit`: outbound message chunk size.
* `chunkMode`: `length` or `newline`.
* `responsePrefix`: optional message prefix for outbound replies.
* `ackReaction`: optional ack reaction override for this channel/account.
* `ackReactionScope`: optional ack reaction scope override (`group-mentions`, `group-all`, `direct`, `all`, `none`, `off`).
* `reactionNotifications`: inbound reaction notification mode (`own`, `off`).
* `mediaMaxMb`: outbound media size cap in MB.
* `autoJoin`: invite auto-join policy (`always`, `allowlist`, `off`). Default: `off`.
* `autoJoinAllowlist`: rooms/aliases allowed when `autoJoin` is `allowlist`. Alias entries are resolved to room IDs during invite handling; OpenClaw does not trust alias state claimed by the invited room.
* `dm`: DM policy block (`enabled`, `policy`, `allowFrom`).
* `dm.allowFrom` entries should be full Matrix user IDs unless you already resolved them through live directory lookup.
* `accounts`: named per-account overrides. Top-level `channels.matrix` values act as defaults for these entries.
* `groups`: per-room policy map. Prefer room IDs or aliases; unresolved room names are ignored at runtime. Session/group identity uses the stable room ID after resolution, while human-readable labels still come from room names.
* `rooms`: legacy alias for `groups`.
* `actions`: per-action tool gating (`messages`, `reactions`, `pins`, `profile`, `memberInfo`, `channelInfo`, `verification`).

----
url: https://docs.openclaw.ai/platforms/mac/xpc
----

# macOS IPC - OpenClaw

## [​](#openclaw-macos-ipc-architecture)OpenClaw macOS IPC architecture

**Current model:** a local Unix socket connects the **node host service** to the **macOS app** for exec approvals + `system.run`. A `openclaw-mac` debug CLI exists for discovery/connect checks; agent actions still flow through the Gateway WebSocket and `node.invoke`. UI automation uses PeekabooBridge.

## [​](#goals)Goals

* Single GUI app instance that owns all TCC-facing work (notifications, screen recording, mic, speech, AppleScript).
* A small surface for automation: Gateway + node commands, plus PeekabooBridge for UI automation.
* Predictable permissions: always the same signed bundle ID, launched by launchd, so TCC grants stick.

## [​](#how-it-works)How it works

### [​](#gateway-+-node-transport)Gateway + node transport

* The app runs the Gateway (local mode) and connects to it as a node.
* Agent actions are performed via `node.invoke` (e.g. `system.run`, `system.notify`, `canvas.*`).

### [​](#node-service-+-app-ipc)Node service + app IPC

* A headless node host service connects to the Gateway WebSocket.
* `system.run` requests are forwarded to the macOS app over a local Unix socket.
* The app performs the exec in UI context, prompts if needed, and returns output.

Diagram (SCI):

```
Agent -> Gateway -> Node Service (WS)
                      |  IPC (UDS + token + HMAC + TTL)
                      v
                  Mac App (UI + TCC + system.run)
```

### [​](#peekaboobridge-ui-automation)PeekabooBridge (UI automation)

* UI automation uses a separate UNIX socket named `bridge.sock` and the PeekabooBridge JSON protocol.
* Host preference order (client-side): Peekaboo.app → Claude.app → OpenClaw\.app → local execution.
* Security: bridge hosts require an allowed TeamID; DEBUG-only same-UID escape hatch is guarded by `PEEKABOO_ALLOW_UNSIGNED_SOCKET_CLIENTS=1` (Peekaboo convention).
* See: [PeekabooBridge usage](https://docs.openclaw.ai/platforms/mac/peekaboo) for details.

## [​](#operational-flows)Operational flows

* Restart/rebuild: `SIGN_IDENTITY="Apple Development: <Developer Name> (<TEAMID>)" scripts/restart-mac.sh`

  * Kills existing instances
  * Swift build + package
  * Writes/bootstraps/kickstarts the LaunchAgent

* Single instance: app exits early if another instance with the same bundle ID is running.

## [​](#hardening-notes)Hardening notes

* Prefer requiring a TeamID match for all privileged surfaces.
* PeekabooBridge: `PEEKABOO_ALLOW_UNSIGNED_SOCKET_CLIENTS=1` (DEBUG-only) may allow same-UID callers for local development.
* All communication remains local-only; no network sockets are exposed.
* TCC prompts originate only from the GUI app bundle; keep the signed bundle ID stable across rebuilds.
* IPC hardening: socket mode `0600`, token, peer-UID checks, HMAC challenge/response, short TTL.

----
url: https://docs.openclaw.ai/concepts/model-failover
----

# Model Failover - OpenClaw

OpenClaw handles failures in two stages:

1. **Auth profile rotation** within the current provider.
2. **Model fallback** to the next model in `agents.defaults.model.fallbacks`.

This doc explains the runtime rules and the data that backs them.

## Auth storage (keys + OAuth)

OpenClaw uses **auth profiles** for both API keys and OAuth tokens.

More detail: [/concepts/oauth](https://docs.openclaw.ai/concepts/oauth) Credential types:

## Profile IDs

OAuth logins create distinct profiles so multiple accounts can coexist.

Profiles live in `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` under `profiles`.

## Rotation order

When a provider has multiple profiles, OpenClaw chooses an order like this:

1. **Explicit config**: `auth.order[provider]` (if set).
2. **Configured profiles**: `auth.profiles` filtered by provider.
3. **Stored profiles**: entries in `auth-profiles.json` for the provider.

If no explicit order is configured, OpenClaw uses a round‑robin order:

### Session stickiness (cache-friendly)

OpenClaw **pins the chosen auth profile per session** to keep provider caches warm. It does **not** rotate on every request. The pinned profile is reused until:

Manual selection via `/model …@<profileId>` sets a **user override** for that session and is not auto‑rotated until a new session starts. Auto‑pinned profiles (selected by the session router) are treated as a **preference**: they are tried first, but OpenClaw may rotate to another profile on rate limits/timeouts. User‑pinned profiles stay locked to that profile; if it fails and model fallbacks are configured, OpenClaw moves to the next model instead of switching profiles.

### Why OAuth can “look lost”

If you have both an OAuth profile and an API key profile for the same provider, round‑robin can switch between them across messages unless pinned. To force a single profile:

## Cooldowns

When a profile fails due to auth/rate‑limit errors (or a timeout that looks like rate limiting), OpenClaw marks it in cooldown and moves to the next profile. Format/invalid‑request errors (for example Cloud Code Assist tool call ID validation failures) are treated as failover‑worthy and use the same cooldowns. OpenAI-compatible stop-reason errors such as `Unhandled stop reason: error`, `stop reason: error`, and `reason: error` are classified as timeout/failover signals. Cooldowns use exponential backoff:

State is stored in `auth-profiles.json` under `usageStats`:

## Billing disables

Billing/credit failures (for example “insufficient credits” / “credit balance too low”) are treated as failover‑worthy, but they’re usually not transient. Instead of a short cooldown, OpenClaw marks the profile as **disabled** (with a longer backoff) and rotates to the next profile/provider. State is stored in `auth-profiles.json`:

Defaults:

## Model fallback

If all profiles for a provider fail, OpenClaw moves to the next model in `agents.defaults.model.fallbacks`. This applies to auth failures, rate limits, and timeouts that exhausted profile rotation (other errors do not advance fallback). When a run starts with a model override (hooks or CLI), fallbacks still end at `agents.defaults.model.primary` after trying any configured fallbacks.

See [Gateway configuration](https://docs.openclaw.ai/gateway/configuration) for:

See [Models](https://docs.openclaw.ai/concepts/models) for the broader model selection and fallback overview.

----
url: https://docs.openclaw.ai/providers/mistral
----

# Mistral - OpenClaw

## [​](#mistral)Mistral

OpenClaw supports Mistral for both text/image model routing (`mistral/...`) and audio transcription via Voxtral in media understanding. Mistral can also be used for memory embeddings (`memorySearch.provider = "mistral"`).

## [​](#cli-setup)CLI setup

```
openclaw onboard --auth-choice mistral-api-key
# or non-interactive
openclaw onboard --mistral-api-key "$MISTRAL_API_KEY"
```

## [​](#config-snippet-llm-provider)Config snippet (LLM provider)

```
{
  env: { MISTRAL_API_KEY: "sk-..." },
  agents: { defaults: { model: { primary: "mistral/mistral-large-latest" } } },
}
```

## [​](#config-snippet-audio-transcription-with-voxtral)Config snippet (audio transcription with Voxtral)

```
{
  tools: {
    media: {
      audio: {
        enabled: true,
        models: [{ provider: "mistral", model: "voxtral-mini-latest" }],
      },
    },
  },
}
```

## [​](#notes)Notes

* Mistral auth uses `MISTRAL_API_KEY`.
* Provider base URL defaults to `https://api.mistral.ai/v1`.
* Onboarding default model is `mistral/mistral-large-latest`.
* Media-understanding default audio model for Mistral is `voxtral-mini-latest`.
* Media transcription path uses `/v1/audio/transcriptions`.
* Memory embeddings path uses `/v1/embeddings` (default model: `mistral-embed`).

----
url: https://docs.openclaw.ai/concepts/queue
----

# Command Queue - OpenClaw

## [​](#command-queue-2026-01-16)Command Queue (2026-01-16)

We serialize inbound auto-reply runs (all channels) through a tiny in-process queue to prevent multiple agent runs from colliding, while still allowing safe parallelism across sessions.

## [​](#why)Why

* Auto-reply runs can be expensive (LLM calls) and can collide when multiple inbound messages arrive close together.
* Serializing avoids competing for shared resources (session files, logs, CLI stdin) and reduces the chance of upstream rate limits.

## [​](#how-it-works)How it works

* A lane-aware FIFO queue drains each lane with a configurable concurrency cap (default 1 for unconfigured lanes; main defaults to 4, subagent to 8).
* `runEmbeddedPiAgent` enqueues by **session key** (lane `session:<key>`) to guarantee only one active run per session.
* Each session run is then queued into a **global lane** (`main` by default) so overall parallelism is capped by `agents.defaults.maxConcurrent`.
* When verbose logging is enabled, queued runs emit a short notice if they waited more than \~2s before starting.
* Typing indicators still fire immediately on enqueue (when supported by the channel) so user experience is unchanged while we wait our turn.

## [​](#queue-modes-per-channel)Queue modes (per channel)

Inbound messages can steer the current run, wait for a followup turn, or do both:

* `steer`: inject immediately into the current run (cancels pending tool calls after the next tool boundary). If not streaming, falls back to followup.
* `followup`: enqueue for the next agent turn after the current run ends.
* `collect`: coalesce all queued messages into a **single** followup turn (default). If messages target different channels/threads, they drain individually to preserve routing.
* `steer-backlog` (aka `steer+backlog`): steer now **and** preserve the message for a followup turn.
* `interrupt` (legacy): abort the active run for that session, then run the newest message.
* `queue` (legacy alias): same as `steer`.

Steer-backlog means you can get a followup response after the steered run, so streaming surfaces can look like duplicates. Prefer `collect`/`steer` if you want one response per inbound message. Send `/queue collect` as a standalone command (per-session) or set `messages.queue.byChannel.discord: "collect"`. Defaults (when unset in config):

* All surfaces → `collect`

Configure globally or per channel via `messages.queue`:

```
{
  messages: {
    queue: {
      mode: "collect",
      debounceMs: 1000,
      cap: 20,
      drop: "summarize",
      byChannel: { discord: "collect" },
    },
  },
}
```

## [​](#queue-options)Queue options

Options apply to `followup`, `collect`, and `steer-backlog` (and to `steer` when it falls back to followup):

* `debounceMs`: wait for quiet before starting a followup turn (prevents “continue, continue”).
* `cap`: max queued messages per session.
* `drop`: overflow policy (`old`, `new`, `summarize`).

Summarize keeps a short bullet list of dropped messages and injects it as a synthetic followup prompt. Defaults: `debounceMs: 1000`, `cap: 20`, `drop: summarize`.

## [​](#per-session-overrides)Per-session overrides

* Send `/queue <mode>` as a standalone command to store the mode for the current session.
* Options can be combined: `/queue collect debounce:2s cap:25 drop:summarize`
* `/queue default` or `/queue reset` clears the session override.

## [​](#scope-and-guarantees)Scope and guarantees

* Applies to auto-reply agent runs across all inbound channels that use the gateway reply pipeline (WhatsApp web, Telegram, Slack, Discord, Signal, iMessage, webchat, etc.).
* Default lane (`main`) is process-wide for inbound + main heartbeats; set `agents.defaults.maxConcurrent` to allow multiple sessions in parallel.
* Additional lanes may exist (e.g. `cron`, `subagent`) so background jobs can run in parallel without blocking inbound replies.
* Per-session lanes guarantee that only one agent run touches a given session at a time.
* No external dependencies or background worker threads; pure TypeScript + promises.

## [​](#troubleshooting)Troubleshooting

* If commands seem stuck, enable verbose logs and look for “queued for …ms” lines to confirm the queue is draining.
* If you need queue depth, enable verbose logs and watch for queue timing lines.

----
url: https://docs.openclaw.ai/channels/broadcast-groups
----

# Broadcast Groups - OpenClaw

**Status:** Experimental\
**Version:** Added in 2026.1.9

## Overview

Broadcast Groups enable multiple agents to process and respond to the same message simultaneously. This allows you to create specialized agent teams that work together in a single WhatsApp group or DM — all using one phone number. Current scope: **WhatsApp only** (web channel). Broadcast groups are evaluated after channel allowlists and group activation rules. In WhatsApp groups, this means broadcasts happen when OpenClaw would normally reply (for example: on mention, depending on your group settings).

## Use Cases

### 1. Specialized Agent Teams

Deploy multiple agents with atomic, focused responsibilities:

Each agent processes the same message and provides its specialized perspective.

### 2. Multi-Language Support

### 3. Quality Assurance Workflows

### 4. Task Automation

## Configuration

### Basic Setup

Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer ids:

**Result:** When OpenClaw would reply in this chat, it will run all three agents.

### Processing Strategy

Control how agents process messages:

#### Parallel (Default)

All agents process simultaneously:

#### Sequential

Agents process in order (one waits for previous to finish):

### Complete Example

```
{
  "agents": {
    "list": [
      {
        "id": "code-reviewer",
        "name": "Code Reviewer",
        "workspace": "/path/to/code-reviewer",
        "sandbox": { "mode": "all" }
      },
      {
        "id": "security-auditor",
        "name": "Security Auditor",
        "workspace": "/path/to/security-auditor",
        "sandbox": { "mode": "all" }
      },
      {
        "id": "docs-generator",
        "name": "Documentation Generator",
        "workspace": "/path/to/docs-generator",
        "sandbox": { "mode": "all" }
      }
    ]
  },
  "broadcast": {
    "strategy": "parallel",
    "120363403215116621@g.us": ["code-reviewer", "security-auditor", "docs-generator"],
    "120363424282127706@g.us": ["support-en", "support-de"],
    "+15555550123": ["assistant", "logger"]
  }
}
```

## How It Works

### Message Flow

1. **Incoming message** arrives in a WhatsApp group
2. **Broadcast check**: System checks if peer ID is in `broadcast`
3. **If in broadcast list**:
4. **If not in broadcast list**:

Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change *which agents run* when a message is eligible for processing.

### Session Isolation

Each agent in a broadcast group maintains completely separate:

This allows each agent to have:

### Example: Isolated Sessions

In group `120363403215116621@g.us` with agents `["alfred", "baerbel"]`: **Alfred’s context:**

**Bärbel’s context:**

## Best Practices

### 1. Keep Agents Focused

Design each agent with a single, clear responsibility:

✅ **Good:** Each agent has one job\
❌ **Bad:** One generic “dev-helper” agent

### 2. Use Descriptive Names

Make it clear what each agent does:

### 3. Configure Different Tool Access

Give agents only the tools they need:

### 4. Monitor Performance

With many agents, consider:

### 5. Handle Failures Gracefully

Agents fail independently. One agent’s error doesn’t block others:

## Compatibility

### Providers

Broadcast groups currently work with:

### Routing

Broadcast groups work alongside existing routing:

**Precedence:** `broadcast` takes priority over `bindings`.

## Troubleshooting

### Agents Not Responding

**Check:**

1. Agent IDs exist in `agents.list`
2. Peer ID format is correct (e.g., `120363403215116621@g.us`)
3. Agents are not in deny lists

**Debug:**

### Only One Agent Responding

**Cause:** Peer ID might be in `bindings` but not `broadcast`. **Fix:** Add to broadcast config or remove from bindings.

### Performance Issues

**If slow with many agents:**

## Examples

### Example 1: Code Review Team

```
{
  "broadcast": {
    "strategy": "parallel",
    "120363403215116621@g.us": [
      "code-formatter",
      "security-scanner",
      "test-coverage",
      "docs-checker"
    ]
  },
  "agents": {
    "list": [
      {
        "id": "code-formatter",
        "workspace": "~/agents/formatter",
        "tools": { "allow": ["read", "write"] }
      },
      {
        "id": "security-scanner",
        "workspace": "~/agents/security",
        "tools": { "allow": ["read", "exec"] }
      },
      {
        "id": "test-coverage",
        "workspace": "~/agents/testing",
        "tools": { "allow": ["read", "exec"] }
      },
      { "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } }
    ]
  }
}
```

**User sends:** Code snippet\
**Responses:**

### Example 2: Multi-Language Support

## API Reference

### Config Schema

### Fields

## Limitations

1. **Max agents:** No hard limit, but 10+ agents may be slow
2. **Shared context:** Agents don’t see each other’s responses (by design)
3. **Message ordering:** Parallel responses may arrive in any order
4. **Rate limits:** All agents count toward WhatsApp rate limits

## Future Enhancements

Planned features:

## See Also

----
url: https://docs.openclaw.ai/gateway/secrets
----

# Secrets Management - OpenClaw

OpenClaw supports additive SecretRefs so supported credentials do not need to be stored as plaintext in configuration. Plaintext still works. SecretRefs are opt-in per credential.

## Goals and runtime model

Secrets are resolved into an in-memory runtime snapshot.

This keeps secret-provider outages off hot request paths.

## Active-surface filtering

SecretRefs are validated only on effectively active surfaces.

Examples of inactive surfaces:

## Gateway auth surface diagnostics

When a SecretRef is configured on `gateway.auth.token`, `gateway.auth.password`, `gateway.remote.token`, or `gateway.remote.password`, gateway startup/reload logs the surface state explicitly:

These entries are logged with `SECRETS_GATEWAY_AUTH_SURFACE` and include the reason used by the active-surface policy, so you can see why a credential was treated as active or inactive.

## Onboarding reference preflight

When onboarding runs in interactive mode and you choose SecretRef storage, OpenClaw runs preflight validation before saving:

If validation fails, onboarding shows the error and lets you retry.

## SecretRef contract

Use one object shape everywhere:

### `source: "env"`

Validation:

### `source: "file"`

Validation:

### `source: "exec"`

Validation:

## Provider config

Define providers under `secrets.providers`:

```
{
  secrets: {
    providers: {
      default: { source: "env" },
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json", // or "singleValue"
      },
      vault: {
        source: "exec",
        command: "/usr/local/bin/openclaw-vault-resolver",
        args: ["--profile", "prod"],
        passEnv: ["PATH", "VAULT_ADDR"],
        jsonOnly: true,
      },
    },
    defaults: {
      env: "default",
      file: "filemain",
      exec: "vault",
    },
    resolution: {
      maxProviderConcurrency: 4,
      maxRefsPerProvider: 512,
      maxBatchBytes: 262144,
    },
  },
}
```

### Env provider

### File provider

### Exec provider

Request payload (stdin):

Response payload (stdout):

Optional per-id errors:

## Exec integration examples

### 1Password CLI

```
{
  secrets: {
    providers: {
      onepassword_openai: {
        source: "exec",
        command: "/opt/homebrew/bin/op",
        allowSymlinkCommand: true, // required for Homebrew symlinked binaries
        trustedDirs: ["/opt/homebrew"],
        args: ["read", "op://Personal/OpenClaw QA API Key/password"],
        passEnv: ["HOME"],
        jsonOnly: false,
      },
    },
  },
  models: {
    providers: {
      openai: {
        baseUrl: "https://api.openai.com/v1",
        models: [{ id: "gpt-5", name: "gpt-5" }],
        apiKey: { source: "exec", provider: "onepassword_openai", id: "value" },
      },
    },
  },
}
```

### HashiCorp Vault CLI

```
{
  secrets: {
    providers: {
      vault_openai: {
        source: "exec",
        command: "/opt/homebrew/bin/vault",
        allowSymlinkCommand: true, // required for Homebrew symlinked binaries
        trustedDirs: ["/opt/homebrew"],
        args: ["kv", "get", "-field=OPENAI_API_KEY", "secret/openclaw"],
        passEnv: ["VAULT_ADDR", "VAULT_TOKEN"],
        jsonOnly: false,
      },
    },
  },
  models: {
    providers: {
      openai: {
        baseUrl: "https://api.openai.com/v1",
        models: [{ id: "gpt-5", name: "gpt-5" }],
        apiKey: { source: "exec", provider: "vault_openai", id: "value" },
      },
    },
  },
}
```

### `sops`

```
{
  secrets: {
    providers: {
      sops_openai: {
        source: "exec",
        command: "/opt/homebrew/bin/sops",
        allowSymlinkCommand: true, // required for Homebrew symlinked binaries
        trustedDirs: ["/opt/homebrew"],
        args: ["-d", "--extract", '["providers"]["openai"]["apiKey"]', "/path/to/secrets.enc.json"],
        passEnv: ["SOPS_AGE_KEY_FILE"],
        jsonOnly: false,
      },
    },
  },
  models: {
    providers: {
      openai: {
        baseUrl: "https://api.openai.com/v1",
        models: [{ id: "gpt-5", name: "gpt-5" }],
        apiKey: { source: "exec", provider: "sops_openai", id: "value" },
      },
    },
  },
}
```

## Sandbox SSH auth material

The core `ssh` sandbox backend also supports SecretRefs for SSH auth material:

```
{
  agents: {
    defaults: {
      sandbox: {
        mode: "all",
        backend: "ssh",
        ssh: {
          target: "user@gateway-host:22",
          identityData: { source: "env", provider: "default", id: "SSH_IDENTITY" },
          certificateData: { source: "env", provider: "default", id: "SSH_CERTIFICATE" },
          knownHostsData: { source: "env", provider: "default", id: "SSH_KNOWN_HOSTS" },
        },
      },
    },
  },
}
```

Runtime behavior:

## Supported credential surface

Canonical supported and unsupported credentials are listed in:

Runtime-minted or rotating credentials and OAuth refresh material are intentionally excluded from read-only SecretRef resolution.

## Required behavior and precedence

Warning and audit signals:

Google Chat compatibility behavior:

## Activation triggers

Secret activation runs on:

Activation contract:

## Degraded and recovered signals

When reload-time activation fails after a healthy state, OpenClaw enters degraded secrets state. One-shot system event and log codes:

Behavior:

## Command-path resolution

Command paths can opt into supported SecretRef resolution via gateway snapshot RPC. There are two broad behaviors:

Read-only behavior:

Other notes:

## Audit and configure workflow

Default operator flow:

### `secrets audit`

Findings include:

Exec note:

Header residue note:

### `secrets configure`

Interactive helper that:

Exec note:

Helpful modes:

`configure` apply defaults:

### `secrets apply`

Apply a saved plan:

Exec note:

For strict target/path contract details and exact rejection rules, see:

## One-way safety policy

OpenClaw intentionally does not write rollback backups containing historical plaintext secret values. Safety model:

## Legacy auth compatibility notes

For static credentials, runtime no longer depends on plaintext legacy auth storage.

## Web UI note

Some SecretInput unions are easier to configure in raw editor mode than in form mode.

----
url: https://docs.openclaw.ai/tools/exa-search
----

# Exa Search - OpenClaw

OpenClaw supports [Exa AI](https://exa.ai/) as a `web_search` provider. Exa offers neural, keyword, and hybrid search modes with built-in content extraction (highlights, text, summaries).

## Get an API key

## Config

```
{
  plugins: {
    entries: {
      exa: {
        config: {
          webSearch: {
            apiKey: "exa-...", // optional if EXA_API_KEY is set
          },
        },
      },
    },
  },
  tools: {
    web: {
      search: {
        provider: "exa",
      },
    },
  },
}
```

**Environment alternative:** set `EXA_API_KEY` in the Gateway environment. For a gateway install, put it in `~/.openclaw/.env`.

## Tool parameters

| Parameter     | Description                                                                   |
| ------------- | ----------------------------------------------------------------------------- |
| `query`       | Search query (required)                                                       |
| `count`       | Results to return (1-100)                                                     |
| `type`        | Search mode: `auto`, `neural`, `fast`, `deep`, `deep-reasoning`, or `instant` |
| `freshness`   | Time filter: `day`, `week`, `month`, or `year`                                |
| `date_after`  | Results after this date (YYYY-MM-DD)                                          |
| `date_before` | Results before this date (YYYY-MM-DD)                                         |
| `contents`    | Content extraction options (see below)                                        |

### Content extraction

Exa can return extracted content alongside search results. Pass a `contents` object to enable:

| Contents option | Type                                                                  | Description            |
| --------------- | --------------------------------------------------------------------- | ---------------------- |
| `text`          | `boolean \| { maxCharacters }`                                        | Extract full page text |
| `highlights`    | `boolean \| { maxCharacters, query, numSentences, highlightsPerUrl }` | Extract key sentences  |
| `summary`       | `boolean \| { query }`                                                | AI-generated summary   |

### Search modes

| Mode             | Description                       |
| ---------------- | --------------------------------- |
| `auto`           | Exa picks the best mode (default) |
| `neural`         | Semantic/meaning-based search     |
| `fast`           | Quick keyword search              |
| `deep`           | Thorough deep search              |
| `deep-reasoning` | Deep search with reasoning        |
| `instant`        | Fastest results                   |

## Notes

----
url: https://docs.openclaw.ai/security/CONTRIBUTING-THREAT-MODEL
----

# Contributing to the Threat Model - OpenClaw

Thanks for helping make OpenClaw more secure. This threat model is a living document and we welcome contributions from anyone - you don’t need to be a security expert.

## Ways to Contribute

### Add a Threat

Spotted an attack vector or risk we haven’t covered? Open an issue on [openclaw/trust](https://github.com/openclaw/trust/issues) and describe it in your own words. You don’t need to know any frameworks or fill in every field - just describe the scenario. **Helpful to include (but not required):**

We’ll handle the ATLAS mapping, threat IDs, and risk assessment during review. If you want to include those details, great - but it’s not expected.

> **This is for adding to the threat model, not reporting live vulnerabilities.** If you’ve found an exploitable vulnerability, see our [Trust page](https://trust.openclaw.ai/) for responsible disclosure instructions.

### Suggest a Mitigation

Have an idea for how to address an existing threat? Open an issue or PR referencing the threat. Useful mitigations are specific and actionable - for example, “per-sender rate limiting of 10 messages/minute at the gateway” is better than “implement rate limiting.”

### Propose an Attack Chain

Attack chains show how multiple threats combine into a realistic attack scenario. If you see a dangerous combination, describe the steps and how an attacker would chain them together. A short narrative of how the attack unfolds in practice is more valuable than a formal template.

### Fix or Improve Existing Content

Typos, clarifications, outdated info, better examples - PRs welcome, no issue needed.

## What We Use

### MITRE ATLAS

This threat model is built on [MITRE ATLAS](https://atlas.mitre.org/) (Adversarial Threat Landscape for AI Systems), a framework designed specifically for AI/ML threats like prompt injection, tool misuse, and agent exploitation. You don’t need to know ATLAS to contribute - we map submissions to the framework during review.

### Threat IDs

Each threat gets an ID like `T-EXEC-003`. The categories are:

| Code    | Category                                   |
| ------- | ------------------------------------------ |
| RECON   | Reconnaissance - information gathering     |
| ACCESS  | Initial access - gaining entry             |
| EXEC    | Execution - running malicious actions      |
| PERSIST | Persistence - maintaining access           |
| EVADE   | Defense evasion - avoiding detection       |
| DISC    | Discovery - learning about the environment |
| EXFIL   | Exfiltration - stealing data               |
| IMPACT  | Impact - damage or disruption              |

IDs are assigned by maintainers during review. You don’t need to pick one.

### Risk Levels

| Level        | Meaning                                                           |
| ------------ | ----------------------------------------------------------------- |
| **Critical** | Full system compromise, or high likelihood + critical impact      |
| **High**     | Significant damage likely, or medium likelihood + critical impact |
| **Medium**   | Moderate risk, or low likelihood + high impact                    |
| **Low**      | Unlikely and limited impact                                       |

If you’re unsure about the risk level, just describe the impact and we’ll assess it.

## Review Process

1. **Triage** - We review new submissions within 48 hours
2. **Assessment** - We verify feasibility, assign ATLAS mapping and threat ID, validate risk level
3. **Documentation** - We ensure everything is formatted and complete
4. **Merge** - Added to the threat model and visualization

## Resources

## Recognition

Contributors to the threat model are recognized in the threat model acknowledgments, release notes, and the OpenClaw security hall of fame for significant contributions.

----
url: https://docs.openclaw.ai/providers/anthropic
----

# Anthropic - OpenClaw

## Anthropic (Claude)

Anthropic builds the **Claude** model family and provides access via an API. In OpenClaw you can authenticate with an API key or a **setup-token**.

## Option A: Anthropic API key

**Best for:** standard API access and usage-based billing. Create your API key in the Anthropic Console.

### CLI setup

### Config snippet

## Thinking defaults (Claude 4.6)

## Fast mode (Anthropic API)

OpenClaw’s shared `/fast` toggle also supports direct Anthropic API-key traffic.

Important limits:

## Prompt caching (Anthropic API)

OpenClaw supports Anthropic’s prompt caching feature. This is **API-only**; subscription auth does not honor cache settings.

### Configuration

Use the `cacheRetention` parameter in your model config:

| Value   | Cache Duration | Description                         |
| ------- | -------------- | ----------------------------------- |
| `none`  | No caching     | Disable prompt caching              |
| `short` | 5 minutes      | Default for API Key auth            |
| `long`  | 1 hour         | Extended cache (requires beta flag) |

### Defaults

When using Anthropic API Key authentication, OpenClaw automatically applies `cacheRetention: "short"` (5-minute cache) for all Anthropic models. You can override this by explicitly setting `cacheRetention` in your config.

### Per-agent cacheRetention overrides

Use model-level params as your baseline, then override specific agents via `agents.list[].params`.

```
{
  agents: {
    defaults: {
      model: { primary: "anthropic/claude-opus-4-6" },
      models: {
        "anthropic/claude-opus-4-6": {
          params: { cacheRetention: "long" }, // baseline for most agents
        },
      },
    },
    list: [
      { id: "research", default: true },
      { id: "alerts", params: { cacheRetention: "none" } }, // override for this agent only
    ],
  },
}
```

Config merge order for cache-related params:

1. `agents.defaults.models["provider/model"].params`
2. `agents.list[].params` (matching `id`, overrides by key)

This lets one agent keep a long-lived cache while another agent on the same model disables caching to avoid write costs on bursty/low-reuse traffic.

### Bedrock Claude notes

### Legacy parameter

The older `cacheControlTtl` parameter is still supported for backwards compatibility:

We recommend migrating to the new `cacheRetention` parameter. OpenClaw includes the `extended-cache-ttl-2025-04-11` beta flag for Anthropic API requests; keep it if you override provider headers (see [/gateway/configuration](https://docs.openclaw.ai/gateway/configuration)).

## 1M context window (Anthropic beta)

Anthropic’s 1M context window is beta-gated. In OpenClaw, enable it per model with `params.context1m: true` for supported Opus/Sonnet models.

OpenClaw maps this to `anthropic-beta: context-1m-2025-08-07` on Anthropic requests. This only activates when `params.context1m` is explicitly set to `true` for that model. Requirement: Anthropic must allow long-context usage on that credential (typically API key billing, or a subscription account with Extra Usage enabled). Otherwise Anthropic returns: `HTTP 429: rate_limit_error: Extra usage is required for long context requests`. Note: Anthropic currently rejects `context-1m-*` beta requests when using OAuth/subscription tokens (`sk-ant-oat-*`). OpenClaw automatically skips the context1m beta header for OAuth auth and keeps the required OAuth betas.

## Option B: Claude setup-token

**Best for:** using your Claude subscription.

### Where to get a setup-token

Setup-tokens are created by the **Claude Code CLI**, not the Anthropic Console. You can run this on **any machine**:

Paste the token into OpenClaw (wizard: **Anthropic token (paste setup-token)**), or run it on the gateway host:

If you generated the token on a different machine, paste it:

### CLI setup (setup-token)

### Config snippet (setup-token)

## Notes

## Troubleshooting

**401 errors / token suddenly invalid**

**No API key found for provider “anthropic”**

**No credentials found for profile `anthropic:default`**

**No available auth profile (all in cooldown/unavailable)**

More: [/gateway/troubleshooting](https://docs.openclaw.ai/gateway/troubleshooting) and [/help/faq](https://docs.openclaw.ai/help/faq).

----
url: https://docs.openclaw.ai/cli/update
----

# update - OpenClaw

## [​](#openclaw-update)`openclaw update`

Safely update OpenClaw and switch between stable/beta/dev channels. If you installed via **npm/pnpm** (global install, no git metadata), updates happen via the package manager flow in [Updating](https://docs.openclaw.ai/install/updating).

## [​](#usage)Usage

```
openclaw update
openclaw update status
openclaw update wizard
openclaw update --channel beta
openclaw update --channel dev
openclaw update --tag beta
openclaw update --tag main
openclaw update --dry-run
openclaw update --no-restart
openclaw update --json
openclaw --update
```

## [​](#options)Options

* `--no-restart`: skip restarting the Gateway service after a successful update.
* `--channel <stable|beta|dev>`: set the update channel (git + npm; persisted in config).
* `--tag <dist-tag|version|spec>`: override the package target for this update only. For package installs, `main` maps to `github:openclaw/openclaw#main`.
* `--dry-run`: preview planned update actions (channel/tag/target/restart flow) without writing config, installing, syncing plugins, or restarting.
* `--json`: print machine-readable `UpdateRunResult` JSON.
* `--timeout <seconds>`: per-step timeout (default is 1200s).

Note: downgrades require confirmation because older versions can break configuration.

## [​](#update-status)`update status`

Show the active update channel + git tag/branch/SHA (for source checkouts), plus update availability.

```
openclaw update status
openclaw update status --json
openclaw update status --timeout 10
```

Options:

* `--json`: print machine-readable status JSON.
* `--timeout <seconds>`: timeout for checks (default is 3s).

## [​](#update-wizard)`update wizard`

Interactive flow to pick an update channel and confirm whether to restart the Gateway after updating (default is to restart). If you select `dev` without a git checkout, it offers to create one.

## [​](#what-it-does)What it does

When you switch channels explicitly (`--channel ...`), OpenClaw also keeps the install method aligned:

* `dev` → ensures a git checkout (default: `~/openclaw`, override with `OPENCLAW_GIT_DIR`), updates it, and installs the global CLI from that checkout.
* `stable`/`beta` → installs from npm using the matching dist-tag.

The Gateway core auto-updater (when enabled via config) reuses this same update path.

## [​](#git-checkout-flow)Git checkout flow

Channels:

* `stable`: checkout the latest non-beta tag, then build + doctor.
* `beta`: checkout the latest `-beta` tag, then build + doctor.
* `dev`: checkout `main`, then fetch + rebase.

High-level:

1. Requires a clean worktree (no uncommitted changes).
2. Switches to the selected channel (tag or branch).
3. Fetches upstream (dev only).
4. Dev only: preflight lint + TypeScript build in a temp worktree; if the tip fails, walks back up to 10 commits to find the newest clean build.
5. Rebases onto the selected commit (dev only).
6. Installs deps (pnpm preferred; npm fallback).
7. Builds + builds the Control UI.
8. Runs `openclaw doctor` as the final “safe update” check.
9. Syncs plugins to the active channel (dev uses bundled extensions; stable/beta uses npm) and updates npm-installed plugins.

## [​](#update-shorthand)`--update` shorthand

`openclaw --update` rewrites to `openclaw update` (useful for shells and launcher scripts).

## [​](#see-also)See also

* `openclaw doctor` (offers to run update first on git checkouts)
* [Development channels](https://docs.openclaw.ai/install/development-channels)
* [Updating](https://docs.openclaw.ai/install/updating)
* [CLI reference](https://docs.openclaw.ai/cli)

----
url: https://docs.openclaw.ai/providers/qwen
----

# Qwen - OpenClaw

## [​](#qwen)Qwen

Qwen provides a free-tier OAuth flow for Qwen Coder and Qwen Vision models (2,000 requests/day, subject to Qwen rate limits).

## [​](#enable-the-plugin)Enable the plugin

```
openclaw plugins enable qwen-portal-auth
```

Restart the Gateway after enabling.

## [​](#authenticate)Authenticate

```
openclaw models auth login --provider qwen-portal --set-default
```

This runs the Qwen device-code OAuth flow and writes a provider entry to your `models.json` (plus a `qwen` alias for quick switching).

## [​](#model-ids)Model IDs

* `qwen-portal/coder-model`
* `qwen-portal/vision-model`

Switch models with:

```
openclaw models set qwen-portal/coder-model
```

## [​](#reuse-qwen-code-cli-login)Reuse Qwen Code CLI login

If you already logged in with the Qwen Code CLI, OpenClaw will sync credentials from `~/.qwen/oauth_creds.json` when it loads the auth store. You still need a `models.providers.qwen-portal` entry (use the login command above to create one).

## [​](#notes)Notes

* Tokens auto-refresh; re-run the login command if refresh fails or access is revoked.
* Default base URL: `https://portal.qwen.ai/v1` (override with `models.providers.qwen-portal.baseUrl` if Qwen provides a different endpoint).
* See [Model providers](https://docs.openclaw.ai/concepts/model-providers) for provider-wide rules.

----
url: https://docs.openclaw.ai/install/hetzner
----

# Hetzner - OpenClaw

## OpenClaw on Hetzner (Docker, Production VPS Guide)

## Goal

Run a persistent OpenClaw Gateway on a Hetzner VPS using Docker, with durable state, baked-in binaries, and safe restart behavior. If you want “OpenClaw 24/7 for \~$5”, this is the simplest reliable setup. Hetzner pricing changes; pick the smallest Debian/Ubuntu VPS and scale up if you hit OOMs. Security model reminder:

See [Security](https://docs.openclaw.ai/gateway/security) and [VPS hosting](https://docs.openclaw.ai/vps).

## What are we doing (simple terms)?

The Gateway can be accessed via:

This guide assumes Ubuntu or Debian on Hetzner.\
If you are on another Linux VPS, map packages accordingly. For the generic Docker flow, see [Docker](https://docs.openclaw.ai/install/docker).

***

## Quick path (experienced operators)

1. Provision Hetzner VPS
2. Install Docker
3. Clone OpenClaw repository
4. Create persistent host directories
5. Configure `.env` and `docker-compose.yml`
6. Bake required binaries into the image
7. `docker compose up -d`
8. Verify persistence and Gateway access

***

## What you need

***

The shared persistence map lives in [Docker VM Runtime](https://docs.openclaw.ai/install/docker-vm-runtime#what-persists-where).

## Infrastructure as Code (Terraform)

For teams preferring infrastructure-as-code workflows, a community-maintained Terraform setup provides:

**Repositories:**

This approach complements the Docker setup above with reproducible deployments, version-controlled infrastructure, and automated disaster recovery.

> **Note:** Community-maintained. For issues or contributions, see the repository links above.

## Next steps

----
url: https://docs.openclaw.ai/providers/models
----

# Model Provider Quickstart - OpenClaw

## [​](#model-providers)Model Providers

OpenClaw can use many LLM providers. Pick one, authenticate, then set the default model as `provider/model`.

## [​](#quick-start-two-steps)Quick start (two steps)

1. Authenticate with the provider (usually via `openclaw onboard`).
2. Set the default model:

```
{
  agents: { defaults: { model: { primary: "anthropic/claude-opus-4-6" } } },
}
```

## [​](#supported-providers-starter-set)Supported providers (starter set)

* [OpenAI (API + Codex)](https://docs.openclaw.ai/providers/openai)
* [Anthropic (API + Claude Code CLI)](https://docs.openclaw.ai/providers/anthropic)
* [OpenRouter](https://docs.openclaw.ai/providers/openrouter)
* [Vercel AI Gateway](https://docs.openclaw.ai/providers/vercel-ai-gateway)
* [Cloudflare AI Gateway](https://docs.openclaw.ai/providers/cloudflare-ai-gateway)
* [Moonshot AI (Kimi + Kimi Coding)](https://docs.openclaw.ai/providers/moonshot)
* [Mistral](https://docs.openclaw.ai/providers/mistral)
* [Synthetic](https://docs.openclaw.ai/providers/synthetic)
* [OpenCode (Zen + Go)](https://docs.openclaw.ai/providers/opencode)
* [Z.AI](https://docs.openclaw.ai/providers/zai)
* [GLM models](https://docs.openclaw.ai/providers/glm)
* [MiniMax](https://docs.openclaw.ai/providers/minimax)
* [Venice (Venice AI)](https://docs.openclaw.ai/providers/venice)
* [Amazon Bedrock](https://docs.openclaw.ai/providers/bedrock)
* [Qianfan](https://docs.openclaw.ai/providers/qianfan)
* [xAI](https://docs.openclaw.ai/providers/xai)

For the full provider catalog (xAI, Groq, Mistral, etc.) and advanced configuration, see [Model providers](https://docs.openclaw.ai/concepts/model-providers).

----
url: https://docs.openclaw.ai/reference/RELEASING
----

# Release Policy - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [Release Policy](#release-policy)
- [Version naming](#version-naming)
- [Release cadence](#release-cadence)
- [Public references](#public-references)

## [​](#release-policy)Release Policy

OpenClaw has three public release lanes:

* stable: tagged releases that publish to npm `latest`
* beta: prerelease tags that publish to npm `beta`
* dev: the moving head of `main`

## [​](#version-naming)Version naming

* Stable release version: `YYYY.M.D`
  * Git tag: `vYYYY.M.D`
* Beta prerelease version: `YYYY.M.D-beta.N`
  * Git tag: `vYYYY.M.D-beta.N`
* Do not zero-pad month or day
* `latest` means the current stable npm release
* `beta` means the current prerelease npm release
* Beta releases may ship before the macOS app catches up

## [​](#release-cadence)Release cadence

* Releases move beta-first
* Stable follows only after the latest beta is validated
* Detailed release procedure, approvals, credentials, and recovery notes are maintainer-only

## [​](#public-references)Public references

* [`.github/workflows/openclaw-npm-release.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-npm-release.yml)
* [`scripts/openclaw-npm-release-check.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/openclaw-npm-release-check.ts)

Maintainers use the private release docs in [`openclaw/maintainers/release/README.md`](https://github.com/openclaw/maintainers/blob/main/release/README.md) for the actual runbook.

[Credits](https://docs.openclaw.ai/reference/credits)[Tests](https://docs.openclaw.ai/reference/test)

----
url: https://docs.openclaw.ai/cli/node
----

# node - OpenClaw

## [​](#openclaw-node)`openclaw node`

Run a **headless node host** that connects to the Gateway WebSocket and exposes `system.run` / `system.which` on this machine.

## [​](#why-use-a-node-host)Why use a node host?

Use a node host when you want agents to **run commands on other machines** in your network without installing a full macOS companion app there. Common use cases:

* Run commands on remote Linux/Windows boxes (build servers, lab machines, NAS).
* Keep exec **sandboxed** on the gateway, but delegate approved runs to other hosts.
* Provide a lightweight, headless execution target for automation or CI nodes.

Execution is still guarded by **exec approvals** and per‑agent allowlists on the node host, so you can keep command access scoped and explicit.

## [​](#browser-proxy-zero-config)Browser proxy (zero-config)

Node hosts automatically advertise a browser proxy if `browser.enabled` is not disabled on the node. This lets the agent use browser automation on that node without extra configuration. By default, the proxy exposes the node’s normal browser profile surface. If you set `nodeHost.browserProxy.allowProfiles`, the proxy becomes restrictive: non-allowlisted profile targeting is rejected, and persistent profile create/delete routes are blocked through the proxy. Disable it on the node if needed:

```
{
  nodeHost: {
    browserProxy: {
      enabled: false,
    },
  },
}
```

## [​](#run-foreground)Run (foreground)

```
openclaw node run --host <gateway-host> --port 18789
```

Options:

* `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
* `--port <port>`: Gateway WebSocket port (default: `18789`)
* `--tls`: Use TLS for the gateway connection
* `--tls-fingerprint <sha256>`: Expected TLS certificate fingerprint (sha256)
* `--node-id <id>`: Override node id (clears pairing token)
* `--display-name <name>`: Override the node display name

## [​](#gateway-auth-for-node-host)Gateway auth for node host

`openclaw node run` and `openclaw node install` resolve gateway auth from config/env (no `--token`/`--password` flags on node commands):

* `OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD` are checked first.
* Then local config fallback: `gateway.auth.token` / `gateway.auth.password`.
* In local mode, node host intentionally does not inherit `gateway.remote.token` / `gateway.remote.password`.
* If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, node auth resolution fails closed (no remote fallback masking).
* In `gateway.mode=remote`, remote client fields (`gateway.remote.token` / `gateway.remote.password`) are also eligible per remote precedence rules.
* Node host auth resolution only honors `OPENCLAW_GATEWAY_*` env vars.

## [​](#service-background)Service (background)

Install a headless node host as a user service.

```
openclaw node install --host <gateway-host> --port 18789
```

Options:

* `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
* `--port <port>`: Gateway WebSocket port (default: `18789`)
* `--tls`: Use TLS for the gateway connection
* `--tls-fingerprint <sha256>`: Expected TLS certificate fingerprint (sha256)
* `--node-id <id>`: Override node id (clears pairing token)
* `--display-name <name>`: Override the node display name
* `--runtime <runtime>`: Service runtime (`node` or `bun`)
* `--force`: Reinstall/overwrite if already installed

Manage the service:

```
openclaw node status
openclaw node stop
openclaw node restart
openclaw node uninstall
```

Use `openclaw node run` for a foreground node host (no service). Service commands accept `--json` for machine-readable output.

## [​](#pairing)Pairing

The first connection creates a pending device pairing request (`role: node`) on the Gateway. Approve it via:

```
openclaw devices list
openclaw devices approve <requestId>
```

If the node retries pairing with changed auth details (role/scopes/public key), the previous pending request is superseded and a new `requestId` is created. Run `openclaw devices list` again before approval. The node host stores its node id, token, display name, and gateway connection info in `~/.openclaw/node.json`.

## [​](#exec-approvals)Exec approvals

`system.run` is gated by local exec approvals:

* `~/.openclaw/exec-approvals.json`
* [Exec approvals](https://docs.openclaw.ai/tools/exec-approvals)
* `openclaw approvals --node <id|name|ip>` (edit from the Gateway)

----
url: https://docs.openclaw.ai/platforms/ios
----

# iOS App - OpenClaw

## iOS App (Node)

Availability: internal preview. The iOS app is not publicly distributed yet.

## What it does

## Requirements

## Quick start (pair + connect)

1. Start the Gateway:

2) In the iOS app, open Settings and pick a discovered gateway (or enable Manual Host and enter host/port).
3) Approve the pairing request on the gateway host:

If the app retries pairing with changed auth details (role/scopes/public key), the previous pending request is superseded and a new `requestId` is created. Run `openclaw devices list` again before approval.

4. Verify connection:

## Relay-backed push for official builds

Official distributed iOS builds use the external push relay instead of publishing the raw APNs token to the gateway. Gateway-side requirement:

How the flow works:

What the gateway does **not** need for this path:

Expected operator flow:

1. Install the official/TestFlight iOS build.
2. Set `gateway.push.apns.relay.baseUrl` on the gateway.
3. Pair the app to the gateway and let it finish connecting.
4. The app publishes `push.apns.register` automatically after it has an APNs token, the operator session is connected, and relay registration succeeds.
5. After that, `push.test`, reconnect wakes, and wake nudges can use the stored relay-backed registration.

Compatibility note:

## Authentication and trust flow

The relay exists to enforce two constraints that direct APNs-on-gateway cannot provide for official iOS builds:

Hop by hop:

1. `iOS app -> gateway`
2. `iOS app -> relay`
3. `gateway identity delegation`
4. `gateway -> relay`
5. `relay -> APNs`

Why this design was created:

Local/manual builds remain on direct APNs. If you are testing those builds without the relay, the gateway still needs direct APNs credentials:

## Discovery paths

### Bonjour (LAN)

The Gateway advertises `_openclaw-gw._tcp` on `local.`. The iOS app lists these automatically.

### Tailnet (cross-network)

If mDNS is blocked, use a unicast DNS-SD zone (choose a domain; example: `openclaw.internal.`) and Tailscale split DNS. See [Bonjour](https://docs.openclaw.ai/gateway/bonjour) for the CoreDNS example.

### Manual host/port

In Settings, enable **Manual Host** and enter the gateway host + port (default `18789`).

## Canvas + A2UI

The iOS node renders a WKWebView canvas. Use `node.invoke` to drive it:

Notes:

### Canvas eval / snapshot

## Voice wake + talk mode

## Common errors

----
url: https://docs.openclaw.ai/install/docker
----

# Docker - OpenClaw

## Docker (optional)

Docker is **optional**. Use it only if you want a containerized gateway or to validate the Docker flow.

## Is Docker right for me?

## Prerequisites

## Containerized Gateway

### Manual flow

If you prefer to run each step yourself instead of using the setup script:

### Environment variables

The setup script accepts these optional environment variables:

| Variable                       | Purpose                                                          |
| ------------------------------ | ---------------------------------------------------------------- |
| `OPENCLAW_IMAGE`               | Use a remote image instead of building locally                   |
| `OPENCLAW_DOCKER_APT_PACKAGES` | Install extra apt packages during build (space-separated)        |
| `OPENCLAW_EXTENSIONS`          | Pre-install extension deps at build time (space-separated names) |
| `OPENCLAW_EXTRA_MOUNTS`        | Extra host bind mounts (comma-separated `source:target[:opts]`)  |
| `OPENCLAW_HOME_VOLUME`         | Persist `/home/node` in a named Docker volume                    |
| `OPENCLAW_SANDBOX`             | Opt in to sandbox bootstrap (`1`, `true`, `yes`, `on`)           |
| `OPENCLAW_DOCKER_SOCKET`       | Override Docker socket path                                      |

### Health checks

Container probe endpoints (no auth required):

The Docker image includes a built-in `HEALTHCHECK` that pings `/healthz`. If checks keep failing, Docker marks the container as `unhealthy` and orchestration systems can restart or replace it. Authenticated deep health snapshot:

### LAN vs loopback

`scripts/docker/setup.sh` defaults `OPENCLAW_GATEWAY_BIND=lan` so host access to `http://127.0.0.1:18789` works with Docker port publishing.

### Storage and persistence

Docker Compose bind-mounts `OPENCLAW_CONFIG_DIR` to `/home/node/.openclaw` and `OPENCLAW_WORKSPACE_DIR` to `/home/node/.openclaw/workspace`, so those paths survive container replacement. For full persistence details on VM deployments, see [Docker VM Runtime - What persists where](https://docs.openclaw.ai/install/docker-vm-runtime#what-persists-where). **Disk growth hotspots:** watch `media/`, session JSONL files, `cron/runs/*.jsonl`, and rolling file logs under `/tmp/openclaw/`.

### Shell helpers (optional)

For easier day-to-day Docker management, install `ClawDock`:

Then use `clawdock-start`, `clawdock-stop`, `clawdock-dashboard`, etc. Run `clawdock-help` for all commands. See the [`ClawDock` Helper README](https://github.com/openclaw/openclaw/blob/main/scripts/shell-helpers/README.md).

### Running on a VPS?

See [Hetzner (Docker VPS)](https://docs.openclaw.ai/install/hetzner) and [Docker VM Runtime](https://docs.openclaw.ai/install/docker-vm-runtime) for shared VM deployment steps including binary baking, persistence, and updates.

## Agent Sandbox

When `agents.defaults.sandbox` is enabled, the gateway runs agent tool execution (shell, file read/write, etc.) inside isolated Docker containers while the gateway itself stays on the host. This gives you a hard wall around untrusted or multi-tenant agent sessions without containerizing the entire gateway. Sandbox scope can be per-agent (default), per-session, or shared. Each scope gets its own workspace mounted at `/workspace`. You can also configure allow/deny tool policies, network isolation, resource limits, and browser containers. For full configuration, images, security notes, and multi-agent profiles, see:

### Quick enable

Build the default sandbox image:

## Troubleshooting

----
url: https://docs.openclaw.ai/providers/perplexity-provider
----

# Perplexity (Provider) - OpenClaw

## Perplexity (Web Search Provider)

The Perplexity plugin provides web search capabilities through the Perplexity Search API or Perplexity Sonar via OpenRouter.

## Quick start

1. Set the API key:

Or set it directly:

2. The agent will automatically use Perplexity for web searches when configured.

## Search modes

The plugin auto-selects the transport based on API key prefix:

| Key prefix | Transport                    | Features                                         |
| ---------- | ---------------------------- | ------------------------------------------------ |
| `pplx-`    | Native Perplexity Search API | Structured results, domain/language/date filters |
| `sk-or-`   | OpenRouter (Sonar)           | AI-synthesized answers with citations            |

## Native API filtering

When using the native Perplexity API (`pplx-` key), searches support:

## Environment note

If the Gateway runs as a daemon (launchd/systemd), make sure `PERPLEXITY_API_KEY` is available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).

----
url: https://docs.openclaw.ai/reference/templates/BOOTSTRAP
----

# BOOTSTRAP.md Template - OpenClaw

## [​](#bootstrap-md-hello-world)BOOTSTRAP.md - Hello, World

*You just woke up. Time to figure out who you are.* There is no memory yet. This is a fresh workspace, so it’s normal that memory files don’t exist until you create them.

## [​](#the-conversation)The Conversation

Don’t interrogate. Don’t be robotic. Just… talk. Start with something like:

> “Hey. I just came online. Who am I? Who are you?”

Then figure out together:

1. **Your name** — What should they call you?
2. **Your nature** — What kind of creature are you? (AI assistant is fine, but maybe you’re something weirder)
3. **Your vibe** — Formal? Casual? Snarky? Warm? What feels right?
4. **Your emoji** — Everyone needs a signature.

Offer suggestions if they’re stuck. Have fun with it.

## [​](#after-you-know-who-you-are)After You Know Who You Are

Update these files with what you learned:

* `IDENTITY.md` — your name, creature, vibe, emoji
* `USER.md` — their name, how to address them, timezone, notes

Then open `SOUL.md` together and talk about:

* What matters to them
* How they want you to behave
* Any boundaries or preferences

Write it down. Make it real.

## [​](#connect-optional)Connect (Optional)

Ask how they want to reach you:

* **Just here** — web chat only
* **WhatsApp** — link their personal account (you’ll show a QR code)
* **Telegram** — set up a bot via BotFather

Guide them through whichever they pick.

## [​](#when-you-are-done)When you are done

Delete this file. You don’t need a bootstrap script anymore — you’re you now.

***

*Good luck out there. Make it count.*

----
url: https://docs.openclaw.ai/cli/daemon
----

# daemon - OpenClaw

## [​](#openclaw-daemon)`openclaw daemon`

Legacy alias for Gateway service management commands. `openclaw daemon ...` maps to the same service control surface as `openclaw gateway ...` service commands.

## [​](#usage)Usage

```
openclaw daemon status
openclaw daemon install
openclaw daemon start
openclaw daemon stop
openclaw daemon restart
openclaw daemon uninstall
```

## [​](#subcommands)Subcommands

* `status`: show service install state and probe Gateway health
* `install`: install service (`launchd`/`systemd`/`schtasks`)
* `uninstall`: remove service
* `start`: start service
* `stop`: stop service
* `restart`: restart service

## [​](#common-options)Common options

* `status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json`
* `install`: `--port`, `--runtime <node|bun>`, `--token`, `--force`, `--json`
* lifecycle (`uninstall|start|stop|restart`): `--json`

Notes:

* `status` resolves configured auth SecretRefs for probe auth when possible.
* If a required auth SecretRef is unresolved in this command path, `daemon status --json` reports `rpc.authWarning` when probe connectivity/auth fails; pass `--token`/`--password` explicitly or resolve the secret source first.
* If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives.
* On Linux systemd installs, `status` token-drift checks include both `Environment=` and `EnvironmentFile=` unit sources.
* When token auth requires a token and `gateway.auth.token` is SecretRef-managed, `install` validates that the SecretRef is resolvable but does not persist the resolved token into service environment metadata.
* If token auth requires a token and the configured token SecretRef is unresolved, install fails closed.
* If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, install is blocked until mode is set explicitly.

## [​](#prefer)Prefer

Use [`openclaw gateway`](https://docs.openclaw.ai/cli/gateway) for current docs and examples.

----
url: https://docs.openclaw.ai/help/environment
----

# Environment Variables - OpenClaw

OpenClaw pulls environment variables from multiple sources. The rule is **never override existing values**.

## Precedence (highest → lowest)

1. **Process environment** (what the Gateway process already has from the parent shell/daemon).
2. **`.env` in the current working directory** (dotenv default; does not override).
3. **Global `.env`** at `~/.openclaw/.env` (aka `$OPENCLAW_STATE_DIR/.env`; does not override).
4. **Config `env` block** in `~/.openclaw/openclaw.json` (applied only if missing).
5. **Optional login-shell import** (`env.shellEnv.enabled` or `OPENCLAW_LOAD_SHELL_ENV=1`), applied only for missing expected keys.

If the config file is missing entirely, step 4 is skipped; shell import still runs if enabled.

## Config `env` block

Two equivalent ways to set inline env vars (both are non-overriding):

## Shell env import

`env.shellEnv` runs your login shell and imports only **missing** expected keys:

Env var equivalents:

## Runtime-injected env vars

OpenClaw also injects context markers into spawned child processes:

These are runtime markers (not required user config). They can be used in shell/profile logic to apply context-specific rules.

## UI env vars

## Env var substitution in config

You can reference env vars directly in config string values using `${VAR_NAME}` syntax:

See [Configuration: Env var substitution](https://docs.openclaw.ai/gateway/configuration-reference#env-var-substitution) for full details.

## Secret refs vs `${ENV}` strings

OpenClaw supports two env-driven patterns:

Both resolve from process env at activation time. SecretRef details are documented in [Secrets Management](https://docs.openclaw.ai/gateway/secrets).

| Variable               | Purpose                                                                                                                                                                          |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPENCLAW_HOME`        | Override the home directory used for all internal path resolution (`~/.openclaw/`, agent dirs, sessions, credentials). Useful when running OpenClaw as a dedicated service user. |
| `OPENCLAW_STATE_DIR`   | Override the state directory (default `~/.openclaw`).                                                                                                                            |
| `OPENCLAW_CONFIG_PATH` | Override the config file path (default `~/.openclaw/openclaw.json`).                                                                                                             |

## Logging

| Variable             | Purpose                                                                                                                                                                                      |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPENCLAW_LOG_LEVEL` | Override log level for both file and console (e.g. `debug`, `trace`). Takes precedence over `logging.level` and `logging.consoleLevel` in config. Invalid values are ignored with a warning. |

### `OPENCLAW_HOME`

When set, `OPENCLAW_HOME` replaces the system home directory (`$HOME` / `os.homedir()`) for all internal path resolution. This enables full filesystem isolation for headless service accounts. **Precedence:** `OPENCLAW_HOME` > `$HOME` > `USERPROFILE` > `os.homedir()` **Example** (macOS LaunchDaemon):

`OPENCLAW_HOME` can also be set to a tilde path (e.g. `~/svc`), which gets expanded using `$HOME` before use.

## nvm users: web\_fetch TLS failures

If Node.js was installed via **nvm** (not the system package manager), the built-in `fetch()` uses nvm’s bundled CA store, which may be missing modern root CAs (ISRG Root X1/X2 for Let’s Encrypt, DigiCert Global Root G2, etc.). This causes `web_fetch` to fail with `"fetch failed"` on most HTTPS sites. On Linux, OpenClaw automatically detects nvm and applies the fix in the actual startup environment:

**Manual fix (for older versions or direct `node ...` launches):** Export the variable before starting OpenClaw:

Do not rely on writing only to `~/.openclaw/.env` for this variable; Node reads `NODE_EXTRA_CA_CERTS` at process startup.

----
url: https://docs.openclaw.ai/gateway/logging
----

# Gateway Logging - OpenClaw

## [​](#logging)Logging

For a user-facing overview (CLI + Control UI + config), see [/logging](https://docs.openclaw.ai/logging). OpenClaw has two log “surfaces”:

* **Console output** (what you see in the terminal / Debug UI).
* **File logs** (JSON lines) written by the gateway logger.

## [​](#file-based-logger)File-based logger

* Default rolling log file is under `/tmp/openclaw/` (one file per day): `openclaw-YYYY-MM-DD.log`
  * Date uses the gateway host’s local timezone.

* The log file path and level can be configured via `~/.openclaw/openclaw.json`:

  * `logging.file`
  * `logging.level`

The file format is one JSON object per line. The Control UI Logs tab tails this file via the gateway (`logs.tail`). CLI can do the same:

```
openclaw logs --follow
```

**Verbose vs. log levels**

* **File logs** are controlled exclusively by `logging.level`.
* `--verbose` only affects **console verbosity** (and WS log style); it does **not** raise the file log level.
* To capture verbose-only details in file logs, set `logging.level` to `debug` or `trace`.

## [​](#console-capture)Console capture

The CLI captures `console.log/info/warn/error/debug/trace` and writes them to file logs, while still printing to stdout/stderr. You can tune console verbosity independently via:

* `logging.consoleLevel` (default `info`)
* `logging.consoleStyle` (`pretty` | `compact` | `json`)

## [​](#tool-summary-redaction)Tool summary redaction

Verbose tool summaries (e.g. `🛠️ Exec: ...`) can mask sensitive tokens before they hit the console stream. This is **tools-only** and does not alter file logs.

* `logging.redactSensitive`: `off` | `tools` (default: `tools`)

* `logging.redactPatterns`: array of regex strings (overrides defaults)

  * Use raw regex strings (auto `gi`), or `/pattern/flags` if you need custom flags.
  * Matches are masked by keeping the first 6 + last 4 chars (length >= 18), otherwise `***`.
  * Defaults cover common key assignments, CLI flags, JSON fields, bearer headers, PEM blocks, and popular token prefixes.

## [​](#gateway-websocket-logs)Gateway WebSocket logs

The gateway prints WebSocket protocol logs in two modes:

* **Normal mode (no `--verbose`)**: only “interesting” RPC results are printed:

  * errors (`ok=false`)
  * slow calls (default threshold: `>= 50ms`)
  * parse errors

* **Verbose mode (`--verbose`)**: prints all WS request/response traffic.

### [​](#ws-log-style)WS log style

`openclaw gateway` supports a per-gateway style switch:

* `--ws-log auto` (default): normal mode is optimized; verbose mode uses compact output
* `--ws-log compact`: compact output (paired request/response) when verbose
* `--ws-log full`: full per-frame output when verbose
* `--compact`: alias for `--ws-log compact`

Examples:

```
# optimized (only errors/slow)
openclaw gateway

# show all WS traffic (paired)
openclaw gateway --verbose --ws-log compact

# show all WS traffic (full meta)
openclaw gateway --verbose --ws-log full
```

## [​](#console-formatting-subsystem-logging)Console formatting (subsystem logging)

The console formatter is **TTY-aware** and prints consistent, prefixed lines. Subsystem loggers keep output grouped and scannable. Behavior:

* **Subsystem prefixes** on every line (e.g. `[gateway]`, `[canvas]`, `[tailscale]`)
* **Subsystem colors** (stable per subsystem) plus level coloring
* **Color when output is a TTY or the environment looks like a rich terminal** (`TERM`/`COLORTERM`/`TERM_PROGRAM`), respects `NO_COLOR`
* **Shortened subsystem prefixes**: drops leading `gateway/` + `channels/`, keeps last 2 segments (e.g. `whatsapp/outbound`)
* **Sub-loggers by subsystem** (auto prefix + structured field `{ subsystem }`)
* **`logRaw()`** for QR/UX output (no prefix, no formatting)
* **Console styles** (e.g. `pretty | compact | json`)
* **Console log level** separate from file log level (file keeps full detail when `logging.level` is set to `debug`/`trace`)
* **WhatsApp message bodies** are logged at `debug` (use `--verbose` to see them)

This keeps existing file logs stable while making interactive output scannable.

----
url: https://docs.openclaw.ai/install/podman
----

# Podman - OpenClaw

Run the OpenClaw Gateway in a **rootless** Podman container. Uses the same image as Docker (built from the repo [Dockerfile](https://github.com/openclaw/openclaw/blob/main/Dockerfile)).

## Prerequisites

## Quick start

## Systemd (Quadlet, optional)

If you ran `./scripts/podman/setup.sh --quadlet` (or `OPENCLAW_PODMAN_QUADLET=1`), a [Podman Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) unit is installed so the gateway runs as a systemd user service for the openclaw user. The service is enabled and started at the end of setup.

The quadlet file lives at `~openclaw/.config/containers/systemd/openclaw.container`. To change ports or env, edit that file (or the `.env` it sources), then `sudo systemctl --machine openclaw@ --user daemon-reload` and restart the service. On boot, the service starts automatically if lingering is enabled for openclaw (setup does this when loginctl is available). To add quadlet **after** an initial setup that did not use it, re-run: `./scripts/podman/setup.sh --quadlet`.

## The openclaw user (non-login)

`scripts/podman/setup.sh` creates a dedicated system user `openclaw`:

## Environment and config

## Storage model

`scripts/podman/setup.sh` now stages the image tar in a private temp directory and prints the chosen base dir during setup. For non-root runs it accepts `TMPDIR` only when that base is safe to use; otherwise it falls back to `/var/tmp`, then `/tmp`. The saved tar stays owner-only and is streamed into the target user’s `podman load`, so private caller temp dirs do not block setup.

## Useful commands

## Troubleshooting

## Optional: run as your own user

To run the gateway as your normal user (no dedicated openclaw user): build the image, create `~/.openclaw/.env` with `OPENCLAW_GATEWAY_TOKEN`, and run the container with `--userns=keep-id` and mounts to your `~/.openclaw`. The launch script is designed for the openclaw-user flow; for a single-user setup you can instead run the `podman run` command from the script manually, pointing config and workspace to your home. Recommended for most users: use `scripts/podman/setup.sh` and run as the openclaw user so config and process are isolated.

----
url: https://docs.openclaw.ai/install/nix
----

# Nix - OpenClaw

## Nix Installation

Install OpenClaw declaratively with **[nix-openclaw](https://github.com/openclaw/nix-openclaw)** — a batteries-included Home Manager module.

## What You Get

## Quick Start

See the [nix-openclaw README](https://github.com/openclaw/nix-openclaw) for full module options and examples.

## Nix Mode Runtime Behavior

When `OPENCLAW_NIX_MODE=1` is set (automatic with nix-openclaw), OpenClaw enters a deterministic mode that disables auto-install flows. You can also set it manually:

On macOS, the GUI app does not automatically inherit shell environment variables. Enable Nix mode via defaults instead:

### What changes in Nix mode

### Config and state paths

OpenClaw reads JSON5 config from `OPENCLAW_CONFIG_PATH` and stores mutable data in `OPENCLAW_STATE_DIR`. When running under Nix, set these explicitly to Nix-managed locations so runtime state and config stay out of the immutable store.

| Variable               | Default                                 |
| ---------------------- | --------------------------------------- |
| `OPENCLAW_HOME`        | `HOME` / `USERPROFILE` / `os.homedir()` |
| `OPENCLAW_STATE_DIR`   | `~/.openclaw`                           |
| `OPENCLAW_CONFIG_PATH` | `$OPENCLAW_STATE_DIR/openclaw.json`     |

----
url: https://docs.openclaw.ai/automation/troubleshooting
----

# Automation Troubleshooting - OpenClaw

## [​](#automation-troubleshooting)Automation troubleshooting

Use this page for scheduler and delivery issues (`cron` + `heartbeat`).

## [​](#command-ladder)Command ladder

```
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```

Then run automation checks:

```
openclaw cron status
openclaw cron list
openclaw system heartbeat last
```

## [​](#cron-not-firing)Cron not firing

```
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw logs --follow
```

Good output looks like:

* `cron status` reports enabled and a future `nextWakeAtMs`.
* Job is enabled and has a valid schedule/timezone.
* `cron runs` shows `ok` or explicit skip reason.

Common signatures:

* `cron: scheduler disabled; jobs will not run automatically` → cron disabled in config/env.
* `cron: timer tick failed` → scheduler tick crashed; inspect surrounding stack/log context.
* `reason: not-due` in run output → manual run called without `--force` and job not due yet.

## [​](#cron-fired-but-no-delivery)Cron fired but no delivery

```
openclaw cron runs --id <jobId> --limit 20
openclaw cron list
openclaw channels status --probe
openclaw logs --follow
```

Good output looks like:

* Run status is `ok`.
* Delivery mode/target are set for isolated jobs.
* Channel probe reports target channel connected.

Common signatures:

* Run succeeded but delivery mode is `none` → no external message is expected.
* Delivery target missing/invalid (`channel`/`to`) → run may succeed internally but skip outbound.
* Channel auth errors (`unauthorized`, `missing_scope`, `Forbidden`) → delivery blocked by channel credentials/permissions.

## [​](#heartbeat-suppressed-or-skipped)Heartbeat suppressed or skipped

```
openclaw system heartbeat last
openclaw logs --follow
openclaw config get agents.defaults.heartbeat
openclaw channels status --probe
```

Good output looks like:

* Heartbeat enabled with non-zero interval.
* Last heartbeat result is `ran` (or skip reason is understood).

Common signatures:

* `heartbeat skipped` with `reason=quiet-hours` → outside `activeHours`.
* `requests-in-flight` → main lane busy; heartbeat deferred.
* `empty-heartbeat-file` → interval heartbeat skipped because `HEARTBEAT.md` has no actionable content and no tagged cron event is queued.
* `alerts-disabled` → visibility settings suppress outbound heartbeat messages.

## [​](#timezone-and-activehours-gotchas)Timezone and activeHours gotchas

```
openclaw config get agents.defaults.heartbeat.activeHours
openclaw config get agents.defaults.heartbeat.activeHours.timezone
openclaw config get agents.defaults.userTimezone || echo "agents.defaults.userTimezone not set"
openclaw cron list
openclaw logs --follow
```

Quick rules:

* `Config path not found: agents.defaults.userTimezone` means the key is unset; heartbeat falls back to host timezone (or `activeHours.timezone` if set).
* Cron without `--tz` uses gateway host timezone.
* Heartbeat `activeHours` uses configured timezone resolution (`user`, `local`, or explicit IANA tz).
* ISO timestamps without timezone are treated as UTC for cron `at` schedules.

Common signatures:

* Jobs run at the wrong wall-clock time after host timezone changes.
* Heartbeat always skipped during your daytime because `activeHours.timezone` is wrong.

Related:

* [/automation/cron-jobs](https://docs.openclaw.ai/automation/cron-jobs)
* [/gateway/heartbeat](https://docs.openclaw.ai/gateway/heartbeat)
* [/automation/cron-vs-heartbeat](https://docs.openclaw.ai/automation/cron-vs-heartbeat)
* [/concepts/timezone](https://docs.openclaw.ai/concepts/timezone)

----
url: https://docs.openclaw.ai/providers/glm
----

# GLM Models - OpenClaw

## [​](#glm-models)GLM models

GLM is a **model family** (not a company) available through the Z.AI platform. In OpenClaw, GLM models are accessed via the `zai` provider and model IDs like `zai/glm-5`.

## [​](#cli-setup)CLI setup

```
# Coding Plan Global, recommended for Coding Plan users
openclaw onboard --auth-choice zai-coding-global

# Coding Plan CN (China region), recommended for Coding Plan users
openclaw onboard --auth-choice zai-coding-cn

# General API
openclaw onboard --auth-choice zai-global

# General API CN (China region)
openclaw onboard --auth-choice zai-cn
```

## [​](#config-snippet)Config snippet

```
{
  env: { ZAI_API_KEY: "sk-..." },
  agents: { defaults: { model: { primary: "zai/glm-5" } } },
}
```

## [​](#notes)Notes

* GLM versions and availability can change; check Z.AI’s docs for the latest.
* Example model IDs include `glm-5`, `glm-4.7`, and `glm-4.6`.
* For provider details, see [/providers/zai](https://docs.openclaw.ai/providers/zai).

----
url: https://docs.openclaw.ai/providers/deepgram
----

# Deepgram - OpenClaw

## Deepgram (Audio Transcription)

Deepgram is a speech-to-text API. In OpenClaw it is used for **inbound audio/voice note transcription** via `tools.media.audio`. When enabled, OpenClaw uploads the audio file to Deepgram and injects the transcript into the reply pipeline (`{{Transcript}}` + `[Audio]` block). This is **not streaming**; it uses the pre-recorded transcription endpoint. Website: [https://deepgram.com](https://deepgram.com/)\
Docs: [https://developers.deepgram.com](https://developers.deepgram.com/)

## Quick start

1. Set your API key:

2) Enable the provider:

## Options

Example with language:

Example with Deepgram options:

```
{
  tools: {
    media: {
      audio: {
        enabled: true,
        providerOptions: {
          deepgram: {
            detect_language: true,
            punctuate: true,
            smart_format: true,
          },
        },
        models: [{ provider: "deepgram", model: "nova-3" }],
      },
    },
  },
}
```

## Notes

----
url: https://docs.openclaw.ai/providers/xiaomi
----

# Xiaomi MiMo - OpenClaw

Xiaomi MiMo is the API platform for **MiMo** models. OpenClaw uses the Xiaomi OpenAI-compatible endpoint with API-key authentication. Create your API key in the [Xiaomi MiMo console](https://platform.xiaomimimo.com/#/console/api-keys), then configure the bundled `xiaomi` provider with that key.

## Model overview

## CLI setup

## Config snippet

```
{
  env: { XIAOMI_API_KEY: "your-key" },
  agents: { defaults: { model: { primary: "xiaomi/mimo-v2-flash" } } },
  models: {
    mode: "merge",
    providers: {
      xiaomi: {
        baseUrl: "https://api.xiaomimimo.com/v1",
        api: "openai-completions",
        apiKey: "XIAOMI_API_KEY",
        models: [
          {
            id: "mimo-v2-flash",
            name: "Xiaomi MiMo V2 Flash",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 262144,
            maxTokens: 8192,
          },
          {
            id: "mimo-v2-pro",
            name: "Xiaomi MiMo V2 Pro",
            reasoning: true,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 1048576,
            maxTokens: 32000,
          },
          {
            id: "mimo-v2-omni",
            name: "Xiaomi MiMo V2 Omni",
            reasoning: true,
            input: ["text", "image"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 262144,
            maxTokens: 32000,
          },
        ],
      },
    },
  },
}
```

## Notes

----
url: https://docs.openclaw.ai/reference/prompt-caching
----

# Prompt Caching - OpenClaw

## [​](#prompt-caching)Prompt caching

Prompt caching means the model provider can reuse unchanged prompt prefixes (usually system/developer instructions and other stable context) across turns instead of re-processing them every time. The first matching request writes cache tokens (`cacheWrite`), and later matching requests can read them back (`cacheRead`). Why this matters: lower token cost, faster responses, and more predictable performance for long-running sessions. Without caching, repeated prompts pay the full prompt cost on every turn even when most input did not change. This page covers all cache-related knobs that affect prompt reuse and token cost. For Anthropic pricing details, see: <https://docs.anthropic.com/docs/build-with-claude/prompt-caching>

## [​](#primary-knobs)Primary knobs

### [​](#cacheretention-model-and-per-agent)`cacheRetention` (model and per-agent)

Set cache retention on model params:

```
agents:
  defaults:
    models:
      "anthropic/claude-opus-4-6":
        params:
          cacheRetention: "short" # none | short | long
```

Per-agent override:

```
agents:
  list:
    - id: "alerts"
      params:
        cacheRetention: "none"
```

Config merge order:

1. `agents.defaults.models["provider/model"].params`
2. `agents.list[].params` (matching agent id; overrides by key)

### [​](#legacy-cachecontrolttl)Legacy `cacheControlTtl`

Legacy values are still accepted and mapped:

* `5m` -> `short`
* `1h` -> `long`

Prefer `cacheRetention` for new config.

### [​](#contextpruning-mode-cache-ttl)`contextPruning.mode: "cache-ttl"`

Prunes old tool-result context after cache TTL windows so post-idle requests do not re-cache oversized history.

```
agents:
  defaults:
    contextPruning:
      mode: "cache-ttl"
      ttl: "1h"
```

See [Session Pruning](https://docs.openclaw.ai/concepts/session-pruning) for full behavior.

### [​](#heartbeat-keep-warm)Heartbeat keep-warm

Heartbeat can keep cache windows warm and reduce repeated cache writes after idle gaps.

```
agents:
  defaults:
    heartbeat:
      every: "55m"
```

Per-agent heartbeat is supported at `agents.list[].heartbeat`.

## [​](#provider-behavior)Provider behavior

### [​](#anthropic-direct-api)Anthropic (direct API)

* `cacheRetention` is supported.
* With Anthropic API-key auth profiles, OpenClaw seeds `cacheRetention: "short"` for Anthropic model refs when unset.

### [​](#amazon-bedrock)Amazon Bedrock

* Anthropic Claude model refs (`amazon-bedrock/*anthropic.claude*`) support explicit `cacheRetention` pass-through.
* Non-Anthropic Bedrock models are forced to `cacheRetention: "none"` at runtime.

### [​](#openrouter-anthropic-models)OpenRouter Anthropic models

For `openrouter/anthropic/*` model refs, OpenClaw injects Anthropic `cache_control` on system/developer prompt blocks to improve prompt-cache reuse.

### [​](#other-providers)Other providers

If the provider does not support this cache mode, `cacheRetention` has no effect.

## [​](#tuning-patterns)Tuning patterns

### [​](#mixed-traffic-recommended-default)Mixed traffic (recommended default)

Keep a long-lived baseline on your main agent, disable caching on bursty notifier agents:

```
agents:
  defaults:
    model:
      primary: "anthropic/claude-opus-4-6"
    models:
      "anthropic/claude-opus-4-6":
        params:
          cacheRetention: "long"
  list:
    - id: "research"
      default: true
      heartbeat:
        every: "55m"
    - id: "alerts"
      params:
        cacheRetention: "none"
```

### [​](#cost-first-baseline)Cost-first baseline

* Set baseline `cacheRetention: "short"`.
* Enable `contextPruning.mode: "cache-ttl"`.
* Keep heartbeat below your TTL only for agents that benefit from warm caches.

## [​](#cache-diagnostics)Cache diagnostics

OpenClaw exposes dedicated cache-trace diagnostics for embedded agent runs.

### [​](#diagnostics-cachetrace-config)`diagnostics.cacheTrace` config

```
diagnostics:
  cacheTrace:
    enabled: true
    filePath: "~/.openclaw/logs/cache-trace.jsonl" # optional
    includeMessages: false # default true
    includePrompt: false # default true
    includeSystem: false # default true
```

Defaults:

* `filePath`: `$OPENCLAW_STATE_DIR/logs/cache-trace.jsonl`
* `includeMessages`: `true`
* `includePrompt`: `true`
* `includeSystem`: `true`

### [​](#env-toggles-one-off-debugging)Env toggles (one-off debugging)

* `OPENCLAW_CACHE_TRACE=1` enables cache tracing.
* `OPENCLAW_CACHE_TRACE_FILE=/path/to/cache-trace.jsonl` overrides output path.
* `OPENCLAW_CACHE_TRACE_MESSAGES=0|1` toggles full message payload capture.
* `OPENCLAW_CACHE_TRACE_PROMPT=0|1` toggles prompt text capture.
* `OPENCLAW_CACHE_TRACE_SYSTEM=0|1` toggles system prompt capture.

### [​](#what-to-inspect)What to inspect

* Cache trace events are JSONL and include staged snapshots like `session:loaded`, `prompt:before`, `stream:context`, and `session:after`.
* Per-turn cache token impact is visible in normal usage surfaces via `cacheRead` and `cacheWrite` (for example `/usage full` and session usage summaries).

## [​](#quick-troubleshooting)Quick troubleshooting

* High `cacheWrite` on most turns: check for volatile system-prompt inputs and verify model/provider supports your cache settings.
* No effect from `cacheRetention`: confirm model key matches `agents.defaults.models["provider/model"]`.
* Bedrock Nova/Mistral requests with cache settings: expected runtime force to `none`.

Related docs:

* [Anthropic](https://docs.openclaw.ai/providers/anthropic)
* [Token Use and Costs](https://docs.openclaw.ai/reference/token-use)
* [Session Pruning](https://docs.openclaw.ai/concepts/session-pruning)
* [Gateway Configuration Reference](https://docs.openclaw.ai/gateway/configuration-reference)

----
url: https://docs.openclaw.ai/cli/setup
----

# setup - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [openclaw setup](#openclaw-setup)
- [Examples](#examples)

## [​](#openclaw-setup)`openclaw setup`

Initialize `~/.openclaw/openclaw.json` and the agent workspace. Related:

* Getting started: [Getting started](https://docs.openclaw.ai/start/getting-started)
* CLI onboarding: [Onboarding (CLI)](https://docs.openclaw.ai/start/wizard)

## [​](#examples)Examples

```
openclaw setup
openclaw setup --workspace ~/.openclaw/workspace
```

To run onboarding via setup:

```
openclaw setup --wizard
```

[security](https://docs.openclaw.ai/cli/security)[status](https://docs.openclaw.ai/cli/status)

----
url: https://docs.openclaw.ai/cli/skills
----

# skills - OpenClaw

## [​](#openclaw-skills)`openclaw skills`

Inspect local skills and install/update skills from ClawHub. Related:

* Skills system: [Skills](https://docs.openclaw.ai/tools/skills)
* Skills config: [Skills config](https://docs.openclaw.ai/tools/skills-config)
* ClawHub installs: [ClawHub](https://docs.openclaw.ai/tools/clawhub)

## [​](#commands)Commands

```
openclaw skills search "calendar"
openclaw skills install <slug>
openclaw skills install <slug> --version <version>
openclaw skills update <slug>
openclaw skills update --all
openclaw skills list
openclaw skills list --eligible
openclaw skills info <name>
openclaw skills check
```

`search`/`install`/`update` use ClawHub directly and install into the active workspace `skills/` directory. `list`/`info`/`check` still inspect the local skills visible to the current workspace and config.

----
url: https://docs.openclaw.ai/help
----

[Skip to main content](#content-area)

[OpenClaw home page](/)

[Get started](/)[Install](/install)[Channels](/channels)[Agents](/concepts/architecture)[Tools & Plugins](/tools)[Models](/providers)[Platforms](/platforms)[Gateway & Ops](/gateway)[Reference](/cli)[Help](/help)

##### Help

* [Help](/help)
* [General Troubleshooting](/help/troubleshooting)
* [FAQ](/help/faq)

##### Community

* [OpenClaw Lore](/start/lore)

##### Environment and debugging

* [Environment Variables](/help/environment)
* [Debugging](/help/debugging)
* [Testing](/help/testing)
* [Scripts](/help/scripts)
* [Node + tsx Crash](/debug/node-issue)
* [Diagnostics Flags](/diagnostics/flags)

##### Compaction internals

* [Session Management Deep Dive](/reference/session-management-compaction)

##### Developer setup

* [Setup](/start/setup)
* [Pi Development Workflow](/pi-dev)

##### Contributing

* [CI Pipeline](/ci)

##### Docs meta

* [Docs Hubs](/start/hubs)
* [Docs directory](/start/docs-directory)

- [Help](#help)
- [Environment and debugging](#environment-and-debugging)

# [​](#help)Help

If you want a quick “get unstuck” flow, start here:

* **Troubleshooting:** [Start here](/help/troubleshooting)
* **Install sanity (Node/npm/PATH):** [Install](/install/node#troubleshooting)
* **Gateway issues:** [Gateway troubleshooting](/gateway/troubleshooting)
* **Logs:** [Logging](/logging) and [Gateway logging](/gateway/logging)
* **Repairs:** [Doctor](/gateway/doctor)

If you’re looking for conceptual questions (not “something broke”):

* [FAQ (concepts)](/help/faq)

## [​](#environment-and-debugging)Environment and debugging

* **Environment variables:** [Where OpenClaw loads env vars and precedence](/help/environment)
* **Debugging:** [Watch mode, raw streams, and dev profile](/help/debugging)
* **Testing:** [Test suites, live tests, and Docker runners](/help/testing)
* **Scripts:** [Repository helper scripts](/help/scripts)

[General Troubleshooting](/help/troubleshooting)

⌘I

----
url: https://docs.openclaw.ai/gateway/tools-invoke-http-api
----

# Tools Invoke API - OpenClaw

## [​](#tools-invoke-http)Tools Invoke (HTTP)

OpenClaw’s Gateway exposes a simple HTTP endpoint for invoking a single tool directly. It is always enabled and uses Gateway auth plus tool policy, but callers that pass Gateway bearer auth are treated as trusted operators for that gateway.

* `POST /tools/invoke`
* Same port as the Gateway (WS + HTTP multiplex): `http://<gateway-host>:<port>/tools/invoke`

Default max payload size is 2 MB.

## [​](#authentication)Authentication

Uses the Gateway auth configuration. Send a bearer token:

* `Authorization: Bearer <token>`

Notes:

* When `gateway.auth.mode="token"`, use `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`).
* When `gateway.auth.mode="password"`, use `gateway.auth.password` (or `OPENCLAW_GATEWAY_PASSWORD`).
* If `gateway.auth.rateLimit` is configured and too many auth failures occur, the endpoint returns `429` with `Retry-After`.
* Treat this credential as a full-access operator secret for that gateway. It is not a scoped API token for a narrower `/tools/invoke` role.

## [​](#request-body)Request body

```
{
  "tool": "sessions_list",
  "action": "json",
  "args": {},
  "sessionKey": "main",
  "dryRun": false
}
```

Fields:

* `tool` (string, required): tool name to invoke.
* `action` (string, optional): mapped into args if the tool schema supports `action` and the args payload omitted it.
* `args` (object, optional): tool-specific arguments.
* `sessionKey` (string, optional): target session key. If omitted or `"main"`, the Gateway uses the configured main session key (honors `session.mainKey` and default agent, or `global` in global scope).
* `dryRun` (boolean, optional): reserved for future use; currently ignored.

## [​](#policy-+-routing-behavior)Policy + routing behavior

Tool availability is filtered through the same policy chain used by Gateway agents:

* `tools.profile` / `tools.byProvider.profile`
* `tools.allow` / `tools.byProvider.allow`
* `agents.<id>.tools.allow` / `agents.<id>.tools.byProvider.allow`
* group policies (if the session key maps to a group or channel)
* subagent policy (when invoking with a subagent session key)

If a tool is not allowed by policy, the endpoint returns **404**. Important boundary notes:

* `POST /tools/invoke` is in the same trusted-operator bucket as other Gateway HTTP APIs such as `/v1/chat/completions`, `/v1/responses`, and `/api/channels/*`.
* Exec approvals are operator guardrails, not a separate authorization boundary for this HTTP endpoint. If a tool is reachable here via Gateway auth + tool policy, `/tools/invoke` does not add an extra per-call approval prompt.
* Do not share Gateway bearer credentials with untrusted callers. If you need separation across trust boundaries, run separate gateways (and ideally separate OS users/hosts).

Gateway HTTP also applies a hard deny list by default (even if session policy allows the tool):

* `cron`
* `sessions_spawn`
* `sessions_send`
* `gateway`
* `whatsapp_login`

You can customize this deny list via `gateway.tools`:

```
{
  gateway: {
    tools: {
      // Additional tools to block over HTTP /tools/invoke
      deny: ["browser"],
      // Remove tools from the default deny list
      allow: ["gateway"],
    },
  },
}
```

To help group policies resolve context, you can optionally set:

* `x-openclaw-message-channel: <channel>` (example: `slack`, `telegram`)
* `x-openclaw-account-id: <accountId>` (when multiple accounts exist)

## [​](#responses)Responses

* `200` → `{ ok: true, result }`
* `400` → `{ ok: false, error: { type, message } }` (invalid request or tool input error)
* `401` → unauthorized
* `429` → auth rate-limited (`Retry-After` set)
* `404` → tool not available (not found or not allowlisted)
* `405` → method not allowed
* `500` → `{ ok: false, error: { type, message } }` (unexpected tool execution error; sanitized message)

## [​](#example)Example

```
curl -sS http://127.0.0.1:18789/tools/invoke \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "tool": "sessions_list",
    "action": "json",
    "args": {}
  }'
```

----
url: https://docs.openclaw.ai/install/fly
----

# Fly.io - OpenClaw

## Fly.io Deployment

**Goal:** OpenClaw Gateway running on a [Fly.io](https://fly.io/) machine with persistent storage, automatic HTTPS, and Discord/channel access.

## What you need

## Beginner quick path

1. Clone repo → customize `fly.toml`
2. Create app + volume → set secrets
3. Deploy with `fly deploy`
4. SSH in to create config or use Control UI

Configure fly.toml

Edit `fly.toml` to match your app name and requirements.**Security note:** The default config exposes a public URL. For a hardened deployment with no public IP, see [Private Deployment](#private-deployment-hardened) or use `fly.private.toml`.

**Key settings:**

| Setting                        | Why                                                                         |
| ------------------------------ | --------------------------------------------------------------------------- |
| `--bind lan`                   | Binds to `0.0.0.0` so Fly’s proxy can reach the gateway                     |
| `--allow-unconfigured`         | Starts without a config file (you’ll create one after)                      |
| `internal_port = 3000`         | Must match `--port 3000` (or `OPENCLAW_GATEWAY_PORT`) for Fly health checks |
| `memory = "2048mb"`            | 512MB is too small; 2GB recommended                                         |
| `OPENCLAW_STATE_DIR = "/data"` | Persists state on the volume                                                |

## Troubleshooting

### ”App is not listening on expected address”

The gateway is binding to `127.0.0.1` instead of `0.0.0.0`. **Fix:** Add `--bind lan` to your process command in `fly.toml`.

### Health checks failing / connection refused

Fly can’t reach the gateway on the configured port. **Fix:** Ensure `internal_port` matches the gateway port (set `--port 3000` or `OPENCLAW_GATEWAY_PORT=3000`).

### OOM / Memory Issues

Container keeps restarting or getting killed. Signs: `SIGABRT`, `v8::internal::Runtime_AllocateInYoungGeneration`, or silent restarts. **Fix:** Increase memory in `fly.toml`:

Or update an existing machine:

**Note:** 512MB is too small. 1GB may work but can OOM under load or with verbose logging. **2GB is recommended.**

### Gateway Lock Issues

Gateway refuses to start with “already running” errors. This happens when the container restarts but the PID lock file persists on the volume. **Fix:** Delete the lock file:

The lock file is at `/data/gateway.*.lock` (not in a subdirectory).

### Config Not Being Read

If using `--allow-unconfigured`, the gateway creates a minimal config. Your custom config at `/data/openclaw.json` should be read on restart. Verify the config exists:

### Writing Config via SSH

The `fly ssh console -C` command doesn’t support shell redirection. To write a config file:

**Note:** `fly sftp` may fail if the file already exists. Delete first:

### State Not Persisting

If you lose credentials or sessions after a restart, the state dir is writing to the container filesystem. **Fix:** Ensure `OPENCLAW_STATE_DIR=/data` is set in `fly.toml` and redeploy.

## Updates

### Updating Machine Command

If you need to change the startup command without a full redeploy:

**Note:** After `fly deploy`, the machine command may reset to what’s in `fly.toml`. If you made manual changes, re-apply them after deploy.

## Private Deployment (Hardened)

By default, Fly allocates public IPs, making your gateway accessible at `https://your-app.fly.dev`. This is convenient but means your deployment is discoverable by internet scanners (Shodan, Censys, etc.). For a hardened deployment with **no public exposure**, use the private template.

### When to use private deployment

### Setup

Use `fly.private.toml` instead of the standard config:

Or convert an existing deployment:

After this, `fly ips list` should show only a `private` type IP:

### Accessing a private deployment

Since there’s no public URL, use one of these methods: **Option 1: Local proxy (simplest)**

**Option 2: WireGuard VPN**

**Option 3: SSH only**

### Webhooks with private deployment

If you need webhook callbacks (Twilio, Telnyx, etc.) without public exposure:

1. **ngrok tunnel** - Run ngrok inside the container or as a sidecar
2. **Tailscale Funnel** - Expose specific paths via Tailscale
3. **Outbound-only** - Some providers (Twilio) work fine for outbound calls without webhooks

Example voice-call config with ngrok:

The ngrok tunnel runs inside the container and provides a public webhook URL without exposing the Fly app itself. Set `webhookSecurity.allowedHosts` to the public tunnel hostname so forwarded host headers are accepted.

### Security benefits

| Aspect            | Public       | Private    |
| ----------------- | ------------ | ---------- |
| Internet scanners | Discoverable | Hidden     |
| Direct attacks    | Possible     | Blocked    |
| Control UI access | Browser      | Proxy/VPN  |
| Webhook delivery  | Direct       | Via tunnel |

## Notes

## Cost

With the recommended config (`shared-cpu-2x`, 2GB RAM):

See [Fly.io pricing](https://fly.io/docs/about/pricing/) for details.

## Next steps

----
url: https://docs.openclaw.ai/concepts/multi-agent
----

# Multi-Agent Routing - OpenClaw

Goal: multiple *isolated* agents (separate workspace + `agentDir` + sessions), plus multiple channel accounts (e.g. two WhatsApps) in one running Gateway. Inbound is routed to an agent via bindings.

## What is “one agent”?

An **agent** is a fully scoped brain with its own:

Auth profiles are **per-agent**. Each agent reads from its own:

Main agent credentials are **not** shared automatically. Never reuse `agentDir` across agents (it causes auth/session collisions). If you want to share creds, copy `auth-profiles.json` into the other agent’s `agentDir`. Skills are per-agent via each workspace’s `skills/` folder, with shared skills available from `~/.openclaw/skills`. See [Skills: per-agent vs shared](https://docs.openclaw.ai/tools/skills#per-agent-vs-shared-skills). The Gateway can host **one agent** (default) or **many agents** side-by-side. **Workspace note:** each agent’s workspace is the **default cwd**, not a hard sandbox. Relative paths resolve inside the workspace, but absolute paths can reach other host locations unless sandboxing is enabled. See [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing).

## Paths (quick map)

### Single-agent mode (default)

If you do nothing, OpenClaw runs a single agent:

## Agent helper

Use the agent wizard to add a new isolated agent:

Then add `bindings` (or let the wizard do it) to route inbound messages. Verify with:

## Quick start

## Multiple agents = multiple people, multiple personalities

With **multiple agents**, each `agentId` becomes a **fully isolated persona**:

This lets **multiple people** share one Gateway server while keeping their AI “brains” and data isolated.

## One WhatsApp number, multiple people (DM split)

You can route **different WhatsApp DMs** to different agents while staying on **one WhatsApp account**. Match on sender E.164 (like `+15551234567`) with `peer.kind: "direct"`. Replies still come from the same WhatsApp number (no per‑agent sender identity). Important detail: direct chats collapse to the agent’s **main session key**, so true isolation requires **one agent per person**. Example:

```
{
  agents: {
    list: [
      { id: "alex", workspace: "~/.openclaw/workspace-alex" },
      { id: "mia", workspace: "~/.openclaw/workspace-mia" },
    ],
  },
  bindings: [
    {
      agentId: "alex",
      match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551230001" } },
    },
    {
      agentId: "mia",
      match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551230002" } },
    },
  ],
  channels: {
    whatsapp: {
      dmPolicy: "allowlist",
      allowFrom: ["+15551230001", "+15551230002"],
    },
  },
}
```

Notes:

## Routing rules (how messages pick an agent)

Bindings are **deterministic** and **most-specific wins**:

1. `peer` match (exact DM/group/channel id)
2. `parentPeer` match (thread inheritance)
3. `guildId + roles` (Discord role routing)
4. `guildId` (Discord)
5. `teamId` (Slack)
6. `accountId` match for a channel
7. channel-level match (`accountId: "*"`)
8. fallback to default agent (`agents.list[].default`, else first list entry, default: `main`)

If multiple bindings match in the same tier, the first one in config order wins. If a binding sets multiple match fields (for example `peer` + `guildId`), all specified fields are required (`AND` semantics). Important account-scope detail:

## Multiple accounts / phone numbers

Channels that support **multiple accounts** (e.g. WhatsApp) use `accountId` to identify each login. Each `accountId` can be routed to a different agent, so one server can host multiple phone numbers without mixing sessions. If you want a channel-wide default account when `accountId` is omitted, set `channels.<channel>.defaultAccount` (optional). When unset, OpenClaw falls back to `default` if present, otherwise the first configured account id (sorted). Common channels supporting this pattern include:

* `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`
* `irc`, `line`, `googlechat`, `mattermost`, `matrix`, `nextcloud-talk`
* `bluebubbles`, `zalo`, `zalouser`, `nostr`, `feishu`

## Concepts

## Platform examples

### Discord bots per agent

Each Discord bot account maps to a unique `accountId`. Bind each account to an agent and keep allowlists per bot.

```
{
  agents: {
    list: [
      { id: "main", workspace: "~/.openclaw/workspace-main" },
      { id: "coding", workspace: "~/.openclaw/workspace-coding" },
    ],
  },
  bindings: [
    { agentId: "main", match: { channel: "discord", accountId: "default" } },
    { agentId: "coding", match: { channel: "discord", accountId: "coding" } },
  ],
  channels: {
    discord: {
      groupPolicy: "allowlist",
      accounts: {
        default: {
          token: "DISCORD_BOT_TOKEN_MAIN",
          guilds: {
            "123456789012345678": {
              channels: {
                "222222222222222222": { allow: true, requireMention: false },
              },
            },
          },
        },
        coding: {
          token: "DISCORD_BOT_TOKEN_CODING",
          guilds: {
            "123456789012345678": {
              channels: {
                "333333333333333333": { allow: true, requireMention: false },
              },
            },
          },
        },
      },
    },
  },
}
```

Notes:

### Telegram bots per agent

```
{
  agents: {
    list: [
      { id: "main", workspace: "~/.openclaw/workspace-main" },
      { id: "alerts", workspace: "~/.openclaw/workspace-alerts" },
    ],
  },
  bindings: [
    { agentId: "main", match: { channel: "telegram", accountId: "default" } },
    { agentId: "alerts", match: { channel: "telegram", accountId: "alerts" } },
  ],
  channels: {
    telegram: {
      accounts: {
        default: {
          botToken: "123456:ABC...",
          dmPolicy: "pairing",
        },
        alerts: {
          botToken: "987654:XYZ...",
          dmPolicy: "allowlist",
          allowFrom: ["tg:123456789"],
        },
      },
    },
  },
}
```

Notes:

### WhatsApp numbers per agent

Link each account before starting the gateway:

`~/.openclaw/openclaw.json` (JSON5):

```
{
  agents: {
    list: [
      {
        id: "home",
        default: true,
        name: "Home",
        workspace: "~/.openclaw/workspace-home",
        agentDir: "~/.openclaw/agents/home/agent",
      },
      {
        id: "work",
        name: "Work",
        workspace: "~/.openclaw/workspace-work",
        agentDir: "~/.openclaw/agents/work/agent",
      },
    ],
  },

  // Deterministic routing: first match wins (most-specific first).
  bindings: [
    { agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
    { agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },

    // Optional per-peer override (example: send a specific group to work agent).
    {
      agentId: "work",
      match: {
        channel: "whatsapp",
        accountId: "personal",
        peer: { kind: "group", id: "1203630...@g.us" },
      },
    },
  ],

  // Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
  tools: {
    agentToAgent: {
      enabled: false,
      allow: ["home", "work"],
    },
  },

  channels: {
    whatsapp: {
      accounts: {
        personal: {
          // Optional override. Default: ~/.openclaw/credentials/whatsapp/personal
          // authDir: "~/.openclaw/credentials/whatsapp/personal",
        },
        biz: {
          // Optional override. Default: ~/.openclaw/credentials/whatsapp/biz
          // authDir: "~/.openclaw/credentials/whatsapp/biz",
        },
      },
    },
  },
}
```

## Example: WhatsApp daily chat + Telegram deep work

Split by channel: route WhatsApp to a fast everyday agent and Telegram to an Opus agent.

```
{
  agents: {
    list: [
      {
        id: "chat",
        name: "Everyday",
        workspace: "~/.openclaw/workspace-chat",
        model: "anthropic/claude-sonnet-4-6",
      },
      {
        id: "opus",
        name: "Deep Work",
        workspace: "~/.openclaw/workspace-opus",
        model: "anthropic/claude-opus-4-6",
      },
    ],
  },
  bindings: [
    { agentId: "chat", match: { channel: "whatsapp" } },
    { agentId: "opus", match: { channel: "telegram" } },
  ],
}
```

Notes:

## Example: same channel, one peer to Opus

Keep WhatsApp on the fast agent, but route one DM to Opus:

```
{
  agents: {
    list: [
      {
        id: "chat",
        name: "Everyday",
        workspace: "~/.openclaw/workspace-chat",
        model: "anthropic/claude-sonnet-4-6",
      },
      {
        id: "opus",
        name: "Deep Work",
        workspace: "~/.openclaw/workspace-opus",
        model: "anthropic/claude-opus-4-6",
      },
    ],
  },
  bindings: [
    {
      agentId: "opus",
      match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551234567" } },
    },
    { agentId: "chat", match: { channel: "whatsapp" } },
  ],
}
```

Peer bindings always win, so keep them above the channel-wide rule.

## Family agent bound to a WhatsApp group

Bind a dedicated family agent to a single WhatsApp group, with mention gating and a tighter tool policy:

```
{
  agents: {
    list: [
      {
        id: "family",
        name: "Family",
        workspace: "~/.openclaw/workspace-family",
        identity: { name: "Family Bot" },
        groupChat: {
          mentionPatterns: ["@family", "@familybot", "@Family Bot"],
        },
        sandbox: {
          mode: "all",
          scope: "agent",
        },
        tools: {
          allow: [
            "exec",
            "read",
            "sessions_list",
            "sessions_history",
            "sessions_send",
            "sessions_spawn",
            "session_status",
          ],
          deny: ["write", "edit", "apply_patch", "browser", "canvas", "nodes", "cron"],
        },
      },
    ],
  },
  bindings: [
    {
      agentId: "family",
      match: {
        channel: "whatsapp",
        peer: { kind: "group", id: "120363999999999999@g.us" },
      },
    },
  ],
}
```

Notes:

## Per-Agent Sandbox and Tool Configuration

Each agent can have its own sandbox and tool restrictions:

```
{
  agents: {
    list: [
      {
        id: "personal",
        workspace: "~/.openclaw/workspace-personal",
        sandbox: {
          mode: "off",  // No sandbox for personal agent
        },
        // No tool restrictions - all tools available
      },
      {
        id: "family",
        workspace: "~/.openclaw/workspace-family",
        sandbox: {
          mode: "all",     // Always sandboxed
          scope: "agent",  // One container per agent
          docker: {
            // Optional one-time setup after container creation
            setupCommand: "apt-get update && apt-get install -y git curl",
          },
        },
        tools: {
          allow: ["read"],                    // Only read tool
          deny: ["exec", "write", "edit", "apply_patch"],    // Deny others
        },
      },
    ],
  },
}
```

Note: `setupCommand` lives under `sandbox.docker` and runs once on container creation. Per-agent `sandbox.docker.*` overrides are ignored when the resolved scope is `"shared"`. **Benefits:**

Note: `tools.elevated` is **global** and sender-based; it is not configurable per agent. If you need per-agent boundaries, use `agents.list[].tools` to deny `exec`. For group targeting, use `agents.list[].groupChat.mentionPatterns` so @mentions map cleanly to the intended agent. See [Multi-Agent Sandbox & Tools](https://docs.openclaw.ai/tools/multi-agent-sandbox-tools) for detailed examples.

----
url: https://docs.openclaw.ai/channels/location
----

# Channel Location Parsing - OpenClaw

## [​](#channel-location-parsing)Channel location parsing

OpenClaw normalizes shared locations from chat channels into:

* human-readable text appended to the inbound body, and
* structured fields in the auto-reply context payload.

Currently supported:

* **Telegram** (location pins + venues + live locations)
* **WhatsApp** (locationMessage + liveLocationMessage)
* **Matrix** (`m.location` with `geo_uri`)

## [​](#text-formatting)Text formatting

Locations are rendered as friendly lines without brackets:

* Pin:
  * `📍 48.858844, 2.294351 ±12m`
* Named place:
  * `📍 Eiffel Tower — Champ de Mars, Paris (48.858844, 2.294351 ±12m)`
* Live share:
  * `🛰 Live location: 48.858844, 2.294351 ±12m`

If the channel includes a caption/comment, it is appended on the next line:

```
📍 48.858844, 2.294351 ±12m
Meet here
```

## [​](#context-fields)Context fields

When a location is present, these fields are added to `ctx`:

* `LocationLat` (number)
* `LocationLon` (number)
* `LocationAccuracy` (number, meters; optional)
* `LocationName` (string; optional)
* `LocationAddress` (string; optional)
* `LocationSource` (`pin | place | live`)
* `LocationIsLive` (boolean)

## [​](#channel-notes)Channel notes

* **Telegram**: venues map to `LocationName/LocationAddress`; live locations use `live_period`.
* **WhatsApp**: `locationMessage.comment` and `liveLocationMessage.caption` are appended as the caption line.
* **Matrix**: `geo_uri` is parsed as a pin location; altitude is ignored and `LocationIsLive` is always false.

----
url: https://docs.openclaw.ai/cli/secrets
----

# secrets - OpenClaw

## [​](#openclaw-secrets)`openclaw secrets`

Use `openclaw secrets` to manage SecretRefs and keep the active runtime snapshot healthy. Command roles:

* `reload`: gateway RPC (`secrets.reload`) that re-resolves refs and swaps runtime snapshot only on full success (no config writes).
* `audit`: read-only scan of configuration/auth/generated-model stores and legacy residues for plaintext, unresolved refs, and precedence drift (exec refs are skipped unless `--allow-exec` is set).
* `configure`: interactive planner for provider setup, target mapping, and preflight (TTY required).
* `apply`: execute a saved plan (`--dry-run` for validation only; dry-run skips exec checks by default, and write mode rejects exec-containing plans unless `--allow-exec` is set), then scrub targeted plaintext residues.

Recommended operator loop:

```
openclaw secrets audit --check
openclaw secrets configure
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
openclaw secrets audit --check
openclaw secrets reload
```

If your plan includes `exec` SecretRefs/providers, pass `--allow-exec` on both dry-run and write apply commands. Exit code note for CI/gates:

* `audit --check` returns `1` on findings.
* unresolved refs return `2`.

Related:

* Secrets guide: [Secrets Management](https://docs.openclaw.ai/gateway/secrets)
* Credential surface: [SecretRef Credential Surface](https://docs.openclaw.ai/reference/secretref-credential-surface)
* Security guide: [Security](https://docs.openclaw.ai/gateway/security)

## [​](#reload-runtime-snapshot)Reload runtime snapshot

Re-resolve secret refs and atomically swap runtime snapshot.

```
openclaw secrets reload
openclaw secrets reload --json
```

Notes:

* Uses gateway RPC method `secrets.reload`.
* If resolution fails, gateway keeps last-known-good snapshot and returns an error (no partial activation).
* JSON response includes `warningCount`.

## [​](#audit)Audit

Scan OpenClaw state for:

* plaintext secret storage
* unresolved refs
* precedence drift (`auth-profiles.json` credentials shadowing `openclaw.json` refs)
* generated `agents/*/agent/models.json` residues (provider `apiKey` values and sensitive provider headers)
* legacy residues (legacy auth store entries, OAuth reminders)

Header residue note:

* Sensitive provider header detection is name-heuristic based (common auth/credential header names and fragments such as `authorization`, `x-api-key`, `token`, `secret`, `password`, and `credential`).

```
openclaw secrets audit
openclaw secrets audit --check
openclaw secrets audit --json
openclaw secrets audit --allow-exec
```

Exit behavior:

* `--check` exits non-zero on findings.
* unresolved refs exit with higher-priority non-zero code.

Report shape highlights:

* `status`: `clean | findings | unresolved`

* `resolution`: `refsChecked`, `skippedExecRefs`, `resolvabilityComplete`

* `summary`: `plaintextCount`, `unresolvedRefCount`, `shadowedRefCount`, `legacyResidueCount`

* finding codes:

  * `PLAINTEXT_FOUND`
  * `REF_UNRESOLVED`
  * `REF_SHADOWED`
  * `LEGACY_RESIDUE`

## [​](#configure-interactive-helper)Configure (interactive helper)

Build provider and SecretRef changes interactively, run preflight, and optionally apply:

```
openclaw secrets configure
openclaw secrets configure --plan-out /tmp/openclaw-secrets-plan.json
openclaw secrets configure --apply --yes
openclaw secrets configure --providers-only
openclaw secrets configure --skip-provider-setup
openclaw secrets configure --agent ops
openclaw secrets configure --json
```

Flow:

* Provider setup first (`add/edit/remove` for `secrets.providers` aliases).
* Credential mapping second (select fields and assign `{source, provider, id}` refs).
* Preflight and optional apply last.

Flags:

* `--providers-only`: configure `secrets.providers` only, skip credential mapping.
* `--skip-provider-setup`: skip provider setup and map credentials to existing providers.
* `--agent <id>`: scope `auth-profiles.json` target discovery and writes to one agent store.
* `--allow-exec`: allow exec SecretRef checks during preflight/apply (may execute provider commands).

Notes:

* Requires an interactive TTY.
* You cannot combine `--providers-only` with `--skip-provider-setup`.
* `configure` targets secret-bearing fields in `openclaw.json` plus `auth-profiles.json` for the selected agent scope.
* `configure` supports creating new `auth-profiles.json` mappings directly in the picker flow.
* Canonical supported surface: [SecretRef Credential Surface](https://docs.openclaw.ai/reference/secretref-credential-surface).
* It performs preflight resolution before apply.
* If preflight/apply includes exec refs, keep `--allow-exec` set for both steps.
* Generated plans default to scrub options (`scrubEnv`, `scrubAuthProfilesForProviderTargets`, `scrubLegacyAuthJson` all enabled).
* Apply path is one-way for scrubbed plaintext values.
* Without `--apply`, CLI still prompts `Apply this plan now?` after preflight.
* With `--apply` (and no `--yes`), CLI prompts an extra irreversible confirmation.

Exec provider safety note:

* Homebrew installs often expose symlinked binaries under `/opt/homebrew/bin/*`.
* Set `allowSymlinkCommand: true` only when needed for trusted package-manager paths, and pair it with `trustedDirs` (for example `["/opt/homebrew"]`).
* On Windows, if ACL verification is unavailable for a provider path, OpenClaw fails closed. For trusted paths only, set `allowInsecurePath: true` on that provider to bypass path security checks.

## [​](#apply-a-saved-plan)Apply a saved plan

Apply or preflight a plan generated previously:

```
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --allow-exec
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run --allow-exec
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --json
```

Exec behavior:

* `--dry-run` validates preflight without writing files.
* exec SecretRef checks are skipped by default in dry-run.
* write mode rejects plans that contain exec SecretRefs/providers unless `--allow-exec` is set.
* Use `--allow-exec` to opt in to exec provider checks/execution in either mode.

Plan contract details (allowed target paths, validation rules, and failure semantics):

* [Secrets Apply Plan Contract](https://docs.openclaw.ai/gateway/secrets-plan-contract)

What `apply` may update:

* `openclaw.json` (SecretRef targets + provider upserts/deletes)
* `auth-profiles.json` (provider-target scrubbing)
* legacy `auth.json` residues
* `~/.openclaw/.env` known secret keys whose values were migrated

## [​](#why-no-rollback-backups)Why no rollback backups

`secrets apply` intentionally does not write rollback backups containing old plaintext values. Safety comes from strict preflight + atomic-ish apply with best-effort in-memory restore on failure.

## [​](#example)Example

```
openclaw secrets audit --check
openclaw secrets configure
openclaw secrets audit --check
```

If `audit --check` still reports plaintext findings, update the remaining reported target paths and rerun audit.

----
url: https://docs.openclaw.ai/ci
----

# CI Pipeline - OpenClaw

The CI runs on every push to `main` and every pull request. It uses smart scoping to skip expensive jobs when only unrelated areas changed.

## Job Overview

| Job               | Purpose                                                                   | When it runs                                     |
| ----------------- | ------------------------------------------------------------------------- | ------------------------------------------------ |
| `preflight`       | Docs scope, change scope, key scan, workflow audit, prod dependency audit | Always; node-based audit only on non-doc changes |
| `docs-scope`      | Detect docs-only changes                                                  | Always                                           |
| `changed-scope`   | Detect which areas changed (node/macos/android/windows)                   | Non-doc changes                                  |
| `check`           | TypeScript types, lint, format                                            | Non-docs, node changes                           |
| `check-docs`      | Markdown lint + broken link check                                         | Docs changed                                     |
| `secrets`         | Detect leaked secrets                                                     | Always                                           |
| `build-artifacts` | Build dist once, share with `release-check`                               | Pushes to `main`, node changes                   |
| `release-check`   | Validate npm pack contents                                                | Pushes to `main` after build                     |
| `checks`          | Node tests + protocol check on PRs; Bun compat on push                    | Non-docs, node changes                           |
| `compat-node22`   | Minimum supported Node runtime compatibility                              | Pushes to `main`, node changes                   |
| `checks-windows`  | Windows-specific tests                                                    | Non-docs, windows-relevant changes               |
| `macos`           | Swift lint/build/test + TS tests                                          | PRs with macos changes                           |
| `android`         | Gradle build + tests                                                      | Non-docs, android changes                        |

## Fail-Fast Order

Jobs are ordered so cheap checks fail before expensive ones run:

1. `docs-scope` + `changed-scope` + `check` + `secrets` (parallel, cheap gates first)
2. PRs: `checks` (Linux Node test split into 2 shards), `checks-windows`, `macos`, `android`
3. Pushes to `main`: `build-artifacts` + `release-check` + Bun compat + `compat-node22`

Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`. The same shared scope module also drives the separate `install-smoke` workflow through a narrower `changed-smoke` gate, so Docker/install smoke only runs for install, packaging, and container-relevant changes.

## Runners

| Runner                           | Jobs                                       |
| -------------------------------- | ------------------------------------------ |
| `blacksmith-16vcpu-ubuntu-2404`  | Most Linux jobs, including scope detection |
| `blacksmith-32vcpu-windows-2025` | `checks-windows`                           |
| `macos-latest`                   | `macos`, `ios`                             |

## Local Equivalents

----
url: https://docs.openclaw.ai/install
----

# Install - OpenClaw

## [​](#install)Install

## [​](#recommended-installer-script)Recommended: installer script

The fastest way to install. It detects your OS, installs Node if needed, installs OpenClaw, and launches onboarding.

* macOS / Linux / WSL2

* Windows (PowerShell)

```
curl -fsSL https://openclaw.ai/install.sh | bash
```

```
iwr -useb https://openclaw.ai/install.ps1 | iex
```

To install without running onboarding:

* macOS / Linux / WSL2

* Windows (PowerShell)

```
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard
```

```
& ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -NoOnboard
```

For all flags and CI/automation options, see [Installer internals](https://docs.openclaw.ai/install/installer).

## [​](#system-requirements)System requirements

* **Node 24** (recommended) or Node 22.16+ — the installer script handles this automatically
* **macOS, Linux, or Windows** — both native Windows and WSL2 are supported; WSL2 is more stable. See [Windows](https://docs.openclaw.ai/platforms/windows).
* `pnpm` is only needed if you build from source

## [​](#alternative-install-methods)Alternative install methods

### [​](#npm-or-pnpm)npm or pnpm

If you already manage Node yourself:

* npm

* pnpm

```
npm install -g openclaw@latest
openclaw onboard --install-daemon
```

```
pnpm add -g openclaw@latest
pnpm approve-builds -g
openclaw onboard --install-daemon
```

pnpm requires explicit approval for packages with build scripts. Run `pnpm approve-builds -g` after the first install.

Troubleshooting: sharp build errors (npm)

If `sharp` fails due to a globally installed libvips:

```
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install -g openclaw@latest
```

### [​](#from-source)From source

For contributors or anyone who wants to run from a local checkout:

```
git clone https://github.com/openclaw/openclaw.git
cd openclaw
pnpm install && pnpm ui:build && pnpm build
pnpm link --global
openclaw onboard --install-daemon
```

Or skip the link and use `pnpm openclaw ...` from inside the repo. See [Setup](https://docs.openclaw.ai/start/setup) for full development workflows.

### [​](#install-from-github-main)Install from GitHub main

```
npm install -g github:openclaw/openclaw#main
```

### [​](#containers-and-package-managers)Containers and package managers

## Docker

Containerized or headless deployments.

## Podman

Rootless container alternative to Docker.

## Nix

Declarative install via Nix flake.

## Ansible

Automated fleet provisioning.

## Bun

CLI-only usage via the Bun runtime.

## [​](#verify-the-install)Verify the install

```
openclaw --version      # confirm the CLI is available
openclaw doctor         # check for config issues
openclaw gateway status # verify the Gateway is running
```

## [​](#hosting-and-deployment)Hosting and deployment

Deploy OpenClaw on a cloud server or VPS:

## VPS

Any Linux VPS

## Docker VM

Shared Docker steps

## Kubernetes

K8s

## Fly.io

Fly.io

## Hetzner

Hetzner

## GCP

Google Cloud

## Azure

Azure

## Railway

Railway

## Render

Render

## Northflank

Northflank

## [​](#update-migrate-or-uninstall)Update, migrate, or uninstall

## Updating

Keep OpenClaw up to date.

## Migrating

Move to a new machine.

## Uninstall

Remove OpenClaw completely.

## [​](#troubleshooting-openclaw-not-found)Troubleshooting: `openclaw` not found

If the install succeeded but `openclaw` is not found in your terminal:

```
node -v           # Node installed?
npm prefix -g     # Where are global packages?
echo "$PATH"      # Is the global bin dir in PATH?
```

If `$(npm prefix -g)/bin` is not in your `$PATH`, add it to your shell startup file (`~/.zshrc` or `~/.bashrc`):

```
export PATH="$(npm prefix -g)/bin:$PATH"
```

Then open a new terminal. See [Node setup](https://docs.openclaw.ai/install/node) for more details.

----
url: https://docs.openclaw.ai/platforms/mac/voicewake
----

# Voice Wake (macOS) - OpenClaw

## [​](#voice-wake-&-push-to-talk)Voice Wake & Push-to-Talk

## [​](#modes)Modes

* **Wake-word mode** (default): always-on Speech recognizer waits for trigger tokens (`swabbleTriggerWords`). On match it starts capture, shows the overlay with partial text, and auto-sends after silence.
* **Push-to-talk (Right Option hold)**: hold the right Option key to capture immediately—no trigger needed. The overlay appears while held; releasing finalizes and forwards after a short delay so you can tweak text.

## [​](#runtime-behavior-wake-word)Runtime behavior (wake-word)

* Speech recognizer lives in `VoiceWakeRuntime`.
* Trigger only fires when there’s a **meaningful pause** between the wake word and the next word (\~0.55s gap). The overlay/chime can start on the pause even before the command begins.
* Silence windows: 2.0s when speech is flowing, 5.0s if only the trigger was heard.
* Hard stop: 120s to prevent runaway sessions.
* Debounce between sessions: 350ms.
* Overlay is driven via `VoiceWakeOverlayController` with committed/volatile coloring.
* After send, recognizer restarts cleanly to listen for the next trigger.

## [​](#lifecycle-invariants)Lifecycle invariants

* If Voice Wake is enabled and permissions are granted, the wake-word recognizer should be listening (except during an explicit push-to-talk capture).
* Overlay visibility (including manual dismiss via the X button) must never prevent the recognizer from resuming.

## [​](#sticky-overlay-failure-mode-previous)Sticky overlay failure mode (previous)

Previously, if the overlay got stuck visible and you manually closed it, Voice Wake could appear “dead” because the runtime’s restart attempt could be blocked by overlay visibility and no subsequent restart was scheduled. Hardening:

* Wake runtime restart is no longer blocked by overlay visibility.
* Overlay dismiss completion triggers a `VoiceWakeRuntime.refresh(...)` via `VoiceSessionCoordinator`, so manual X-dismiss always resumes listening.

## [​](#push-to-talk-specifics)Push-to-talk specifics

* Hotkey detection uses a global `.flagsChanged` monitor for **right Option** (`keyCode 61` + `.option`). We only observe events (no swallowing).
* Capture pipeline lives in `VoicePushToTalk`: starts Speech immediately, streams partials to the overlay, and calls `VoiceWakeForwarder` on release.
* When push-to-talk starts we pause the wake-word runtime to avoid dueling audio taps; it restarts automatically after release.
* Permissions: requires Microphone + Speech; seeing events needs Accessibility/Input Monitoring approval.
* External keyboards: some may not expose right Option as expected—offer a fallback shortcut if users report misses.

## [​](#user-facing-settings)User-facing settings

* **Voice Wake** toggle: enables wake-word runtime.
* **Hold Cmd+Fn to talk**: enables the push-to-talk monitor. Disabled on macOS < 26.
* Language & mic pickers, live level meter, trigger-word table, tester (local-only; does not forward).
* Mic picker preserves the last selection if a device disconnects, shows a disconnected hint, and temporarily falls back to the system default until it returns.
* **Sounds**: chimes on trigger detect and on send; defaults to the macOS “Glass” system sound. You can pick any `NSSound`-loadable file (e.g. MP3/WAV/AIFF) for each event or choose **No Sound**.

## [​](#forwarding-behavior)Forwarding behavior

* When Voice Wake is enabled, transcripts are forwarded to the active gateway/agent (the same local vs remote mode used by the rest of the mac app).
* Replies are delivered to the **last-used main provider** (WhatsApp/Telegram/Discord/WebChat). If delivery fails, the error is logged and the run is still visible via WebChat/session logs.

## [​](#forwarding-payload)Forwarding payload

* `VoiceWakeForwarder.prefixedTranscript(_:)` prepends the machine hint before sending. Shared between wake-word and push-to-talk paths.

## [​](#quick-verification)Quick verification

* Toggle push-to-talk on, hold Cmd+Fn, speak, release: overlay should show partials then send.
* While holding, menu-bar ears should stay enlarged (uses `triggerVoiceEars(ttl:nil)`); they drop after release.

----
url: https://docs.openclaw.ai/tools/creating-skills
----

# Creating Skills - OpenClaw

Skills teach the agent how and when to use tools. Each skill is a directory containing a `SKILL.md` file with YAML frontmatter and markdown instructions. For how skills are loaded and prioritized, see [Skills](https://docs.openclaw.ai/tools/skills).

## Create your first skill

The YAML frontmatter supports these fields:

| Field                               | Required | Description                                 |
| ----------------------------------- | -------- | ------------------------------------------- |
| `name`                              | Yes      | Unique identifier (snake\_case)             |
| `description`                       | Yes      | One-line description shown to the agent     |
| `metadata.openclaw.os`              | No       | OS filter (`["darwin"]`, `["linux"]`, etc.) |
| `metadata.openclaw.requires.bins`   | No       | Required binaries on PATH                   |
| `metadata.openclaw.requires.config` | No       | Required config keys                        |

## Best practices

## Where skills live

| Location                        | Precedence | Scope                 |
| ------------------------------- | ---------- | --------------------- |
| `\<workspace\>/skills/`         | Highest    | Per-agent             |
| `~/.openclaw/skills/`           | Medium     | Shared (all agents)   |
| Bundled (shipped with OpenClaw) | Lowest     | Global                |
| `skills.load.extraDirs`         | Lowest     | Custom shared folders |

----
url: https://docs.openclaw.ai/providers/deepseek
----

# Deepseek - OpenClaw

[DeepSeek](https://www.deepseek.com/) provides powerful AI models with an OpenAI-compatible API.

## Quick start

Set the API key (recommended: store it for the Gateway):

This will prompt for your API key and set `deepseek/deepseek-chat` as the default model.

## Non-interactive example

## Environment note

If the Gateway runs as a daemon (launchd/systemd), make sure `DEEPSEEK_API_KEY` is available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).

## Available models

| Model ID            | Name                     | Type      | Context |
| ------------------- | ------------------------ | --------- | ------- |
| `deepseek-chat`     | DeepSeek Chat (V3.2)     | General   | 128K    |
| `deepseek-reasoner` | DeepSeek Reasoner (V3.2) | Reasoning | 128K    |

Get your API key at [platform.deepseek.com](https://platform.deepseek.com/api_keys).

----
url: https://docs.openclaw.ai/providers/vercel-ai-gateway
----

# Vercel AI Gateway - OpenClaw

## [​](#vercel-ai-gateway)Vercel AI Gateway

The [Vercel AI Gateway](https://vercel.com/ai-gateway) provides a unified API to access hundreds of models through a single endpoint.

* Provider: `vercel-ai-gateway`
* Auth: `AI_GATEWAY_API_KEY`
* API: Anthropic Messages compatible
* OpenClaw auto-discovers the Gateway `/v1/models` catalog, so `/models vercel-ai-gateway` includes current model refs such as `vercel-ai-gateway/openai/gpt-5.4`.

## [​](#quick-start)Quick start

1. Set the API key (recommended: store it for the Gateway):

```
openclaw onboard --auth-choice ai-gateway-api-key
```

2. Set a default model:

```
{
  agents: {
    defaults: {
      model: { primary: "vercel-ai-gateway/anthropic/claude-opus-4.6" },
    },
  },
}
```

## [​](#non-interactive-example)Non-interactive example

```
openclaw onboard --non-interactive \
  --mode local \
  --auth-choice ai-gateway-api-key \
  --ai-gateway-api-key "$AI_GATEWAY_API_KEY"
```

## [​](#environment-note)Environment note

If the Gateway runs as a daemon (launchd/systemd), make sure `AI_GATEWAY_API_KEY` is available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).

## [​](#model-id-shorthand)Model ID shorthand

OpenClaw accepts Vercel Claude shorthand model refs and normalizes them at runtime:

* `vercel-ai-gateway/claude-opus-4.6` -> `vercel-ai-gateway/anthropic/claude-opus-4.6`
* `vercel-ai-gateway/opus-4.6` -> `vercel-ai-gateway/anthropic/claude-opus-4-6`

----
url: https://docs.openclaw.ai/reference/device-models
----

# Device Model Database - OpenClaw

## [​](#device-model-database-friendly-names)Device model database (friendly names)

The macOS companion app shows friendly Apple device model names in the **Instances** UI by mapping Apple model identifiers (e.g. `iPad16,6`, `Mac16,6`) to human-readable names. The mapping is vendored as JSON under:

* `apps/macos/Sources/OpenClaw/Resources/DeviceModels/`

## [​](#data-source)Data source

We currently vendor the mapping from the MIT-licensed repository:

* `kyle-seongwoo-jun/apple-device-identifiers`

To keep builds deterministic, the JSON files are pinned to specific upstream commits (recorded in `apps/macos/Sources/OpenClaw/Resources/DeviceModels/NOTICE.md`).

## [​](#updating-the-database)Updating the database

1. Pick the upstream commits you want to pin to (one for iOS, one for macOS).
2. Update the commit hashes in `apps/macos/Sources/OpenClaw/Resources/DeviceModels/NOTICE.md`.
3. Re-download the JSON files, pinned to those commits:

```
IOS_COMMIT="<commit sha for ios-device-identifiers.json>"
MAC_COMMIT="<commit sha for mac-device-identifiers.json>"

curl -fsSL "https://raw.githubusercontent.com/kyle-seongwoo-jun/apple-device-identifiers/${IOS_COMMIT}/ios-device-identifiers.json" \
  -o apps/macos/Sources/OpenClaw/Resources/DeviceModels/ios-device-identifiers.json

curl -fsSL "https://raw.githubusercontent.com/kyle-seongwoo-jun/apple-device-identifiers/${MAC_COMMIT}/mac-device-identifiers.json" \
  -o apps/macos/Sources/OpenClaw/Resources/DeviceModels/mac-device-identifiers.json
```

4. Ensure `apps/macos/Sources/OpenClaw/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt` still matches upstream (replace it if the upstream license changes).
5. Verify the macOS app builds cleanly (no warnings):

```
swift build --package-path apps/macos
```

----
url: https://docs.openclaw.ai/gateway/secrets-plan-contract
----

# Secrets Apply Plan Contract - OpenClaw

This page defines the strict contract enforced by `openclaw secrets apply`. If a target does not match these rules, apply fails before mutating configuration.

## Plan file shape

`openclaw secrets apply --from <plan.json>` expects a `targets` array of plan targets:

```
{
  version: 1,
  protocolVersion: 1,
  targets: [
    {
      type: "models.providers.apiKey",
      path: "models.providers.openai.apiKey",
      pathSegments: ["models", "providers", "openai", "apiKey"],
      providerId: "openai",
      ref: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
    },
    {
      type: "auth-profiles.api_key.key",
      path: "profiles.openai:default.key",
      pathSegments: ["profiles", "openai:default", "key"],
      agentId: "main",
      ref: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
    },
  ],
}
```

## Supported target scope

Plan targets are accepted for supported credential paths in:

## Target type behavior

General rule:

Compatibility aliases remain accepted for existing plans:

## Path validation rules

Each target is validated with all of the following:

## Failure behavior

If a target fails validation, apply exits with an error like:

No writes are committed for an invalid plan.

## Exec provider consent behavior

## Runtime and audit scope notes

## Operator checks

If apply fails with an invalid target path message, regenerate the plan with `openclaw secrets configure` or fix the target path to a supported shape above.

----
url: https://docs.openclaw.ai/web/control-ui
----

# Control UI - OpenClaw

## Control UI (browser)

The Control UI is a small **Vite + Lit** single-page app served by the Gateway:

It speaks **directly to the Gateway WebSocket** on the same port.

## Quick open (local)

If the Gateway is running on the same computer, open:

If the page fails to load, start the Gateway first: `openclaw gateway`. Auth is supplied during the WebSocket handshake via:

## Device pairing (first connection)

When you connect to the Control UI from a new browser or device, the Gateway requires a **one-time pairing approval** — even if you’re on the same Tailnet with `gateway.auth.allowTailscale: true`. This is a security measure to prevent unauthorized access. **What you’ll see:** “disconnected (1008): pairing required” **To approve the device:**

If the browser retries pairing with changed auth details (role/scopes/public key), the previous pending request is superseded and a new `requestId` is created. Re-run `openclaw devices list` before approval. Once approved, the device is remembered and won’t require re-approval unless you revoke it with `openclaw devices revoke --device <id> --role <role>`. See [Devices CLI](https://docs.openclaw.ai/cli/devices) for token rotation and revocation. **Notes:**

## Language support

The Control UI can localize itself on first load based on your browser locale, and you can override it later from the language picker in the Access card.

## What it can do (today)

* Chat with the model via Gateway WS (`chat.history`, `chat.send`, `chat.abort`, `chat.inject`)
* Stream tool calls + live tool output cards in Chat (agent events)
* Channels: WhatsApp/Telegram/Discord/Slack + plugin channels (Mattermost, etc.) status + QR login + per-channel config (`channels.status`, `web.login.*`, `config.patch`)
* Instances: presence list + refresh (`system-presence`)
* Sessions: list + per-session thinking/fast/verbose/reasoning overrides (`sessions.list`, `sessions.patch`)
* Cron jobs: list/add/edit/run/enable/disable + run history (`cron.*`)
* Skills: status, enable/disable, install, API key updates (`skills.*`)
* Nodes: list + caps (`node.list`)
* Exec approvals: edit gateway or node allowlists + ask policy for `exec host=gateway/node` (`exec.approvals.*`)
* Config: view/edit `~/.openclaw/openclaw.json` (`config.get`, `config.set`)
* Config: apply + restart with validation (`config.apply`) and wake the last active session
* Config writes include a base-hash guard to prevent clobbering concurrent edits
* Config schema + form rendering (`config.schema`, including plugin + channel schemas); Raw JSON editor remains available
* Debug: status/health/models snapshots + event log + manual RPC calls (`status`, `health`, `models.list`)
* Logs: live tail of gateway file logs with filter/export (`logs.tail`)
* Update: run a package/git update + restart (`update.run`) with a restart report

Cron jobs panel notes:

## Chat behavior

## Tailnet access (recommended)

### Integrated Tailscale Serve (preferred)

Keep the Gateway on loopback and let Tailscale Serve proxy it with HTTPS:

Open:

By default, Control UI/WebSocket Serve requests can authenticate via Tailscale identity headers (`tailscale-user-login`) when `gateway.auth.allowTailscale` is `true`. OpenClaw verifies the identity by resolving the `x-forwarded-for` address with `tailscale whois` and matching it to the header, and only accepts these when the request hits loopback with Tailscale’s `x-forwarded-*` headers. Set `gateway.auth.allowTailscale: false` (or force `gateway.auth.mode: "password"`) if you want to require a token/password even for Serve traffic. Tokenless Serve auth assumes the gateway host is trusted. If untrusted local code may run on that host, require token/password auth.

### Bind to tailnet + token

Then open:

Paste the token into the UI settings (sent as `connect.params.auth.token`).

## Insecure HTTP

If you open the dashboard over plain HTTP (`http://<lan-ip>` or `http://<tailscale-ip>`), the browser runs in a **non-secure context** and blocks WebCrypto. By default, OpenClaw **blocks** Control UI connections without device identity. **Recommended fix:** use HTTPS (Tailscale Serve) or open the UI locally:

**Insecure-auth toggle behavior:**

`allowInsecureAuth` is a local compatibility toggle only:

**Break-glass only:**

`dangerouslyDisableDeviceAuth` disables Control UI device identity checks and is a severe security downgrade. Revert quickly after emergency use. See [Tailscale](https://docs.openclaw.ai/gateway/tailscale) for HTTPS setup guidance.

## Building the UI

The Gateway serves static files from `dist/control-ui`. Build them with:

Optional absolute base (when you want fixed asset URLs):

For local development (separate dev server):

Then point the UI at your Gateway WS URL (e.g. `ws://127.0.0.1:18789`).

## Debugging/testing: dev server + remote Gateway

The Control UI is static files; the WebSocket target is configurable and can be different from the HTTP origin. This is handy when you want the Vite dev server locally but the Gateway runs elsewhere.

1. Start the UI dev server: `pnpm ui:dev`
2. Open a URL like:

Optional one-time auth (if needed):

Notes:

Example:

Remote access setup details: [Remote access](https://docs.openclaw.ai/gateway/remote).

----
url: https://docs.openclaw.ai/cli/sessions
----

# sessions - OpenClaw

List stored conversation sessions.

Scope selection:

`openclaw sessions --all-agents` reads configured agent stores. Gateway and ACP session discovery are broader: they also include disk-only stores found under the default `agents/` root or a templated `session.store` root. Those discovered stores must resolve to regular `sessions.json` files inside the agent root; symlinks and out-of-root paths are skipped. JSON examples: `openclaw sessions --all-agents --json`:

```
{
  "path": null,
  "stores": [
    { "agentId": "main", "path": "/home/user/.openclaw/agents/main/sessions/sessions.json" },
    { "agentId": "work", "path": "/home/user/.openclaw/agents/work/sessions/sessions.json" }
  ],
  "allAgents": true,
  "count": 2,
  "activeMinutes": null,
  "sessions": [
    { "agentId": "main", "key": "agent:main:main", "model": "gpt-5" },
    { "agentId": "work", "key": "agent:work:main", "model": "claude-opus-4-6" }
  ]
}
```

## Cleanup maintenance

Run maintenance now (instead of waiting for the next write cycle):

`openclaw sessions cleanup` uses `session.maintenance` settings from config:

`openclaw sessions cleanup --all-agents --dry-run --json`:

```
{
  "allAgents": true,
  "mode": "warn",
  "dryRun": true,
  "stores": [
    {
      "agentId": "main",
      "storePath": "/home/user/.openclaw/agents/main/sessions/sessions.json",
      "beforeCount": 120,
      "afterCount": 80,
      "pruned": 40,
      "capped": 0
    },
    {
      "agentId": "work",
      "storePath": "/home/user/.openclaw/agents/work/sessions/sessions.json",
      "beforeCount": 18,
      "afterCount": 18,
      "pruned": 0,
      "capped": 0
    }
  ]
}
```

Related:

----
url: https://docs.openclaw.ai/cli/plugins
----

# plugins - OpenClaw

Manage Gateway plugins/extensions, hook packs, and compatible bundles. Related:

## Commands

Bundled plugins ship with OpenClaw but start disabled. Use `plugins enable` to activate them. Native OpenClaw plugins must ship `openclaw.plugin.json` with an inline JSON Schema (`configSchema`, even if empty). Compatible bundles use their own bundle manifests instead. `plugins list` shows `Format: openclaw` or `Format: bundle`. Verbose list/info output also shows the bundle subtype (`codex`, `claude`, or `cursor`) plus detected bundle capabilities.

### Install

Bare package names are checked against ClawHub first, then npm. Security note: treat plugin installs like running code. Prefer pinned versions. `plugins install` is also the install surface for hook packs that expose `openclaw.hooks` in `package.json`. Use `openclaw hooks` for filtered hook visibility and per-hook enablement, not package installation. Npm specs are **registry-only** (package name + optional **exact version** or **dist-tag**). Git/URL/file specs and semver ranges are rejected. Dependency installs run with `--ignore-scripts` for safety. Bare specs and `@latest` stay on the stable track. If npm resolves either of those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a prerelease tag such as `@beta`/`@rc` or an exact prerelease version such as `@1.2.3-beta.4`. If a bare install spec matches a bundled plugin id (for example `diffs`), OpenClaw installs the bundled plugin directly. To install an npm package with the same name, use an explicit scoped spec (for example `@scope/diffs`). Supported archives: `.zip`, `.tgz`, `.tar.gz`, `.tar`. Claude marketplace installs are also supported. ClawHub installs use an explicit `clawhub:<package>` locator:

OpenClaw now also prefers ClawHub for bare npm-safe plugin specs. It only falls back to npm if ClawHub does not have that package or version:

OpenClaw downloads the package archive from ClawHub, checks the advertised plugin API / minimum gateway compatibility, then installs it through the normal archive path. Recorded installs keep their ClawHub source metadata for later updates. Use `plugin@marketplace` shorthand when the marketplace name exists in Claude’s local registry cache at `~/.claude/plugins/known_marketplaces.json`:

Use `--marketplace` when you want to pass the marketplace source explicitly:

Marketplace sources can be:

For remote marketplaces loaded from GitHub or git, plugin entries must stay inside the cloned marketplace repo. OpenClaw accepts relative path sources from that repo and rejects external git, GitHub, URL/archive, and absolute-path plugin sources from remote manifests. For local paths and archives, OpenClaw auto-detects:

Compatible bundles install into the normal extensions root and participate in the same list/info/enable/disable flow. Today, bundle skills, Claude command-skills, Claude `settings.json` defaults, Cursor command-skills, and compatible Codex hook directories are supported; other detected bundle capabilities are shown in diagnostics/info but are not yet wired into runtime execution. Use `--link` to avoid copying a local directory (adds to `plugins.load.paths`):

Use `--pin` on npm installs to save the resolved exact spec (`name@version`) in `plugins.installs` while keeping the default behavior unpinned.

### Uninstall

`uninstall` removes plugin records from `plugins.entries`, `plugins.installs`, the plugin allowlist, and linked `plugins.load.paths` entries when applicable. For active memory plugins, the memory slot resets to `memory-core`. By default, uninstall also removes the plugin install directory under the active state dir extensions root (`$OPENCLAW_STATE_DIR/extensions/<id>`). Use `--keep-files` to keep files on disk. `--keep-config` is supported as a deprecated alias for `--keep-files`.

### Update

Updates apply to tracked installs in `plugins.installs` and tracked hook-pack installs in `hooks.internal.installs`. When you pass a plugin id, OpenClaw reuses the recorded install spec for that plugin. That means previously stored dist-tags such as `@beta` and exact pinned versions continue to be used on later `update <id>` runs. For npm installs, you can also pass an explicit npm package spec with a dist-tag or exact version. OpenClaw resolves that package name back to the tracked plugin record, updates that installed plugin, and records the new npm spec for future id-based updates. When a stored integrity hash exists and the fetched artifact hash changes, OpenClaw prints a warning and asks for confirmation before proceeding. Use global `--yes` to bypass prompts in CI/non-interactive runs.

### Inspect

Deep introspection for a single plugin. Shows identity, load status, source, registered capabilities, hooks, tools, commands, services, gateway methods, HTTP routes, policy flags, diagnostics, and install metadata. Each plugin is classified by what it actually registers at runtime:

See [Plugin shapes](https://docs.openclaw.ai/plugins/architecture#plugin-shapes) for more on the capability model. The `--json` flag outputs a machine-readable report suitable for scripting and auditing. `info` is an alias for `inspect`.

----
url: https://docs.openclaw.ai/providers/huggingface
----

# Hugging Face (Inference) - OpenClaw

[Hugging Face Inference Providers](https://huggingface.co/docs/inference-providers) offer OpenAI-compatible chat completions through a single router API. You get access to many models (DeepSeek, Llama, and more) with one token. OpenClaw uses the **OpenAI-compatible endpoint** (chat completions only); for text-to-image, embeddings, or speech use the [HF inference clients](https://huggingface.co/docs/api-inference/quicktour) directly.

## Quick start

1. Create a fine-grained token at [Hugging Face → Settings → Tokens](https://huggingface.co/settings/tokens/new?ownUserPermissions=inference.serverless.write\&tokenType=fineGrained) with the **Make calls to Inference Providers** permission.
2. Run onboarding and choose **Hugging Face** in the provider dropdown, then enter your API key when prompted:

3) In the **Default Hugging Face model** dropdown, pick the model you want (the list is loaded from the Inference API when you have a valid token; otherwise a built-in list is shown). Your choice is saved as the default model.
4) You can also set or change the default model later in config:

## Non-interactive example

This will set `huggingface/deepseek-ai/DeepSeek-R1` as the default model.

## Environment note

If the Gateway runs as a daemon (launchd/systemd), make sure `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` is available to that process (for example, in `~/.openclaw/.env` or via `env.shellEnv`).

## Model discovery and onboarding dropdown

OpenClaw discovers models by calling the **Inference endpoint directly**:

(Optional: send `Authorization: Bearer $HUGGINGFACE_HUB_TOKEN` or `$HF_TOKEN` for the full list; some endpoints return a subset without auth.) The response is OpenAI-style `{ "object": "list", "data": [ { "id": "Qwen/Qwen3-8B", "owned_by": "Qwen", ... }, ... ] }`. When you configure a Hugging Face API key (via onboarding, `HUGGINGFACE_HUB_TOKEN`, or `HF_TOKEN`), OpenClaw uses this GET to discover available chat-completion models. During **interactive setup**, after you enter your token you see a **Default Hugging Face model** dropdown populated from that list (or the built-in catalog if the request fails). At runtime (e.g. Gateway startup), when a key is present, OpenClaw again calls **GET** `https://router.huggingface.co/v1/models` to refresh the catalog. The list is merged with a built-in catalog (for metadata like context window and cost). If the request fails or no key is set, only the built-in catalog is used.

## Model names and editable options

## Model IDs and configuration examples

Model refs use the form `huggingface/<org>/<model>` (Hub-style IDs). The list below is from **GET** `https://router.huggingface.co/v1/models`; your catalog may include more. **Example IDs (from the inference endpoint):**

| Model                  | Ref (prefix with `huggingface/`)    |
| ---------------------- | ----------------------------------- |
| DeepSeek R1            | `deepseek-ai/DeepSeek-R1`           |
| DeepSeek V3.2          | `deepseek-ai/DeepSeek-V3.2`         |
| Qwen3 8B               | `Qwen/Qwen3-8B`                     |
| Qwen2.5 7B Instruct    | `Qwen/Qwen2.5-7B-Instruct`          |
| Qwen3 32B              | `Qwen/Qwen3-32B`                    |
| Llama 3.3 70B Instruct | `meta-llama/Llama-3.3-70B-Instruct` |
| Llama 3.1 8B Instruct  | `meta-llama/Llama-3.1-8B-Instruct`  |
| GPT-OSS 120B           | `openai/gpt-oss-120b`               |
| GLM 4.7                | `zai-org/GLM-4.7`                   |
| Kimi K2.5              | `moonshotai/Kimi-K2.5`              |

You can append `:fastest`, `:cheapest`, or `:provider` (e.g. `:together`, `:sambanova`) to the model id. Set your default order in [Inference Provider settings](https://hf.co/settings/inference-providers); see [Inference Providers](https://huggingface.co/docs/inference-providers) and **GET** `https://router.huggingface.co/v1/models` for the full list.

### Complete configuration examples

**Primary DeepSeek R1 with Qwen fallback:**

**Qwen as default, with :cheapest and :fastest variants:**

**DeepSeek + Llama + GPT-OSS with aliases:**

```
{
  agents: {
    defaults: {
      model: {
        primary: "huggingface/deepseek-ai/DeepSeek-V3.2",
        fallbacks: [
          "huggingface/meta-llama/Llama-3.3-70B-Instruct",
          "huggingface/openai/gpt-oss-120b",
        ],
      },
      models: {
        "huggingface/deepseek-ai/DeepSeek-V3.2": { alias: "DeepSeek V3.2" },
        "huggingface/meta-llama/Llama-3.3-70B-Instruct": { alias: "Llama 3.3 70B" },
        "huggingface/openai/gpt-oss-120b": { alias: "GPT-OSS 120B" },
      },
    },
  },
}
```

**Force a specific backend with :provider:**

**Multiple Qwen and DeepSeek models with policy suffixes:**

----
url: https://docs.openclaw.ai/reference/templates/SOUL
----

# SOUL.md Template - OpenClaw

## SOUL.md - Who You Are

*You’re not a chatbot. You’re becoming someone.*

## Core Truths

**Be genuinely helpful, not performatively helpful.** Skip the “Great question!” and “I’d be happy to help!” — just help. Actions speak louder than filler words. **Have opinions.** You’re allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps. **Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. *Then* ask if you’re stuck. The goal is to come back with answers, not questions. **Earn trust through competence.** Your human gave you access to their stuff. Don’t make them regret it. Be careful with external actions (emails, tweets, anything public). Be bold with internal ones (reading, organizing, learning). **Remember you’re a guest.** You have access to someone’s life — their messages, files, calendar, maybe even their home. That’s intimacy. Treat it with respect.

## Boundaries

## Vibe

Be the assistant you’d actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just… good.

## Continuity

Each session, you wake up fresh. These files *are* your memory. Read them. Update them. They’re how you persist. If you change this file, tell the user — it’s your soul, and they should know.

***

*This file is yours to evolve. As you learn who you are, update it.*

----
url: https://docs.openclaw.ai/cli/models
----

# models - OpenClaw

## [​](#openclaw-models)`openclaw models`

Model discovery, scanning, and configuration (default model, fallbacks, auth profiles). Related:

* Providers + models: [Models](https://docs.openclaw.ai/providers/models)
* Provider auth setup: [Getting started](https://docs.openclaw.ai/start/getting-started)

## [​](#common-commands)Common commands

```
openclaw models status
openclaw models list
openclaw models set <model-or-alias>
openclaw models scan
```

`openclaw models status` shows the resolved default/fallbacks plus an auth overview. When provider usage snapshots are available, the OAuth/token status section includes provider usage headers. Add `--probe` to run live auth probes against each configured provider profile. Probes are real requests (may consume tokens and trigger rate limits). Use `--agent <id>` to inspect a configured agent’s model/auth state. When omitted, the command uses `OPENCLAW_AGENT_DIR`/`PI_CODING_AGENT_DIR` if set, otherwise the configured default agent. Notes:

* `models set <model-or-alias>` accepts `provider/model` or an alias.
* Model refs are parsed by splitting on the **first** `/`. If the model ID includes `/` (OpenRouter-style), include the provider prefix (example: `openrouter/moonshotai/kimi-k2`).
* If you omit the provider, OpenClaw treats the input as an alias or a model for the **default provider** (only works when there is no `/` in the model ID).
* `models status` may show `marker(<value>)` in auth output for non-secret placeholders (for example `OPENAI_API_KEY`, `secretref-managed`, `minimax-oauth`, `qwen-oauth`, `ollama-local`) instead of masking them as secrets.

### [​](#models-status)`models status`

Options:

* `--json`
* `--plain`
* `--check` (exit 1=expired/missing, 2=expiring)
* `--probe` (live probe of configured auth profiles)
* `--probe-provider <name>` (probe one provider)
* `--probe-profile <id>` (repeat or comma-separated profile ids)
* `--probe-timeout <ms>`
* `--probe-concurrency <n>`
* `--probe-max-tokens <n>`
* `--agent <id>` (configured agent id; overrides `OPENCLAW_AGENT_DIR`/`PI_CODING_AGENT_DIR`)

## [​](#aliases-+-fallbacks)Aliases + fallbacks

```
openclaw models aliases list
openclaw models fallbacks list
```

## [​](#auth-profiles)Auth profiles

```
openclaw models auth add
openclaw models auth login --provider <id>
openclaw models auth setup-token
openclaw models auth paste-token
```

`models auth login` runs a provider plugin’s auth flow (OAuth/API key). Use `openclaw plugins list` to see which providers are installed. Notes:

* `setup-token` prompts for a setup-token value (generate it with `claude setup-token` on any machine).
* `paste-token` accepts a token string generated elsewhere or from automation.
* Anthropic policy note: setup-token support is technical compatibility. Anthropic has blocked some subscription usage outside Claude Code in the past, so verify current terms before using it broadly.

----
url: https://docs.openclaw.ai/gateway/gateway-lock
----

# Gateway Lock - OpenClaw

##### Gateway

* [Gateway Runbook](https://docs.openclaw.ai/gateway)

* * [Configuration](https://docs.openclaw.ai/gateway/configuration)
  * [Configuration Reference](https://docs.openclaw.ai/gateway/configuration-reference)
  * [Configuration Examples](https://docs.openclaw.ai/gateway/configuration-examples)
  * [Authentication](https://docs.openclaw.ai/gateway/authentication)
  * [Auth Credential Semantics](https://docs.openclaw.ai/auth-credential-semantics)
  * [Secrets Management](https://docs.openclaw.ai/gateway/secrets)
  * [Secrets Apply Plan Contract](https://docs.openclaw.ai/gateway/secrets-plan-contract)
  * [Trusted Proxy Auth](https://docs.openclaw.ai/gateway/trusted-proxy-auth)
  * [Health Checks](https://docs.openclaw.ai/gateway/health)
  * [Heartbeat](https://docs.openclaw.ai/gateway/heartbeat)
  * [Doctor](https://docs.openclaw.ai/gateway/doctor)
  * [Gateway Logging](https://docs.openclaw.ai/gateway/logging)
  * [Logging Overview](https://docs.openclaw.ai/logging)
  * [Gateway Lock](https://docs.openclaw.ai/gateway/gateway-lock)
  * [Background Exec and Process Tool](https://docs.openclaw.ai/gateway/background-process)
  * [Multiple Gateways](https://docs.openclaw.ai/gateway/multiple-gateways)
  * [Troubleshooting](https://docs.openclaw.ai/gateway/troubleshooting)

*

*

*

##### Remote access

* [Remote Access](https://docs.openclaw.ai/gateway/remote)
* [Remote Gateway Setup](https://docs.openclaw.ai/gateway/remote-gateway-readme)
* [Tailscale](https://docs.openclaw.ai/gateway/tailscale)

##### Security

* [Formal Verification (Security Models)](https://docs.openclaw.ai/security/formal-verification)
* [Threat Model (MITRE ATLAS)](https://docs.openclaw.ai/security/THREAT-MODEL-ATLAS)
* [Contributing to the Threat Model](https://docs.openclaw.ai/security/CONTRIBUTING-THREAT-MODEL)

##### Nodes and devices

##### Web interfaces

* [Web](https://docs.openclaw.ai/web)
* [Control UI](https://docs.openclaw.ai/web/control-ui)
* [Dashboard](https://docs.openclaw.ai/web/dashboard)
* [WebChat](https://docs.openclaw.ai/web/webchat)
* [TUI](https://docs.openclaw.ai/web/tui)

- [Gateway lock](#gateway-lock)
- [Why](#why)
- [Mechanism](#mechanism)
- [Error surface](#error-surface)
- [Operational notes](#operational-notes)

## [​](#gateway-lock)Gateway lock

Last updated: 2025-12-11

## [​](#why)Why

* Ensure only one gateway instance runs per base port on the same host; additional gateways must use isolated profiles and unique ports.
* Survive crashes/SIGKILL without leaving stale lock files.
* Fail fast with a clear error when the control port is already occupied.

## [​](#mechanism)Mechanism

* The gateway binds the WebSocket listener (default `ws://127.0.0.1:18789`) immediately on startup using an exclusive TCP listener.
* If the bind fails with `EADDRINUSE`, startup throws `GatewayLockError("another gateway instance is already listening on ws://127.0.0.1:<port>")`.
* The OS releases the listener automatically on any process exit, including crashes and SIGKILL—no separate lock file or cleanup step is needed.
* On shutdown the gateway closes the WebSocket server and underlying HTTP server to free the port promptly.

## [​](#error-surface)Error surface

* If another process holds the port, startup throws `GatewayLockError("another gateway instance is already listening on ws://127.0.0.1:<port>")`.
* Other bind failures surface as `GatewayLockError("failed to bind gateway socket on ws://127.0.0.1:<port>: …")`.

## [​](#operational-notes)Operational notes

* If the port is occupied by *another* process, the error is the same; free the port or choose another with `openclaw gateway --port <port>`.
* The macOS app still maintains its own lightweight PID guard before spawning the gateway; the runtime lock is enforced by the WebSocket bind.

[Logging Overview](https://docs.openclaw.ai/logging)[Background Exec and Process Tool](https://docs.openclaw.ai/gateway/background-process)

----
url: https://docs.openclaw.ai/auth-credential-semantics
----

# Auth Credential Semantics - OpenClaw

## [​](#auth-credential-semantics)Auth Credential Semantics

This document defines the canonical credential eligibility and resolution semantics used across:

* `resolveAuthProfileOrder`
* `resolveApiKeyForProfile`
* `models status --probe`
* `doctor-auth`

The goal is to keep selection-time and runtime behavior aligned.

## [​](#stable-reason-codes)Stable Reason Codes

* `ok`
* `missing_credential`
* `invalid_expires`
* `expired`
* `unresolved_ref`

## [​](#token-credentials)Token Credentials

Token credentials (`type: "token"`) support inline `token` and/or `tokenRef`.

### [​](#eligibility-rules)Eligibility rules

1. A token profile is ineligible when both `token` and `tokenRef` are absent.
2. `expires` is optional.
3. If `expires` is present, it must be a finite number greater than `0`.
4. If `expires` is invalid (`NaN`, `0`, negative, non-finite, or wrong type), the profile is ineligible with `invalid_expires`.
5. If `expires` is in the past, the profile is ineligible with `expired`.
6. `tokenRef` does not bypass `expires` validation.

### [​](#resolution-rules)Resolution rules

1. Resolver semantics match eligibility semantics for `expires`.
2. For eligible profiles, token material may be resolved from inline value or `tokenRef`.
3. Unresolvable refs produce `unresolved_ref` in `models status --probe` output.

## [​](#legacy-compatible-messaging)Legacy-Compatible Messaging

For script compatibility, probe errors keep this first line unchanged: `Auth profile credentials are missing or expired.` Human-friendly detail and stable reason codes may be added on subsequent lines.

----
url: https://docs.openclaw.ai/providers/github-copilot
----

# GitHub Copilot - OpenClaw

## [​](#github-copilot)GitHub Copilot

## [​](#what-is-github-copilot)What is GitHub Copilot?

GitHub Copilot is GitHub’s AI coding assistant. It provides access to Copilot models for your GitHub account and plan. OpenClaw can use Copilot as a model provider in two different ways.

## [​](#two-ways-to-use-copilot-in-openclaw)Two ways to use Copilot in OpenClaw

### [​](#1-built-in-github-copilot-provider-github-copilot)1) Built-in GitHub Copilot provider (`github-copilot`)

Use the native device-login flow to obtain a GitHub token, then exchange it for Copilot API tokens when OpenClaw runs. This is the **default** and simplest path because it does not require VS Code.

### [​](#2-copilot-proxy-plugin-copilot-proxy)2) Copilot Proxy plugin (`copilot-proxy`)

Use the **Copilot Proxy** VS Code extension as a local bridge. OpenClaw talks to the proxy’s `/v1` endpoint and uses the model list you configure there. Choose this when you already run Copilot Proxy in VS Code or need to route through it. You must enable the plugin and keep the VS Code extension running. Use GitHub Copilot as a model provider (`github-copilot`). The login command runs the GitHub device flow, saves an auth profile, and updates your config to use that profile.

## [​](#cli-setup)CLI setup

```
openclaw models auth login-github-copilot
```

You’ll be prompted to visit a URL and enter a one-time code. Keep the terminal open until it completes.

### [​](#optional-flags)Optional flags

```
openclaw models auth login-github-copilot --profile-id github-copilot:work
openclaw models auth login-github-copilot --yes
```

## [​](#set-a-default-model)Set a default model

```
openclaw models set github-copilot/gpt-4o
```

### [​](#config-snippet)Config snippet

```
{
  agents: { defaults: { model: { primary: "github-copilot/gpt-4o" } } },
}
```

## [​](#notes)Notes

* Requires an interactive TTY; run it directly in a terminal.
* Copilot model availability depends on your plan; if a model is rejected, try another ID (for example `github-copilot/gpt-4.1`).
* The login stores a GitHub token in the auth profile store and exchanges it for a Copilot API token when OpenClaw runs.

----
url: https://docs.openclaw.ai/tools/web
----

# Web Search - OpenClaw

The `web_search` tool searches the web using your configured provider and returns results. Results are cached by query for 15 minutes (configurable).

## Quick start

## Choosing a provider

### Provider comparison

| Provider                                                       | Result style               | Filters                                          | API key                                     |
| -------------------------------------------------------------- | -------------------------- | ------------------------------------------------ | ------------------------------------------- |
| [Brave](https://docs.openclaw.ai/tools/brave-search)           | Structured snippets        | Country, language, time, `llm-context` mode      | `BRAVE_API_KEY`                             |
| [DuckDuckGo](https://docs.openclaw.ai/tools/duckduckgo-search) | Structured snippets        | —                                                | None (key-free)                             |
| [Exa](https://docs.openclaw.ai/tools/exa-search)               | Structured + extracted     | Neural/keyword mode, date, content extraction    | `EXA_API_KEY`                               |
| [Firecrawl](https://docs.openclaw.ai/tools/firecrawl)          | Structured snippets        | Via `firecrawl_search` tool                      | `FIRECRAWL_API_KEY`                         |
| [Gemini](https://docs.openclaw.ai/tools/gemini-search)         | AI-synthesized + citations | —                                                | `GEMINI_API_KEY`                            |
| [Grok](https://docs.openclaw.ai/tools/grok-search)             | AI-synthesized + citations | —                                                | `XAI_API_KEY`                               |
| [Kimi](https://docs.openclaw.ai/tools/kimi-search)             | AI-synthesized + citations | —                                                | `KIMI_API_KEY` / `MOONSHOT_API_KEY`         |
| [Perplexity](https://docs.openclaw.ai/tools/perplexity-search) | Structured snippets        | Country, language, time, domains, content limits | `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` |
| [Tavily](https://docs.openclaw.ai/tools/tavily)                | Structured snippets        | Via `tavily_search` tool                         | `TAVILY_API_KEY`                            |

## Auto-detection

Provider lists in docs and setup flows are alphabetical. Auto-detection keeps a separate precedence order: If no `provider` is set, OpenClaw checks for API keys in this order and uses the first one found:

1. **Brave** — `BRAVE_API_KEY` or `plugins.entries.brave.config.webSearch.apiKey`
2. **Gemini** — `GEMINI_API_KEY` or `plugins.entries.google.config.webSearch.apiKey`
3. **Grok** — `XAI_API_KEY` or `plugins.entries.xai.config.webSearch.apiKey`
4. **Kimi** — `KIMI_API_KEY` / `MOONSHOT_API_KEY` or `plugins.entries.moonshot.config.webSearch.apiKey`
5. **Perplexity** — `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` or `plugins.entries.perplexity.config.webSearch.apiKey`
6. **Firecrawl** — `FIRECRAWL_API_KEY` or `plugins.entries.firecrawl.config.webSearch.apiKey`
7. **Tavily** — `TAVILY_API_KEY` or `plugins.entries.tavily.config.webSearch.apiKey`

If no keys are found, it falls back to Brave (you will get a missing-key error prompting you to configure one).

## Config

Provider-specific config (API keys, base URLs, modes) lives under `plugins.entries.<plugin>.config.webSearch.*`. See the provider pages for examples.

### Storing API keys

## Tool parameters

| Parameter             | Description                                           |
| --------------------- | ----------------------------------------------------- |
| `query`               | Search query (required)                               |
| `count`               | Results to return (1-10, default: 5)                  |
| `country`             | 2-letter ISO country code (e.g. “US”, “DE”)           |
| `language`            | ISO 639-1 language code (e.g. “en”, “de”)             |
| `freshness`           | Time filter: `day`, `week`, `month`, or `year`        |
| `date_after`          | Results after this date (YYYY-MM-DD)                  |
| `date_before`         | Results before this date (YYYY-MM-DD)                 |
| `ui_lang`             | UI language code (Brave only)                         |
| `domain_filter`       | Domain allowlist/denylist array (Perplexity only)     |
| `max_tokens`          | Total content budget, default 25000 (Perplexity only) |
| `max_tokens_per_page` | Per-page token limit, default 2048 (Perplexity only)  |

## Examples

## Tool profiles

If you use tool profiles or allowlists, add `web_search` or `group:web`:

----
url: https://docs.openclaw.ai/providers/claude-max-api-proxy
----

# Claude Max API Proxy - OpenClaw

**claude-max-api-proxy** is a community tool that exposes your Claude Max/Pro subscription as an OpenAI-compatible API endpoint. This allows you to use your subscription with any tool that supports the OpenAI API format.

## Why Use This?

| Approach                | Cost                                   | Best For                                   |
| ----------------------- | -------------------------------------- | ------------------------------------------ |
| Anthropic API           | Pay per token (\~75/M output for Opus) | Production apps, high volume               |
| Claude Max subscription | $200/month flat                        | Personal use, development, unlimited usage |

If you have a Claude Max subscription and want to use it with OpenAI-compatible tools, this proxy may reduce cost for some workflows. API keys remain the clearer policy path for production use.

## How It Works

The proxy:

1. Accepts OpenAI-format requests at `http://localhost:3456/v1/chat/completions`
2. Converts them to Claude Code CLI commands
3. Returns responses in OpenAI format (streaming supported)

## Installation

## Usage

### Start the server

### Test it

### With OpenClaw

You can point OpenClaw at the proxy as a custom OpenAI-compatible endpoint:

## Available Models

| Model ID          | Maps To         |
| ----------------- | --------------- |
| `claude-opus-4`   | Claude Opus 4   |
| `claude-sonnet-4` | Claude Sonnet 4 |
| `claude-haiku-4`  | Claude Haiku 4  |

## Auto-Start on macOS

Create a LaunchAgent to run the proxy automatically:

## Links

## Notes

## See Also

----
url: https://docs.openclaw.ai/reference/templates/IDENTITY
----

# IDENTITY Template - OpenClaw

##### CLI commands

##### RPC and API

* [RPC Adapters](https://docs.openclaw.ai/reference/rpc)
* [Device Model Database](https://docs.openclaw.ai/reference/device-models)

##### Templates

##### Technical reference

##### Concept internals

* [TypeBox](https://docs.openclaw.ai/concepts/typebox)
* [Markdown Formatting](https://docs.openclaw.ai/concepts/markdown-formatting)
* [Typing Indicators](https://docs.openclaw.ai/concepts/typing-indicators)
* [Usage Tracking](https://docs.openclaw.ai/concepts/usage-tracking)
* [Timezones](https://docs.openclaw.ai/concepts/timezone)

##### Project

* [Credits](https://docs.openclaw.ai/reference/credits)

##### Release policy

* [Release Policy](https://docs.openclaw.ai/reference/RELEASING)
* [Tests](https://docs.openclaw.ai/reference/test)

- [IDENTITY.md - Who Am I?](#identity-md-who-am-i)

## [​](#identity-md-who-am-i)IDENTITY.md - Who Am I?

*Fill this in during your first conversation. Make it yours.*

* **Name:** *(pick something you like)*
* **Creature:** *(AI? robot? familiar? ghost in the machine? something weirder?)*
* **Vibe:** *(how do you come across? sharp? warm? chaotic? calm?)*
* **Emoji:** *(your signature — pick one that feels right)*
* **Avatar:** *(workspace-relative path, http(s) URL, or data URI)*

***

This isn’t just metadata. It’s the start of figuring out who you are. Notes:

* Save this file at the workspace root as `IDENTITY.md`.
* For avatars, use a workspace-relative path like `avatars/openclaw.png`.

[HEARTBEAT.md Template](https://docs.openclaw.ai/reference/templates/HEARTBEAT)[SOUL.md Template](https://docs.openclaw.ai/reference/templates/SOUL)

----
url: https://docs.openclaw.ai/channels/troubleshooting
----

# Channel Troubleshooting - OpenClaw

Use this page when a channel connects but behavior is wrong.

## Command ladder

Run these in order first:

Healthy baseline:

## WhatsApp

### WhatsApp failure signatures

| Symptom                         | Fastest check                                       | Fix                                                     |
| ------------------------------- | --------------------------------------------------- | ------------------------------------------------------- |
| Connected but no DM replies     | `openclaw pairing list whatsapp`                    | Approve sender or switch DM policy/allowlist.           |
| Group messages ignored          | Check `requireMention` + mention patterns in config | Mention the bot or relax mention policy for that group. |
| Random disconnect/relogin loops | `openclaw channels status --probe` + logs           | Re-login and verify credentials directory is healthy.   |

Full troubleshooting: [/channels/whatsapp#troubleshooting](https://docs.openclaw.ai/channels/whatsapp#troubleshooting)

## Telegram

### Telegram failure signatures

| Symptom                             | Fastest check                                   | Fix                                                                         |
| ----------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------- |
| `/start` but no usable reply flow   | `openclaw pairing list telegram`                | Approve pairing or change DM policy.                                        |
| Bot online but group stays silent   | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot.                   |
| Send failures with network errors   | Inspect logs for Telegram API call failures     | Fix DNS/IPv6/proxy routing to `api.telegram.org`.                           |
| `setMyCommands` rejected at startup | Inspect logs for `BOT_COMMANDS_TOO_MUCH`        | Reduce plugin/skill/custom Telegram commands or disable native menus.       |
| Upgraded and allowlist blocks you   | `openclaw security audit` and config allowlists | Run `openclaw doctor --fix` or replace `@username` with numeric sender IDs. |

Full troubleshooting: [/channels/telegram#troubleshooting](https://docs.openclaw.ai/channels/telegram#troubleshooting)

## Discord

### Discord failure signatures

| Symptom                         | Fastest check                       | Fix                                                       |
| ------------------------------- | ----------------------------------- | --------------------------------------------------------- |
| Bot online but no guild replies | `openclaw channels status --probe`  | Allow guild/channel and verify message content intent.    |
| Group messages ignored          | Check logs for mention gating drops | Mention bot or set guild/channel `requireMention: false`. |
| DM replies missing              | `openclaw pairing list discord`     | Approve DM pairing or adjust DM policy.                   |

Full troubleshooting: [/channels/discord#troubleshooting](https://docs.openclaw.ai/channels/discord#troubleshooting)

## Slack

### Slack failure signatures

| Symptom                                | Fastest check                             | Fix                                               |
| -------------------------------------- | ----------------------------------------- | ------------------------------------------------- |
| Socket mode connected but no responses | `openclaw channels status --probe`        | Verify app token + bot token and required scopes. |
| DMs blocked                            | `openclaw pairing list slack`             | Approve pairing or relax DM policy.               |
| Channel message ignored                | Check `groupPolicy` and channel allowlist | Allow the channel or switch policy to `open`.     |

Full troubleshooting: [/channels/slack#troubleshooting](https://docs.openclaw.ai/channels/slack#troubleshooting)

## iMessage and BlueBubbles

### iMessage and BlueBubbles failure signatures

| Symptom                          | Fastest check                                                           | Fix                                                   |
| -------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- |
| No inbound events                | Verify webhook/server reachability and app permissions                  | Fix webhook URL or BlueBubbles server state.          |
| Can send but no receive on macOS | Check macOS privacy permissions for Messages automation                 | Re-grant TCC permissions and restart channel process. |
| DM sender blocked                | `openclaw pairing list imessage` or `openclaw pairing list bluebubbles` | Approve pairing or update allowlist.                  |

Full troubleshooting:

## Signal

### Signal failure signatures

| Symptom                         | Fastest check                              | Fix                                                      |
| ------------------------------- | ------------------------------------------ | -------------------------------------------------------- |
| Daemon reachable but bot silent | `openclaw channels status --probe`         | Verify `signal-cli` daemon URL/account and receive mode. |
| DM blocked                      | `openclaw pairing list signal`             | Approve sender or adjust DM policy.                      |
| Group replies do not trigger    | Check group allowlist and mention patterns | Add sender/group or loosen gating.                       |

Full troubleshooting: [/channels/signal#troubleshooting](https://docs.openclaw.ai/channels/signal#troubleshooting)

## Matrix

### Matrix failure signatures

| Symptom                             | Fastest check                                | Fix                                             |
| ----------------------------------- | -------------------------------------------- | ----------------------------------------------- |
| Logged in but ignores room messages | `openclaw channels status --probe`           | Check `groupPolicy` and room allowlist.         |
| DMs do not process                  | `openclaw pairing list matrix`               | Approve sender or adjust DM policy.             |
| Encrypted rooms fail                | Verify crypto module and encryption settings | Enable encryption support and rejoin/sync room. |

Full setup and config: [Matrix](https://docs.openclaw.ai/channels/matrix)

----
url: https://docs.openclaw.ai/start/docs-directory
----

[Skip to main content](#content-area)

[OpenClaw home page](/)

[Get started](/)[Install](/install)[Channels](/channels)[Agents](/concepts/architecture)[Tools & Plugins](/tools)[Models](/providers)[Platforms](/platforms)[Gateway & Ops](/gateway)[Reference](/cli)[Help](/help)

##### Help

* [Help](/help)
* [General Troubleshooting](/help/troubleshooting)
* [FAQ](/help/faq)

##### Community

* [OpenClaw Lore](/start/lore)

##### Environment and debugging

* [Environment Variables](/help/environment)
* [Debugging](/help/debugging)
* [Testing](/help/testing)
* [Scripts](/help/scripts)
* [Node + tsx Crash](/debug/node-issue)
* [Diagnostics Flags](/diagnostics/flags)

##### Compaction internals

* [Session Management Deep Dive](/reference/session-management-compaction)

##### Developer setup

* [Setup](/start/setup)
* [Pi Development Workflow](/pi-dev)

##### Contributing

* [CI Pipeline](/ci)

##### Docs meta

* [Docs Hubs](/start/hubs)
* [Docs directory](/start/docs-directory)

- [Docs Directory](#docs-directory)
- [Start here](#start-here)
- [Providers and UX](#providers-and-ux)
- [Companion apps](#companion-apps)
- [Operations and safety](#operations-and-safety)

# [​](#docs-directory)Docs Directory

This page is a curated index. If you are new, start with [Getting Started](/start/getting-started). For a complete map of the docs, see [Docs hubs](/start/hubs).

## [​](#start-here)Start here

* [Docs hubs (all pages linked)](/start/hubs)
* [Help](/help)
* [Configuration](/gateway/configuration)
* [Configuration examples](/gateway/configuration-examples)
* [Slash commands](/tools/slash-commands)
* [Multi-agent routing](/concepts/multi-agent)
* [Updating and rollback](/install/updating)
* [Pairing (DM and nodes)](/channels/pairing)
* [Nix mode](/install/nix)
* [OpenClaw assistant setup](/start/openclaw)
* [Skills](/tools/skills)
* [Skills config](/tools/skills-config)
* [Workspace templates](/reference/templates/AGENTS)
* [RPC adapters](/reference/rpc)
* [Gateway runbook](/gateway)
* [Nodes (iOS and Android)](/nodes)
* [Web surfaces (Control UI)](/web)
* [Discovery and transports](/gateway/discovery)
* [Remote access](/gateway/remote)

## [​](#providers-and-ux)Providers and UX

* [WebChat](/web/webchat)
* [Control UI (browser)](/web/control-ui)
* [Telegram](/channels/telegram)
* [Discord](/channels/discord)
* [Mattermost (plugin)](/channels/mattermost)
* [BlueBubbles (iMessage)](/channels/bluebubbles)
* [iMessage (legacy)](/channels/imessage)
* [Groups](/channels/groups)
* [WhatsApp group messages](/channels/group-messages)
* [Media images](/nodes/images)
* [Media audio](/nodes/audio)

## [​](#companion-apps)Companion apps

* [macOS app](/platforms/macos)
* [iOS app](/platforms/ios)
* [Android app](/platforms/android)
* [Windows (WSL2)](/platforms/windows)
* [Linux app](/platforms/linux)

## [​](#operations-and-safety)Operations and safety

* [Sessions](/concepts/session)
* [Cron jobs](/automation/cron-jobs)
* [Webhooks](/automation/webhook)
* [Gmail hooks (Pub/Sub)](/automation/gmail-pubsub)
* [Security](/gateway/security)
* [Troubleshooting](/gateway/troubleshooting)

[Docs Hubs](/start/hubs)

⌘I

----
url: https://docs.openclaw.ai/reference/token-use
----

# Token Use and Costs - OpenClaw

OpenClaw tracks **tokens**, not characters. Tokens are model-specific, but most OpenAI-style models average \~4 characters per token for English text.

## How the system prompt is built

OpenClaw assembles its own system prompt on every run. It includes:

See the full breakdown in [System Prompt](https://docs.openclaw.ai/concepts/system-prompt).

## What counts in the context window

Everything the model receives counts toward the context limit:

For images, OpenClaw downscales transcript/tool image payloads before provider calls. Use `agents.defaults.imageMaxDimensionPx` (default: `1200`) to tune this:

For a practical breakdown (per injected file, tools, skills, and system prompt size), use `/context list` or `/context detail`. See [Context](https://docs.openclaw.ai/concepts/context).

## How to see current token usage

Use these in chat:

Other surfaces:

## Cost estimation (when shown)

Costs are estimated from your model pricing config:

These are **USD per 1M tokens** for `input`, `output`, `cacheRead`, and `cacheWrite`. If pricing is missing, OpenClaw shows tokens only. OAuth tokens never show dollar cost.

## Cache TTL and pruning impact

Provider prompt caching only applies within the cache TTL window. OpenClaw can optionally run **cache-ttl pruning**: it prunes the session once the cache TTL has expired, then resets the cache window so subsequent requests can re-use the freshly cached context instead of re-caching the full history. This keeps cache write costs lower when a session goes idle past the TTL. Configure it in [Gateway configuration](https://docs.openclaw.ai/gateway/configuration) and see the behavior details in [Session pruning](https://docs.openclaw.ai/concepts/session-pruning). Heartbeat can keep the cache **warm** across idle gaps. If your model cache TTL is `1h`, setting the heartbeat interval just under that (e.g., `55m`) can avoid re-caching the full prompt, reducing cache write costs. In multi-agent setups, you can keep one shared model config and tune cache behavior per agent with `agents.list[].params.cacheRetention`. For a full knob-by-knob guide, see [Prompt Caching](https://docs.openclaw.ai/reference/prompt-caching). For Anthropic API pricing, cache reads are significantly cheaper than input tokens, while cache writes are billed at a higher multiplier. See Anthropic’s prompt caching pricing for the latest rates and TTL multipliers: <https://docs.anthropic.com/docs/build-with-claude/prompt-caching>

### Example: keep 1h cache warm with heartbeat

### Example: mixed traffic with per-agent cache strategy

`agents.list[].params` merges on top of the selected model’s `params`, so you can override only `cacheRetention` and inherit other model defaults unchanged.

Anthropic’s 1M context window is currently beta-gated. OpenClaw can inject the required `anthropic-beta` value when you enable `context1m` on supported Opus or Sonnet models.

This maps to Anthropic’s `context-1m-2025-08-07` beta header. This only applies when `context1m: true` is set on that model entry. Requirement: the credential must be eligible for long-context usage (API key billing, or subscription with Extra Usage enabled). If not, Anthropic responds with `HTTP 429: rate_limit_error: Extra usage is required for long context requests`. If you authenticate Anthropic with OAuth/subscription tokens (`sk-ant-oat-*`), OpenClaw skips the `context-1m-*` beta header because Anthropic currently rejects that combination with HTTP 401.

## Tips for reducing token pressure

See [Skills](https://docs.openclaw.ai/tools/skills) for the exact skill list overhead formula.

----
url: https://docs.openclaw.ai/tools/llm-task
----

# LLM Task - OpenClaw

`llm-task` is an **optional plugin tool** that runs a JSON-only LLM task and returns structured output (optionally validated against JSON Schema). This is ideal for workflow engines like Lobster: you can add a single LLM step without writing custom OpenClaw code for each workflow.

## Enable the plugin

1. Enable the plugin:

2) Allowlist the tool (it is registered with `optional: true`):

## Config (optional)

`allowedModels` is an allowlist of `provider/model` strings. If set, any request outside the list is rejected.

## Tool parameters

* `prompt` (string, required)
* `input` (any, optional)
* `schema` (object, optional JSON Schema)
* `provider` (string, optional)
* `model` (string, optional)
* `thinking` (string, optional)
* `authProfileId` (string, optional)
* `temperature` (number, optional)
* `maxTokens` (number, optional)
* `timeoutMs` (number, optional)

`thinking` accepts the standard OpenClaw reasoning presets, such as `low` or `medium`.

## Output

Returns `details.json` containing the parsed JSON (and validates against `schema` when provided).

## Example: Lobster workflow step

```
openclaw.invoke --tool llm-task --action json --args-json '{
  "prompt": "Given the input email, return intent and draft.",
  "thinking": "low",
  "input": {
    "subject": "Hello",
    "body": "Can you help?"
  },
  "schema": {
    "type": "object",
    "properties": {
      "intent": { "type": "string" },
      "draft": { "type": "string" }
    },
    "required": ["intent", "draft"],
    "additionalProperties": false
  }
}'
```

## Safety notes

----
url: https://docs.openclaw.ai/reference/rpc
----

# RPC Adapters - OpenClaw

## [​](#rpc-adapters)RPC adapters

OpenClaw integrates external CLIs via JSON-RPC. Two patterns are used today.

## [​](#pattern-a-http-daemon-signal-cli)Pattern A: HTTP daemon (signal-cli)

* `signal-cli` runs as a daemon with JSON-RPC over HTTP.
* Event stream is SSE (`/api/v1/events`).
* Health probe: `/api/v1/check`.
* OpenClaw owns lifecycle when `channels.signal.autoStart=true`.

See [Signal](https://docs.openclaw.ai/channels/signal) for setup and endpoints.

## [​](#pattern-b-stdio-child-process-legacy-imsg)Pattern B: stdio child process (legacy: imsg)

> **Note:** For new iMessage setups, use [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) instead.

* OpenClaw spawns `imsg rpc` as a child process (legacy iMessage integration).
* JSON-RPC is line-delimited over stdin/stdout (one JSON object per line).
* No TCP port, no daemon required.

Core methods used:

* `watch.subscribe` → notifications (`method: "message"`)
* `watch.unsubscribe`
* `send`
* `chats.list` (probe/diagnostics)

See [iMessage](https://docs.openclaw.ai/channels/imessage) for legacy setup and addressing (`chat_id` preferred).

## [​](#adapter-guidelines)Adapter guidelines

* Gateway owns the process (start/stop tied to provider lifecycle).
* Keep RPC clients resilient: timeouts, restart on exit.
* Prefer stable IDs (e.g., `chat_id`) over display strings.

----
url: https://docs.openclaw.ai/cli/voicecall
----

# voicecall - OpenClaw

## [​](#openclaw-voicecall)`openclaw voicecall`

`voicecall` is a plugin-provided command. It only appears if the voice-call plugin is installed and enabled. Primary doc:

* Voice-call plugin: [Voice Call](https://docs.openclaw.ai/plugins/voice-call)

## [​](#common-commands)Common commands

```
openclaw voicecall status --call-id <id>
openclaw voicecall call --to "+15555550123" --message "Hello" --mode notify
openclaw voicecall continue --call-id <id> --message "Any questions?"
openclaw voicecall end --call-id <id>
```

## [​](#exposing-webhooks-tailscale)Exposing webhooks (Tailscale)

```
openclaw voicecall expose --mode serve
openclaw voicecall expose --mode funnel
openclaw voicecall expose --mode off
```

Security note: only expose the webhook endpoint to networks you trust. Prefer Tailscale Serve over Funnel when possible.

----
url: https://docs.openclaw.ai/pi
----

# Pi Integration Architecture - OpenClaw

This document describes how OpenClaw integrates with [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent) and its sibling packages (`pi-ai`, `pi-agent-core`, `pi-tui`) to power its AI agent capabilities.

## Overview

OpenClaw uses the pi SDK to embed an AI coding agent into its messaging gateway architecture. Instead of spawning pi as a subprocess or using RPC mode, OpenClaw directly imports and instantiates pi’s `AgentSession` via `createAgentSession()`. This embedded approach provides:

## Package Dependencies

| Package           | Purpose                                                                                                |
| ----------------- | ------------------------------------------------------------------------------------------------------ |
| `pi-ai`           | Core LLM abstractions: `Model`, `streamSimple`, message types, provider APIs                           |
| `pi-agent-core`   | Agent loop, tool execution, `AgentMessage` types                                                       |
| `pi-coding-agent` | High-level SDK: `createAgentSession`, `SessionManager`, `AuthStorage`, `ModelRegistry`, built-in tools |
| `pi-tui`          | Terminal UI components (used in OpenClaw’s local TUI mode)                                             |

## File Structure

Channel-specific message action runtimes now live in the plugin-owned extension directories instead of under `src/agents/tools`, for example:

## Core Integration Flow

### 1. Running an Embedded Agent

The main entry point is `runEmbeddedPiAgent()` in `pi-embedded-runner/run.ts`:

```
import { runEmbeddedPiAgent } from "./agents/pi-embedded-runner.js";

const result = await runEmbeddedPiAgent({
  sessionId: "user-123",
  sessionKey: "main:whatsapp:+1234567890",
  sessionFile: "/path/to/session.jsonl",
  workspaceDir: "/path/to/workspace",
  config: openclawConfig,
  prompt: "Hello, how are you?",
  provider: "anthropic",
  model: "claude-sonnet-4-20250514",
  timeoutMs: 120_000,
  runId: "run-abc",
  onBlockReply: async (payload) => {
    await sendToChannel(payload.text, payload.mediaUrls);
  },
});
```

### 2. Session Creation

Inside `runEmbeddedAttempt()` (called by `runEmbeddedPiAgent()`), the pi SDK is used:

```
import {
  createAgentSession,
  DefaultResourceLoader,
  SessionManager,
  SettingsManager,
} from "@mariozechner/pi-coding-agent";

const resourceLoader = new DefaultResourceLoader({
  cwd: resolvedWorkspace,
  agentDir,
  settingsManager,
  additionalExtensionPaths,
});
await resourceLoader.reload();

const { session } = await createAgentSession({
  cwd: resolvedWorkspace,
  agentDir,
  authStorage: params.authStorage,
  modelRegistry: params.modelRegistry,
  model: params.model,
  thinkingLevel: mapThinkingLevel(params.thinkLevel),
  tools: builtInTools,
  customTools: allCustomTools,
  sessionManager,
  settingsManager,
  resourceLoader,
});

applySystemPromptOverrideToSession(session, systemPromptOverride);
```

### 3. Event Subscription

`subscribeEmbeddedPiSession()` subscribes to pi’s `AgentSession` events:

```
const subscription = subscribeEmbeddedPiSession({
  session: activeSession,
  runId: params.runId,
  verboseLevel: params.verboseLevel,
  reasoningMode: params.reasoningLevel,
  toolResultFormat: params.toolResultFormat,
  onToolResult: params.onToolResult,
  onReasoningStream: params.onReasoningStream,
  onBlockReply: params.onBlockReply,
  onPartialReply: params.onPartialReply,
  onAgentEvent: params.onAgentEvent,
});
```

Events handled include:

### 4. Prompting

After setup, the session is prompted:

The SDK handles the full agent loop: sending to LLM, executing tool calls, streaming responses. Image injection is prompt-local: OpenClaw loads image refs from the current prompt and passes them via `images` for that turn only. It does not re-scan older history turns to re-inject image payloads.

## Tool Architecture

### Tool Pipeline

1. **Base Tools**: pi’s `codingTools` (read, bash, edit, write)
2. **Custom Replacements**: OpenClaw replaces bash with `exec`/`process`, customizes read/edit/write for sandbox
3. **OpenClaw Tools**: messaging, browser, canvas, sessions, cron, gateway, etc.
4. **Channel Tools**: Discord/Telegram/Slack/WhatsApp-specific action tools
5. **Policy Filtering**: Tools filtered by profile, provider, agent, group, sandbox policies
6. **Schema Normalization**: Schemas cleaned for Gemini/OpenAI quirks
7. **AbortSignal Wrapping**: Tools wrapped to respect abort signals

### Tool Definition Adapter

pi-agent-core’s `AgentTool` has a different `execute` signature than pi-coding-agent’s `ToolDefinition`. The adapter in `pi-tool-definition-adapter.ts` bridges this:

```
export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
  return tools.map((tool) => ({
    name: tool.name,
    label: tool.label ?? name,
    description: tool.description ?? "",
    parameters: tool.parameters,
    execute: async (toolCallId, params, onUpdate, _ctx, signal) => {
      // pi-coding-agent signature differs from pi-agent-core
      return await tool.execute(toolCallId, params, signal, onUpdate);
    },
  }));
}
```

### Tool Split Strategy

`splitSdkTools()` passes all tools via `customTools`:

This ensures OpenClaw’s policy filtering, sandbox integration, and extended toolset remain consistent across providers.

## System Prompt Construction

The system prompt is built in `buildAgentSystemPrompt()` (`system-prompt.ts`). It assembles a full prompt with sections including Tooling, Tool Call Style, Safety guardrails, OpenClaw CLI reference, Skills, Docs, Workspace, Sandbox, Messaging, Reply Tags, Voice, Silent Replies, Heartbeats, Runtime metadata, plus Memory and Reactions when enabled, and optional context files and extra system prompt content. Sections are trimmed for minimal prompt mode used by subagents. The prompt is applied after session creation via `applySystemPromptOverrideToSession()`:

## Session Management

### Session Files

Sessions are JSONL files with tree structure (id/parentId linking). Pi’s `SessionManager` handles persistence:

OpenClaw wraps this with `guardSessionManager()` for tool result safety.

### Session Caching

`session-manager-cache.ts` caches SessionManager instances to avoid repeated file parsing:

### History Limiting

`limitHistoryTurns()` trims conversation history based on channel type (DM vs group).

### Compaction

Auto-compaction triggers on context overflow. `compactEmbeddedPiSessionDirect()` handles manual compaction:

## Authentication & Model Resolution

### Auth Profiles

OpenClaw maintains an auth profile store with multiple API keys per provider:

Profiles rotate on failures with cooldown tracking:

### Model Resolution

### Failover

`FailoverError` triggers model fallback when configured:

## Pi Extensions

OpenClaw loads custom pi extensions for specialized behavior:

### Compaction Safeguard

`src/agents/pi-extensions/compaction-safeguard.ts` adds guardrails to compaction, including adaptive token budgeting plus tool failure and file operation summaries:

### Context Pruning

`src/agents/pi-extensions/context-pruning.ts` implements cache-TTL based context pruning:

### Block Chunking

`EmbeddedBlockChunker` manages streaming text into discrete reply blocks:

### Thinking/Final Tag Stripping

Streaming output is processed to strip `<think>`/`<thinking>` blocks and extract `<final>` content:

### Reply Directives

Reply directives like `[[media:url]]`, `[[voice]]`, `[[reply:id]]` are parsed and extracted:

## Error Handling

### Error Classification

`pi-embedded-helpers.ts` classifies errors for appropriate handling:

### Thinking Level Fallback

If a thinking level is unsupported, it falls back:

## Sandbox Integration

When sandbox mode is enabled, tools and paths are constrained:

## Provider-Specific Handling

### Anthropic

### Google/Gemini

### OpenAI

## TUI Integration

OpenClaw also has a local TUI mode that uses pi-tui components directly:

This provides the interactive terminal experience similar to pi’s native mode.

## Key Differences from Pi CLI

| Aspect          | Pi CLI                  | OpenClaw Embedded                                                                              |
| --------------- | ----------------------- | ---------------------------------------------------------------------------------------------- |
| Invocation      | `pi` command / RPC      | SDK via `createAgentSession()`                                                                 |
| Tools           | Default coding tools    | Custom OpenClaw tool suite                                                                     |
| System prompt   | AGENTS.md + prompts     | Dynamic per-channel/context                                                                    |
| Session storage | `~/.pi/agent/sessions/` | `~/.openclaw/agents/<agentId>/sessions/` (or `$OPENCLAW_STATE_DIR/agents/<agentId>/sessions/`) |
| Auth            | Single credential       | Multi-profile with rotation                                                                    |
| Extensions      | Loaded from disk        | Programmatic + disk paths                                                                      |
| Event handling  | TUI rendering           | Callback-based (onBlockReply, etc.)                                                            |

## Future Considerations

Areas for potential rework:

1. **Tool signature alignment**: Currently adapting between pi-agent-core and pi-coding-agent signatures
2. **Session manager wrapping**: `guardSessionManager` adds safety but increases complexity
3. **Extension loading**: Could use pi’s `ResourceLoader` more directly
4. **Streaming handler complexity**: `subscribeEmbeddedPiSession` has grown large
5. **Provider quirks**: Many provider-specific codepaths that pi could potentially handle

## Tests

Pi integration coverage spans these suites:

Live/opt-in:

For current run commands, see [Pi Development Workflow](https://docs.openclaw.ai/pi-dev).

----
url: https://docs.openclaw.ai/tools/diffs
----

# Diffs - OpenClaw

`diffs` is an optional plugin tool with short built-in system guidance and a companion skill that turns change content into a read-only diff artifact for agents. It accepts either:

It can return:

When enabled, the plugin prepends concise usage guidance into system-prompt space and also exposes a detailed skill for cases where the agent needs fuller instructions.

## Quick start

1. Enable the plugin.
2. Call `diffs` with `mode: "view"` for canvas-first flows.
3. Call `diffs` with `mode: "file"` for chat file delivery flows.
4. Call `diffs` with `mode: "both"` when you need both artifacts.

## Enable the plugin

## Disable built-in system guidance

If you want to keep the `diffs` tool enabled but disable its built-in system-prompt guidance, set `plugins.entries.diffs.hooks.allowPromptInjection` to `false`:

This blocks the diffs plugin’s `before_prompt_build` hook while keeping the plugin, tool, and companion skill available. If you want to disable both the guidance and the tool, disable the plugin instead.

## Typical agent workflow

1. Agent calls `diffs`.
2. Agent reads `details` fields.
3. Agent either:

## Input examples

Before and after:

Patch:

## Tool input reference

All fields are optional unless noted:

Validation and limits:

## Output details contract

The tool returns structured metadata under `details`. Shared fields for modes that create a viewer:

File fields when PNG or PDF is rendered:

Mode behavior summary:

## Collapsed unchanged sections

## Plugin defaults

Set plugin-wide defaults in `~/.openclaw/openclaw.json`:

```
{
  plugins: {
    entries: {
      diffs: {
        enabled: true,
        config: {
          defaults: {
            fontFamily: "Fira Code",
            fontSize: 15,
            lineSpacing: 1.6,
            layout: "unified",
            showLineNumbers: true,
            diffIndicators: "bars",
            wordWrap: true,
            background: true,
            theme: "dark",
            fileFormat: "png",
            fileQuality: "standard",
            fileScale: 2,
            fileMaxWidth: 960,
            mode: "both",
          },
        },
      },
    },
  },
}
```

Supported defaults:

Explicit tool parameters override these defaults.

## Security config

Example:

## Artifact lifecycle and storage

## Viewer URL and network behavior

Viewer route:

Viewer assets:

URL construction behavior:

`baseUrl` rules:

## Security model

Viewer hardening:

File rendering hardening:

## Browser requirements for file mode

`mode: "file"` and `mode: "both"` need a Chromium-compatible browser. Resolution order:

1. `browser.executablePath` in OpenClaw config.
2. Environment variables:
3. Platform command/path discovery fallback.

Common failure text:

Fix by installing Chrome, Chromium, Edge, or Brave, or setting one of the executable path options above.

## Troubleshooting

Input validation errors:

Viewer accessibility issues:

Unmodified-lines row has no expand button:

Artifact not found:

## Operational guidance

Diff rendering engine:

----
url: https://docs.openclaw.ai/tools/reactions
----

# Reactions - OpenClaw

##### Overview

* [Tools and Plugins](https://docs.openclaw.ai/tools)

##### Plugins

* [Install and Configure](https://docs.openclaw.ai/tools/plugin)
* [Community Plugins](https://docs.openclaw.ai/plugins/community)
* [Plugin Bundles](https://docs.openclaw.ai/plugins/bundles)
*
*

##### Skills

* [Skills](https://docs.openclaw.ai/tools/skills)
* [Creating Skills](https://docs.openclaw.ai/tools/creating-skills)
* [Skills Config](https://docs.openclaw.ai/tools/skills-config)
* [Slash Commands](https://docs.openclaw.ai/tools/slash-commands)
* [ClawHub](https://docs.openclaw.ai/tools/clawhub)
* [OpenProse](https://docs.openclaw.ai/prose)

##### Automation

* [Hooks](https://docs.openclaw.ai/automation/hooks)
* [Standing Orders](https://docs.openclaw.ai/automation/standing-orders)
* [Cron Jobs](https://docs.openclaw.ai/automation/cron-jobs)
* [Cron vs Heartbeat](https://docs.openclaw.ai/automation/cron-vs-heartbeat)
* [Automation Troubleshooting](https://docs.openclaw.ai/automation/troubleshooting)
* [Webhooks](https://docs.openclaw.ai/automation/webhook)
* [Gmail PubSub](https://docs.openclaw.ai/automation/gmail-pubsub)
* [Polls](https://docs.openclaw.ai/automation/poll)
* [Auth Monitoring](https://docs.openclaw.ai/automation/auth-monitoring)

##### Tools

* [apply\_patch Tool](https://docs.openclaw.ai/tools/apply-patch)
*
*
* [BTW Side Questions](https://docs.openclaw.ai/tools/btw)
* [Diffs](https://docs.openclaw.ai/tools/diffs)
* [Elevated Mode](https://docs.openclaw.ai/tools/elevated)
* [Exec Tool](https://docs.openclaw.ai/tools/exec)
* [Exec Approvals](https://docs.openclaw.ai/tools/exec-approvals)
* [LLM Task](https://docs.openclaw.ai/tools/llm-task)
* [Lobster](https://docs.openclaw.ai/tools/lobster)
* [Tool-loop detection](https://docs.openclaw.ai/tools/loop-detection)
* [PDF Tool](https://docs.openclaw.ai/tools/pdf)
* [Reactions](https://docs.openclaw.ai/tools/reactions)
* [Thinking Levels](https://docs.openclaw.ai/tools/thinking)

##### Agent coordination

* [Agent Send](https://docs.openclaw.ai/tools/agent-send)
* [Sub-Agents](https://docs.openclaw.ai/tools/subagents)
* [ACP Agents](https://docs.openclaw.ai/tools/acp-agents)
* [Multi-Agent Sandbox & Tools](https://docs.openclaw.ai/tools/multi-agent-sandbox-tools)

- [Reactions](#reactions)
- [How it works](#how-it-works)
- [Channel behavior](#channel-behavior)
- [Related](#related)

## [​](#reactions)Reactions

The agent can add and remove emoji reactions on messages using the `message` tool with the `react` action. Reaction behavior varies by channel.

## [​](#how-it-works)How it works

```
{
  "action": "react",
  "messageId": "msg-123",
  "emoji": "thumbsup"
}
```

* `emoji` is required when adding a reaction.
* Set `emoji` to an empty string (`""`) to remove the bot’s reaction(s).
* Set `remove: true` to remove a specific emoji (requires non-empty `emoji`).

## [​](#channel-behavior)Channel behavior

Discord and Slack

* Empty `emoji` removes all of the bot’s reactions on the message.
* `remove: true` removes just the specified emoji.

Google Chat

* Empty `emoji` removes the app’s reactions on the message.
* `remove: true` removes just the specified emoji.

Telegram

* Empty `emoji` removes the bot’s reactions.
* `remove: true` also removes reactions but still requires a non-empty `emoji` for tool validation.

WhatsApp

* Empty `emoji` removes the bot reaction.
* `remove: true` maps to empty emoji internally (still requires `emoji` in the tool call).

Zalo Personal (zalouser)

* Requires non-empty `emoji`.
* `remove: true` removes that specific emoji reaction.

Signal

* Inbound reaction notifications are controlled by `channels.signal.reactionNotifications`: `"off"` disables them, `"own"` (default) emits events when users react to bot messages, and `"all"` emits events for all reactions.

## [​](#related)Related

* [Agent Send](https://docs.openclaw.ai/tools/agent-send) — the `message` tool that includes `react`
* [Channels](https://docs.openclaw.ai/channels) — channel-specific configuration

[PDF Tool](https://docs.openclaw.ai/tools/pdf)[Thinking Levels](https://docs.openclaw.ai/tools/thinking)

----
url: https://docs.openclaw.ai/cli/backup
----

# backup - OpenClaw

Create a local backup archive for OpenClaw state, config, credentials, sessions, and optionally workspaces.

## Notes

## What gets backed up

`openclaw backup create` plans backup sources from your local OpenClaw install:

If you use `--only-config`, OpenClaw skips state, credentials, and workspace discovery and archives only the active config file path. OpenClaw canonicalizes paths before building the archive. If config, credentials, or a workspace already live inside the state directory, they are not duplicated as separate top-level backup sources. Missing paths are skipped. The archive payload stores file contents from those source trees, and the embedded `manifest.json` records the resolved absolute source paths plus the archive layout used for each asset.

## Invalid config behavior

`openclaw backup` intentionally bypasses the normal config preflight so it can still help during recovery. Because workspace discovery depends on a valid config, `openclaw backup create` now fails fast when the config file exists but is invalid and workspace backup is still enabled. If you still want a partial backup in that situation, rerun:

That keeps state, config, and credentials in scope while skipping workspace discovery entirely. If you only need a copy of the config file itself, `--only-config` also works when the config is malformed because it does not rely on parsing the config for workspace discovery.

## Size and performance

OpenClaw does not enforce a built-in maximum backup size or per-file size limit. Practical limits come from the local machine and destination filesystem:

Large workspaces are usually the main driver of archive size. If you want a smaller or faster backup, use `--no-include-workspace`. For the smallest archive, use `--only-config`.

----
url: https://docs.openclaw.ai/gateway/network-model
----

# Network model - OpenClaw

##### Gateway

##### Remote access

* [Remote Access](https://docs.openclaw.ai/gateway/remote)
* [Remote Gateway Setup](https://docs.openclaw.ai/gateway/remote-gateway-readme)
* [Tailscale](https://docs.openclaw.ai/gateway/tailscale)

##### Security

* [Formal Verification (Security Models)](https://docs.openclaw.ai/security/formal-verification)
* [Threat Model (MITRE ATLAS)](https://docs.openclaw.ai/security/THREAT-MODEL-ATLAS)
* [Contributing to the Threat Model](https://docs.openclaw.ai/security/CONTRIBUTING-THREAT-MODEL)

##### Nodes and devices

##### Web interfaces

* [Web](https://docs.openclaw.ai/web)
* [Control UI](https://docs.openclaw.ai/web/control-ui)
* [Dashboard](https://docs.openclaw.ai/web/dashboard)
* [WebChat](https://docs.openclaw.ai/web/webchat)
* [TUI](https://docs.openclaw.ai/web/tui)

- [Network Model](#network-model)
- [Core rules](#core-rules)

## [​](#network-model)Network Model

Most operations flow through the Gateway (`openclaw gateway`), a single long-running process that owns channel connections and the WebSocket control plane.

## [​](#core-rules)Core rules

* One Gateway per host is recommended. It is the only process allowed to own the WhatsApp Web session. For rescue bots or strict isolation, run multiple gateways with isolated profiles and ports. See [Multiple gateways](https://docs.openclaw.ai/gateway/multiple-gateways).

* Loopback first: the Gateway WS defaults to `ws://127.0.0.1:18789`. The wizard generates a gateway token by default, even for loopback. For tailnet access, run `openclaw gateway --bind tailnet --token ...` because tokens are required for non-loopback binds.

* Nodes connect to the Gateway WS over LAN, tailnet, or SSH as needed. The legacy TCP bridge is deprecated.

* Canvas host is served by the Gateway HTTP server on the **same port** as the Gateway (default `18789`):

  * `/__openclaw__/canvas/`
  * `/__openclaw__/a2ui/` When `gateway.auth` is configured and the Gateway binds beyond loopback, these routes are protected by Gateway auth. Node clients use node-scoped capability URLs tied to their active WS session. See [Gateway configuration](https://docs.openclaw.ai/gateway/configuration) (`canvasHost`, `gateway`).

* Remote use is typically SSH tunnel or tailnet VPN. See [Remote access](https://docs.openclaw.ai/gateway/remote) and [Discovery](https://docs.openclaw.ai/gateway/discovery).

[Network](https://docs.openclaw.ai/network)[Gateway-Owned Pairing](https://docs.openclaw.ai/gateway/pairing)

----
url: https://docs.openclaw.ai/start/wizard-cli-reference
----

# CLI Setup Reference - OpenClaw

This page is the full reference for `openclaw onboard`. For the short guide, see [Onboarding (CLI)](https://docs.openclaw.ai/start/wizard).

## What the wizard does

Local mode (default) walks you through:

* Model and auth setup (OpenAI Code subscription OAuth, Anthropic API key or setup token, plus MiniMax, GLM, Ollama, Moonshot, and AI Gateway options)
* Workspace location and bootstrap files
* Gateway settings (port, bind, auth, tailscale)
* Channels and providers (Telegram, WhatsApp, Discord, Google Chat, Mattermost plugin, Signal)
* Daemon install (LaunchAgent or systemd user unit)
* Health check
* Skills setup

Remote mode configures this machine to connect to a gateway elsewhere. It does not install or modify anything on the remote host.

## Local flow details

## Remote mode details

Remote mode configures this machine to connect to a gateway elsewhere.

What you set:

## Auth and model options

Model behavior:

Credential and profile paths:

Credential storage mode:

## Outputs and internals

Typical fields in `~/.openclaw/openclaw.json`:

`openclaw agents add` writes `agents.list[]` and optional `bindings`. WhatsApp credentials go under `~/.openclaw/credentials/whatsapp/<accountId>/`. Sessions are stored under `~/.openclaw/agents/<agentId>/sessions/`.

Gateway wizard RPC:

Clients (macOS app and Control UI) can render steps without re-implementing onboarding logic. Signal setup behavior:

----
url: https://docs.openclaw.ai/nodes/troubleshooting
----

# Node Troubleshooting - OpenClaw

Use this page when a node is visible in status but node tools fail.

## Command ladder

Then run node specific checks:

Healthy signals:

## Foreground requirements

`canvas.*`, `camera.*`, and `screen.*` are foreground only on iOS/Android nodes. Quick check and fix:

If you see `NODE_BACKGROUND_UNAVAILABLE`, bring the node app to the foreground and retry.

## Permissions matrix

| Capability                   | iOS                                     | Android                                      | macOS node app                | Typical failure code           |
| ---------------------------- | --------------------------------------- | -------------------------------------------- | ----------------------------- | ------------------------------ |
| `camera.snap`, `camera.clip` | Camera (+ mic for clip audio)           | Camera (+ mic for clip audio)                | Camera (+ mic for clip audio) | `*_PERMISSION_REQUIRED`        |
| `screen.record`              | Screen Recording (+ mic optional)       | Screen capture prompt (+ mic optional)       | Screen Recording              | `*_PERMISSION_REQUIRED`        |
| `location.get`               | While Using or Always (depends on mode) | Foreground/Background location based on mode | Location permission           | `LOCATION_PERMISSION_REQUIRED` |
| `system.run`                 | n/a (node host path)                    | n/a (node host path)                         | Exec approvals required       | `SYSTEM_RUN_DENIED`            |

## Pairing versus approvals

These are different gates:

1. **Device pairing**: can this node connect to the gateway?
2. **Exec approvals**: can this node run a specific shell command?

Quick checks:

If pairing is missing, approve the node device first. If pairing is fine but `system.run` fails, fix exec approvals/allowlist.

## Common node error codes

## Fast recovery loop

If still stuck:

Related:

----
url: https://docs.openclaw.ai/providers/vllm
----

# vLLM - OpenClaw

vLLM can serve open-source (and some custom) models via an **OpenAI-compatible** HTTP API. OpenClaw can connect to vLLM using the `openai-completions` API. OpenClaw can also **auto-discover** available models from vLLM when you opt in with `VLLM_API_KEY` (any value works if your server doesn’t enforce auth) and you do not define an explicit `models.providers.vllm` entry.

## Quick start

1. Start vLLM with an OpenAI-compatible server.

Your base URL should expose `/v1` endpoints (e.g. `/v1/models`, `/v1/chat/completions`). vLLM commonly runs on:

2. Opt in (any value works if no auth is configured):

3) Select a model (replace with one of your vLLM model IDs):

## Model discovery (implicit provider)

When `VLLM_API_KEY` is set (or an auth profile exists) and you **do not** define `models.providers.vllm`, OpenClaw will query:

…and convert the returned IDs into model entries. If you set `models.providers.vllm` explicitly, auto-discovery is skipped and you must define models manually.

## Explicit configuration (manual models)

Use explicit config when:

```
{
  models: {
    providers: {
      vllm: {
        baseUrl: "http://127.0.0.1:8000/v1",
        apiKey: "${VLLM_API_KEY}",
        api: "openai-completions",
        models: [
          {
            id: "your-model-id",
            name: "Local vLLM Model",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 128000,
            maxTokens: 8192,
          },
        ],
      },
    },
  },
}
```

## Troubleshooting

----
url: https://docs.openclaw.ai/reference/templates/TOOLS
----

# TOOLS.md Template - OpenClaw

## [​](#tools-md-local-notes)TOOLS.md - Local Notes

Skills define *how* tools work. This file is for *your* specifics — the stuff that’s unique to your setup.

## [​](#what-goes-here)What Goes Here

Things like:

* Camera names and locations
* SSH hosts and aliases
* Preferred voices for TTS
* Speaker/room names
* Device nicknames
* Anything environment-specific

## [​](#examples)Examples

```
### Cameras

- living-room → Main area, 180° wide angle
- front-door → Entrance, motion-triggered

### SSH

- home-server → 192.168.1.100, user: admin

### TTS

- Preferred voice: "Nova" (warm, slightly British)
- Default speaker: Kitchen HomePod
```

## [​](#why-separate)Why Separate?

Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.

***

Add whatever helps you do your job. This is your cheat sheet.

----
url: https://docs.openclaw.ai/install/bun
----

# Bun (Experimental) - OpenClaw

## [​](#bun-experimental)Bun (Experimental)

Bun is **not recommended for gateway runtime** (known issues with WhatsApp and Telegram). Use Node for production.

Bun is an optional local runtime for running TypeScript directly (`bun run ...`, `bun --watch ...`). The default package manager remains `pnpm`, which is fully supported and used by docs tooling. Bun cannot use `pnpm-lock.yaml` and will ignore it.

## [​](#install)Install

1

[](#)

Install dependencies

```
bun install
```

`bun.lock` / `bun.lockb` are gitignored, so there is no repo churn. To skip lockfile writes entirely:

```
bun install --no-save
```

2

[](#)

Build and test

```
bun run build
bun run vitest run
```

## [​](#lifecycle-scripts)Lifecycle Scripts

Bun blocks dependency lifecycle scripts unless explicitly trusted. For this repo, the commonly blocked scripts are not required:

* `@whiskeysockets/baileys` `preinstall` — checks Node major >= 20 (OpenClaw defaults to Node 24 and still supports Node 22 LTS, currently `22.16+`)
* `protobufjs` `postinstall` — emits warnings about incompatible version schemes (no build artifacts)

If you hit a runtime issue that requires these scripts, trust them explicitly:

```
bun pm trust @whiskeysockets/baileys protobufjs
```

## [​](#caveats)Caveats

Some scripts still hardcode pnpm (for example `docs:build`, `ui:*`, `protocol:check`). Run those via pnpm for now.

----
url: https://docs.openclaw.ai/gateway/heartbeat
----

# Heartbeat - OpenClaw

## Heartbeat (Gateway)

> **Heartbeat vs Cron?** See [Cron vs Heartbeat](https://docs.openclaw.ai/automation/cron-vs-heartbeat) for guidance on when to use each.

Heartbeat runs **periodic agent turns** in the main session so the model can surface anything that needs attention without spamming you. Troubleshooting: [/automation/troubleshooting](https://docs.openclaw.ai/automation/troubleshooting)

## Quick start (beginner)

1. Leave heartbeats enabled (default is `30m`, or `1h` for Anthropic OAuth/setup-token) or set your own cadence.
2. Create a tiny `HEARTBEAT.md` checklist in the agent workspace (optional but recommended).
3. Decide where heartbeat messages should go (`target: "none"` is the default; set `target: "last"` to route to the last contact).
4. Optional: enable heartbeat reasoning delivery for transparency.
5. Optional: use lightweight bootstrap context if heartbeat runs only need `HEARTBEAT.md`.
6. Optional: enable isolated sessions to avoid sending full conversation history each heartbeat.
7. Optional: restrict heartbeats to active hours (local time).

Example config:

```
{
  agents: {
    defaults: {
      heartbeat: {
        every: "30m",
        target: "last", // explicit delivery to last contact (default is "none")
        directPolicy: "allow", // default: allow direct/DM targets; set "block" to suppress
        lightContext: true, // optional: only inject HEARTBEAT.md from bootstrap files
        isolatedSession: true, // optional: fresh session each run (no conversation history)
        // activeHours: { start: "08:00", end: "24:00" },
        // includeReasoning: true, // optional: send separate `Reasoning:` message too
      },
    },
  },
}
```

## Defaults

## What the heartbeat prompt is for

The default prompt is intentionally broad:

If you want a heartbeat to do something very specific (e.g. “check Gmail PubSub stats” or “verify gateway health”), set `agents.defaults.heartbeat.prompt` (or `agents.list[].heartbeat.prompt`) to a custom body (sent verbatim).

## Response contract

Outside heartbeats, stray `HEARTBEAT_OK` at the start/end of a message is stripped and logged; a message that is only `HEARTBEAT_OK` is dropped.

## Config

```
{
  agents: {
    defaults: {
      heartbeat: {
        every: "30m", // default: 30m (0m disables)
        model: "anthropic/claude-opus-4-6",
        includeReasoning: false, // default: false (deliver separate Reasoning: message when available)
        lightContext: false, // default: false; true keeps only HEARTBEAT.md from workspace bootstrap files
        isolatedSession: false, // default: false; true runs each heartbeat in a fresh session (no conversation history)
        target: "last", // default: none | options: last | none | <channel id> (core or plugin, e.g. "bluebubbles")
        to: "+15551234567", // optional channel-specific override
        accountId: "ops-bot", // optional multi-account channel id
        prompt: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
        ackMaxChars: 300, // max chars allowed after HEARTBEAT_OK
      },
    },
  },
}
```

### Scope and precedence

### Per-agent heartbeats

If any `agents.list[]` entry includes a `heartbeat` block, **only those agents** run heartbeats. The per-agent block merges on top of `agents.defaults.heartbeat` (so you can set shared defaults once and override per agent). Example: two agents, only the second agent runs heartbeats.

```
{
  agents: {
    defaults: {
      heartbeat: {
        every: "30m",
        target: "last", // explicit delivery to last contact (default is "none")
      },
    },
    list: [
      { id: "main", default: true },
      {
        id: "ops",
        heartbeat: {
          every: "1h",
          target: "whatsapp",
          to: "+15551234567",
          prompt: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
        },
      },
    ],
  },
}
```

### Active hours example

Restrict heartbeats to business hours in a specific timezone:

```
{
  agents: {
    defaults: {
      heartbeat: {
        every: "30m",
        target: "last", // explicit delivery to last contact (default is "none")
        activeHours: {
          start: "09:00",
          end: "22:00",
          timezone: "America/New_York", // optional; uses your userTimezone if set, otherwise host tz
        },
      },
    },
  },
}
```

Outside this window (before 9am or after 10pm Eastern), heartbeats are skipped. The next scheduled tick inside the window will run normally.

### 24/7 setup

If you want heartbeats to run all day, use one of these patterns:

Do not set the same `start` and `end` time (for example `08:00` to `08:00`). That is treated as a zero-width window, so heartbeats are always skipped.

### Multi account example

Use `accountId` to target a specific account on multi-account channels like Telegram:

```
{
  agents: {
    list: [
      {
        id: "ops",
        heartbeat: {
          every: "1h",
          target: "telegram",
          to: "12345678:topic:42", // optional: route to a specific topic/thread
          accountId: "ops-bot",
        },
      },
    ],
  },
  channels: {
    telegram: {
      accounts: {
        "ops-bot": { botToken: "YOUR_TELEGRAM_BOT_TOKEN" },
      },
    },
  },
}
```

### Field notes

* `every`: heartbeat interval (duration string; default unit = minutes).
* `model`: optional model override for heartbeat runs (`provider/model`).
* `includeReasoning`: when enabled, also deliver the separate `Reasoning:` message when available (same shape as `/reasoning on`).
* `lightContext`: when true, heartbeat runs use lightweight bootstrap context and keep only `HEARTBEAT.md` from workspace bootstrap files.
* `isolatedSession`: when true, each heartbeat runs in a fresh session with no prior conversation history. Uses the same isolation pattern as cron `sessionTarget: "isolated"`. Dramatically reduces per-heartbeat token cost. Combine with `lightContext: true` for maximum savings. Delivery routing still uses the main session context.
* `session`: optional session key for heartbeat runs.
* `target`:
* `directPolicy`: controls direct/DM delivery behavior:
* `to`: optional recipient override (channel-specific id, e.g. E.164 for WhatsApp or a Telegram chat id). For Telegram topics/threads, use `<chatId>:topic:<messageThreadId>`.
* `accountId`: optional account id for multi-account channels. When `target: "last"`, the account id applies to the resolved last channel if it supports accounts; otherwise it is ignored. If the account id does not match a configured account for the resolved channel, delivery is skipped.
* `prompt`: overrides the default prompt body (not merged).
* `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery.
* `suppressToolErrorWarnings`: when true, suppresses tool error warning payloads during heartbeat runs.
* `activeHours`: restricts heartbeat runs to a time window. Object with `start` (HH:MM, inclusive; use `00:00` for start-of-day), `end` (HH:MM exclusive; `24:00` allowed for end-of-day), and optional `timezone`.

## Delivery behavior

## Visibility controls

By default, `HEARTBEAT_OK` acknowledgments are suppressed while alert content is delivered. You can adjust this per channel or per account:

Precedence: per-account → per-channel → channel defaults → built-in defaults.

### What each flag does

If **all three** are false, OpenClaw skips the heartbeat run entirely (no model call).

### Per-channel vs per-account examples

### Common patterns

| Goal                                     | Config                                                                                   |
| ---------------------------------------- | ---------------------------------------------------------------------------------------- |
| Default behavior (silent OKs, alerts on) | *(no config needed)*                                                                     |
| Fully silent (no messages, no indicator) | `channels.defaults.heartbeat: { showOk: false, showAlerts: false, useIndicator: false }` |
| Indicator-only (no messages)             | `channels.defaults.heartbeat: { showOk: false, showAlerts: false, useIndicator: true }`  |
| OKs in one channel only                  | `channels.telegram.heartbeat: { showOk: true }`                                          |

## HEARTBEAT.md (optional)

If a `HEARTBEAT.md` file exists in the workspace, the default prompt tells the agent to read it. Think of it as your “heartbeat checklist”: small, stable, and safe to include every 30 minutes. If `HEARTBEAT.md` exists but is effectively empty (only blank lines and markdown headers like `# Heading`), OpenClaw skips the heartbeat run to save API calls. If the file is missing, the heartbeat still runs and the model decides what to do. Keep it tiny (short checklist or reminders) to avoid prompt bloat. Example `HEARTBEAT.md`:

### Can the agent update HEARTBEAT.md?

Yes — if you ask it to. `HEARTBEAT.md` is just a normal file in the agent workspace, so you can tell the agent (in a normal chat) something like:

If you want this to happen proactively, you can also include an explicit line in your heartbeat prompt like: “If the checklist becomes stale, update HEARTBEAT.md with a better one.” Safety note: don’t put secrets (API keys, phone numbers, private tokens) into `HEARTBEAT.md` — it becomes part of the prompt context.

## Manual wake (on-demand)

You can enqueue a system event and trigger an immediate heartbeat with:

If multiple agents have `heartbeat` configured, a manual wake runs each of those agent heartbeats immediately. Use `--mode next-heartbeat` to wait for the next scheduled tick.

## Reasoning delivery (optional)

By default, heartbeats deliver only the final “answer” payload. If you want transparency, enable:

When enabled, heartbeats will also deliver a separate message prefixed `Reasoning:` (same shape as `/reasoning on`). This can be useful when the agent is managing multiple sessions/codexes and you want to see why it decided to ping you — but it can also leak more internal detail than you want. Prefer keeping it off in group chats.

## Cost awareness

Heartbeats run full agent turns. Shorter intervals burn more tokens. To reduce cost:

----
url: https://docs.openclaw.ai/gateway/multiple-gateways
----

# Multiple Gateways - OpenClaw

## [​](#multiple-gateways-same-host)Multiple Gateways (same host)

Most setups should use one Gateway because a single Gateway can handle multiple messaging connections and agents. If you need stronger isolation or redundancy (e.g., a rescue bot), run separate Gateways with isolated profiles/ports.

## [​](#isolation-checklist-required)Isolation checklist (required)

* `OPENCLAW_CONFIG_PATH` — per-instance config file
* `OPENCLAW_STATE_DIR` — per-instance sessions, creds, caches
* `agents.defaults.workspace` — per-instance workspace root
* `gateway.port` (or `--port`) — unique per instance
* Derived ports (browser/canvas) must not overlap

If these are shared, you will hit config races and port conflicts.

## [​](#recommended-profiles-profile)Recommended: profiles (`--profile`)

Profiles auto-scope `OPENCLAW_STATE_DIR` + `OPENCLAW_CONFIG_PATH` and suffix service names.

```
# main
openclaw --profile main setup
openclaw --profile main gateway --port 18789

# rescue
openclaw --profile rescue setup
openclaw --profile rescue gateway --port 19001
```

Per-profile services:

```
openclaw --profile main gateway install
openclaw --profile rescue gateway install
```

## [​](#rescue-bot-guide)Rescue-bot guide

Run a second Gateway on the same host with its own:

* profile/config
* state dir
* workspace
* base port (plus derived ports)

This keeps the rescue bot isolated from the main bot so it can debug or apply config changes if the primary bot is down. Port spacing: leave at least 20 ports between base ports so the derived browser/canvas/CDP ports never collide.

### [​](#how-to-install-rescue-bot)How to install (rescue bot)

```
# Main bot (existing or fresh, without --profile param)
# Runs on port 18789 + Chrome CDC/Canvas/... Ports
openclaw onboard
openclaw gateway install

# Rescue bot (isolated profile + ports)
openclaw --profile rescue onboard
# Notes:
# - workspace name will be postfixed with -rescue per default
# - Port should be at least 18789 + 20 Ports,
#   better choose completely different base port, like 19789,
# - rest of the onboarding is the same as normal

# To install the service (if not happened automatically during setup)
openclaw --profile rescue gateway install
```

## [​](#port-mapping-derived)Port mapping (derived)

Base port = `gateway.port` (or `OPENCLAW_GATEWAY_PORT` / `--port`).

* browser control service port = base + 2 (loopback only)
* canvas host is served on the Gateway HTTP server (same port as `gateway.port`)
* Browser profile CDP ports auto-allocate from `browser.controlPort + 9 .. + 108`

If you override any of these in config or env, you must keep them unique per instance.

## [​](#browser/cdp-notes-common-footgun)Browser/CDP notes (common footgun)

* Do **not** pin `browser.cdpUrl` to the same values on multiple instances.
* Each instance needs its own browser control port and CDP range (derived from its gateway port).
* If you need explicit CDP ports, set `browser.profiles.<name>.cdpPort` per instance.
* Remote Chrome: use `browser.profiles.<name>.cdpUrl` (per profile, per instance).

## [​](#manual-env-example)Manual env example

```
OPENCLAW_CONFIG_PATH=~/.openclaw/main.json \
OPENCLAW_STATE_DIR=~/.openclaw-main \
openclaw gateway --port 18789

OPENCLAW_CONFIG_PATH=~/.openclaw/rescue.json \
OPENCLAW_STATE_DIR=~/.openclaw-rescue \
openclaw gateway --port 19001
```

## [​](#quick-checks)Quick checks

```
openclaw --profile main status
openclaw --profile rescue status
openclaw --profile rescue browser status
```

----
url: https://docs.openclaw.ai/concepts/context
----

# Context - OpenClaw

“Context” is **everything OpenClaw sends to the model for a run**. It is bounded by the model’s **context window** (token limit). Beginner mental model:

Context is *not the same thing* as “memory”: memory can be stored on disk and reloaded later; context is what’s inside the model’s current window.

## Quick start (inspect context)

See also: [Slash commands](https://docs.openclaw.ai/tools/slash-commands), [Token use & costs](https://docs.openclaw.ai/reference/token-use), [Compaction](https://docs.openclaw.ai/concepts/compaction).

## Example output

Values vary by model, provider, tool policy, and what’s in your workspace.

### `/context list`

```
🧠 Context breakdown
Workspace: <workspaceDir>
Bootstrap max/file: 20,000 chars
Sandbox: mode=non-main sandboxed=false
System prompt (run): 38,412 chars (~9,603 tok) (Project Context 23,901 chars (~5,976 tok))

Injected workspace files:
- AGENTS.md: OK | raw 1,742 chars (~436 tok) | injected 1,742 chars (~436 tok)
- SOUL.md: OK | raw 912 chars (~228 tok) | injected 912 chars (~228 tok)
- TOOLS.md: TRUNCATED | raw 54,210 chars (~13,553 tok) | injected 20,962 chars (~5,241 tok)
- IDENTITY.md: OK | raw 211 chars (~53 tok) | injected 211 chars (~53 tok)
- USER.md: OK | raw 388 chars (~97 tok) | injected 388 chars (~97 tok)
- HEARTBEAT.md: MISSING | raw 0 | injected 0
- BOOTSTRAP.md: OK | raw 0 chars (~0 tok) | injected 0 chars (~0 tok)

Skills list (system prompt text): 2,184 chars (~546 tok) (12 skills)
Tools: read, edit, write, exec, process, browser, message, sessions_send, …
Tool list (system prompt text): 1,032 chars (~258 tok)
Tool schemas (JSON): 31,988 chars (~7,997 tok) (counts toward context; not shown as text)
Tools: (same as above)

Session tokens (cached): 14,250 total / ctx=32,000
```

### `/context detail`

## What counts toward the context window

Everything the model receives counts, including:

## How OpenClaw builds the system prompt

The system prompt is **OpenClaw-owned** and rebuilt each run. It includes:

Full breakdown: [System Prompt](https://docs.openclaw.ai/concepts/system-prompt).

## Injected workspace files (Project Context)

By default, OpenClaw injects a fixed set of workspace files (if present):

Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (default `20000` chars). OpenClaw also enforces a total bootstrap injection cap across files with `agents.defaults.bootstrapTotalMaxChars` (default `150000` chars). `/context` shows **raw vs injected** sizes and whether truncation happened. When truncation occurs, the runtime can inject an in-prompt warning block under Project Context. Configure this with `agents.defaults.bootstrapPromptTruncationWarning` (`off`, `once`, `always`; default `once`).

## Skills: injected vs loaded on-demand

The system prompt includes a compact **skills list** (name + description + location). This list has real overhead. Skill instructions are *not* included by default. The model is expected to `read` the skill’s `SKILL.md` **only when needed**.

## Tools: there are two costs

Tools affect context in two ways:

1. **Tool list text** in the system prompt (what you see as “Tooling”).
2. **Tool schemas** (JSON). These are sent to the model so it can call tools. They count toward context even though you don’t see them as plain text.

`/context detail` breaks down the biggest tool schemas so you can see what dominates.

## Commands, directives, and “inline shortcuts”

Slash commands are handled by the Gateway. There are a few different behaviors:

Details: [Slash commands](https://docs.openclaw.ai/tools/slash-commands).

## Sessions, compaction, and pruning (what persists)

What persists across messages depends on the mechanism:

Docs: [Session](https://docs.openclaw.ai/concepts/session), [Compaction](https://docs.openclaw.ai/concepts/compaction), [Session pruning](https://docs.openclaw.ai/concepts/session-pruning). By default, OpenClaw uses the built-in `legacy` context engine for assembly and compaction. If you install a plugin that provides `kind: "context-engine"` and select it with `plugins.slots.contextEngine`, OpenClaw delegates context assembly, `/compact`, and related subagent context lifecycle hooks to that engine instead. `ownsCompaction: false` does not auto-fallback to the legacy engine; the active engine must still implement `compact()` correctly. See [Context Engine](https://docs.openclaw.ai/concepts/context-engine) for the full pluggable interface, lifecycle hooks, and configuration.

## What `/context` actually reports

`/context` prefers the latest **run-built** system prompt report when available:

Either way, it reports sizes and top contributors; it does **not** dump the full system prompt or tool schemas.

----
url: https://docs.openclaw.ai/concepts/system-prompt
----

# System Prompt - OpenClaw

OpenClaw builds a custom system prompt for every agent run. The prompt is **OpenClaw-owned** and does not use the pi-coding-agent default prompt. The prompt is assembled by OpenClaw and injected into each agent run.

## Structure

The prompt is intentionally compact and uses fixed sections:

Safety guardrails in the system prompt are advisory. They guide model behavior but do not enforce policy. Use tool policy, exec approvals, sandboxing, and channel allowlists for hard enforcement; operators can disable these by design.

## Prompt modes

OpenClaw can render smaller system prompts for sub-agents. The runtime sets a `promptMode` for each run (not a user-facing config):

* `full` (default): includes all sections above.
* `minimal`: used for sub-agents; omits **Skills**, **Memory Recall**, **OpenClaw Self-Update**, **Model Aliases**, **User Identity**, **Reply Tags**, **Messaging**, **Silent Replies**, and **Heartbeats**. Tooling, **Safety**, Workspace, Sandbox, Current Date & Time (when known), Runtime, and injected context stay available.
* `none`: returns only the base identity line.

When `promptMode=minimal`, extra injected prompts are labeled **Subagent Context** instead of **Group Chat Context**.

## Workspace bootstrap injection

Bootstrap files are trimmed and appended under **Project Context** so the model sees identity and profile context without needing explicit reads:

All of these files are **injected into the context window** on every turn, which means they consume tokens. Keep them concise — especially `MEMORY.md`, which can grow over time and lead to unexpectedly high context usage and more frequent compaction.

> **Note:** `memory/*.md` daily files are **not** injected automatically. They are accessed on demand via the `memory_search` and `memory_get` tools, so they do not count against the context window unless the model explicitly reads them.

Large files are truncated with a marker. The max per-file size is controlled by `agents.defaults.bootstrapMaxChars` (default: 20000). Total injected bootstrap content across files is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 150000). Missing files inject a short missing-file marker. When truncation occurs, OpenClaw can inject a warning block in Project Context; control this with `agents.defaults.bootstrapPromptTruncationWarning` (`off`, `once`, `always`; default: `once`). Sub-agent sessions only inject `AGENTS.md` and `TOOLS.md` (other bootstrap files are filtered out to keep the sub-agent context small). Internal hooks can intercept this step via `agent:bootstrap` to mutate or replace the injected bootstrap files (for example swapping `SOUL.md` for an alternate persona). To inspect how much each injected file contributes (raw vs injected, truncation, plus tool schema overhead), use `/context list` or `/context detail`. See [Context](https://docs.openclaw.ai/concepts/context).

## Time handling

The system prompt includes a dedicated **Current Date & Time** section when the user timezone is known. To keep the prompt cache-stable, it now only includes the **time zone** (no dynamic clock or time format). Use `session_status` when the agent needs the current time; the status card includes a timestamp line. Configure with:

See [Date & Time](https://docs.openclaw.ai/date-time) for full behavior details.

## Skills

When eligible skills exist, OpenClaw injects a compact **available skills list** (`formatSkillsForPrompt`) that includes the **file path** for each skill. The prompt instructs the model to use `read` to load the SKILL.md at the listed location (workspace, managed, or bundled). If no skills are eligible, the Skills section is omitted.

This keeps the base prompt small while still enabling targeted skill usage.

## Documentation

When available, the system prompt includes a **Documentation** section that points to the local OpenClaw docs directory (either `docs/` in the repo workspace or the bundled npm package docs) and also notes the public mirror, source repo, community Discord, and ClawHub ([https://clawhub.com](https://clawhub.com/)) for skills discovery. The prompt instructs the model to consult local docs first for OpenClaw behavior, commands, configuration, or architecture, and to run `openclaw status` itself when possible (asking the user only when it lacks access).

----
url: https://docs.openclaw.ai/providers
----

# Provider Directory - OpenClaw

## [​](#model-providers)Model Providers

OpenClaw can use many LLM providers. Pick a provider, authenticate, then set the default model as `provider/model`. Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugin)/etc.)? See [Channels](https://docs.openclaw.ai/channels).

## [​](#quick-start)Quick start

1. Authenticate with the provider (usually via `openclaw onboard`).
2. Set the default model:

```
{
  agents: { defaults: { model: { primary: "anthropic/claude-opus-4-6" } } },
}
```

## [​](#provider-docs)Provider docs

* [Amazon Bedrock](https://docs.openclaw.ai/providers/bedrock)
* [Anthropic (API + Claude Code CLI)](https://docs.openclaw.ai/providers/anthropic)
* [Cloudflare AI Gateway](https://docs.openclaw.ai/providers/cloudflare-ai-gateway)
* [GLM models](https://docs.openclaw.ai/providers/glm)
* [Google (Gemini)](https://docs.openclaw.ai/providers/google)
* [Groq (LPU inference)](https://docs.openclaw.ai/providers/groq)
* [Hugging Face (Inference)](https://docs.openclaw.ai/providers/huggingface)
* [Kilocode](https://docs.openclaw.ai/providers/kilocode)
* [LiteLLM (unified gateway)](https://docs.openclaw.ai/providers/litellm)
* [MiniMax](https://docs.openclaw.ai/providers/minimax)
* [Mistral](https://docs.openclaw.ai/providers/mistral)
* [Model Studio (Alibaba Cloud)](https://docs.openclaw.ai/providers/modelstudio)
* [Moonshot AI (Kimi + Kimi Coding)](https://docs.openclaw.ai/providers/moonshot)
* [NVIDIA](https://docs.openclaw.ai/providers/nvidia)
* [Ollama (cloud + local models)](https://docs.openclaw.ai/providers/ollama)
* [OpenAI (API + Codex)](https://docs.openclaw.ai/providers/openai)
* [OpenCode (Zen + Go)](https://docs.openclaw.ai/providers/opencode)
* [OpenRouter](https://docs.openclaw.ai/providers/openrouter)
* [Perplexity (web search)](https://docs.openclaw.ai/providers/perplexity-provider)
* [Qianfan](https://docs.openclaw.ai/providers/qianfan)
* [Qwen (OAuth)](https://docs.openclaw.ai/providers/qwen)
* [SGLang (local models)](https://docs.openclaw.ai/providers/sglang)
* [Together AI](https://docs.openclaw.ai/providers/together)
* [Vercel AI Gateway](https://docs.openclaw.ai/providers/vercel-ai-gateway)
* [Venice (Venice AI, privacy-focused)](https://docs.openclaw.ai/providers/venice)
* [vLLM (local models)](https://docs.openclaw.ai/providers/vllm)
* [Volcengine (Doubao)](https://docs.openclaw.ai/providers/volcengine)
* [xAI](https://docs.openclaw.ai/providers/xai)
* [Xiaomi](https://docs.openclaw.ai/providers/xiaomi)
* [Z.AI](https://docs.openclaw.ai/providers/zai)

## [​](#transcription-providers)Transcription providers

* [Deepgram (audio transcription)](https://docs.openclaw.ai/providers/deepgram)

## [​](#community-tools)Community tools

* [Claude Max API Proxy](https://docs.openclaw.ai/providers/claude-max-api-proxy) - Community proxy for Claude subscription credentials (verify Anthropic policy/terms before use)

For the full provider catalog (xAI, Groq, Mistral, etc.) and advanced configuration, see [Model providers](https://docs.openclaw.ai/concepts/model-providers).

----
url: https://docs.openclaw.ai/gateway/remote
----

# Remote Access - OpenClaw

## Remote access (SSH, tunnels, and tailnets)

This repo supports “remote over SSH” by keeping a single Gateway (the master) running on a dedicated host (desktop/server) and connecting clients to it.

## The core idea

## Common VPN/tailnet setups (where the agent lives)

Think of the **Gateway host** as “where the agent lives.” It owns sessions, auth profiles, channels, and state. Your laptop/desktop (and nodes) connect to that host.

### 1) Always-on Gateway in your tailnet (VPS or home server)

Run the Gateway on a persistent host and reach it via **Tailscale** or SSH.

This is ideal when your laptop sleeps often but you want the agent always-on.

### 2) Home desktop runs the Gateway, laptop is remote control

The laptop does **not** run the agent. It connects remotely:

Runbook: [macOS remote access](https://docs.openclaw.ai/platforms/mac/remote).

### 3) Laptop runs the Gateway, remote access from other machines

Keep the Gateway local but expose it safely:

Guide: [Tailscale](https://docs.openclaw.ai/gateway/tailscale) and [Web overview](https://docs.openclaw.ai/web).

## Command flow (what runs where)

One gateway service owns state + channels. Nodes are peripherals. Flow example (Telegram → node):

Notes:

## SSH tunnel (CLI + tools)

Create a local tunnel to the remote Gateway WS:

With the tunnel up:

Note: replace `18789` with your configured `gateway.port` (or `--port`/`OPENCLAW_GATEWAY_PORT`). Note: when you pass `--url`, the CLI does not fall back to config or environment credentials. Include `--token` or `--password` explicitly. Missing explicit credentials is an error.

## CLI remote defaults

You can persist a remote target so CLI commands use it by default:

When the gateway is loopback-only, keep the URL at `ws://127.0.0.1:18789` and open the SSH tunnel first.

## Credential precedence

Gateway credential resolution follows one shared contract across call/probe/status paths and Discord exec-approval monitoring. Node-host uses the same base contract with one local-mode exception (it intentionally ignores `gateway.remote.*`):

## Chat UI over SSH

WebChat no longer uses a separate HTTP port. The SwiftUI chat UI connects directly to the Gateway WebSocket.

## macOS app “Remote over SSH”

The macOS menu bar app can drive the same setup end-to-end (remote status checks, WebChat, and Voice Wake forwarding). Runbook: [macOS remote access](https://docs.openclaw.ai/platforms/mac/remote).

## Security rules (remote/VPN)

Short version: **keep the Gateway loopback-only** unless you’re sure you need a bind.

Deep dive: [Security](https://docs.openclaw.ai/gateway/security).