Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled

Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
2026-07-05 09:36:54 +00:00
parent 3216769225
commit bedb527145
21108 changed files with 6010766 additions and 0 deletions

View File

@@ -0,0 +1,346 @@
---
summary: "Setting up ACP agents: acpx harness config, plugin setup, permissions"
read_when:
- Installing or configuring the acpx harness for Claude Code / Codex / Gemini CLI
- Enabling the plugin-tools or OpenClaw-tools MCP bridge
- Configuring ACP permission modes
title: "ACP agents — setup"
---
For the overview, operator runbook, and concepts, see [ACP agents](/tools/acp-agents).
This page covers acpx harness config, plugin setup for the MCP bridges, and permission configuration.
Use this page only when you are setting up the ACP/acpx route. For native Codex
app-server runtime config, use [Codex harness](/plugins/codex-harness). For
OpenAI API keys or Codex OAuth model-provider config, use
[OpenAI](/providers/openai).
Codex has two OpenClaw routes:
| Route | Config/command | Setup page |
| -------------------------- | ------------------------------------------------------ | --------------------------------------- |
| Native Codex app-server | `/codex ...`, `openai/gpt-*` agent refs | [Codex harness](/plugins/codex-harness) |
| Explicit Codex ACP adapter | `/acp spawn codex`, `runtime: "acp", agentId: "codex"` | This page |
Prefer the native route unless you explicitly need ACP/acpx behavior.
## acpx harness support (current)
Built-in acpx harness aliases (from the pinned `acpx` dependency):
| Alias | Wraps |
| ------------ | --------------------------------------------------------------------------------------------------------------- |
| `claude` | [Claude Code](https://claude.ai/code) |
| `codex` | [Codex CLI](https://codex.openai.com) |
| `copilot` | [GitHub Copilot CLI](https://docs.github.com/copilot/how-tos/copilot-chat/use-copilot-chat-in-the-command-line) |
| `cursor` | [Cursor CLI](https://cursor.com/docs/cli/acp) (`cursor-agent acp`) |
| `droid` | [Factory Droid](https://www.factory.ai) |
| `fast-agent` | [fast-agent](https://fast-agent.ai) |
| `gemini` | [Gemini CLI](https://github.com/google/gemini-cli) |
| `iflow` | [iFlow CLI](https://github.com/iflow-ai/iflow-cli) |
| `kilocode` | [Kilocode](https://kilocode.ai) |
| `kimi` | [Kimi CLI](https://github.com/MoonshotAI/kimi-cli) |
| `kiro` | [Kiro CLI](https://kiro.dev) |
| `mux` | [Mux](https://mux.coder.com) |
| `opencode` | [OpenCode](https://opencode.ai) |
| `openclaw` | OpenClaw ACP bridge (native `openclaw acp`) |
| `pi` | [Pi Coding Agent](https://github.com/mariozechner/pi) |
| `qoder` | [Qoder CLI](https://docs.qoder.com/cli/acp) |
| `qwen` | [Qwen Code](https://github.com/QwenLM/qwen-code) |
| `trae` | [Trae CLI](https://docs.trae.cn/cli) |
`factory-droid` and `factorydroid` also resolve to the built-in `droid` adapter.
When OpenClaw uses the acpx backend, prefer these values for `agentId` unless your acpx config defines custom agent aliases.
If your local Cursor install still exposes ACP as `agent acp`, override the `cursor` agent command in your acpx config instead of changing the built-in default.
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).
Model control is adapter-capability dependent. Codex ACP model refs are
normalized by OpenClaw before startup. Other harnesses need ACP `models` plus
`session/set_model` support; if a harness exposes neither that ACP capability
nor its own startup model flag, OpenClaw/acpx cannot force a model selection.
## Required config
Core ACP baseline:
```json5
{
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: [
"claude",
"codex",
"copilot",
"cursor",
"droid",
"gemini",
"iflow",
"kilocode",
"kimi",
"kiro",
"openclaw",
"opencode",
"qwen",
],
maxConcurrentSessions: 8,
stream: {
// Defaults are coalesceIdleMs: 350, maxChunkChars: 1800; shown explicitly here.
coalesceIdleMs: 350,
maxChunkChars: 1800,
},
runtime: {
ttlMinutes: 120,
},
},
}
```
Thread binding config is channel-adapter specific. Example for Discord:
```json5
{
session: {
threadBindings: {
enabled: true,
idleHours: 24,
maxAgeHours: 0,
},
},
channels: {
discord: {
threadBindings: {
enabled: true,
// Default is already true; shown explicitly here.
spawnSessions: true,
},
},
},
}
```
If thread-bound ACP spawn does not work, verify the adapter feature flag first:
- Discord: `channels.discord.threadBindings.spawnSessions=true`
Current-conversation binds do not require child-thread creation. They require an active conversation context and a channel adapter that exposes ACP conversation bindings.
See [Configuration Reference](/gateway/configuration-reference).
## Plugin setup for acpx backend
Packaged installs use the official `@openclaw/acpx` runtime plugin for ACP.
Install and enable it before using ACP harness sessions:
```bash
openclaw plugins install @openclaw/acpx
openclaw config set plugins.entries.acpx.enabled true
```
Source checkouts can also use the local workspace plugin after `pnpm install`.
Start with:
```text
/acp doctor
```
If you disabled `acpx`, denied it via `plugins.allow` / `plugins.deny`, or want
to switch back to the packaged plugin, use the explicit package path:
```bash
openclaw plugins install @openclaw/acpx
openclaw config set plugins.entries.acpx.enabled true
```
Local workspace install during development:
```bash
openclaw plugins install ./path/to/local/acpx-plugin
```
Then verify backend health:
```text
/acp doctor
```
### acpx runtime startup probe
The `acpx` plugin embeds the ACP runtime directly (no separate `acpx` binary or
version to configure). By default it registers the embedded backend during
Gateway startup and waits for a startup probe before the gateway `ready`
signal. Set `OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=0` or
`OPENCLAW_SKIP_ACPX_RUNTIME_PROBE=1` only for scripts or environments that
intentionally keep the startup probe disabled. Run `/acp doctor` for an explicit
on-demand probe.
Override an individual ACP agent command with structured arguments when a path
or flag value should remain one argv token:
```json
{
"plugins": {
"entries": {
"acpx": {
"enabled": true,
"config": {
"agents": {
"claude": {
"command": "node",
"args": ["/path/to/custom adapter.mjs", "--verbose"]
}
}
}
}
}
}
}
```
- `agents.<id>.command` is the executable or existing command string for that ACP agent.
- `agents.<id>.args` is optional. Each array item is shell-quoted before OpenClaw passes it through the current acpx command-string registry.
See [Plugins](/tools/plugin).
### Automatic adapter download
`acpx` auto-downloads ACP adapters (for example the Claude and Codex ACP
bridges) via `npx` on first use. You do not need to install adapter packages
manually, and there is no separate postinstall step for OpenClaw itself. If an
adapter download or spawn fails, `/acp doctor` reports the failure.
### Plugin tools MCP bridge
By default, ACPX sessions do **not** expose OpenClaw plugin-registered tools to
the ACP harness.
If you want ACP agents such as Codex or Claude Code to call installed
OpenClaw plugin tools such as memory recall/store, enable the dedicated bridge:
```bash
openclaw config set plugins.entries.acpx.config.pluginToolsMcpBridge true
```
What this does:
- Injects a built-in MCP server named `openclaw-plugin-tools` into ACPX session
bootstrap.
- Exposes plugin tools already registered by installed and enabled OpenClaw
plugins.
- Keeps the feature explicit and default-off.
Security and trust notes:
- This expands the ACP harness tool surface.
- ACP agents get access only to plugin tools already active in the gateway.
- Treat this as the same trust boundary as letting those plugins execute in
OpenClaw itself.
- Review installed plugins before enabling it.
Custom `mcpServers` still work as before. The built-in plugin-tools bridge is an
additional opt-in convenience, not a replacement for generic MCP server config.
### OpenClaw tools MCP bridge
By default, ACPX sessions also do **not** expose built-in OpenClaw tools through
MCP. Enable the separate core-tools bridge when an ACP agent needs selected
built-in tools such as `cron`:
```bash
openclaw config set plugins.entries.acpx.config.openClawToolsMcpBridge true
```
What this does:
- Injects a built-in MCP server named `openclaw-tools` into ACPX session
bootstrap.
- Exposes selected built-in OpenClaw tools. The initial server exposes `cron`.
- Keeps core-tool exposure explicit and default-off.
### Runtime operation timeout configuration
The `acpx` plugin gives embedded runtime startup and control operations 120
seconds by default. This gives slower harnesses such as Gemini CLI enough time
to complete ACP startup and initialization. Override it if your host needs a
different operation limit:
```bash
openclaw config set plugins.entries.acpx.config.timeoutSeconds 180
```
Runtime turns use OpenClaw agent/run timeouts, including `/acp timeout`.
`sessions_spawn` does not accept per-call timeout overrides; the operator path
is `agents.defaults.subagents.runTimeoutSeconds`. Restart the gateway after
changing `timeoutSeconds`.
### Health probe agent configuration
When `/acp doctor` or the startup probe checks the backend, the bundled `acpx`
plugin probes one harness agent. If `acp.allowedAgents` is set, it defaults to
the first allowed agent; otherwise it defaults to `codex`. If your deployment
needs a different ACP agent for health checks, set the probe agent explicitly:
```bash
openclaw config set plugins.entries.acpx.config.probeAgent claude
```
Restart the gateway after changing this value.
## 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:
These ACPX harness permissions are separate from OpenClaw exec approvals and separate from CLI-backend vendor bypass flags such as Claude CLI `--permission-mode bypassPermissions`. ACPX `approve-all` is the harness-level break-glass switch for ACP sessions.
For the broader comparison between OpenClaw `tools.exec.mode`, Codex Guardian
approvals, and ACPX harness permissions, see
[Permission modes](/tools/permission-modes).
### `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 `PermissionPromptUnavailableError`. **(default)** |
| `deny` | Silently deny the permission and continue (graceful degradation). |
### Configuration
Set via plugin config:
```bash
openclaw config set plugins.entries.acpx.config.permissionMode approve-all
openclaw config set plugins.entries.acpx.config.nonInteractivePermissions fail
```
Restart the gateway after changing these values.
<Warning>
OpenClaw 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 `PermissionPromptUnavailableError: Permission prompt unavailable in non-interactive mode`.
If you need to restrict permissions, set `nonInteractivePermissions` to `deny` so sessions degrade gracefully instead of crashing.
</Warning>
## Related
- [ACP agents](/tools/acp-agents) — overview, operator runbook, concepts
- [Sub-agents](/tools/subagents)
- [Multi-agent routing](/concepts/multi-agent)

868
docs/tools/acp-agents.md Normal file
View File

@@ -0,0 +1,868 @@
---
summary: "Run external coding harnesses (Claude Code, Cursor, Gemini CLI, explicit Codex ACP, OpenClaw ACP, OpenCode) through the ACP backend"
read_when:
- Running coding harnesses through ACP
- Setting up conversation-bound ACP sessions on messaging channels
- Binding a message-channel conversation to a persistent ACP session
- Troubleshooting ACP backend, plugin wiring, or completion delivery
- Operating /acp commands from chat
title: "ACP agents"
sidebarTitle: "ACP agents"
---
[Agent Client Protocol (ACP)](https://agentclientprotocol.com/) sessions let
OpenClaw run external coding harnesses (Claude Code, Cursor, Copilot, Droid,
OpenClaw ACP, OpenCode, Gemini CLI, and other supported ACPX harnesses)
through an ACP backend plugin. Each spawn is tracked as a
[background task](/automation/tasks).
<Note>
**ACP is the external-harness path, not the default Codex path.** The native
Codex app-server plugin owns `/codex ...` controls and the default
`openai/gpt-*` embedded runtime for agent turns; ACP owns `/acp ...` controls
and `sessions_spawn({ runtime: "acp" })` sessions.
To let Codex or Claude Code connect as an external MCP client directly to
existing OpenClaw channel conversations, use
[`openclaw mcp serve`](/cli/mcp) instead of ACP.
</Note>
## Which page do I want?
| You want to... | Use this | Notes |
| ----------------------------------------------------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bind or control Codex in the current conversation | `/codex bind`, `/codex threads` | Native Codex app-server path when the `codex` plugin is enabled: bound chat replies, image forwarding, model/fast/permissions, stop, and steer. ACP is an explicit fallback |
| Run Claude Code, Gemini CLI, explicit Codex ACP, or another external harness _through_ OpenClaw | This page | Chat-bound sessions, `/acp spawn`, `sessions_spawn({ runtime: "acp" })`, background tasks, runtime controls |
| Expose an OpenClaw Gateway session _as_ an ACP server for an editor or client | [`openclaw acp`](/cli/acp) | Bridge mode: an IDE/client speaks ACP to OpenClaw over stdio/WebSocket |
| Reuse a local AI CLI as a text-only fallback model | [CLI Backends](/gateway/cli-backends) | Not ACP: no OpenClaw tools, no ACP controls, no harness runtime |
## Does this work out of the box?
Yes, after installing the official ACP runtime plugin:
```bash
openclaw plugins install @openclaw/acpx
openclaw config set plugins.entries.acpx.enabled true
```
Source checkouts can use the local `extensions/acpx` workspace plugin after
`pnpm install`. Run `/acp doctor` for a readiness check.
OpenClaw only teaches agents about ACP spawning when ACP is **truly usable**:
ACP must be enabled, dispatch must not be disabled, the current session must
not be sandbox-blocked, and a runtime backend must be loaded and healthy. If
any condition fails, ACP skills and `sessions_spawn` ACP guidance stay hidden
so the agent does not suggest an unavailable backend.
<AccordionGroup>
<Accordion title="First-run gotchas">
- If `plugins.allow` is set, it is a restrictive plugin inventory and **must** include `acpx`, or the installed ACP backend is intentionally blocked (`/acp doctor` reports the missing allowlist entry).
- The Codex ACP adapter ships with the `acpx` plugin and launches locally when possible.
- Codex ACP runs with an isolated `CODEX_HOME`. OpenClaw copies trusted project trust entries plus safe model/provider routing config (`model`, `model_provider`, `model_reasoning_effort`, `sandbox_mode`, and safe `model_providers.<name>` fields) from the host Codex config; auth, notifications, and hooks stay on the host config only.
- Other target harness adapters may be fetched on demand with `npx` on first use.
- Vendor auth must already exist on the host for that harness.
- If the host has no npm or network access, first-run adapter fetches fail until caches are pre-warmed or the adapter is installed another way.
</Accordion>
<Accordion title="Runtime prerequisites">
ACP launches a real external harness process. OpenClaw owns routing,
background-task state, delivery, bindings, and policy; the harness owns
its provider login, model catalog, filesystem behavior, and native tools.
Before blaming OpenClaw, verify:
- `/acp doctor` reports an enabled, healthy backend.
- The target id is allowed by `acp.allowedAgents` when that allowlist is set.
- The harness command can start on the Gateway host.
- Provider auth is present for that harness (`claude`, `codex`, `gemini`, `opencode`, `droid`, etc.).
- The selected model exists for that harness - model ids are not portable across harnesses.
- The requested `cwd` exists and is accessible, or omit `cwd` and let the backend use its default.
- Permission mode matches the work. Non-interactive sessions cannot click native permission prompts, so write/exec-heavy coding runs usually need an ACPX permission profile that can proceed headlessly.
</Accordion>
</AccordionGroup>
OpenClaw plugin tools and built-in OpenClaw tools are **not** exposed to ACP
harnesses by default. Enable the explicit MCP bridges in
[ACP agents - setup](/tools/acp-agents-setup) only when the harness should
call those tools directly.
## Supported harness targets
With the `acpx` backend, use these ids as `/acp spawn <id>` or
`sessions_spawn({ runtime: "acp", agentId: "<id>" })` targets:
| Harness id | Typical backend | Notes |
| ------------ | ---------------------------------------------- | ----------------------------------------------------------------------------------- |
| `claude` | Claude Code ACP adapter | Requires Claude Code auth on the host. |
| `codex` | Codex ACP adapter | Explicit ACP fallback only when native `/codex` is unavailable or ACP is requested. |
| `copilot` | GitHub Copilot ACP adapter | Requires Copilot CLI/runtime auth. |
| `cursor` | Cursor CLI ACP (`cursor-agent acp`) | Override the acpx command if a local install exposes a different ACP entrypoint. |
| `droid` | Factory Droid CLI | Requires Factory/Droid auth or `FACTORY_API_KEY` in the harness environment. |
| `fast-agent` | fast-agent-mcp ACP adapter | Fetched on demand with `uvx`. |
| `gemini` | Gemini CLI ACP adapter | Requires Gemini CLI auth or API key setup. |
| `iflow` | iFlow CLI | Adapter availability and model control depend on the installed CLI. |
| `kilocode` | Kilo Code CLI | Adapter availability and model control depend on the installed CLI. |
| `kimi` | Kimi/Moonshot CLI | Requires Kimi/Moonshot auth on the host. |
| `kiro` | Kiro CLI | Adapter availability and model control depend on the installed CLI. |
| `mux` | Mux CLI ACP adapter | Fetched on demand with `npx`. |
| `opencode` | OpenCode ACP adapter | Requires OpenCode CLI/provider auth. |
| `openclaw` | OpenClaw Gateway bridge through `openclaw acp` | Lets an ACP-aware harness talk back to an OpenClaw Gateway session. |
| `qoder` | Qoder CLI | Adapter availability and model control depend on the installed CLI. |
| `qwen` | Qwen Code / Qwen CLI | Requires Qwen-compatible auth on the host. |
| `trae` | Trae CLI ACP adapter | Adapter availability and model control depend on the installed CLI. |
`pi` (pi-acp) is also registered in the acpx backend but is not a coding
harness in the same sense as the others above.
Custom acpx agent aliases can be configured in acpx itself, but OpenClaw
policy still checks `acp.allowedAgents` and any
`agents.list[].runtime.acp.agent` mapping before dispatch.
## Operator runbook
Quick `/acp` flow from chat:
<Steps>
<Step title="Spawn">
`/acp spawn claude --bind here`,
`/acp spawn gemini --mode persistent --thread auto`, or explicit
`/acp spawn codex --bind here`.
</Step>
<Step title="Work">
Continue in the bound conversation or thread (or target the session key
explicitly).
</Step>
<Step title="Check state">
`/acp status`
</Step>
<Step title="Tune">
`/acp model <provider/model>`, `/acp permissions <profile>`,
`/acp timeout <seconds>`.
</Step>
<Step title="Steer">
Without replacing context: `/acp steer tighten logging and continue`.
</Step>
<Step title="Stop">
`/acp cancel` (current turn) or `/acp close` (session + bindings).
</Step>
</Steps>
<AccordionGroup>
<Accordion title="Lifecycle details">
- Spawn creates or resumes an ACP runtime session, records ACP metadata in the OpenClaw session store, and may create a background task when the run is parent-owned.
- Parent-owned ACP sessions are treated as background work even when the runtime session is persistent; completion and cross-surface delivery go through the parent task notifier rather than acting like a normal user-facing chat session.
- Task maintenance closes terminal or orphaned parent-owned one-shot ACP sessions. Persistent ACP sessions are preserved while an active conversation binding remains; stale persistent sessions without an active binding are closed so they cannot be silently resumed after the owning task is done or its task record is gone.
- Bound follow-up messages go directly to the ACP session until the binding is closed, unfocused, reset, or expired.
- Gateway commands stay local. `/acp ...`, `/status`, and `/unfocus` are never sent as normal prompt text to a bound ACP harness.
- `cancel` aborts the active turn when the backend supports cancellation; it does not delete the binding or session metadata.
- `close` ends the ACP session from OpenClaw's point of view and removes the binding. A harness may still keep its own upstream history if it supports resume.
- The acpx plugin cleans up OpenClaw-owned wrapper and adapter process trees after `close`, and reaps stale OpenClaw-owned ACPX orphans during Gateway startup.
- Idle runtime workers are eligible for cleanup after `acp.runtime.ttlMinutes`; stored session metadata remains available for `/acp sessions`.
</Accordion>
<Accordion title="Native Codex routing rules">
Natural-language triggers that should route to the **native Codex plugin**
when it is enabled:
- "Bind this Discord channel to Codex."
- "Attach this chat to Codex thread `<id>`."
- "Show Codex threads, then bind this one."
Native Codex conversation binding is the default chat-control path.
OpenClaw dynamic tools still execute through OpenClaw, while Codex-native
tools such as shell/apply-patch execute inside Codex. For Codex-native
tool events, OpenClaw injects a per-turn native hook relay so plugin hooks
can block `before_tool_call`, observe `after_tool_call`, and route Codex
`PermissionRequest` events through OpenClaw approvals. Codex `Stop` hooks
are relayed to OpenClaw `before_agent_finalize`, where plugins can request
one more model pass before Codex finalizes its answer. The relay stays
deliberately conservative: it does not mutate Codex-native tool arguments
or rewrite Codex thread records. Use explicit ACP only when you want the
ACP runtime/session model. The embedded Codex support boundary is
documented in the
[Codex harness v1 support contract](/plugins/codex-harness-runtime#v1-support-contract).
</Accordion>
<Accordion title="Model / provider / runtime selection cheat sheet">
- legacy Codex model refs - legacy Codex OAuth/subscription model route repaired by doctor.
- `openai/*` - native Codex app-server embedded runtime for OpenAI agent turns.
- `/codex ...` - native Codex conversation control.
- `/acp ...` or `runtime: "acp"` - explicit ACP/acpx control.
</Accordion>
<Accordion title="ACP-routing natural-language triggers">
Triggers that should route to the ACP runtime:
- "Run this as a one-shot Claude Code ACP session and summarize the result."
- "Use Gemini CLI for this task in a thread, then keep follow-ups in that same thread."
- "Run Codex through ACP in a background thread."
OpenClaw picks `runtime: "acp"`, resolves the harness `agentId`, binds to
the current conversation or thread when supported, and routes follow-ups
to that session until close/expiry. Codex only follows this path when
ACP/acpx is explicit or the native Codex plugin is unavailable for the
requested operation.
For `sessions_spawn`, `runtime: "acp"` is advertised only when ACP is
enabled, the requester is not sandboxed, and an ACP runtime backend is
loaded. `acp.dispatch.enabled=false` pauses automatic ACP thread dispatch
but does not hide or block explicit `sessions_spawn({ runtime: "acp" })`
calls. It targets ACP harness ids such as `codex`, `claude`, `droid`,
`gemini`, or `opencode`. Do not pass a normal OpenClaw config agent id
from `agents_list` unless that entry is explicitly configured with
`agents.list[].runtime.type="acp"`; otherwise use the default sub-agent
runtime. When an OpenClaw agent is configured with
`runtime.type="acp"`, OpenClaw uses `runtime.acp.agent` as the underlying
harness id.
</Accordion>
</AccordionGroup>
## ACP versus sub-agents
Use ACP when you want an external harness runtime. Use **native Codex
app-server** for Codex conversation binding/control when the `codex` plugin
is enabled. 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](/tools/subagents).
## How ACP runs Claude Code
For Claude Code through ACP, the stack is:
1. OpenClaw ACP session control plane.
2. Official `@openclaw/acpx` runtime plugin.
3. Claude ACP adapter.
4. Claude-side runtime/session machinery.
ACP Claude is a **harness session** with ACP controls, session resume,
background-task tracking, and optional conversation/thread binding.
CLI backends are separate text-only local fallback runtimes - see
[CLI Backends](/gateway/cli-backends).
For operators, the practical rule is:
- **Want `/acp spawn`, bindable sessions, runtime controls, or persistent harness work?** Use ACP.
- **Want simple local text fallback through the raw CLI?** Use CLI backends.
## Bound sessions
### Mental model
- **Chat surface** - where people keep talking (Discord channel, Telegram topic, iMessage chat).
- **ACP session** - the durable Codex/Claude/Gemini runtime state OpenClaw routes to.
- **Child thread/topic** - an optional extra messaging surface created only by `--thread ...`.
- **Runtime workspace** - the filesystem location (`cwd`, repo checkout, backend workspace) where the harness runs. Independent of the chat surface.
### Current-conversation binds
`/acp spawn <harness> --bind here` pins the current conversation to the
spawned ACP session - no child thread, same chat surface. OpenClaw keeps
owning transport, auth, safety, and delivery. Follow-up messages in that
conversation route to the same session; `/new` and `/reset` reset the session
in place; `/acp close` removes the binding.
Examples:
```text
/codex bind # native Codex bind, route future messages here
/codex model gpt-5.4 # tune the bound native Codex thread
/codex stop # control the active native Codex turn
/acp spawn codex --bind here # explicit ACP fallback for Codex
/acp spawn codex --thread auto # may create a child thread/topic and bind there
/acp spawn codex --bind here --cwd /workspace/repo # same chat binding, Codex runs in /workspace/repo
```
<AccordionGroup>
<Accordion title="Binding rules and exclusivity">
- `--bind here` and `--thread ...` are mutually exclusive.
- `--bind here` only works on channels that advertise current-conversation binding; OpenClaw returns a clear unsupported message otherwise. Bindings persist across gateway restarts.
- On Discord, `spawnSessions` gates child thread creation for `--thread auto|here` - not `--bind here`.
- If you spawn to a different ACP agent without `--cwd`, OpenClaw inherits the **target agent's** workspace by default. Missing inherited paths (`ENOENT`/`ENOTDIR`) fall back to the backend default; other access errors (e.g. `EACCES`) surface as spawn errors.
- Gateway management commands stay local in bound conversations - `/acp ...` commands are handled by OpenClaw even when normal follow-up text routes to the bound ACP session; `/status` and `/unfocus` also stay local whenever command handling is enabled for that surface.
</Accordion>
<Accordion title="Thread-bound sessions">
When thread bindings are enabled for a channel adapter:
- OpenClaw binds a thread to a target ACP session.
- Follow-up messages in that thread route to the bound ACP session.
- ACP output is delivered back to the same thread.
- Unfocus/close/archive/idle-timeout or max-age expiry removes the binding.
- `/acp close`, `/acp cancel`, `/acp status`, `/status`, and `/unfocus` are Gateway commands, not prompts to the ACP harness.
Required feature flags for thread-bound ACP:
- `acp.enabled=true`
- `acp.dispatch.enabled` is on by default (set `false` to pause automatic ACP thread dispatch; explicit `sessions_spawn({ runtime: "acp" })` calls still work).
- Channel-adapter thread session spawns enabled (default: `true`):
- Discord: `channels.discord.threadBindings.spawnSessions=true`
- Telegram: `channels.telegram.threadBindings.spawnSessions=true`
Thread binding support is adapter-specific. If the active channel adapter
does not support thread bindings, OpenClaw returns a clear
unsupported/unavailable message.
</Accordion>
<Accordion title="Thread-supporting channels">
- Any channel adapter that exposes session/thread binding capability.
- Current built-in support: **Discord** threads/channels, **Telegram** topics (forum topics in groups/supergroups and DM topics).
- Plugin channels can add support through the same binding interface.
</Accordion>
</AccordionGroup>
## Persistent channel bindings
For non-ephemeral workflows, configure persistent ACP bindings in top-level
`bindings[]` entries.
### Binding model
<ParamField path="bindings[].type" type='"acp"'>
Marks a persistent ACP conversation binding.
</ParamField>
<ParamField path="bindings[].match" type="object">
Identifies the target conversation. Per-channel shapes:
- **Discord channel/thread:** `match.channel="discord"` + `match.peer.id="<channelOrThreadId>"`
- **Slack channel/DM:** `match.channel="slack"` + `match.peer.id="<channelId|channel:<channelId>|#<channelId>|userId|user:<userId>|slack:<userId>|<@userId>>"`. Prefer stable Slack ids; channel bindings also match replies inside that channel's threads.
- **Telegram forum topic:** `match.channel="telegram"` + `match.peer.id="<chatId>:topic:<topicId>"`
- **WhatsApp DM/group:** `match.channel="whatsapp"` + `match.peer.id="<E.164|group JID>"`. Use E.164 numbers such as `+15555550123` for direct chats and WhatsApp group JIDs such as `120363424282127706@g.us` for groups.
- **iMessage DM/group:** `match.channel="imessage"` + `match.peer.id="<handle|chat_id:*|chat_guid:*|chat_identifier:*>"`. Prefer `chat_id:*` for stable group bindings.
</ParamField>
<ParamField path="bindings[].agentId" type="string">
The owning OpenClaw agent id.
</ParamField>
<ParamField path="bindings[].acp.mode" type='"persistent" | "oneshot"'>
Optional ACP override.
</ParamField>
<ParamField path="bindings[].acp.label" type="string">
Optional operator-facing label.
</ParamField>
<ParamField path="bindings[].acp.cwd" type="string">
Optional runtime working directory.
</ParamField>
<ParamField path="bindings[].acp.backend" type="string">
Optional backend override.
</ParamField>
### Runtime defaults per agent
Use `agents.list[].runtime` to define ACP defaults once per agent:
- `agents.list[].runtime.type="acp"`
- `agents.list[].runtime.acp.agent` (harness id, e.g. `codex` or `claude`)
- `agents.list[].runtime.acp.backend`
- `agents.list[].runtime.acp.mode`
- `agents.list[].runtime.acp.cwd`
**Override precedence for ACP bound sessions:**
1. `bindings[].acp.*`
2. `agents.list[].runtime.acp.*`
3. Global ACP defaults (e.g. `acp.backend`)
### Example
```json5
{
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
- OpenClaw ensures the configured ACP session exists after channel-specific admission and before use.
- Messages in that channel, topic, or chat route to the configured ACP session.
- Configured ACP bindings own their session route. Channel broadcast fan-out does not replace the configured ACP session for a matched binding.
- In bound conversations, `/new` and `/reset` reset the same ACP session key in place.
- Temporary runtime bindings (for example created by thread-focus flows) still apply where present.
- For cross-agent ACP spawns without an explicit `cwd`, OpenClaw inherits the target agent workspace from agent config.
- Missing inherited workspace paths fall back to the backend default cwd; non-missing access failures surface as spawn errors.
## Start ACP sessions
Two ways to start an ACP session:
<Tabs>
<Tab title="From sessions_spawn">
Use `runtime: "acp"` to start an ACP session from an agent turn or tool
call.
```json
{
"task": "Open the repo and summarize failing tests",
"runtime": "acp",
"agentId": "codex",
"thread": true,
"mode": "session"
}
```
<Note>
`runtime` defaults to `subagent`, so set `runtime: "acp"` explicitly for
ACP sessions. If `agentId` is omitted, OpenClaw uses `acp.defaultAgent`
when configured. `mode: "session"` requires `thread: true` to keep a
persistent bound conversation.
</Note>
</Tab>
<Tab title="From /acp command">
Use `/acp spawn` for explicit operator control from chat.
```text
/acp spawn codex --mode persistent --thread auto
/acp spawn codex --mode oneshot --thread off
/acp spawn codex --bind here
/acp spawn codex --thread here
```
Key flags:
- `--mode persistent|oneshot`
- `--bind here|off`
- `--thread auto|here|off`
- `--cwd <absolute-path>`
- `--label <name>`
See [Slash commands](/tools/slash-commands).
</Tab>
</Tabs>
### `sessions_spawn` parameters
<ParamField path="task" type="string" required>
Initial prompt sent to the ACP session.
</ParamField>
<ParamField path="runtime" type='"acp"' required>
Must be `"acp"` for ACP sessions.
</ParamField>
<ParamField path="agentId" type="string">
ACP target harness id. Falls back to `acp.defaultAgent` if set.
</ParamField>
<ParamField path="thread" type="boolean" default="false">
Request thread binding flow where supported.
</ParamField>
<ParamField path="mode" type='"run" | "session"' default="run">
`"run"` is one-shot; `"session"` is persistent. If `thread: true` and
`mode` is omitted, OpenClaw may default to persistent behaviour per
runtime path. `mode: "session"` requires `thread: true`.
</ParamField>
<ParamField path="cwd" type="string">
Requested runtime working directory (validated by backend/runtime policy).
If omitted, ACP spawn inherits the target agent workspace when configured;
missing inherited paths fall back to backend defaults, while real access
errors are returned.
</ParamField>
<ParamField path="label" type="string">
Operator-facing label used in session/banner text.
</ParamField>
<ParamField path="resumeSessionId" type="string">
Resume an existing ACP session instead of creating a new one. The agent
replays its conversation history via `session/load`. Requires
`runtime: "acp"`.
</ParamField>
<ParamField path="streamTo" type='"parent"'>
`"parent"` streams initial ACP run progress summaries back to the requester
session as system events. Accepted responses include `streamLogPath`
pointing to a session-scoped JSONL log (`<sessionId>.acp-stream.jsonl`) you
can tail for full relay history. Parent progress streams show assistant
commentary and ACP status progress by default unless
`streaming.progress.commentary=false`. Discord also defaults parent
previews to progress mode when no stream mode is configured. Status
progress still honors `acp.stream.tagVisibility`, so tags such as `plan`
remain hidden unless explicitly enabled.
</ParamField>
ACP `sessions_spawn` runs use `agents.defaults.subagents.runTimeoutSeconds`
for their default child turn limit. The tool does not accept per-call
timeout overrides (`runTimeoutSeconds`/`timeoutSeconds` are rejected with a
config-the-default error).
<ParamField path="model" type="string">
Explicit model override for the ACP child session. Codex ACP spawns
normalize OpenAI refs such as `openai/gpt-5.4` to Codex ACP startup config
before `session/new`; slash forms such as `openai/gpt-5.4/high` also set
Codex ACP reasoning effort. When omitted, `sessions_spawn({ runtime: "acp" })`
uses existing subagent model defaults (`agents.defaults.subagents.model` or
`agents.list[].subagents.model`) when configured; otherwise it lets the ACP
harness use its own default model. Other harnesses must advertise ACP
`models` and support `session/set_model`; otherwise OpenClaw/acpx fails
clearly instead of silently falling back to the target agent default.
</ParamField>
<ParamField path="thinking" type="string">
Explicit thinking/reasoning effort. For Codex ACP, `minimal` maps to low
effort, `low`/`medium`/`high`/`xhigh` map directly, and `off` omits the
reasoning-effort startup override. When omitted, ACP spawns use existing
subagent thinking defaults and per-model
`agents.defaults.models["provider/model"].params.thinking` for the selected
model.
</ParamField>
## Spawn bind and thread modes
<Tabs>
<Tab title="--bind here|off">
| Mode | Behavior |
| ------ | ----------------------------------------------------------------------- |
| `here` | Bind the current active conversation in place; fail if none is active. |
| `off` | Do not create a current-conversation binding. |
Notes:
- `--bind here` is the simplest operator path for "make this channel or chat Codex-backed."
- `--bind here` does not create a child thread.
- `--bind here` is only available on channels that expose current-conversation binding support.
- `--bind` and `--thread` cannot be combined in the same `/acp spawn` call.
</Tab>
<Tab title="--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:
- On non-thread binding surfaces, default behavior is effectively `off`.
- Thread-bound spawn requires channel policy support:
- Discord: `channels.discord.threadBindings.spawnSessions=true`
- Telegram: `channels.telegram.threadBindings.spawnSessions=true`
- Use `--bind here` when you want to pin the current conversation without creating a child thread.
</Tab>
</Tabs>
## Delivery model
ACP sessions can be either interactive workspaces or parent-owned background
work. The delivery path depends on that shape.
<AccordionGroup>
<Accordion title="Interactive ACP sessions">
Interactive sessions are meant to keep talking on a visible chat surface:
- `/acp spawn ... --bind here` binds the current conversation to the ACP session.
- `/acp spawn ... --thread ...` binds a channel thread/topic to the ACP session.
- Persistent configured `bindings[].type="acp"` route matching conversations to the same ACP session.
Follow-up messages in the bound conversation route directly to the ACP
session, and ACP output is delivered back to that same
channel/thread/topic.
What OpenClaw sends to the harness:
- Normal bound follow-ups are sent as prompt text, plus attachments only when the harness/backend supports them.
- `/acp` management commands and local Gateway commands are intercepted before ACP dispatch.
- Runtime-generated completion events are materialized per target. OpenClaw agents get OpenClaw's internal runtime-context envelope; external ACP harnesses get a plain prompt with the child result and instruction. The raw `<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>` envelope should never be sent to external harnesses or persisted as ACP user transcript text.
- ACP transcript entries use the user-visible trigger text or the plain completion prompt. Internal event metadata stays structured in OpenClaw where possible and is not treated as user-authored chat content.
</Accordion>
<Accordion title="Parent-owned one-shot ACP sessions">
One-shot ACP sessions spawned by another agent run are background
children, similar to sub-agents:
- The parent asks for work with `sessions_spawn({ runtime: "acp", mode: "run" })`.
- The child runs in its own ACP harness session.
- Child turns run on the same background lane used by native sub-agent spawns, so a slow ACP harness does not block unrelated main-session work.
- Completion reports back through the task-completion announce path. OpenClaw converts internal completion metadata into a plain ACP prompt before sending it to an external harness, so harnesses do not see OpenClaw-only runtime context markers.
- The parent rewrites the child result in normal assistant voice when a user-facing reply is useful.
Do **not** treat this path as a peer-to-peer chat between parent and
child. The child already has a completion channel back to the parent.
</Accordion>
<Accordion title="sessions_send and A2A delivery">
`sessions_send` can target another session after spawn. For normal peer
sessions, OpenClaw uses an agent-to-agent (A2A) follow-up path after
injecting the message:
- Wait for the target session's reply.
- Optionally let requester and target exchange a bounded number of follow-up turns.
- Ask the target to produce an announce message.
- Deliver that announce to the visible channel or thread.
That A2A path is a fallback for peer sends where the sender needs a
visible follow-up. It stays enabled when an unrelated session can see and
message an ACP target, for example under broad `tools.sessions.visibility`
settings.
OpenClaw skips the A2A follow-up only when the requester is the parent of
its own parent-owned one-shot ACP child. In that case, running A2A on top
of task completion can wake the parent with the child's result, forward
the parent's reply back into the child, and create a parent/child echo
loop. The `sessions_send` result reports `delivery.status="skipped"` for
that owned-child case because the completion path is already responsible
for the result.
</Accordion>
<Accordion title="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.
```json
{
"task": "Continue where we left off - fix the remaining test failures",
"runtime": "acp",
"agentId": "codex",
"resumeSessionId": "<previous-session-id>"
}
```
Common use cases:
- Hand off a Codex session from your laptop to your phone - tell your agent to pick up where you left off.
- Continue a coding session you started interactively in the CLI, now headlessly through your agent.
- Pick up work that was interrupted by a gateway restart or idle timeout.
Notes:
- `resumeSessionId` only applies when `runtime: "acp"`; the default sub-agent runtime ignores this ACP-only field.
- `streamTo` only applies when `runtime: "acp"`; the default sub-agent runtime ignores this ACP-only field.
- `resumeSessionId` is a host-local ACP/harness resume id, not an OpenClaw channel session key; OpenClaw still checks ACP spawn policy and target agent policy before dispatch, while the ACP backend or harness owns authorization for loading that upstream id.
- `resumeSessionId` restores the upstream ACP conversation history; `thread` and `mode` still apply normally to the new OpenClaw session you are creating, so `mode: "session"` still requires `thread: true`.
- The target agent must support `session/load` (Codex and Claude Code do).
- If the session id is not found, the spawn fails with a clear error - no silent fallback to a new session.
</Accordion>
<Accordion title="Post-deploy smoke test">
After a gateway deploy, run a live end-to-end check rather than trusting
unit tests:
1. Verify the deployed gateway version and commit on the target host.
2. Open a temporary ACPX bridge session to a live agent.
3. Ask that agent to call `sessions_spawn` with `runtime: "acp"`, `agentId: "codex"`, `mode: "run"`, and task `Reply with exactly LIVE-ACP-SPAWN-OK`.
4. Verify `accepted=yes`, a real `childSessionKey`, and no validator error.
5. Clean up the temporary bridge session.
Keep the gate on `mode: "run"` and skip `streamTo: "parent"` -
thread-bound `mode: "session"` and stream-relay paths are separate richer
integration passes.
</Accordion>
</AccordionGroup>
## Sandbox compatibility
ACP sessions currently run on the host runtime, **not** inside the OpenClaw
sandbox.
<Warning>
**Security boundary:**
- The external harness can read/write according to its own CLI permissions and the selected `cwd`.
- OpenClaw's sandbox policy does **not** wrap ACP harness execution.
- OpenClaw still enforces ACP feature gates, allowed agents, session ownership, channel bindings, and Gateway delivery policy.
- Use `runtime: "subagent"` for sandbox-enforced OpenClaw-native work.
</Warning>
Current limitations:
- If the requester session is sandboxed, ACP spawns are blocked for both `sessions_spawn({ runtime: "acp" })` and `/acp spawn`.
- `sessions_spawn` with `runtime: "acp"` does not support `sandbox: "require"`.
## 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`)
- tries key
- then UUID-shaped session id
- then label
2. Current thread binding (if this conversation/thread is bound to an ACP session).
3. Current requester session fallback.
Current-conversation bindings and thread bindings both participate in step 2.
If no target resolves, OpenClaw returns a clear error
(`Unable to resolve session target: ...`).
## ACP controls
| Command | What it does | Example |
| -------------------- | --------------------------------------------------------- | ------------------------------------------------------------- |
| `/acp spawn` | Create ACP session; optional current bind or thread bind. | `/acp spawn codex --bind here --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.4` |
| `/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` |
Runtime controls (`spawn`, `cancel`, `steer`, `close`, `status`, `set-mode`,
`set`, `cwd`, `permissions`, `timeout`, `model`, and `reset-options`) require
owner identity from external channels and `operator.admin` from internal
Gateway clients. Authorized non-owner senders can still use `sessions`,
`doctor`, `install`, and `help`.
`/acp status` shows the effective runtime options plus runtime-level and
backend-level session identifiers. Unsupported-control errors surface
clearly when a backend lacks a capability. `/acp sessions` reads the store
for the current bound or requester session; target tokens (`session-key`,
`session-id`, or `session-label`) resolve through gateway session discovery,
including custom per-agent `session.store` roots.
### Runtime options mapping
`/acp` has convenience commands and a generic setter. Equivalent operations:
| Command | Maps to | Notes |
| ---------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/acp model <id>` | runtime config key `model` | For Codex ACP, OpenClaw normalizes `openai/<model>` to the adapter model id and maps slash reasoning suffixes such as `openai/gpt-5.4/high` to `reasoning_effort`. |
| `/acp set thinking <level>` | canonical option `thinking` | OpenClaw sends the backend-advertised equivalent when present, preferring `thinking`, then `effort`, `reasoning_effort`, or `thought_level`. For Codex ACP, the adapter maps values to `reasoning_effort`. |
| `/acp permissions <profile>` | canonical option `permissionProfile` | OpenClaw sends the backend-advertised equivalent when present, such as `approval_policy`, `permission_profile`, `permissions`, or `permission_mode`. |
| `/acp timeout <seconds>` | canonical option `timeoutSeconds` | OpenClaw sends the backend-advertised equivalent when present, such as `timeout` or `timeout_seconds`. |
| `/acp cwd <path>` | runtime cwd override | Direct update. |
| `/acp set <key> <value>` | generic | `key=cwd` uses the cwd override path. |
| `/acp reset-options` | clears all runtime overrides | - |
## acpx harness, plugin setup, and permissions
For acpx harness configuration (Claude Code / Codex / Gemini CLI aliases),
the plugin-tools and OpenClaw-tools MCP bridges, and ACP permission modes,
see [ACP agents - setup](/tools/acp-agents-setup).
## Troubleshooting
| Symptom | Likely cause | Fix |
| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ACP runtime backend is not configured` | Backend plugin missing, disabled, or blocked by `plugins.allow`. | Install and enable backend plugin, include `acpx` in `plugins.allow` when that allowlist is set, 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)` | Automatic dispatch from normal thread messages disabled. | Set `acp.dispatch.enabled=true` to resume automatic thread routing; explicit `sessions_spawn({ runtime: "acp" })` calls still work. |
| `ACP agent "<id>" is not allowed by policy` | Agent not in allowlist. | Use allowed `agentId` or update `acp.allowedAgents`. |
| `/acp doctor` reports backend not ready right after startup | Backend plugin is missing, disabled, blocked by allow/deny policy, or its configured executable is unavailable. | Install/enable the backend plugin, rerun `/acp doctor`, and inspect the backend install or policy error if it stays unhealthy. |
| Harness command not found | Adapter CLI is not installed, the external plugin is missing, or first-run `npx` fetch failed for a non-Codex adapter. | Run `/acp doctor`, install/prewarm the adapter on the Gateway host, or configure the acpx agent command explicitly. |
| Model-not-found from the harness | Model id is valid for another provider/harness but not this ACP target. | Use a model listed by that harness, configure the model in the harness, or omit the override. |
| Vendor auth error from the harness | OpenClaw is healthy, but the target CLI/provider is not logged in. | Log in or provide the required provider key on the Gateway host environment. |
| `Unable to resolve session target: ...` | Bad key/id/label token. | Run `/acp sessions`, copy exact key/label, retry. |
| `--bind here requires running /acp spawn inside an active ... conversation` | `--bind here` used without an active bindable conversation. | Move to the target chat/channel and retry, or use unbound spawn. |
| `Conversation bindings are unavailable for <channel>.` | Adapter lacks current-conversation ACP binding capability. | Use `/acp spawn ... --thread ...` where supported, configure top-level `bindings[]`, or move to a supported channel. |
| `--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 channel/conversation/thread.` | Another user owns the active binding target. | Rebind as owner or use a different conversation or 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. |
| `Cannot apply --model ... did not advertise model support` | The target harness does not expose generic ACP model switching. | Use a harness that advertises ACP `models`/`session/set_model`, use Codex ACP model refs, or configure the model directly in the harness if it has its own startup flag. |
| Missing ACP metadata for bound session | Stale/deleted ACP session metadata. | Recreate with `/acp spawn`, then rebind/focus thread. |
| `PermissionPromptUnavailableError: 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](/tools/acp-agents-setup#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. | Update OpenClaw; current acpx cleanup reaps OpenClaw-owned stale wrapper and adapter processes on close and Gateway startup. |
| Harness sees `<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>` | Internal event envelope leaked across the ACP boundary. | Update OpenClaw and rerun the completion flow; external harnesses should receive plain completion prompts only. |
<Note>
`Command blocked by PreToolUse hook: Native hook relay unavailable` belongs to
the native Codex hook relay, not ACP/acpx. In a bound Codex chat, start a
fresh session with `/new` or `/reset`; if it works once and then returns on
the next native tool call, restart the Codex app-server or OpenClaw Gateway
instead of repeating `/new`. See
[Codex harness troubleshooting](/plugins/codex-harness#troubleshooting).
</Note>
## Related
- [ACP agents - setup](/tools/acp-agents-setup)
- [Agent send](/tools/agent-send)
- [CLI Backends](/gateway/cli-backends)
- [Codex harness](/plugins/codex-harness)
- [Codex harness runtime](/plugins/codex-harness-runtime)
- [Multi-agent sandbox tools](/tools/multi-agent-sandbox-tools)
- [`openclaw acp` (bridge mode)](/cli/acp)
- [Sub-agents](/tools/subagents)

154
docs/tools/agent-send.md Normal file
View File

@@ -0,0 +1,154 @@
---
summary: "Run agent turns from the CLI and optionally deliver replies to channels"
read_when:
- You want to trigger agent runs from scripts or the command line
- You need to deliver agent replies to a chat channel programmatically
title: "Agent send"
---
`openclaw agent` runs a single agent turn from the command line without an
inbound chat message. Use it for scripted workflows, testing, and
programmatic delivery. Full flag and behavior reference:
[Agent CLI reference](/cli/agent).
## Quick start
<Steps>
<Step title="Run a simple agent turn">
```bash
openclaw agent --agent main --message "What is the weather today?"
```
Sends the message through the Gateway and prints the reply.
</Step>
<Step title="Send a multiline prompt from a file">
```bash
openclaw agent --agent ops --message-file ./task.md
```
Reads a valid UTF-8 file as the agent message body.
</Step>
<Step title="Target a specific agent or session">
```bash
# Target a specific agent
openclaw agent --agent ops --message "Summarize logs"
# Target a phone number (derives session key)
openclaw agent --to +15555550123 --message "Status update"
# Reuse an existing session
openclaw agent --session-id abc123 --message "Continue the task"
# Target an exact session key
openclaw agent --session-key agent:ops:incident-42 --message "Summarize status"
```
</Step>
<Step title="Deliver the reply to a channel">
```bash
# Deliver to WhatsApp (default channel)
openclaw agent --to +15555550123 --message "Report ready" --deliver
# Deliver to Slack
openclaw agent --agent ops --message "Generate report" \
--deliver --reply-channel slack --reply-to "#reports"
```
</Step>
</Steps>
## Flags
| Flag | Description |
| --------------------------- | -------------------------------------------------------------------- |
| `--message <text>` | Inline message to send |
| `--message-file <path>` | Read the message from a valid UTF-8 file |
| `--to <dest>` | Derive session key from a target (phone, chat id) |
| `--session-key <key>` | Use an explicit session key |
| `--agent <id>` | Target a configured agent (uses its `main` session) |
| `--session-id <id>` | Reuse an existing session by id |
| `--model <id>` | Model override for this run (`provider/model` or model id) |
| `--local` | Force local embedded runtime (skip Gateway) |
| `--deliver` | Send the reply to a chat channel |
| `--channel <name>` | Delivery channel (discord, slack, telegram, whatsapp, 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 for the selected model profile |
| `--verbose <on\|full\|off>` | Persist verbose level for the session (`full` also logs tool output) |
| `--timeout <seconds>` | Override agent timeout (default 600, or config value) |
| `--json` | Output structured JSON |
## Behavior
- By default, the CLI goes **through the Gateway**. Add `--local` to force the
embedded runtime on the current machine.
- Pass exactly one of `--message` or `--message-file`. File messages preserve
multiline content after removing an optional UTF-8 BOM.
- If the Gateway request fails, the CLI **falls back** to the local embedded
run; a Gateway timeout falls back with a fresh session instead of racing the
original transcript.
- Session selection: `--to` derives the session key (group/channel targets
preserve isolation; direct chats collapse to `main`).
- `--session-key` selects an explicit key. Agent-prefixed keys must use
`agent:<agent-id>:<session-key>`, and `--agent` must match that agent id when
both are supplied. Bare non-sentinel keys are scoped to `--agent` when
supplied; for example, `--agent ops --session-key incident-42` routes to
`agent:ops:incident-42`. Without `--agent`, bare non-sentinel keys are scoped
to the configured default agent. Literal `global` and `unknown` remain
unscoped only when no `--agent` is supplied; the embedded fallback path
resolves those sentinel sessions to the configured default agent.
- `--channel`, `--reply-channel`, and `--reply-account` affect reply delivery,
not session routing.
- Thinking and verbose flags persist into the session store.
- Output: plain text by default, or `--json` for structured payload + metadata.
- With `--json --deliver`, the JSON includes delivery status for sent,
suppressed, partial, and failed sends. See
[JSON delivery status](/cli/agent#json-delivery-status).
## Examples
```bash
# Simple turn with JSON output
openclaw agent --to +15555550123 --message "Trace logs" --verbose on --json
# Turn with a model override
openclaw agent --agent ops --model openai/gpt-5.4 --message "Summarize logs"
# Turn with thinking level
openclaw agent --session-id 1234 --message "Summarize inbox" --thinking medium
# Multiline prompt from a file
openclaw agent --agent ops --message-file ./task.md
# Exact session key
openclaw agent --session-key agent:ops:incident-42 --message "Summarize status"
# Legacy key scoped to an agent
openclaw agent --agent ops --session-key incident-42 --message "Summarize status"
# Deliver to a different channel than the session
openclaw agent --agent ops --message "Alert" --deliver --reply-channel telegram --reply-to "@admin"
```
## Related
<CardGroup cols={2}>
<Card title="Agent CLI reference" href="/cli/agent" icon="terminal">
Full `openclaw agent` flag and option reference.
</Card>
<Card title="Sub-agents" href="/tools/subagents" icon="users">
Background sub-agent spawning.
</Card>
<Card title="Sessions" href="/concepts/session" icon="comments">
How session keys work and how `--to`, `--agent`, and `--session-id` resolve them.
</Card>
<Card title="Slash commands" href="/tools/slash-commands" icon="slash">
Native command catalog used inside agent sessions.
</Card>
</CardGroup>

64
docs/tools/apply-patch.md Normal file
View File

@@ -0,0 +1,64 @@
---
summary: "Apply multi-file patches with the apply_patch tool"
read_when:
- You need structured file edits across multiple files
- You want to document or debug patch-based edits
title: "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:
```text
*** Begin Patch
*** Add File: path/to/file.txt
+line 1
+line 2
*** Update File: src/app.ts
@@ optional change context
-old line
+new line
*** Delete File: obsolete.txt
*** End Patch
```
## Parameters
- `input` (required): Full patch contents including `*** Begin Patch` and `*** End Patch`.
## 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.
- Enabled by default for every model. Set `tools.exec.applyPatch.enabled: false`
to disable it, or restrict it to specific models with
`tools.exec.applyPatch.allowModels` (accepts raw ids like `gpt-5.4` or full
ids like `openai/gpt-5.4`).
- Config lives under `tools.exec.applyPatch.*`.
## Example
```json
{
"tool": "apply_patch",
"input": "*** Begin Patch\n*** Update File: src/index.ts\n@@\n-const foo = 1\n+const foo = 2\n*** End Patch"
}
```
## Related
<CardGroup cols={2}>
<Card title="Diffs" href="/tools/diffs" icon="code-compare">
Read-only diff viewer for change presentation.
</Card>
<Card title="Exec tool" href="/tools/exec" icon="terminal">
Shell command execution from the agent.
</Card>
<Card title="Code execution" href="/tools/code-execution" icon="square-code">
Sandboxed remote Python analysis with xAI.
</Card>
</CardGroup>

138
docs/tools/brave-search.md Normal file
View File

@@ -0,0 +1,138 @@
---
summary: "Brave Search API setup for web_search"
read_when:
- You want to use Brave Search for web_search
- You need a BRAVE_API_KEY or plan details
title: "Brave search"
---
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/](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
```json5
{
plugins: {
entries: {
brave: {
config: {
webSearch: {
apiKey: "BRAVE_API_KEY_HERE",
mode: "web", // or "llm-context"
baseUrl: "https://api.search.brave.com", // optional proxy/base URL override
},
},
},
},
},
tools: {
web: {
search: {
provider: "brave",
maxResults: 5,
timeoutSeconds: 30,
},
},
},
}
```
Provider-specific Brave search settings live under `plugins.entries.brave.config.webSearch.*`; this is the canonical config path. A shared top-level `tools.web.search.apiKey` and a scoped `tools.web.search.brave.*` still load through a compatibility merge, but new config should use the plugin-scoped path above.
`webSearch.mode` controls the Brave transport:
- `web` (default): normal Brave web search with titles, URLs, and snippets
- `llm-context`: Brave LLM Context API with pre-extracted text chunks and sources for grounding
`webSearch.baseUrl` can point Brave requests at a trusted Brave-compatible proxy
or gateway. OpenClaw appends `/res/v1/web/search` or `/res/v1/llm/context` to
the configured base URL and keeps the base URL in the cache key. Public
endpoints must use `https://`; `http://` is accepted only for trusted loopback
or private-network proxy hosts.
## Tool parameters
<ParamField path="query" type="string" required>
Search query.
</ParamField>
<ParamField path="count" type="number" default="5">
Number of results to return (110).
</ParamField>
<ParamField path="country" type="string">
2-letter ISO country code (e.g. `US`, `DE`).
</ParamField>
<ParamField path="language" type="string">
ISO 639-1 language code for search results (e.g. `en`, `de`, `fr`).
</ParamField>
<ParamField path="search_lang" type="string">
Brave search-language code (e.g. `en`, `en-gb`, `zh-hans`).
</ParamField>
<ParamField path="ui_lang" type="string">
ISO language code for UI elements.
</ParamField>
<ParamField path="freshness" type="'day' | 'week' | 'month' | 'year'">
Time filter — `day` is 24 hours.
</ParamField>
<ParamField path="date_after" type="string">
Only results published after this date (`YYYY-MM-DD`).
</ParamField>
<ParamField path="date_before" type="string">
Only results published before this date (`YYYY-MM-DD`).
</ParamField>
**Examples:**
```javascript
// 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",
});
```
## Notes
- OpenClaw uses the Brave **Search** plan. If you have a legacy subscription (e.g. the original Free plan with 2,000 queries/month), it remains valid but does not include newer features like LLM Context or higher rate limits.
- Each Brave plan includes **\$5/month in free credit** (renewing). The Search plan costs \$5 per 1,000 requests, so the credit covers 1,000 queries/month. Set your usage limit in the Brave dashboard to avoid unexpected charges. See the [Brave API portal](https://brave.com/search/api/) for current plans.
- The Search plan includes the LLM Context endpoint and AI inference rights. Storing results to train or tune models requires a plan with explicit storage rights. See the Brave [Terms of Service](https://api-dashboard.search.brave.com/terms-of-service).
- `llm-context` mode returns grounded source entries instead of the normal web-search snippet shape.
- `llm-context` mode supports `freshness` and bounded `date_after` + `date_before` ranges. It does not support `ui_lang`; `date_before` without `date_after` is rejected because Brave requires custom freshness ranges to include both start and end dates.
- `ui_lang` must include a region subtag like `en-US`.
- Results are cached for 15 minutes by default (configurable via `cacheTtlMinutes`).
- Custom `webSearch.baseUrl` values are included in Brave cache identity, so
proxy-specific responses do not collide.
- Enable the `brave.http` diagnostics flag to log Brave request URLs/query params, response status/timing, and search-cache hit/miss/write events while troubleshooting. The flag never logs the API key or response bodies, but search queries can be sensitive.
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [Perplexity Search](/tools/perplexity-search) -- structured results with domain filtering
- [Exa Search](/tools/exa-search) -- neural search with content extraction

View File

@@ -0,0 +1,442 @@
---
summary: "OpenClaw browser control API, CLI reference, and scripting actions"
read_when:
- Scripting or debugging the agent browser via the local control API
- Looking for the `openclaw browser` CLI reference
- Adding custom browser automation with snapshots and refs
title: "Browser control API"
---
For setup, configuration, and troubleshooting, see [Browser](/tools/browser).
This page is the reference for the local control HTTP API, the `openclaw browser`
CLI, and scripting patterns (snapshots, refs, waits, debug flows).
## Control API (optional)
For local integrations only, the Gateway exposes a small loopback HTTP API.
This standalone server is opt-in — set the environment variable
`OPENCLAW_EAGER_BROWSER_CONTROL_SERVER=1` in the gateway service environment
and restart the gateway before the HTTP endpoints become available. Without
this variable the browser control runtime still works through the CLI and
agent tools, but nothing listens on the loopback control port.
- Status/start/stop: `GET /`, `GET /doctor`, `POST /start`, `POST /stop`, `POST /reset-profile`
- Profiles: `GET /profiles`, `POST /profiles/create`, `DELETE /profiles/:name`
- Tabs: `GET /tabs`, `POST /tabs/open`, `POST /tabs/focus`, `DELETE /tabs/:targetId`, `POST /tabs/action`
- 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`
- Permissions: `POST /permissions/grant`
- Debugging: `GET /console`, `POST /pdf`
- Debugging: `GET /errors`, `GET /requests`, `GET /dialogs`, `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`
`POST /tabs/action` is the batched form the CLI uses internally for
`browser tab` subcommands (`{"action":"new"|"label"|"select"|"close"|"list", ...}`);
prefer the single-purpose tab routes above when scripting directly.
All endpoints accept `?profile=<name>`. `POST /start?headless=true` requests a
one-shot headless launch for local managed profiles without changing persisted
browser config; attach-only, remote CDP, and existing-session profiles reject
that override because OpenClaw does not launch those browser processes.
For tab endpoints, `targetId` is the compatibility field name. Prefer passing
`suggestedTargetId` from `GET /tabs` or `POST /tabs/open`; labels and `tabId`
handles such as `t1` are also accepted. Raw CDP target ids and unique raw
target-id prefixes still work, but they are volatile diagnostic handles.
If shared-secret gateway auth is configured, browser HTTP routes require auth too:
- `Authorization: Bearer <gateway token>`
- `x-openclaw-password: <gateway password>` or HTTP Basic auth with that password
Notes:
- This standalone loopback browser API does **not** consume trusted-proxy or
Tailscale Serve identity headers.
- If `gateway.auth.mode` is `none` or `trusted-proxy`, these loopback browser
routes do not inherit those identity-bearing modes; keep them loopback-only.
### `/act` error contract
`POST /act` uses a structured error response for route-level validation and
policy failures:
```json
{ "error": "<message>", "code": "ACT_*" }
```
Current `code` values:
- `ACT_KIND_REQUIRED` (HTTP 400): `kind` is missing or unrecognized.
- `ACT_INVALID_REQUEST` (HTTP 400): action payload failed normalization or validation.
- `ACT_SELECTOR_UNSUPPORTED` (HTTP 400): `selector` was used with an unsupported action kind.
- `ACT_EVALUATE_DISABLED` (HTTP 403): `evaluate` (or `wait --fn`) is disabled by config.
- `ACT_TARGET_ID_MISMATCH` (HTTP 403): top-level or batched `targetId` conflicts with request target.
- `ACT_EXISTING_SESSION_UNSUPPORTED` (HTTP 501): action is not supported for existing-session profiles.
Other runtime failures may still return `{ "error": "<message>" }` without a
`code` field.
### 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.
What still works without Playwright:
- ARIA snapshots
- Role-style accessibility snapshots (`--interactive`, `--compact`,
`--depth`, `--efficient`) when a per-tab CDP WebSocket is available. This is
a fallback for inspection and ref discovery; Playwright remains the primary
action engine.
- Page screenshots for the managed `openclaw` browser when a per-tab CDP
WebSocket is available
- Page screenshots for `existing-session` / Chrome MCP profiles
- `existing-session` ref-based screenshots (`--ref`) from snapshot output
What still needs Playwright:
- `navigate`
- `act`
- AI snapshots that depend on Playwright's native AI snapshot format
- CSS-selector element screenshots (`--element`)
- full browser PDF export
Element screenshots also reject `--full-page`; the route returns `fullPage is
not supported for element screenshots`.
If you see `Playwright is not available in this gateway build`, the packaged
Gateway is missing the core browser runtime dependency. Reinstall or update
OpenClaw, then restart the gateway. For Docker, also install the Chromium
browser binaries as shown below.
#### Docker Playwright install
If your Gateway runs in Docker, avoid `npx playwright` (npm override conflicts).
For custom images, bake Chromium into the image:
```bash
OPENCLAW_INSTALL_BROWSER=1 ./scripts/docker/setup.sh
```
For an existing image, install through the bundled CLI instead:
```bash
docker compose run --rm openclaw-cli \
node /app/node_modules/playwright-core/cli.js install chromium
```
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. OpenClaw auto-detects the persisted
Chromium on Linux. See [Docker](/install/docker).
## How it works (internal)
A small loopback control server accepts HTTP requests and connects to Chromium-based browsers via CDP. Advanced actions (click/type/snapshot/PDF) go through Playwright on top of CDP; when Playwright is missing, only non-Playwright operations are available. The agent sees one stable interface while local/remote browsers and profiles swap freely underneath.
## CLI quick reference
All commands accept `--browser-profile <name>` to target a specific profile, and `--json` for machine-readable output.
<AccordionGroup>
<Accordion title="Basics: status, tabs, open/focus/close">
```bash
openclaw browser status
openclaw browser doctor
openclaw browser doctor --deep # add a live snapshot probe
openclaw browser start
openclaw browser start --headless # one-shot local managed headless launch
openclaw browser stop # also clears emulation on attach-only/remote CDP
openclaw browser reset-profile # moves the profile's browser data to Trash
openclaw browser tabs
openclaw browser tab # shortcut for current tab
openclaw browser tab new
openclaw browser tab new --label research
openclaw browser tab label abcd1234 research
openclaw browser tab select 2
openclaw browser tab close 2
openclaw browser open https://example.com
openclaw browser focus abcd1234
openclaw browser close abcd1234
```
</Accordion>
<Accordion title="Profiles: list, create, delete">
```bash
openclaw browser profiles
openclaw browser create-profile --name research --color "#0066CC"
openclaw browser create-profile --name attach --driver existing-session --cdp-url http://127.0.0.1:9222
openclaw browser delete-profile --name research
```
</Accordion>
<Accordion title="Inspection: screenshot, snapshot, console, errors, requests">
```bash
openclaw browser screenshot
openclaw browser screenshot --full-page
openclaw browser screenshot --ref 12 # or --ref e12
openclaw browser screenshot --labels
openclaw browser snapshot
openclaw browser snapshot --format aria --limit 200
openclaw browser snapshot --interactive --compact --depth 6
openclaw browser snapshot --efficient
openclaw browser snapshot --labels
openclaw browser snapshot --urls
openclaw browser snapshot --selector "#main" --interactive
openclaw browser snapshot --frame "iframe#main" --interactive
openclaw browser snapshot --out snapshot.txt
openclaw browser console --level error
openclaw browser errors --clear
openclaw browser requests --filter api --clear
openclaw browser pdf
openclaw browser responsebody "**/api" --max-chars 5000
```
</Accordion>
<Accordion title="Actions: navigate, click, type, drag, wait, evaluate">
```bash
openclaw browser navigate https://example.com
openclaw browser resize 1280 720
openclaw browser click 12 --double # or e12 for role refs
openclaw browser click-coords 120 340 # viewport coordinates
openclaw browser type 23 "hello" --submit
openclaw browser press Enter
openclaw browser hover 44
openclaw browser scrollintoview e12
openclaw browser drag 10 11
openclaw browser select 9 OptionA OptionB
openclaw browser download e12 report.pdf
openclaw browser waitfordownload report.pdf
openclaw browser upload /tmp/openclaw/uploads/file.pdf
openclaw browser upload media://inbound/file.pdf
openclaw browser fill --fields '[{"ref":"1","type":"text","value":"Ada"}]'
openclaw browser dialog --accept
openclaw browser dialog --dismiss --dialog-id d1
openclaw browser wait --text "Done"
openclaw browser wait "#main" --url "**/dash" --load networkidle --fn "window.ready===true"
openclaw browser evaluate --fn '(el) => el.textContent' --ref 7
openclaw browser evaluate --fn 'const title = document.title; return title;'
openclaw browser evaluate --timeout-ms 30000 --fn 'async () => { await window.ready; return true; }'
openclaw browser highlight e12
openclaw browser trace start
openclaw browser trace stop
```
</Accordion>
<Accordion title="State: cookies, storage, offline, headers, geo, device">
```bash
openclaw browser cookies
openclaw browser cookies set session abc123 --url "https://example.com"
openclaw browser cookies clear
openclaw browser storage local get
openclaw browser storage local set theme dark
openclaw browser storage session clear
openclaw browser set offline on
openclaw browser set headers --headers-json '{"X-Debug":"1"}'
openclaw browser set credentials user pass # --clear to remove
openclaw browser set geo 37.7749 -122.4194 --origin "https://example.com"
openclaw browser set media dark
openclaw browser set timezone America/New_York
openclaw browser set locale en-US
openclaw browser set device "iPhone 14"
```
</Accordion>
</AccordionGroup>
Notes:
- `upload` and `dialog` are **arming** calls; run them before the click/press that triggers the chooser/dialog. If an action opens a modal, the action response includes `blockedByDialog` and `browserState.dialogs.pending`; pass that `dialogId` to respond directly. Dialogs handled outside OpenClaw appear under `browserState.dialogs.recent`.
- `click`/`type`/etc require a `ref` from `snapshot` (numeric `12`, role ref `e12`, or actionable ARIA ref `ax12`). CSS selectors are intentionally not supported for actions. Use `click-coords` when the visible viewport position is the only reliable target.
- Download and trace paths are constrained to OpenClaw temp roots: `/tmp/openclaw{,/downloads}` (fallback: `${os.tmpdir()}/openclaw/...`).
- `upload` accepts files from the OpenClaw temp uploads root and
OpenClaw-managed inbound media. Managed inbound media can be referenced as
`media://inbound/<id>`, sandbox-relative `media/inbound/<id>`, or a resolved
path inside the managed inbound media directory. Nested media refs,
traversal, symlinks, hardlinks, and arbitrary local paths are still rejected.
- `upload` can also set file inputs directly via `--input-ref` or `--element`.
Stable tab ids and labels survive Chromium raw-target replacement when OpenClaw
can prove the replacement tab, such as same URL or a single old tab becoming a
single new tab after form submission. Raw target ids are still volatile; prefer
`suggestedTargetId` from `tabs` in scripts.
Snapshot flags at a glance:
- `--format ai` (default with Playwright): AI snapshot with numeric refs (`aria-ref="<n>"`).
- `--format aria`: accessibility tree with `axN` refs. When Playwright is available, OpenClaw binds refs with backend DOM ids to the live page so follow-up actions can use them; otherwise treat the output as inspection-only.
- `--efficient` (or `--mode efficient`): compact role snapshot preset. Set `browser.snapshotDefaults.mode: "efficient"` to make this the default (see [Gateway configuration](/gateway/configuration-reference#browser)).
- `--interactive`, `--compact`, `--depth`, `--selector` force a role snapshot with `ref=e12` refs. `--frame "<iframe>"` scopes role snapshots to an iframe.
- With Playwright, `--labels` adds a screenshot with overlayed ref labels
(prints `MEDIA:<path>`) plus an `annotations` array with each ref's bounding
box. On `screenshot`, Playwright-backed labels work with `--full-page`,
`--ref`, and `--element`; on `snapshot`, the accompanying screenshot remains
viewport-only. Existing-session/chrome-mcp profiles render overlay labels on
page screenshots but do not return `annotations` or use the Playwright
full-page/ref/element projection helper. Without Playwright or chrome-mcp,
labeled screenshots are not available.
- `--urls` appends discovered link destinations to AI snapshots.
## Snapshots and refs
OpenClaw supports two "snapshot" styles:
- **AI snapshot (numeric refs)**: `openclaw browser snapshot` (default; `--format ai`)
- Output: a text snapshot that includes numeric refs.
- Actions: `openclaw browser click 12`, `openclaw browser type 23 "hello"`.
- Internally, the ref is resolved via Playwright's `aria-ref`.
- **Role snapshot (role refs like `e12`)**: `openclaw browser snapshot --interactive` (or `--compact`, `--depth`, `--selector`, `--frame`)
- Output: a role-based list/tree with `[ref=e12]` (and optional `[nth=1]`).
- Actions: `openclaw browser click e12`, `openclaw browser highlight e12`.
- Internally, the ref is resolved via `getByRole(...)` (plus `nth()` for duplicates).
- Add `--labels` to include a screenshot with overlayed `e12` labels. On
Playwright-backed profiles this also returns per-ref bounding-box metadata
(`annotations[]`).
- Add `--urls` when link text is ambiguous and the agent needs concrete
navigation targets.
- **ARIA snapshot (ARIA refs like `ax12`)**: `openclaw browser snapshot --format aria`
- Output: the accessibility tree as structured nodes.
- Actions: `openclaw browser click ax12` works when the snapshot path can bind
the ref through Playwright and Chrome backend DOM ids.
- If Playwright is unavailable, ARIA snapshots can still be useful for
inspection, but refs may not be actionable. Re-snapshot with `--format ai`
or `--interactive` when you need action refs.
- Docker proof for the raw-CDP fallback path: `pnpm test:docker:browser-cdp-snapshot`
starts Chromium with CDP, runs `browser doctor --deep`, and verifies role
snapshots include link URLs, cursor-promoted clickables, and iframe metadata.
Ref behavior:
- Refs are **not stable across navigations**; if something fails, re-run `snapshot` and use a fresh ref.
- `/act` returns the current raw `targetId` after action-triggered replacement
when it can prove the replacement tab. Keep using stable tab ids/labels for
follow-up commands.
- If the role snapshot was taken with `--frame`, role refs are scoped to that iframe until the next role snapshot.
- Unknown or stale `axN` refs fail fast instead of falling through to
Playwright's `aria-ref` selector. Run a fresh snapshot on the same tab when
that happens.
## Wait power-ups
You can wait on more than just time/text:
- Wait for URL (globs supported by Playwright):
- `openclaw browser wait --url "**/dash"`
- Wait for load state:
- `openclaw browser wait --load networkidle`
- Supported on managed `openclaw` and raw/remote CDP profiles. Profiles using the `existing-session` driver (including the default `user` profile) reject `networkidle`; use `--url`, `--text`, a selector, or `--fn` waits there.
- Wait for a JS predicate:
- `openclaw browser wait --fn "window.ready===true"`
- Wait for a selector to become visible:
- `openclaw browser wait "#main"`
These can be combined:
```bash
openclaw browser wait "#main" \
--url "**/dash" \
--load networkidle \
--fn "window.ready===true" \
--timeout-ms 15000
```
## 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:
- `openclaw browser errors --clear`
- `openclaw browser requests --filter api --clear`
5. For deep debugging: record a trace:
- `openclaw browser trace start`
- reproduce the issue
- `openclaw browser trace stop` (prints `TRACE:<path>`)
## JSON output
`--json` is for scripting and structured tooling.
Examples:
```bash
openclaw browser status --json
openclaw browser snapshot --interactive --json
openclaw browser requests --filter api --json
openclaw browser cookies --json
```
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:
- Cookies: `cookies`, `cookies set`, `cookies clear`
- Storage: `storage local|session get|set|clear`
- Offline: `set offline on|off`
- Headers: `set headers --headers-json '{"X-Debug":"1"}'` (or the positional form `set headers '{"X-Debug":"1"}'`)
- HTTP basic auth: `set credentials user pass` (or `--clear`)
- Geolocation: `set geo <lat> <lon> --origin "https://example.com"` (or `--clear`)
- Media: `set media dark|light|no-preference|none`
- Timezone / locale: `set timezone ...`, `set locale ...`
- Device / viewport:
- `set device "iPhone 14"` (Playwright device presets)
- `set viewport 1280 720`
## Security and privacy
- The openclaw browser profile may contain logged-in sessions; treat it as sensitive.
- `browser act kind=evaluate` / `openclaw browser evaluate` and `wait --fn`
execute arbitrary JavaScript in the page context. Prompt injection can steer
this. Disable it with `browser.evaluateEnabled=false` if you do not need it.
- `openclaw browser evaluate --fn` accepts a function source, an expression, or
a statement body. Statement bodies are wrapped as async functions, so use
`return` for the value you want back. Use `--timeout-ms <ms>` when the
page-side function may need longer than the default evaluate timeout.
- For logins and anti-bot notes (X/Twitter, etc.), see [Browser login + X/Twitter posting](/tools/browser-login).
- Keep the Gateway/node host private (loopback or tailnet-only).
- Remote CDP endpoints are powerful; tunnel and protect them.
Strict-mode example (block private/internal destinations by default):
```json5
{
browser: {
ssrfPolicy: {
dangerouslyAllowPrivateNetwork: false,
hostnameAllowlist: ["*.example.com", "example.com"],
allowedHostnames: ["localhost"], // optional exact allow
},
},
}
```
## Related
- [Browser](/tools/browser) - overview, configuration, profiles, security
- [Browser login](/tools/browser-login) - signing in to sites
- [Browser Linux troubleshooting](/tools/browser-linux-troubleshooting)
- [Browser WSL2 troubleshooting](/tools/browser-wsl2-windows-remote-cdp-troubleshooting)

View File

@@ -0,0 +1,168 @@
---
summary: "Fix Chrome/Brave/Edge/Chromium CDP startup issues for OpenClaw browser control on Linux"
read_when: "Browser control fails on Linux, especially with snap Chromium"
title: "Browser troubleshooting"
---
## Problem: Failed to start Chrome CDP on port 18800
```json
{ "error": "Error: Failed to start Chrome CDP on port 18800 for profile \"openclaw\"." }
```
### Root cause
On Ubuntu and most Linux distros, `apt install chromium` installs a snap
wrapper, not a real browser:
```text
Note, selecting 'chromium-browser' instead of 'chromium'
chromium-browser is already the newest version (2:1snap1-0ubuntu2).
```
Snap's AppArmor confinement interferes with how OpenClaw spawns and monitors
the browser process.
Other common Linux launch failures:
- `The profile appears to be in use by another Chromium process`: stale
`Singleton*` lock files in the managed profile directory. OpenClaw removes
these locks and retries once when the lock points at a dead or
different-host process.
- `Missing X server or $DISPLAY`: a visible browser was explicitly requested
on a host without a desktop session. Local managed profiles fall back to
headless mode on Linux when both `DISPLAY` and `WAYLAND_DISPLAY` are unset.
If you set `OPENCLAW_BROWSER_HEADLESS=0`, `browser.headless: false`, or
`browser.profiles.<name>.headless: false`, remove that headed override, set
`OPENCLAW_BROWSER_HEADLESS=1`, start `Xvfb`, run
`openclaw browser start --headless` for a one-shot managed launch, or run
OpenClaw in a real desktop session.
### Solution 1: install Google Chrome (recommended)
```bash
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb
sudo apt --fix-broken install -y # if there are dependency errors
```
Update `~/.openclaw/openclaw.json`:
```json
{
"browser": {
"enabled": true,
"executablePath": "/usr/bin/google-chrome-stable",
"headless": true,
"noSandbox": true
}
}
```
### Solution 2: use snap Chromium in attach-only mode
If you must keep snap Chromium, configure OpenClaw to attach to a
manually-started browser instead of launching it:
```json
{
"browser": {
"enabled": true,
"attachOnly": true,
"headless": true,
"noSandbox": true
}
}
```
Start Chromium manually:
```bash
chromium-browser --headless --no-sandbox --disable-gpu \
--remote-debugging-port=18800 \
--user-data-dir=$HOME/.openclaw/browser/openclaw/user-data \
about:blank &
```
Optionally auto-start it with a systemd user service:
```ini
# ~/.config/systemd/user/openclaw-browser.service
[Unit]
Description=OpenClaw Browser (Chrome CDP)
After=network.target
[Service]
ExecStart=/snap/bin/chromium --headless --no-sandbox --disable-gpu --remote-debugging-port=18800 --user-data-dir=%h/.openclaw/browser/openclaw/user-data about:blank
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
```
```bash
systemctl --user enable --now openclaw-browser.service
```
### Verify the browser works
```bash
curl -s http://127.0.0.1:18791/ | jq '{running, pid, chosenBrowser}'
curl -s -X POST http://127.0.0.1:18791/start
curl -s http://127.0.0.1:18791/tabs
```
### 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 the OS default browser when Chromium-based) |
| `browser.headless` | Run without GUI | `false` |
| `OPENCLAW_BROWSER_HEADLESS` | Per-process override for local managed browser headless mode | unset |
| `browser.noSandbox` | Add `--no-sandbox` flag (needed for some Linux setups) | `false` |
| `browser.attachOnly` | Do not launch a browser; only attach to an existing one | `false` |
| `browser.cdpPortRangeStart` | Starting local CDP port for auto-assigned profiles | `18800` (derived from the gateway port) |
| `browser.localLaunchTimeoutMs` | Local managed Chrome discovery timeout, up to `120000` | `15000` |
| `browser.localCdpReadyTimeoutMs` | Local managed post-launch CDP readiness timeout, up to `120000` | `8000` |
Both timeout values must be positive integers up to `120000` ms; other values
are rejected at config load. On Raspberry Pi, older VPS hosts, or slow
storage, raise `browser.localLaunchTimeoutMs` when Chrome needs more time to
expose its CDP HTTP endpoint. Raise `browser.localCdpReadyTimeoutMs` when
launch succeeds but `openclaw browser start` still reports `not reachable
after start`.
### Problem: No Chrome tabs found for profile="user"
You are using the `user` (`existing-session` / Chrome MCP) profile and no
tabs are open to attach to.
Fix options:
1. Use the managed browser instead:
`openclaw browser --browser-profile openclaw start` (or set
`browser.defaultProfile: "openclaw"`).
2. Keep local Chrome running with at least one open tab, then retry with
`--browser-profile user`.
Notes:
- `user` is host-only. On Linux servers, containers, or remote hosts, prefer
CDP profiles instead.
- `user` and other `existing-session` profiles share the current Chrome MCP
limits: ref-driven actions only, one file per upload, no dialog `timeoutMs`
overrides, no `wait --load networkidle`, and no `responsebody`, PDF export,
download interception, or batch actions.
- Local `openclaw`-driver profiles auto-assign `cdpPort`/`cdpUrl`; only set
those manually for remote CDP.
- Remote CDP profiles accept `http://`, `https://`, `ws://`, and `wss://`.
Use HTTP(S) for `/json/version` discovery, or WS(S) when your browser
service gives you a direct DevTools socket URL.
## Related
- [Browser](/tools/browser)
- [Browser login](/tools/browser-login)
- [Browser WSL2 troubleshooting](/tools/browser-wsl2-windows-remote-cdp-troubleshooting)

View File

@@ -0,0 +1,86 @@
---
summary: "Manual logins for browser automation + X/Twitter posting"
read_when:
- You need to log into sites for browser automation
- You want to post updates to X/Twitter
title: "Browser login"
---
## Manual login (recommended)
When a site requires login, sign in manually in the host browser's `openclaw`
profile. Do not give the model your credentials: automated logins often
trigger anti-bot defenses and can lock the account.
Use the host browser (manual login) for both reading (search/threads) and
posting on X/Twitter and other bot-sensitive sites. Sandboxed browser sessions
are more likely to trigger bot detection.
Back to the main browser docs: [Browser](/tools/browser).
## Which Chrome profile is used?
OpenClaw controls a dedicated Chrome profile named `openclaw` (orange-tinted
UI), separate from your daily browser profile.
For agent browser tool calls:
- Default choice: the agent uses its isolated `openclaw` browser.
- Use `profile="user"` only when existing logged-in sessions matter and you
are at the computer to click/approve any attach prompt.
- If you have multiple user-browser profiles, specify the profile explicitly
instead of guessing.
Two ways to access the `openclaw` profile:
1. Ask the agent to open the browser, then log in yourself.
2. Open it via CLI:
```bash
openclaw browser start
openclaw browser open https://x.com
```
For a non-default profile, put `--browser-profile <name>` before the
subcommand (default is `openclaw`):
```bash
openclaw browser --browser-profile <name> open https://x.com
```
## Sandboxing: allow host browser access
If the agent is sandboxed, its `browser` tool calls default to the sandbox
browser, not the host. To let the agent target the host browser instead:
```json5
{
agents: {
defaults: {
sandbox: {
mode: "non-main",
browser: {
allowHostControl: true,
},
},
},
},
}
```
CLI invocations always target the host browser, never the sandbox, so you can
open the host browser yourself regardless of this setting:
```bash
openclaw browser --browser-profile openclaw open https://x.com
```
Once `sandbox.browser.allowHostControl: true` is set, the agent's `browser`
tool calls can target the host too. Alternatively, disable sandboxing for the
agent that posts updates.
## Related
- [Browser](/tools/browser)
- [Browser Linux troubleshooting](/tools/browser-linux-troubleshooting)
- [Browser WSL2 troubleshooting](/tools/browser-wsl2-windows-remote-cdp-troubleshooting)

View File

@@ -0,0 +1,183 @@
---
summary: "Troubleshoot WSL2 Gateway + Windows Chrome remote CDP in layers"
read_when:
- Running OpenClaw Gateway in WSL2 while Chrome lives on Windows
- Seeing overlapping browser/control-ui errors across WSL2 and Windows
- Deciding between host-local Chrome MCP and raw remote CDP in split-host setups
title: "WSL2 + Windows + remote Chrome CDP troubleshooting"
---
In the common split-host setup, OpenClaw Gateway runs inside WSL2, Chrome runs
on Windows, and browser control must cross the WSL2/Windows boundary. Several
independent problems can surface at once (see
[issue #39369](https://github.com/openclaw/openclaw/issues/39369)): CDP
transport, Control UI origin security, and token/pairing can each fail on
their own while producing similar-looking errors. Work through the layers
below in order instead of guessing which one is broken.
## Choose the right browser mode first
### Option 1: raw remote CDP from WSL2 to Windows
Use a remote browser profile pointing from WSL2 to a Windows Chrome CDP
endpoint. Choose this when the Gateway stays inside WSL2, Chrome runs on
Windows, and browser control needs to cross the WSL2/Windows boundary.
### Option 2: host-local Chrome MCP
Use the `existing-session` driver (`user` profile) only when the Gateway runs
on the same host as Chrome, you want the local signed-in browser state, you do
not need cross-host browser transport, and you do not need `responsebody`,
PDF export, download interception, or batch actions (Chrome MCP profiles do
not support these).
For WSL2 Gateway + Windows Chrome, use raw remote CDP. Chrome MCP is
host-local, not a WSL2-to-Windows bridge.
## Working architecture
- WSL2 runs the Gateway on `127.0.0.1:18789`
- Windows opens the Control UI in a normal browser at `http://127.0.0.1:18789/`
- Windows Chrome exposes a CDP endpoint on port `9222`
- WSL2 can reach that Windows CDP endpoint
- OpenClaw points a browser profile at the address reachable from WSL2
## Critical rule for the Control UI
When the UI is opened from Windows, use Windows localhost unless you have a
deliberate HTTPS setup:
```text
http://127.0.0.1:18789/
```
Do not default to a LAN IP. Plain HTTP on a LAN or tailnet address can
trigger insecure-origin/device-auth behavior unrelated to CDP itself. See
[Control UI](/web/control-ui).
## Validate in layers
Work top to bottom; do not skip ahead. Fixing one layer can still leave a
different error visible from a layer further down.
### Layer 1: verify Chrome is serving CDP on Windows
```powershell
chrome.exe --remote-debugging-port=9222
```
From Windows, verify Chrome itself first:
```powershell
curl http://127.0.0.1:9222/json/version
curl http://127.0.0.1:9222/json/list
```
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`:
```bash
curl http://WINDOWS_HOST_OR_IP:9222/json/version
curl http://WINDOWS_HOST_OR_IP:9222/json/list
```
Good result:
- `/json/version` returns JSON with Browser / Protocol-Version metadata
- `/json/list` returns JSON (an empty array is fine if no pages are open)
If this fails, Windows is not exposing the port to WSL2 yet, the address is
wrong for the WSL2 side, or firewall/port-forwarding/proxying is missing. Fix
that before touching OpenClaw config.
### Layer 3: configure the correct browser profile
Point OpenClaw at the address reachable from WSL2:
```json5
{
browser: {
enabled: true,
defaultProfile: "remote",
profiles: {
remote: {
cdpUrl: "http://WINDOWS_HOST_OR_IP:9222",
attachOnly: true,
color: "#00AA00",
},
},
},
}
```
Notes:
- use the WSL2-reachable address, not whatever only works on Windows
- keep `attachOnly: true` for externally managed browsers
- `cdpUrl` can be `http://`, `https://`, `ws://`, or `wss://`
- use HTTP(S) when you want OpenClaw to discover `/json/version`
- use WS(S) only when the browser provider gives you a direct DevTools
socket URL
- test the same URL with `curl` before expecting OpenClaw to succeed
### Layer 4: verify the Control UI layer separately
Open `http://127.0.0.1:18789/` from Windows, then verify:
- the page origin matches what `gateway.controlUi.allowedOrigins` expects
- token auth or pairing is configured correctly
- you are not debugging a Control UI auth problem as if it were a browser
problem
Helpful page: [Control UI](/web/control-ui).
### Layer 5: verify end-to-end browser control
From WSL2:
```bash
openclaw browser --browser-profile remote open https://example.com
openclaw browser --browser-profile remote tabs
```
Good result:
- the tab opens in Windows Chrome
- `browser tabs` returns the target
- later actions (`snapshot`, `screenshot`, `navigate`) work from the same
profile
## Common misleading errors
| Message | Meaning |
| --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `control-ui-insecure-auth` | UI origin/secure-context problem, not a CDP transport problem |
| `token_missing` | auth configuration problem |
| `pairing required` | device approval problem |
| `Remote CDP for profile "remote" is not reachable` | WSL2 cannot reach the configured `cdpUrl` |
| `Browser attachOnly is enabled and CDP websocket for profile "remote" is not reachable` | the HTTP endpoint answered, but the DevTools WebSocket could not be opened |
| stale viewport / dark-mode / locale / offline overrides after a remote session | run `openclaw browser --browser-profile remote stop` to close the session and release the cached Playwright/CDP connection without restarting the Gateway or the external browser |
| timeout around `remoteCdpTimeoutMs` (default 1500ms) | usually still CDP reachability, or a slow/unreachable remote endpoint |
| `No Chrome tabs found for profile="user"` | local Chrome MCP profile selected where no host-local tabs are available |
## 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?
Verify the Windows Chrome endpoint locally first, verify the same endpoint
from WSL2 second, and only then debug OpenClaw config or Control UI auth.
## Related
- [Browser](/tools/browser)
- [Browser login](/tools/browser-login)
- [Browser Linux troubleshooting](/tools/browser-linux-troubleshooting)

867
docs/tools/browser.md Normal file
View File

@@ -0,0 +1,867 @@
---
summary: "Integrated browser control service + action commands"
read_when:
- Adding agent-controlled browser automation
- Debugging why openclaw is interfering with your own Chrome
- Implementing browser settings + lifecycle in the macOS app
title: "Browser (OpenClaw-managed)"
---
OpenClaw can run a **dedicated Chrome/Brave/Edge/Chromium profile** that the agent controls. It runs through a small local control service inside the Gateway (loopback only) and is isolated from your personal browser.
- Think of it as a **separate, agent-only browser**. The `openclaw` profile never touches your personal browser profile.
- The agent opens tabs, reads pages, clicks, and types in this isolated lane.
- The built-in `user` profile attaches to your real signed-in Chrome session instead, via Chrome DevTools MCP.
## What you get
- A separate browser profile named **openclaw** (orange accent by default).
- Deterministic tab control (list/open/focus/close).
- Agent actions (click/type/drag/select), snapshots, screenshots, PDFs.
- A bundled `browser-automation` skill that teaches agents the snapshot,
stable-tab, stale-ref, and manual-blocker recovery loop when the browser
plugin is enabled.
- Optional multi-profile support (`openclaw`, `work`, `remote`, ...).
This browser is **not** your daily driver. It is a safe, isolated surface for
agent automation and verification.
## Quick start
```bash
openclaw browser --browser-profile openclaw doctor
openclaw browser --browser-profile openclaw doctor --deep
openclaw browser --browser-profile openclaw status
openclaw browser --browser-profile openclaw start
openclaw browser --browser-profile openclaw open https://example.com
openclaw browser --browser-profile openclaw snapshot
```
"Browser disabled" means the plugin or `browser.enabled` is off; see
[Configuration](#configuration) and [Plugin control](#plugin-control).
If `openclaw browser` is missing entirely, or the agent says the browser tool
is unavailable, jump to [Missing browser command or tool](#missing-browser-command-or-tool).
## Plugin control
The default `browser` tool is a bundled plugin. Disable it to replace it with another plugin that registers the same `browser` tool name:
```json5
{
plugins: {
entries: {
browser: {
enabled: false,
},
},
},
}
```
Defaults need both `plugins.entries.browser.enabled` **and** `browser.enabled=true`. Disabling only the plugin removes the `openclaw browser` CLI, `browser.request` gateway method, agent tool, and control service as one unit; your `browser.*` config stays intact for a replacement.
Browser config changes require a Gateway restart so the plugin can re-register its service.
## Agent guidance
Tool-profile note: `tools.profile: "coding"` includes `web_search` and
`web_fetch`, but not the full `browser` tool. To let the agent or a
spawned sub-agent use browser automation, add browser at the profile
stage:
```json5
{
tools: {
profile: "coding",
alsoAllow: ["browser"],
},
}
```
For a single agent, use `agents.list[].tools.alsoAllow: ["browser"]`.
`tools.subagents.tools.allow: ["browser"]` alone is not enough because sub-agent
policy is applied after profile filtering.
The browser plugin ships two levels of agent guidance:
- The `browser` tool description carries the compact always-on contract: pick
the right profile, keep refs on the same tab, use `tabId`/labels for tab
targeting, and load the browser skill for multi-step work.
- The bundled `browser-automation` skill carries the longer operating loop:
check status/tabs first, label task tabs, snapshot before acting, resnapshot
after UI changes, recover stale refs once, and report login/2FA/captcha or
camera/microphone blockers as manual action instead of guessing.
Plugin-bundled skills are listed in the agent's available skills when the
plugin is enabled. The full skill instructions load on demand, so routine
turns do not pay the full token cost.
## Missing browser command or tool
If `openclaw browser` is unknown after an upgrade, `browser.request` is missing, or the agent reports the browser tool as unavailable, the usual cause is a `plugins.allow` list that omits `browser` and no root `browser` config block exists. Add it:
```json5
{
plugins: {
allow: ["telegram", "browser"],
},
}
```
An explicit root `browser` block (any key under `browser`, such as
`browser.enabled=true` or `browser.profiles.<name>`) activates the bundled
browser plugin even under a restrictive `plugins.allow`, matching bundled
channel config behavior. `plugins.entries.browser.enabled=true` and
`tools.alsoAllow: ["browser"]` do not substitute for allowlist membership by
themselves. Removing `plugins.allow` entirely also restores the default.
## Profiles: `openclaw` vs `user`
- `openclaw`: managed, isolated browser (no extension required).
- `user`: built-in Chrome DevTools MCP attach profile for your **real
signed-in Chrome** session.
For agent browser tool calls:
- Default: use the isolated `openclaw` browser.
- Prefer `profile="user"` when existing logged-in sessions matter and the user
is at the computer to click/approve any attach prompt.
- `profile` is the explicit override when you want a specific browser mode.
Set `browser.defaultProfile: "openclaw"` if you want managed mode by default.
## Configuration
Browser settings live in `~/.openclaw/openclaw.json`.
```json5
{
browser: {
enabled: true, // default: true
evaluateEnabled: true, // default: true; false disables act:evaluate (arbitrary JS)
ssrfPolicy: {
// dangerouslyAllowPrivateNetwork: true, // opt in only for trusted private-network access
// 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)
localLaunchTimeoutMs: 15000, // local managed Chrome discovery timeout (ms)
localCdpReadyTimeoutMs: 8000, // local managed post-launch CDP readiness timeout (ms)
actionTimeoutMs: 60000, // default browser act timeout (ms)
tabCleanup: {
enabled: true, // default: true
idleMinutes: 120, // set 0 to disable idle cleanup
maxTabsPerSession: 8, // set 0 to disable the per-session cap
sweepMinutes: 5,
},
// snapshotDefaults: { mode: "efficient" }, // default snapshot mode when the caller omits one
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",
headless: true,
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
},
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" },
},
},
}
```
`browser.snapshotDefaults.mode: "efficient"` changes the default `snapshot`
extraction mode when a caller does not pass an explicit `snapshotFormat` or
`mode`; see [Browser control API](/tools/browser-control) for per-call
snapshot options.
### Screenshot vision (text-only model support)
When the main model is text-only (no vision/multimodal support), browser
screenshots return image blocks that the model cannot read. Browser screenshots
reuse the existing image-understanding configuration, so an image model
configured for media understanding can describe screenshots as text without any
browser-specific model settings.
```json5
{
tools: {
media: {
image: {
models: [
{ provider: "bytedance", model: "doubao-seed-2.0-pro" },
// Add fallback candidates; first success wins
{ provider: "openai", model: "gpt-4o" },
],
},
// Shared media models also work when tagged for image support.
// models: [{ provider: "openai", model: "gpt-4o", capabilities: ["image"] }],
},
},
agents: {
defaults: {
// Existing image-model defaults are also honored.
// imageModel: { primary: "openai/gpt-4o" },
},
},
}
```
**How it works:**
1. Agent calls `browser screenshot` and an image is captured to disk as usual.
2. The browser tool asks the existing image-understanding runtime whether it
can describe the screenshot using configured media image models, shared media
models, image-model defaults, or an auth-backed image provider.
3. The vision model returns a text description, which is wrapped with
`wrapExternalContent` (prompt injection guard) and returned to the agent
as a text block instead of an image block.
4. If image understanding is unavailable, skipped, or fails, the browser falls
back to returning the original image block.
Use the existing `tools.media.image` / `tools.media.models` fields for model
fallbacks, timeouts, byte limits, profiles, and provider request settings.
If the active main model already supports vision and no explicit image
understanding model is configured, OpenClaw keeps the normal image result so the
main model can read the screenshot directly.
<AccordionGroup>
<Accordion title="Ports and reachability">
- Control service binds to loopback on a port derived from `gateway.port` (default `18791` = gateway + 2). `OPENCLAW_GATEWAY_PORT` takes priority over `gateway.port`; either shifts the derived ports in the same family.
- Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl` from a range starting 9 ports above the control port (default `18800`-`18899`); set those only for
remote CDP profiles or existing-session endpoint attach. `cdpUrl` defaults to
the managed local CDP port when unset.
- `remoteCdpTimeoutMs` applies to remote and `attachOnly` CDP HTTP reachability
checks and tab-opening HTTP requests; `remoteCdpHandshakeTimeoutMs` applies to
their CDP WebSocket handshakes.
- `localLaunchTimeoutMs` is the budget for a locally launched managed Chrome
process to expose its CDP HTTP endpoint. `localCdpReadyTimeoutMs` is the
follow-up budget for CDP websocket readiness after the process is discovered.
Raise these on Raspberry Pi, low-end VPS, or older hardware where Chromium
starts slowly. Values must be positive integers up to `120000` ms; invalid
config values are rejected.
- Repeated managed Chrome launch/readiness failures are circuit-broken per
profile. After several consecutive failures, OpenClaw pauses new launch
attempts briefly instead of spawning Chromium on every browser tool call. Fix
the startup problem, disable the browser if it is not needed, or restart the
Gateway after repair.
- `actionTimeoutMs` is the default budget for browser `act` requests when the caller does not pass `timeoutMs`. The client transport adds a small slack window so long waits can finish instead of timing out at the HTTP boundary.
- `tabCleanup` is best-effort cleanup for tabs opened by primary-agent browser sessions. Subagent, cron, and ACP lifecycle cleanup still closes their explicit tracked tabs at session end; primary sessions keep active tabs reusable, then close idle or excess tracked tabs in the background.
</Accordion>
<Accordion title="SSRF policy">
- Browser navigation and open-tab are SSRF-guarded before navigation and best-effort re-checked on the final `http(s)` URL afterwards.
- In strict SSRF mode, remote CDP endpoint discovery and `/json/version` probes (`cdpUrl`) are checked too.
- Gateway/provider `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, and `NO_PROXY` environment variables do not automatically proxy the OpenClaw-managed browser. Managed Chrome launches direct by default so provider proxy settings do not weaken browser SSRF checks.
- OpenClaw-managed local CDP readiness probes and DevTools WebSocket connections bypass the managed network proxy for the exact launched loopback endpoint, so `openclaw browser start` still works when an operator proxy blocks loopback egress.
- To proxy the managed browser itself, pass explicit Chrome proxy flags through `browser.extraArgs`, such as `--proxy-server=...` or `--proxy-pac-url=...`. Strict SSRF mode blocks explicit browser proxy routing unless private-network browser access is intentionally enabled.
- `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` is off by default; enable only when private-network browser access is intentionally trusted.
- `browser.ssrfPolicy.allowPrivateNetwork` remains supported as a legacy alias.
</Accordion>
<Accordion title="Profile behavior">
- `attachOnly: true` means never launch a local browser; only attach if one is already running.
- `headless` can be set globally or per local managed profile. Per-profile values override `browser.headless`, so one locally launched profile can stay headless while another remains visible.
- `POST /start?headless=true` and `openclaw browser start --headless` request a
one-shot headless launch for local managed profiles without rewriting
`browser.headless` or profile config. Existing-session, attach-only, and
remote CDP profiles reject the override because OpenClaw does not launch those
browser processes.
- On Linux hosts without `DISPLAY` or `WAYLAND_DISPLAY`, local managed profiles
default to headless automatically when neither the environment nor profile/global
config explicitly chooses headed mode. `openclaw browser status --json`
reports `headlessSource` as `env`, `profile`, `config`,
`request`, `linux-display-fallback`, or `default`.
- `OPENCLAW_BROWSER_HEADLESS=1` forces local managed launches headless for the
current process. `OPENCLAW_BROWSER_HEADLESS=0` forces headed mode for ordinary
starts and returns an actionable error on Linux hosts without a display server;
an explicit `start --headless` request still wins for that one launch.
- `executablePath` can be set globally or per local managed profile. Per-profile values override `browser.executablePath`, so different managed profiles can launch different Chromium-based browsers. Both forms accept `~` for your OS home directory.
- `color` (top-level and per-profile) tints the browser UI so you can see which profile is active.
- Default profile is `openclaw` (managed standalone). Use `defaultProfile: "user"` to opt into the signed-in user browser.
- Auto-detect order: system default browser if Chromium-based; otherwise Chrome, Brave, Edge, Chromium, Chrome Canary.
- `driver: "existing-session"` uses Chrome DevTools MCP instead of raw CDP. It can attach through Chrome MCP auto-connect, or through `cdpUrl` when you already have a DevTools endpoint for the running browser.
- Set `browser.profiles.<name>.userDataDir` when an existing-session profile should attach to a non-default Chromium user profile (Brave, Edge, etc.). This path also accepts `~` for your OS home directory.
</Accordion>
</AccordionGroup>
## 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. Top-level and per-profile `executablePath` values accept `~`
for your OS home directory:
```bash
openclaw config set browser.executablePath "/usr/bin/google-chrome"
openclaw config set browser.profiles.work.executablePath "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
```
Or set it in config, per platform:
<Tabs>
<Tab title="macOS">
```json5
{
browser: {
executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
},
}
```
</Tab>
<Tab title="Windows">
```json5
{
browser: {
executablePath: "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
},
}
```
</Tab>
<Tab title="Linux">
```json5
{
browser: {
executablePath: "/usr/bin/brave-browser",
},
}
```
</Tab>
</Tabs>
Per-profile `executablePath` only affects local managed profiles that OpenClaw
launches. `existing-session` profiles attach to an already-running browser
instead, and remote CDP profiles use the browser behind `cdpUrl`.
## Local vs remote control
- **Local control (default):** the Gateway starts the loopback control service and can launch a local browser.
- **Remote control (node host):** run a node host on the machine that has the browser; the Gateway proxies browser actions to it.
- **Remote CDP:** set `browser.profiles.<name>.cdpUrl` (or `browser.cdpUrl`) to
attach to a remote Chromium-based browser. In this case, OpenClaw will not launch a local browser.
- For externally managed CDP services on loopback (for example Browserless in
Docker published to `127.0.0.1`), also set `attachOnly: true`. Loopback CDP
without `attachOnly` is treated as a local OpenClaw-managed browser profile.
- `headless` only affects local managed profiles that OpenClaw launches. It does not restart or change existing-session or remote CDP browsers.
- `executablePath` follows the same local managed profile rule. Changing it on a
running local managed profile marks that profile for restart/reconcile so the
next launch uses the new binary.
Stopping behavior differs by profile mode:
- local managed profiles: `openclaw browser stop` stops the browser process that
OpenClaw launched
- attach-only and remote CDP profiles: `openclaw browser stop` closes the active
control session and releases Playwright/CDP emulation overrides (viewport,
color scheme, locale, timezone, offline mode, and similar state), even
though no browser process was launched by OpenClaw
Remote CDP URLs can include auth:
- Query tokens (e.g., `https://provider.example?token=<token>`)
- HTTP Basic auth (e.g., `https://user:pass@provider.example`)
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:
- The node host exposes its local browser control server via a **proxy command**.
- Profiles come from the node's own `browser.profiles` config (same as local).
- The proxy command never allows persistent profile mutations (`create-profile`, `delete-profile`, `reset-profile`) regardless of `allowProfiles`; make those changes on the node directly.
- `nodeHost.browserProxy.allowProfiles` is optional. Leave it empty for the legacy/default behavior: all configured profiles remain reachable through the proxy.
- If you set `nodeHost.browserProxy.allowProfiles`, OpenClaw treats it as a least-privilege boundary limiting which profile names the proxy will target.
- Disable if you don't want it:
- On the node: `nodeHost.browserProxy.enabled=false`
- On the gateway: `gateway.nodes.browser.mode="off"` (also accepts `"auto"` to pick a single connected browser node, or `"manual"` to require an explicit node param)
## Browserless (hosted remote CDP)
[Browserless](https://browserless.io) is a hosted Chromium service that exposes
CDP connection URLs over HTTPS and WebSocket. OpenClaw can use either form, but
for a remote browser profile the simplest option is the direct WebSocket URL
from Browserless' connection docs.
Example:
```json5
{
browser: {
enabled: true,
defaultProfile: "browserless",
remoteCdpTimeoutMs: 2000,
remoteCdpHandshakeTimeoutMs: 4000,
profiles: {
browserless: {
cdpUrl: "wss://production-sfo.browserless.io?token=<BROWSERLESS_API_KEY>",
color: "#00AA00",
},
},
},
}
```
Notes:
- Replace `<BROWSERLESS_API_KEY>` with your real Browserless token.
- Choose the region endpoint that matches your Browserless account (see their docs).
- If Browserless gives you an HTTPS base URL, you can either convert it to
`wss://` for a direct CDP connection or keep the HTTPS URL and let OpenClaw
discover `/json/version`.
### Browserless Docker on the same host
When Browserless is self-hosted in Docker and OpenClaw runs on the host, treat
Browserless as an externally managed CDP service:
```json5
{
browser: {
enabled: true,
defaultProfile: "browserless",
profiles: {
browserless: {
cdpUrl: "ws://127.0.0.1:3000",
attachOnly: true,
color: "#00AA00",
},
},
},
}
```
The address in `browser.profiles.browserless.cdpUrl` must be reachable from the
OpenClaw process. Browserless must also advertise a matching reachable endpoint;
set Browserless `EXTERNAL` to that same public-to-OpenClaw WebSocket base, such
as `ws://127.0.0.1:3000`, `ws://browserless:3000`, or a stable private Docker
network address. If `/json/version` returns `webSocketDebuggerUrl` pointing at
an address OpenClaw cannot reach, CDP HTTP can look healthy while the WebSocket
attach still fails.
Do not leave `attachOnly` unset for a loopback Browserless profile. Without
`attachOnly`, OpenClaw treats the loopback port as a local managed browser
profile and may report that the port is in use but not owned by OpenClaw.
## Direct WebSocket CDP providers
Some hosted browser services expose a **direct WebSocket** endpoint rather than
the standard HTTP-based CDP discovery (`/json/version`). OpenClaw accepts three
CDP URL shapes and picks the right connection strategy automatically:
- **HTTP(S) discovery** - `http://host[:port]` or `https://host[:port]`.
OpenClaw calls `/json/version` to discover the WebSocket debugger URL, then
connects. No WebSocket fallback.
- **Direct WebSocket endpoints** - `ws://host[:port]/devtools/<kind>/<id>` or
`wss://...` with a `/devtools/browser|page|worker|shared_worker|service_worker/<id>`
path. OpenClaw connects directly via a WebSocket handshake and skips
`/json/version` entirely.
- **Bare WebSocket roots** - `ws://host[:port]` or `wss://host[:port]` with no
`/devtools/...` path (e.g. [Browserless](https://browserless.io),
[Browserbase](https://www.browserbase.com)). OpenClaw tries HTTP
`/json/version` discovery first (normalising the scheme to `http`/`https`);
if discovery returns a `webSocketDebuggerUrl` it is used, otherwise OpenClaw
falls back to a direct WebSocket handshake at the bare root. If the advertised
WebSocket endpoint rejects the CDP handshake but the configured bare root
accepts it, OpenClaw falls back to that root as well. This lets a bare `ws://`
pointed at a local Chrome still connect, since Chrome only accepts WebSocket
upgrades on the specific per-target path from `/json/version`, while hosted
providers can still use their root WebSocket endpoint when their discovery
endpoint advertises a short-lived URL that is not suitable for Playwright CDP.
`openclaw browser doctor` uses the same discovery-first, WebSocket-fallback
logic as runtime attach, so a bare-root URL that connects successfully is not
reported as unreachable by diagnostics.
### Browserbase
[Browserbase](https://www.browserbase.com) is a cloud platform for running
headless browsers with built-in CAPTCHA solving, stealth mode, and residential
proxies.
```json5
{
browser: {
enabled: true,
defaultProfile: "browserbase",
remoteCdpTimeoutMs: 3000,
remoteCdpHandshakeTimeoutMs: 5000,
profiles: {
browserbase: {
cdpUrl: "wss://connect.browserbase.com?apiKey=<BROWSERBASE_API_KEY>",
color: "#F97316",
},
},
},
}
```
Notes:
- [Sign up](https://www.browserbase.com/sign-up) and copy your **API Key**
from the [Overview dashboard](https://www.browserbase.com/overview).
- Replace `<BROWSERBASE_API_KEY>` with your real Browserbase API key.
- Browserbase auto-creates a browser session on WebSocket connect, so no
manual session creation step is needed.
- See [pricing](https://www.browserbase.com/pricing) for current free-tier limits and paid plans.
- See the [Browserbase docs](https://docs.browserbase.com) for full API
reference, SDK guides, and integration examples.
### Notte
[Notte](https://www.notte.cc) is a cloud platform for running headless
browsers with built-in stealth, residential proxies, and a CDP-native
WebSocket gateway.
```json5
{
browser: {
enabled: true,
defaultProfile: "notte",
remoteCdpTimeoutMs: 3000,
remoteCdpHandshakeTimeoutMs: 5000,
profiles: {
notte: {
cdpUrl: "wss://us-prod.notte.cc/sessions/connect?token=<NOTTE_API_KEY>",
color: "#7C3AED",
},
},
},
}
```
Notes:
- [Sign up](https://console.notte.cc) and copy your **API Key** from the
console settings page.
- Replace `<NOTTE_API_KEY>` with your real Notte API key.
- Notte auto-creates a browser session on WebSocket connect, so no manual
session creation step is needed. The session is destroyed when the
WebSocket disconnects.
- See [pricing](https://www.notte.cc/#pricing) for current free-tier limits and paid plans.
- See the [Notte docs](https://docs.notte.cc) for full API reference, SDK
guides, and integration examples.
## Security
Key ideas:
- Browser control is loopback-only; access flows through the Gateway's auth or node pairing.
- The standalone loopback browser HTTP API uses **shared-secret auth only**:
gateway token bearer auth, `x-openclaw-password`, or HTTP Basic auth with the
configured gateway password.
- Tailscale Serve identity headers and `gateway.auth.mode: "trusted-proxy"` do
**not** authenticate this standalone loopback browser API.
- If browser control is enabled and no shared-secret auth is configured, OpenClaw
auto-generates and persists a browser-control credential at startup:
a token when `gateway.auth.mode` is `none`, or a password when it is
`trusted-proxy` (persisted through `gateway.auth.password` so out-of-process
loopback clients can resolve it). Auto-generation is skipped when an explicit
string credential is already configured for that mode, or when
`gateway.auth.mode` is `password`.
- Configure `gateway.auth.token`, `gateway.auth.password`, `OPENCLAW_GATEWAY_TOKEN`, or
`OPENCLAW_GATEWAY_PASSWORD` explicitly if you want a stable secret you control
instead of the generated one.
Remote CDP tips:
- Prefer encrypted endpoints (HTTPS or WSS) and short-lived tokens where possible.
- Avoid embedding long-lived tokens directly in config files.
- Keep the Gateway and any node hosts on a private network (Tailscale); avoid public exposure.
- Treat remote CDP URLs/tokens as secrets; prefer env vars or a secrets manager.
## Profiles (multi-browser)
OpenClaw supports multiple named profiles (routing configs). Profiles can be:
- **openclaw-managed**: a dedicated Chromium-based browser instance with its own user data directory + CDP port
- **remote**: an explicit CDP URL (Chromium-based browser running elsewhere)
- **existing session**: your existing Chrome profile via Chrome DevTools MCP auto-connect
Defaults:
- The `openclaw` profile is auto-created if missing.
- The `user` profile is built-in for Chrome MCP existing-session attach.
- Existing-session profiles are opt-in beyond `user`; create them with `--driver existing-session`.
- Local CDP ports allocate from **18800-18899** by default.
- Deleting a profile moves its local data directory to Trash.
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:
- [Chrome for Developers: Use Chrome DevTools MCP with your browser session](https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session)
- [Chrome DevTools MCP README](https://github.com/ChromeDevTools/chrome-devtools-mcp)
Built-in profile: `user`. Create your own custom existing-session profile if
you want a different name, color, or browser data directory.
By default the built-in `user` profile uses Chrome MCP auto-connect, which
targets the default local Google Chrome profile. Use `userDataDir` for Brave,
Edge, Chromium, or a non-default Chrome profile. `~` expands to your OS home
directory:
```json5
{
browser: {
profiles: {
brave: {
driver: "existing-session",
attachOnly: true,
userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
color: "#FB542B",
},
},
},
}
```
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:
- Chrome: `chrome://inspect/#remote-debugging`
- Brave: `brave://inspect/#remote-debugging`
- Edge: `edge://inspect/#remote-debugging`
Live attach smoke test:
```bash
openclaw browser --browser-profile user start
openclaw browser --browser-profile user status
openclaw browser --browser-profile user tabs
openclaw browser --browser-profile user snapshot --format ai
```
What success looks like:
- `status` shows `driver: existing-session`
- `status` shows `transport: chrome-mcp`
- `status` shows `running: true`
- `tabs` lists your already-open browser tabs
- `snapshot` returns refs from the selected live tab
What to check if attach does not work:
- the target Chromium-based browser is version `144+`
- remote debugging is enabled in that browser's inspect page
- the browser showed and you accepted the attach consent prompt
- if Chrome was started with an explicit `--remote-debugging-port`, set
`browser.profiles.<name>.cdpUrl` to that DevTools endpoint instead of relying
on Chrome MCP auto-connect
- `openclaw doctor` migrates old extension-based browser config and checks that
Chrome is installed locally for default auto-connect profiles, but it cannot
enable browser-side remote debugging for you
Agent use:
- Use `profile="user"` when you need the user's logged-in browser state.
- If you use a custom existing-session profile, pass that explicit profile name.
- Only choose this mode when the user is at the computer to approve the attach
prompt.
- The Gateway or node host can spawn `npx chrome-devtools-mcp@latest --autoConnect`.
Notes:
- This path is higher-risk than the isolated `openclaw` profile because it can
act inside your signed-in browser session.
- OpenClaw does not launch the browser for this driver; it only attaches.
- OpenClaw uses the official Chrome DevTools MCP `--autoConnect` flow here. If
`userDataDir` is set, it is passed through to target that user data directory.
- Existing-session can attach on the selected host or through a connected
browser node. If Chrome lives elsewhere and no browser node is connected, use
remote CDP or a node host instead.
### Custom Chrome MCP launch
Override the spawned Chrome DevTools MCP server per profile when the default
`npx chrome-devtools-mcp@latest` flow is not what you want (offline hosts,
pinned versions, vendored binaries):
| Field | What it does |
| ------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `mcpCommand` | Executable to spawn instead of `npx`. Resolved as-is; absolute paths are honored. |
| `mcpArgs` | Argument array passed verbatim to `mcpCommand`. Replaces the default `chrome-devtools-mcp@latest --autoConnect` arguments. |
When `cdpUrl` is set on an existing-session profile, OpenClaw skips
`--autoConnect` and forwards the endpoint to Chrome MCP automatically:
- `http(s)://...``--browserUrl <url>` (DevTools HTTP discovery endpoint).
- `ws(s)://...``--wsEndpoint <url>` (direct CDP WebSocket).
Endpoint flags and `userDataDir` cannot be combined: when `cdpUrl` is set,
`userDataDir` is ignored for Chrome MCP launch, since Chrome MCP attaches to
the running browser behind the endpoint rather than opening a profile
directory.
<Accordion title="Existing-session feature limitations">
Compared to the managed `openclaw` profile, existing-session drivers are more constrained:
- **Screenshots** - page captures and `--ref` element captures work; CSS `--element` selectors do not. Playwright is not required for page or ref-based element screenshots. (`--full-page` cannot combine with `--ref` or `--element` on any profile, not just existing-session.)
- **Actions** - `click`, `type`, `hover`, `scrollIntoView`, `drag`, and `select` require snapshot refs (no CSS selectors). `click-coords` clicks visible viewport coordinates and does not require a snapshot ref. `click` is left-button only (no button overrides or modifiers). `type` does not support `slowly=true`; use `fill` or `press`. `press` does not support `delayMs`. `type`, `hover`, `scrollIntoView`, `drag`, `select`, `fill`, and `evaluate` do not support per-call `timeoutMs` overrides. `select` accepts a single value. `batch` is not supported; send actions individually.
- **Wait / upload / dialog** - `wait --url` supports exact, substring, and glob patterns (same as managed); `wait --load networkidle` is not supported on existing-session profiles (it works on managed and raw/remote CDP profiles). Upload hooks require `ref` or `inputRef`, one file at a time, no CSS `element`. Dialog hooks do not support timeout overrides or `dialogId`.
- **Dialog visibility** - Managed browser action responses include `blockedByDialog` and `browserState.dialogs.pending` when an action opens a modal dialog; snapshots also include pending dialog state. Respond with `browser dialog --accept/--dismiss --dialog-id <id>` while a dialog is pending. Dialogs handled outside OpenClaw appear under `browserState.dialogs.recent`.
- **Managed-only features** - PDF export, download interception, and `responsebody` still require the managed browser path.
</Accordion>
## Isolation guarantees
- **Dedicated user data dir**: never touches your personal browser profile.
- **Dedicated ports**: avoids `9222` to prevent collisions with dev workflows.
- **Deterministic tab control**: `tabs` returns `suggestedTargetId` first, then
stable `tabId` handles such as `t1`, optional labels, and the raw `targetId`.
Agents should reuse `suggestedTargetId`; raw ids remain available for
debugging and compatibility.
## 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:
- macOS: checks `/Applications` and `~/Applications`.
- Linux: checks common Chrome/Brave/Edge/Chromium locations under `/usr/bin`,
`/snap/bin`, `/opt/google`, `/opt/brave.com`, `/usr/lib/chromium`, and
`/usr/lib/chromium-browser`, plus Playwright-managed Chromium under
`PLAYWRIGHT_BROWSERS_PATH` or `~/.cache/ms-playwright`.
- Windows: checks common install locations.
## Control API (optional)
For scripting and debugging, the Gateway exposes a small **loopback-only HTTP
control API** plus a matching `openclaw browser` CLI (snapshots, refs, wait
power-ups, JSON output, debug workflows). See
[Browser control API](/tools/browser-control) for the full reference.
## Troubleshooting
For Linux-specific issues (especially snap Chromium), see
[Browser troubleshooting](/tools/browser-linux-troubleshooting).
For WSL2 Gateway + Windows Chrome split-host setups, see
[WSL2 + Windows + remote Chrome CDP troubleshooting](/tools/browser-wsl2-windows-remote-cdp-troubleshooting).
### CDP startup failure vs navigation SSRF block
These are different failure classes and they point to different code paths.
- **CDP startup or readiness failure** means OpenClaw cannot confirm that the browser control plane is healthy.
- **Navigation SSRF block** means the browser control plane is healthy, but a page navigation target is rejected by policy.
Common examples:
- CDP startup or readiness failure:
- `Chrome CDP websocket for profile "openclaw" is not reachable after start`
- `Remote CDP for profile "<name>" is not reachable at <cdpUrl>`
- `Port <port> is in use for profile "<name>" but not by openclaw` when a
loopback external CDP service is configured without `attachOnly: true`
- Navigation SSRF block:
- `open`, `navigate`, snapshot, or tab-opening flows fail with a browser/network policy error while `start` and `tabs` still work
Use this minimal sequence to separate the two:
```bash
openclaw browser --browser-profile openclaw start
openclaw browser --browser-profile openclaw tabs
openclaw browser --browser-profile openclaw open https://example.com
```
How to read the results:
- If `start` fails with `not reachable after start`, troubleshoot CDP readiness first.
- If `start` succeeds but `tabs` fails, the control plane is still unhealthy. Treat this as a CDP reachability problem, not a page-navigation problem.
- If `start` and `tabs` succeed but `open` or `navigate` fails, the browser control plane is up and the failure is in navigation policy or the target page.
- If `start`, `tabs`, and `open` all succeed, the basic managed-browser control path is healthy.
Important behavior details:
- Browser config defaults to a fail-closed SSRF policy object even when you do not configure `browser.ssrfPolicy`.
- For the local loopback `openclaw` managed profile, CDP health checks intentionally skip browser SSRF reachability enforcement for OpenClaw's own local control plane.
- Navigation protection is separate. A successful `start` or `tabs` result does not mean a later `open` or `navigate` target is allowed.
Security guidance:
- Do **not** relax browser SSRF policy by default.
- Prefer narrow host exceptions such as `hostnameAllowlist` or `allowedHostnames` over broad private-network access.
- Use `dangerouslyAllowPrivateNetwork: true` only in intentionally trusted environments where private-network browser access is required and reviewed.
## Agent tools + how control works
The agent gets **one tool** for browser automation:
- `browser` - doctor/status/start/stop/tabs/open/focus/close/snapshot/screenshot/navigate/act
How it maps:
- `browser snapshot` returns a stable UI tree (AI or ARIA).
- `browser act` uses the snapshot `ref` IDs to click/type/drag/select.
- `browser screenshot` captures pixels (full page, element, or labeled refs).
- `browser doctor` checks Gateway, plugin, profile, browser, and tab readiness.
- `browser` accepts:
- `profile` to choose a named browser profile (openclaw, chrome, or remote CDP).
- `target` (`sandbox` | `host` | `node`) to select where the browser lives.
- In sandboxed sessions, `target: "host"` requires `agents.defaults.sandbox.browser.allowHostControl=true`.
- If `target` is omitted: sandboxed sessions default to `sandbox`, non-sandbox sessions default to `host`.
- If a browser-capable node is connected, the tool may auto-route to it unless you pin `target="host"` or `target="node"`.
This keeps the agent deterministic and avoids brittle selectors.
## Related
- [Tools Overview](/tools) - all available agent tools
- [Sandboxing](/gateway/sandboxing) - browser control in sandboxed environments
- [Security](/gateway/security) - browser control risks and hardening

95
docs/tools/btw.md Normal file
View File

@@ -0,0 +1,95 @@
---
summary: "Ephemeral side questions with /btw"
read_when:
- You want to ask a quick side question about the current session
- You are implementing or debugging BTW behavior across clients
title: "BTW side questions"
---
`/btw` (alias `/side`) asks a quick side question about the **current
session** without adding it to conversation history. It is modeled after
Claude Code's `/btw`, adapted to OpenClaw's Gateway and multi-channel
architecture.
```text
/btw what changed?
/side what does this error mean?
```
## What it does
1. Snapshots the current session as background context (including any
in-flight main-run prompt).
2. Runs a separate, one-shot side query telling the model to answer only the
side question and not resume or steer the main task.
3. Delivers the answer as a live side result, not a normal assistant message.
4. Never writes the question or answer to session history or `chat.history`.
The main run, if one is active, is left untouched.
For Codex harness sessions, BTW forks the active Codex app-server thread into
an ephemeral child thread instead of running a separate provider call. This
keeps Codex OAuth and native tool/thread behavior intact, and the forked
thread keeps the parent thread's current approval policy, sandbox, and native
tool surface. The forked thread gets a boundary prompt telling the model that
everything before it is inherited reference context, not active instructions,
and that only messages after the boundary are live. `/btw` requires an
existing Codex thread; send a normal message first.
For CLI runtime aliases, BTW invokes the owning CLI backend in one-shot
side-question mode: it seeds sanitized conversation context into a fresh CLI
invocation with tool bundling and reusable session state disabled, and adds
any no-resume/no-tools flags the backend supports. Direct (non-CLI) runtimes
use a direct one-shot provider call instead.
## What it does not do
`/btw` does not create a durable session, continue the unfinished main task,
persist question/answer data to transcript history, or survive a reload.
## Delivery model
Normal assistant chat uses the Gateway `chat` event. BTW uses a separate
`chat.side_result` event so clients cannot mistake it for regular
conversation history. Because it is not replayed from `chat.history`, it
disappears after reload.
## Surface behavior
| Surface | Behavior |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| TUI | Rendered inline in the chat log, visibly distinct from a normal reply, dismissible with `Enter` or `Esc`. |
| External channels | Delivered as a clearly labeled one-off reply (Telegram, WhatsApp, Discord have no local ephemeral overlay). |
| Control UI / web | Gateway emits `chat.side_result` correctly and it is excluded from `chat.history`, but Control UI has no consumer yet to render it live in the browser. |
## When to use it
Use `/btw` for a quick clarification, a factual side answer while a long run
is still in progress, or a temporary answer that should not enter future
session context.
```text
/btw what file are we editing?
/btw summarize the current task in one sentence
/btw what is 17 * 19?
```
For anything you want to become part of the session's future working
context, ask normally in the main session instead.
## Related
<CardGroup cols={2}>
<Card title="Slash commands" href="/tools/slash-commands" icon="terminal">
Native command catalog and chat directives.
</Card>
<Card title="Thinking levels" href="/tools/thinking" icon="brain">
Reasoning effort levels for the side-question model call.
</Card>
<Card title="Session" href="/concepts/session" icon="comments">
Session keys, history, and persistence semantics.
</Card>
<Card title="Steer command" href="/tools/steer" icon="arrow-right">
Inject a steering message into the active run without ending it.
</Card>
</CardGroup>

View File

@@ -0,0 +1,12 @@
---
summary: "Redirect to /plugins/adding-capabilities"
title: "Adding capabilities (redirect)"
redirect: /plugins/adding-capabilities
---
This contributor guide moved to [Adding capabilities](/plugins/adding-capabilities).
## Related
- [Plugin internals](/plugins/architecture)
- [Building plugins](/plugins/building-plugins)

5
docs/tools/clawhub.md Normal file
View File

@@ -0,0 +1,5 @@
---
summary: "Redirect to /clawhub"
title: "ClawHub (redirect)"
redirect: /clawhub
---

View File

@@ -0,0 +1,161 @@
---
summary: "code_execution: run sandboxed remote Python analysis with xAI"
read_when:
- You want to enable or configure code_execution
- You want remote analysis without local shell access
- You want to combine x_search or web_search with remote Python analysis
title: "Code execution"
---
`code_execution` runs sandboxed remote Python analysis on xAI's Responses API
(`https://api.x.ai/v1/responses`, same endpoint `x_search` uses). It is
registered by the bundled `xai` plugin under the `tools` contract.
| Property | Value |
| ------------------ | --------------------------------------------------------------------------------- |
| Tool name | `code_execution` |
| Provider plugin | `xai` (bundled, `enabledByDefault: true`) |
| Auth | xAI auth profile, `XAI_API_KEY`, or `plugins.entries.xai.config.webSearch.apiKey` |
| Default model | `grok-4-1-fast` |
| Default timeout | 30 seconds |
| Default `maxTurns` | unset (xAI applies its own internal limit) |
Use it for calculations, tabulation, quick statistics, and chart-style
analysis, including data returned by `x_search` or `web_search`. It has no
access to local files, your shell, your repo, or paired devices, and it does
not persist state between calls, so treat each call as ephemeral analysis, not
a notebook session. For fresh X data, run [`x_search`](/tools/web#x_search)
first and pipe the result in.
For local execution, use [`exec`](/tools/exec) instead.
## Setup
<Steps>
<Step title="Provide xAI credentials">
OAuth requires an eligible SuperGrok or X Premium subscription
(device-code verification, so it works from remote hosts without a
localhost callback):
```bash
openclaw models auth login --provider xai --method oauth
```
During a fresh install, the same choice is available in onboarding:
```bash
openclaw onboard --install-daemon --auth-choice xai-oauth
```
Or an API key:
```bash
openclaw models auth login --provider xai --method api-key
export XAI_API_KEY=xai-...
```
Or via config:
```json5
{
plugins: {
entries: {
xai: {
config: {
webSearch: {
apiKey: "xai-...",
},
},
},
},
},
}
```
Any of these three also power `x_search` and Grok `web_search`.
</Step>
<Step title="Enable and tune code_execution">
`code_execution` is available whenever xAI credentials resolve. Set
`plugins.entries.xai.config.codeExecution.enabled` to `false` to disable
it, or use the same block to override the model, turn cap, or timeout:
```json5
{
plugins: {
entries: {
xai: {
config: {
codeExecution: {
enabled: true,
model: "grok-4-1-fast", // override the default xAI code-execution model
maxTurns: 2, // optional cap on internal tool turns
timeoutSeconds: 30, // request timeout (default: 30)
},
},
},
},
},
}
```
</Step>
<Step title="Restart the Gateway">
```bash
openclaw gateway restart
```
`code_execution` appears in the agent's tool list once the xAI plugin
re-registers with `enabled: true`.
</Step>
</Steps>
## How to use it
Make the analysis intent explicit; the tool takes a single `task` parameter,
so send the full request and any inline data in one prompt:
```text
Use code_execution to calculate the 7-day moving average for these numbers: ...
```
```text
Use x_search to find posts mentioning OpenClaw this week, then use code_execution to count them by day.
```
```text
Use web_search to gather the latest AI benchmark numbers, then use code_execution to compare percent changes.
```
## Errors
Without auth, the tool returns a structured JSON error (not a thrown
exception), so the agent can self-correct:
```json
{
"error": "missing_xai_api_key",
"message": "code_execution needs xAI credentials. Run `openclaw onboard --auth-choice xai-oauth` to sign in with Grok, run `openclaw onboard --auth-choice xai-api-key`, set `XAI_API_KEY` in the Gateway environment, or configure `plugins.entries.xai.config.webSearch.apiKey`.",
"docs": "https://docs.openclaw.ai/tools/code-execution"
}
```
## Related
<CardGroup cols={2}>
<Card title="Exec tool" href="/tools/exec" icon="terminal">
Local shell execution on your machine or paired node.
</Card>
<Card title="Exec approvals" href="/tools/exec-approvals" icon="shield">
Allow/deny policy for shell execution.
</Card>
<Card title="Web tools" href="/tools/web" icon="globe">
`web_search`, `x_search`, and `web_fetch`.
</Card>
<Card title="xAI provider" href="/providers/xai" icon="microchip">
Grok models, web/x search, and code execution config.
</Card>
</CardGroup>

View File

@@ -0,0 +1,268 @@
---
title: "Creating skills"
sidebarTitle: "Creating skills"
summary: "Build, test, and publish custom SKILL.md workspace skills for your OpenClaw agents."
read_when:
- You are creating a new custom skill
- You need a quick starter workflow for SKILL.md-based skills
- You want to use Skill Workshop to propose a skill for agent review
---
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.
OpenClaw loads skills from several roots in a defined [precedence order](/tools/skills#loading-order).
## Create your first skill
<Steps>
<Step title="Create the skill directory">
Skills live in your workspace `skills/` folder:
```bash
mkdir -p ~/.openclaw/workspace/skills/hello-world
```
You can group skills in subfolders for organization — the skill is still
named by the `SKILL.md` frontmatter, not the folder path:
```bash
mkdir -p ~/.openclaw/workspace/skills/personal/hello-world
# skill name is still "hello-world", invoked as /hello-world
```
</Step>
<Step title="Write SKILL.md">
The frontmatter defines metadata; the body gives the agent instructions.
```markdown
---
name: hello-world
description: A simple skill that prints a greeting.
---
# Hello World
When the user asks for a greeting, use the `exec` tool to run:
```bash
echo "Hello from your custom skill!"
```
```
Naming rules:
- Use lowercase letters, digits, and hyphens for `name`.
- Keep the directory name and frontmatter `name` aligned.
- `description` is shown to the agent and in slash-command discovery —
keep it one line and under 160 characters.
</Step>
<Step title="Verify the skill loaded">
```bash
openclaw skills list
```
OpenClaw watches `SKILL.md` files under skills roots by default. If the
watcher is disabled or you are continuing an existing session, start a new
one so the agent receives the refreshed list:
```bash
# From chat — archive current session and start fresh
/new
# Or restart the gateway
openclaw gateway restart
```
</Step>
<Step title="Test it">
```bash
openclaw agent --message "give me a greeting"
```
Or open a chat and ask the agent directly. Use `/skill hello-world` to
invoke it explicitly by name.
</Step>
</Steps>
## SKILL.md reference
### Required fields
| Field | Description |
| ------------- | --------------------------------------------------------------- |
| `name` | Unique slug using lowercase letters, digits, and hyphens |
| `description` | One-line description shown to the agent and in discovery output |
### Optional frontmatter keys
| Field | Default | Description |
| -------------------------- | ------- | -------------------------------------------------------------------------------- |
| `user-invocable` | `true` | Expose the skill as a user slash command |
| `disable-model-invocation` | `false` | Keep the skill out of the agent's system prompt (still runs via `/skill`) |
| `command-dispatch` | — | Set to `tool` to route the slash command directly to a tool, bypassing the model |
| `command-tool` | — | Tool name to invoke when `command-dispatch: tool` is set |
| `command-arg-mode` | `raw` | For tool dispatch, forwards the raw args string to the tool |
| `homepage` | — | URL shown as "Website" in the macOS Skills UI |
For gating fields (`requires.bins`, `requires.env`, etc.) see
[Skills — Gating](/tools/skills#gating).
### Using `{baseDir}`
Reference files inside the skill directory without hardcoding paths — the
agent resolves `{baseDir}` against the skill's own directory:
```markdown
Run the helper script at `{baseDir}/scripts/run.sh`.
```
## Adding conditional activation
Gate your skill so it only loads when its dependencies are available:
```markdown
---
name: gemini-search
description: Search using Gemini CLI.
metadata: { "openclaw": { "requires": { "bins": ["gemini"] }, "primaryEnv": "GEMINI_API_KEY" } }
---
```
<AccordionGroup>
<Accordion title="Gating options">
| Key | Description |
| --- | --- |
| `requires.bins` | All binaries must exist on `PATH` |
| `requires.anyBins` | At least one binary must exist on `PATH` |
| `requires.env` | Each env var must exist in the process or config |
| `requires.config` | Each `openclaw.json` path must be truthy |
| `os` | Platform filter: `["darwin"]`, `["linux"]`, `["win32"]` |
| `always` | Set `true` to skip all gates and always include the skill |
Full reference: [Skills — Gating](/tools/skills#gating).
</Accordion>
<Accordion title="Environment and API keys">
Wire an API key to a skill entry in `openclaw.json`:
```json5
{
skills: {
entries: {
"gemini-search": {
enabled: true,
apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" },
},
},
},
}
```
The key is injected into the host process for that agent turn only.
It does not reach the sandbox — see
[sandboxed env vars](/tools/skills-config#sandboxed-skills-and-env-vars).
</Accordion>
</AccordionGroup>
## Propose via Skill Workshop
For agent-drafted skills or when you want operator review before a skill goes
live, use [Skill Workshop](/tools/skill-workshop) proposals instead of writing
`SKILL.md` directly.
```bash
# Propose a brand-new skill
openclaw skills workshop propose-create \
--name "hello-world" \
--description "A simple skill that prints a greeting." \
--proposal ./PROPOSAL.md
# Propose an update to an existing skill
openclaw skills workshop propose-update hello-world \
--proposal ./PROPOSAL.md \
--description "Updated greeting skill"
```
Use `--proposal-dir` when the proposal includes support files:
```bash
openclaw skills workshop propose-create \
--name "hello-world" \
--description "A simple skill that prints a greeting." \
--proposal-dir ./hello-world-proposal/
```
The directory must contain `PROPOSAL.md` at its root. Support files go under
`assets/`, `examples/`, `references/`, `scripts/`, or `templates/`.
After review:
```bash
openclaw skills workshop inspect <proposal-id>
openclaw skills workshop apply <proposal-id>
```
See [Skill Workshop](/tools/skill-workshop) for the full proposal lifecycle.
## Publishing to ClawHub
<Steps>
<Step title="Ensure your SKILL.md is complete">
Make sure `name`, `description`, and any `metadata.openclaw` gating fields
are set. Add a `homepage` URL if you have a project page.
</Step>
<Step title="Install the standalone ClawHub CLI and log in">
```bash
npm i -g clawhub
clawhub login
```
</Step>
<Step title="Publish">
```bash
clawhub skill publish ./path/to/hello-world
```
Add `--version <version>` or `--owner <owner>` to override the inferred
version or publish under a specific owner. See
[ClawHub — Publishing](/clawhub/publishing) and
[ClawHub CLI](/clawhub/cli) for the full flow, owner scoping, and other
maintenance commands (`clawhub sync`, `clawhub skill rename`, ...).
</Step>
</Steps>
## Best practices
<Tip>
- **Be concise** — instruct the model on *what* to do, not how to be an AI.
- **Safety first** — if your skill uses `exec`, ensure prompts do not allow
arbitrary command injection from untrusted input.
- **Test locally** — use `openclaw agent --message "..."` before sharing.
- **Use ClawHub** — browse community skills at [clawhub.ai](https://clawhub.ai)
before building from scratch.
</Tip>
## Related
<CardGroup cols={2}>
<Card title="Skills reference" href="/tools/skills" icon="puzzle-piece">
Loading order, gating, allowlists, and SKILL.md format.
</Card>
<Card title="Skill Workshop" href="/tools/skill-workshop" icon="flask">
Proposal queue for agent-drafted skills.
</Card>
<Card title="Skills config" href="/tools/skills-config" icon="gear">
Full `skills.*` config schema.
</Card>
<Card title="ClawHub" href="/clawhub" icon="cloud">
Browse and publish skills on the public registry.
</Card>
<Card title="Building plugins" href="/plugins/building-plugins" icon="plug">
Plugins can ship skills alongside the tools they document.
</Card>
</CardGroup>

413
docs/tools/diffs.md Normal file
View File

@@ -0,0 +1,413 @@
---
summary: "Read-only diff viewer and file renderer for agents (optional plugin tool)"
title: "Diffs"
sidebarTitle: "Diffs"
read_when:
- You want agents to show code or markdown edits as diffs
- You want a canvas-ready viewer URL or a rendered diff file
- You need controlled, temporary diff artifacts with secure defaults
---
`diffs` is an optional bundled plugin tool that turns before/after text or a unified patch into a read-only diff artifact. It also prepends short agent guidance into the system prompt and ships a companion skill for fuller instructions.
Input: `before` + `after` text, or a unified `patch` (mutually exclusive).
Output: a gateway viewer URL for canvas presentation, a rendered PNG/PDF file path for message delivery, or both.
## Quick start
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install diffs
```
</Step>
<Step title="Enable the plugin">
```json5
{
plugins: {
entries: {
diffs: {
enabled: true,
},
},
},
}
```
</Step>
<Step title="Pick a mode">
<Tabs>
<Tab title="view">
Canvas-first flows: agents call `diffs` with `mode: "view"` and open `details.viewerUrl` with `canvas present`.
</Tab>
<Tab title="file">
Chat file delivery: agents call `diffs` with `mode: "file"` and send `details.filePath` with `message` using `path` or `filePath`.
</Tab>
<Tab title="both">
Combined (default): agents call `diffs` with `mode: "both"` to get both artifacts in one call.
</Tab>
</Tabs>
</Step>
</Steps>
## Disable built-in system guidance
To keep the tool but drop the prepended system-prompt guidance, set `plugins.entries.diffs.hooks.allowPromptInjection` to `false`:
```json5
{
plugins: {
entries: {
diffs: {
enabled: true,
hooks: {
allowPromptInjection: false,
},
},
},
},
}
```
This blocks the plugin's `before_prompt_build` hook while keeping the tool and skill available. To disable both guidance and the tool, disable the plugin instead.
## Tool input reference
All fields are optional unless noted.
<ParamField path="before" type="string">
Original text. Required with `after` when `patch` is omitted.
</ParamField>
<ParamField path="after" type="string">
Updated text. Required with `before` when `patch` is omitted.
</ParamField>
<ParamField path="patch" type="string">
Unified diff text. Mutually exclusive with `before` and `after`.
</ParamField>
<ParamField path="path" type="string">
Display filename for before/after mode.
</ParamField>
<ParamField path="lang" type="string">
Language override hint for before/after mode. Unknown values and languages outside the default viewer set fall back to plain text unless the
Diff Viewer Language Pack plugin is installed.
</ParamField>
<ParamField path="title" type="string">
Viewer title override.
</ParamField>
<ParamField path="mode" type='"view" | "file" | "both"'>
Output mode. Defaults to plugin default `defaults.mode` (`both`). Deprecated alias: `"image"` behaves identically to `"file"`.
</ParamField>
<ParamField path="theme" type='"light" | "dark"'>
Viewer theme. Defaults to plugin default `defaults.theme`.
</ParamField>
<ParamField path="layout" type='"unified" | "split"'>
Diff layout. Defaults to plugin default `defaults.layout`.
</ParamField>
<ParamField path="expandUnchanged" type="boolean">
Expand unchanged sections when full context is available. Per-call option only (not a plugin default key).
</ParamField>
<ParamField path="fileFormat" type='"png" | "pdf"'>
Rendered file format. Defaults to plugin default `defaults.fileFormat`.
</ParamField>
<ParamField path="fileQuality" type='"standard" | "hq" | "print"'>
Quality preset for PNG/PDF rendering.
</ParamField>
<ParamField path="fileScale" type="number">
Device scale override (`1`-`4`).
</ParamField>
<ParamField path="fileMaxWidth" type="number">
Max render width in CSS pixels (`640`-`2400`).
</ParamField>
<ParamField path="ttlSeconds" type="number" default="1800">
Artifact TTL in seconds for viewer and standalone file outputs. Max `21600`.
</ParamField>
<ParamField path="baseUrl" type="string">
Viewer URL origin override. Overrides plugin `viewerBaseUrl`. Must be `http` or `https`, no query/hash.
</ParamField>
<AccordionGroup>
<Accordion title="Legacy input aliases">
Still accepted for backward compatibility:
- `format` -> `fileFormat`
- `imageFormat` -> `fileFormat`
- `imageQuality` -> `fileQuality`
- `imageScale` -> `fileScale`
- `imageMaxWidth` -> `fileMaxWidth`
</Accordion>
<Accordion title="Validation and limits">
- `before`/`after`: max 512 KiB each.
- `patch`: max 2 MiB.
- `path`: max 2048 bytes.
- `lang`: max 128 bytes.
- `title`: max 1024 bytes.
- Patch complexity cap: max 128 files and 120000 total lines.
- `patch` together with `before`/`after` is rejected.
- Rendered file safety limits (PNG and PDF):
- `fileQuality: "standard"`: max 8 MP (8,000,000 rendered pixels).
- `fileQuality: "hq"`: max 14 MP.
- `fileQuality: "print"`: max 24 MP.
- PDF also caps at 50 pages.
</Accordion>
</AccordionGroup>
## Syntax highlighting
Built-in languages:
`javascript`, `typescript`, `tsx`, `jsx`, `json`, `markdown`, `yaml`, `css`, `html`, `sh`, `python`, `go`, `rust`, `java`, `c`, `cpp`, `csharp`, `php`, `sql`, `docker`, `ruby`, `swift`, `kotlin`, `r`, `dart`, `lua`, `powershell`, `xml`, and `toml`.
Common aliases (`js`, `ts`, `bash`, `md`, `yml`, `c++`, `dockerfile`, `rb`, `kt`, `ps1`, etc.) normalize to those languages.
Install the Diff Viewer Language Pack plugin for more languages (Astro, Vue, Svelte, MDX, GraphQL, Terraform/HCL, Nix, Clojure, Elixir, Haskell, OCaml, Scala, Zig, Solidity, Verilog/VHDL, Fortran, MATLAB, LaTeX, Mermaid, Sass/Less/SCSS, Nginx, Apache, CSV, dotenv, INI, diff, and more):
```bash
openclaw plugins install clawhub:@openclaw/diffs-language-pack
```
Without the pack, unsupported languages still render as readable plain text. See [Diffs Language Pack plugin](/plugins/reference/diffs-language-pack) and [Shiki languages](https://shiki.style/languages) for the upstream catalog.
## Output details contract
<AccordionGroup>
<Accordion title="Viewer fields (view and both modes)">
- `artifactId`
- `viewerUrl`
- `viewerPath`
- `title`
- `expiresAt`
- `inputKind`
- `fileCount`
- `mode`
- `context` (`agentId`, `sessionId`, `messageChannel`, `agentAccountId` when available)
</Accordion>
<Accordion title="File fields (file and both modes)">
- `artifactId`
- `expiresAt`
- `filePath`
- `path` (same value as `filePath`, for message tool compatibility)
- `fileBytes`
- `fileFormat`
- `fileQuality`
- `fileScale`
- `fileMaxWidth`
</Accordion>
<Accordion title="Compatibility aliases (always returned)">
- `format` (= `fileFormat`)
- `imagePath` (= `filePath`)
- `imageBytes` (= `fileBytes`)
- `imageQuality` (= `fileQuality`)
- `imageScale` (= `fileScale`)
- `imageMaxWidth` (= `fileMaxWidth`)
</Accordion>
</AccordionGroup>
| Mode | Returns |
| -------- | ------------------------------------------------------------------------------------------------------------ |
| `"view"` | Viewer fields only. |
| `"file"` | File fields only, no viewer artifact. |
| `"both"` | Viewer fields plus file fields. If file rendering fails, viewer still returns with `fileError`/`imageError`. |
### Collapsed unchanged sections
The viewer shows rows like `N unmodified lines`. Expand controls only appear when the rendered diff has expandable context data (typical for before/after input). Many unified patches omit context bodies in their hunks, so the row can appear without an expand control -- expected, not a bug. `expandUnchanged` only applies when expandable context exists.
## Plugin defaults
Set plugin-wide defaults in `~/.openclaw/openclaw.json`:
```json5
{
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",
ttlSeconds: 21600,
},
},
},
},
},
}
```
Supported `defaults` keys: `fontFamily`, `fontSize`, `lineSpacing`, `layout`, `showLineNumbers`, `diffIndicators`, `wordWrap`, `background`, `theme`, `fileFormat`, `fileQuality`, `fileScale`, `fileMaxWidth`, `mode`, `ttlSeconds`. Explicit tool call parameters override these.
### Persistent viewer URL config
<ParamField path="viewerBaseUrl" type="string">
Plugin-owned fallback for returned viewer links when a tool call does not pass `baseUrl`. Must be `http` or `https`, no query/hash.
</ParamField>
```json5
{
plugins: {
entries: {
diffs: {
enabled: true,
config: {
viewerBaseUrl: "https://gateway.example.com/openclaw",
},
},
},
},
}
```
## Security config
<ParamField path="security.allowRemoteViewer" type="boolean" default="false">
`false`: non-loopback requests to viewer routes are denied. `true`: remote viewers are allowed if the tokenized path is valid.
</ParamField>
```json5
{
plugins: {
entries: {
diffs: {
enabled: true,
config: {
security: {
allowRemoteViewer: false,
},
},
},
},
},
}
```
## Artifact lifecycle and storage
- Artifacts live under `$TMPDIR/openclaw-diffs`.
- Viewer metadata stores a random 20-hex-char artifact ID, a random 48-hex-char token, `createdAt`/`expiresAt`, and the stored `viewer.html` path.
- Default artifact TTL: 30 minutes. Maximum accepted TTL: 6 hours.
- Cleanup runs opportunistically after each artifact create call; expired artifacts are deleted.
- Fallback sweep removes stale folders older than 24 hours when metadata is missing.
## Viewer URL and network behavior
Viewer route: `/plugins/diffs/view/{artifactId}/{token}`
Viewer assets:
- `/plugins/diffs/assets/viewer.js`
- `/plugins/diffs/assets/viewer-runtime.js`
- `/plugins/diffs-language-pack/assets/viewer.js` (only when the diff uses a language pack language)
The viewer document resolves these assets relative to the viewer URL, so an optional `baseUrl` path prefix carries through to asset requests too.
URL resolution order: tool-call `baseUrl` (after strict validation) -> plugin `viewerBaseUrl` -> loopback `127.0.0.1` default. If gateway bind mode is `custom` and `gateway.customBindHost` is set, that host is used instead of loopback.
`baseUrl` rules: must be `http://` or `https://`; query and hash are rejected; origin plus optional base path is allowed.
## Security model
<AccordionGroup>
<Accordion title="Viewer hardening">
- Loopback-only by default.
- Tokenized viewer paths with strict ID and token pattern validation.
- Viewer response CSP: `default-src 'none'`; scripts/assets only from self; no outbound `connect-src`.
- Remote miss throttling when remote access is enabled: 40 failures per 60 seconds triggers a 60-second lockout (`429 Too Many Requests`).
</Accordion>
<Accordion title="File rendering hardening">
- Screenshot browser request routing is deny-by-default.
- Only local viewer assets from `http://127.0.0.1/plugins/diffs/assets/*` are allowed.
- External network requests are blocked.
</Accordion>
</AccordionGroup>
## Browser requirements for file mode
`mode: "file"` and `mode: "both"` need a Chromium-compatible browser.
Resolution order:
<Steps>
<Step title="Config">
`browser.executablePath` in OpenClaw config.
</Step>
<Step title="Environment variables">
- `OPENCLAW_BROWSER_EXECUTABLE_PATH`
- `BROWSER_EXECUTABLE_PATH`
- `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH`
</Step>
<Step title="Platform fallback">
Common install paths and `PATH` lookups for Chrome, Chromium, Edge, and Brave.
</Step>
</Steps>
Common failure text: `Diff PNG/PDF rendering requires a Chromium-compatible browser...`. Fix by installing Chrome, Chromium, Edge, or Brave, or setting one of the executable path options above.
## Troubleshooting
<AccordionGroup>
<Accordion title="Input validation errors">
- `Provide patch or both before and after text.` -- include both `before` and `after`, or provide `patch`.
- `Provide either patch or before/after input, not both.` -- do not mix input modes.
- `Invalid baseUrl: ...` -- use an `http(s)` origin with optional path, no query/hash.
- `{field} exceeds maximum size (...)` -- reduce payload size.
- Large patch rejection -- reduce patch file count or total lines.
</Accordion>
<Accordion title="Viewer accessibility">
- Viewer URL resolves to `127.0.0.1` by default.
- For remote access, either set plugin `viewerBaseUrl`, pass `baseUrl` per call, or use `gateway.bind=custom` with `gateway.customBindHost`.
- If `gateway.trustedProxies` includes loopback for a same-host proxy (for example Tailscale Serve), raw loopback viewer requests without forwarded client-IP headers fail closed by design.
- For that proxy topology, prefer `mode: "file"`/`"both"` for an attachment, or intentionally enable `security.allowRemoteViewer` plus plugin `viewerBaseUrl`/a proxy `baseUrl` for a shareable viewer link.
- Enable `security.allowRemoteViewer` only when external viewer access is intended.
</Accordion>
<Accordion title="Unmodified-lines row has no expand button">
Expected for patch input that lacks expandable context; not a viewer failure.
</Accordion>
<Accordion title="Artifact not found">
- Artifact expired due to TTL.
- Token or path changed.
- Cleanup removed stale data.
</Accordion>
</AccordionGroup>
## Operational guidance
- Prefer `mode: "view"` for local interactive reviews in canvas.
- Prefer `mode: "file"` for outbound chat channels that need an attachment.
- Keep `allowRemoteViewer` disabled unless your deployment requires remote viewer URLs.
- Set an explicit short `ttlSeconds` for sensitive diffs.
- Avoid sending secrets in diff input when not required.
- If your channel compresses images aggressively (for example Telegram or WhatsApp), prefer PDF output (`fileFormat: "pdf"`).
<Note>
Diff rendering engine powered by [Diffs](https://diffs.com).
</Note>
## Related
- [Browser](/tools/browser)
- [Plugins](/tools/plugin)
- [Tools overview](/tools)

View File

@@ -0,0 +1,100 @@
---
summary: "DuckDuckGo web search -- key-free provider (experimental, HTML-based)"
read_when:
- You want a web search provider that requires no API key
- You want to use DuckDuckGo for web_search
- You want an explicitly selected key-free search provider
title: "DuckDuckGo search"
---
OpenClaw supports DuckDuckGo as a **key-free** `web_search` provider. No API key or account is required.
<Warning>
DuckDuckGo is an **experimental, unofficial** integration that scrapes DuckDuckGo's non-JavaScript HTML search pages -- not an official API. Expect occasional breakage from bot-challenge pages or HTML changes.
</Warning>
## Setup
DuckDuckGo is never auto-selected, since auto-detection only considers providers with usable credentials. Set it explicitly:
<Steps>
<Step title="Configure">
```bash
openclaw configure --section web
# Select "duckduckgo" as the provider
```
</Step>
</Steps>
## Config
Set the provider directly in config:
```json5
{
tools: {
web: {
search: {
provider: "duckduckgo",
},
},
},
}
```
Optional plugin-level settings for region and SafeSearch:
```json5
{
plugins: {
entries: {
duckduckgo: {
config: {
webSearch: {
region: "us-en", // DuckDuckGo region code
safeSearch: "moderate", // "strict", "moderate", or "off"
},
},
},
},
},
}
```
## Tool parameters
<ParamField path="query" type="string" required>
Search query.
</ParamField>
<ParamField path="count" type="number" default="5">
Results to return (1-10).
</ParamField>
<ParamField path="region" type="string">
DuckDuckGo region code (e.g. `us-en`, `uk-en`, `de-de`).
</ParamField>
<ParamField path="safeSearch" type="'strict' | 'moderate' | 'off'" default="moderate">
SafeSearch level.
</ParamField>
`region` and `safeSearch` tool parameters override the plugin config values above on a per-query basis.
## Notes
- **No API key** -- works once DuckDuckGo is selected as the `web_search` provider.
- **Experimental** -- scrapes DuckDuckGo's non-JavaScript HTML search pages, not an official API or SDK. Results depend on page structure, which can change without notice.
- **Bot-challenge risk** -- DuckDuckGo may serve CAPTCHAs or block requests under heavy or automated use.
- **Explicit selection only** -- OpenClaw's auto-detect only considers providers with usable credentials, so a key-free provider like DuckDuckGo is never chosen automatically; you must set `provider: "duckduckgo"`.
- **SafeSearch defaults to `moderate`** when not configured.
<Tip>
For production use, consider [Brave Search](/tools/brave-search) (free tier available) or another API-backed provider.
</Tip>
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [Brave Search](/tools/brave-search) -- structured results with free tier
- [Exa Search](/tools/exa-search) -- neural search with content extraction

127
docs/tools/elevated.md Normal file
View File

@@ -0,0 +1,127 @@
---
summary: "Elevated exec mode: run commands outside the sandbox from a sandboxed agent"
read_when:
- Adjusting elevated mode defaults, allowlists, or slash command behavior
- Understanding how sandboxed agents can access the host
title: "Elevated mode"
---
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 outside the sandbox instead, with configurable approval gates.
<Info>
Elevated mode only changes behavior when the agent is **sandboxed**. For unsandboxed agents, exec already runs on the host.
</Info>
## Directives
Control elevated mode per-session with slash commands:
| Directive | What it does |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `/elevated on` | Run outside the sandbox on the configured host path, keep approvals |
| `/elevated ask` | Same as `on` (alias) |
| `/elevated full` | Run outside the sandbox on the configured host path and skip approvals when the mode/host approval policy is already permissive |
| `/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
<Steps>
<Step title="Check availability">
Elevated must be enabled in config and the sender must be on the allowlist:
```json5
{
tools: {
elevated: {
enabled: true,
allowFrom: {
discord: ["user-id-123"],
whatsapp: ["+15555550123"],
},
},
},
}
```
</Step>
<Step title="Set the level">
Send a directive-only message to set the session default:
```
/elevated full
```
Or use it inline (applies to that message only):
```
/elevated on run the deployment script
```
</Step>
<Step title="Commands run outside the sandbox">
With elevated active, `exec` calls leave the sandbox. The effective host is
`gateway` by default, or `node` when the configured/session exec target is
`node`. In `full` mode, exec approvals are skipped when the resolved exec
mode/host approval policy is already fully permissive (security `full`,
ask `off`); otherwise the normal approval policy still applies. In
`on`/`ask` mode, configured approval rules always apply.
</Step>
</Steps>
## 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
- **Global gate**: `tools.elevated.enabled` (must be `true`)
- **Sender allowlist**: `tools.elevated.allowFrom` with per-channel lists
- **Per-agent gate**: `agents.list[].tools.elevated.enabled` (can only further restrict; both the global and per-agent gate must be `true`)
- **Per-agent allowlist**: `agents.list[].tools.elevated.allowFrom` (sender must match both global + per-agent)
- **Channel-provided fallback allowlist**: channel plugins can optionally supply a fallback allowlist through an SDK adapter hook, used when `tools.elevated.allowFrom.<provider>` is not configured. No bundled channel currently implements this hook, so in practice every provider needs an explicit `tools.elevated.allowFrom.<provider>` entry today.
- **All gates must pass**; otherwise elevated is treated as unavailable
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
- **Tool policy**: if `exec` is denied by tool policy, elevated cannot override it.
- **Host selection policy**: elevated does not turn `auto` into a free cross-host override. It uses the configured/session exec target rules, choosing `node` only when the target is already `node`.
- **Separate from `/exec`**: the `/exec` directive adjusts per-session exec defaults (host, security, ask, node) for authorized senders and does not require elevated mode.
<Note>
The bash chat command (`!` prefix; `/bash` alias) is a separate gate that requires `tools.elevated` to be enabled in addition to its own `tools.bash.enabled` flag. Disabling elevated locks `!` shell commands out as well.
</Note>
## Related
<CardGroup cols={2}>
<Card title="Exec tool" href="/tools/exec" icon="terminal">
Shell command execution from the agent.
</Card>
<Card title="Exec approvals" href="/tools/exec-approvals" icon="shield">
Approval and allowlist system for `exec`.
</Card>
<Card title="Sandboxing" href="/gateway/sandboxing" icon="box">
Gateway-level sandbox configuration.
</Card>
<Card title="Sandbox vs Tool Policy vs Elevated" href="/gateway/sandbox-vs-tool-policy-vs-elevated" icon="scale-balanced">
How the three gates compose during a tool call.
</Card>
</CardGroup>

157
docs/tools/exa-search.md Normal file
View File

@@ -0,0 +1,157 @@
---
summary: "Exa AI search -- neural and keyword search with content extraction"
read_when:
- You want to use Exa for web_search
- You need an EXA_API_KEY
- You want neural search or content extraction
title: "Exa search"
---
[Exa AI](https://exa.ai/) is a `web_search` provider with neural, keyword, and
hybrid search modes plus built-in content extraction (highlights, text,
summaries).
## Install plugin
```bash
openclaw plugins install @openclaw/exa-plugin
openclaw gateway restart
```
## Get an API key
<Steps>
<Step title="Create an account">
Sign up at [exa.ai](https://exa.ai/) and generate an API key from your
dashboard.
</Step>
<Step title="Store the key">
Set `EXA_API_KEY` in the Gateway environment, or configure via:
```bash
openclaw configure --section web
```
</Step>
</Steps>
## Config
```json5
{
plugins: {
entries: {
exa: {
config: {
webSearch: {
apiKey: "exa-...", // optional if EXA_API_KEY is set
baseUrl: "https://api.exa.ai", // optional; OpenClaw appends /search
},
},
},
},
},
tools: {
web: {
search: {
provider: "exa",
},
},
},
}
```
**Environment alternative:** set `EXA_API_KEY` in the Gateway environment. For
a gateway install, put it in `~/.openclaw/.env`. See
[Env vars](/help/faq#env-vars-and-env-loading).
## Base URL override
Set `plugins.entries.exa.config.webSearch.baseUrl` to route Exa search
requests through a compatible proxy or alternate endpoint. OpenClaw
normalizes bare hosts by prepending `https://` and appends `/search` unless
the path already ends there. The resolved endpoint is part of the search
cache key, so results from different endpoints are never shared.
## Tool parameters
<ParamField path="query" type="string" required>
Search query.
</ParamField>
<ParamField path="count" type="number" default="5">
Results to return (1-100, subject to Exa search-type limits).
</ParamField>
<ParamField path="type" type="'auto' | 'neural' | 'fast' | 'deep' | 'deep-reasoning' | 'instant'">
Search mode.
</ParamField>
<ParamField path="freshness" type="'day' | 'week' | 'month' | 'year'">
Time filter. Cannot be combined with `date_after`/`date_before`.
</ParamField>
<ParamField path="date_after" type="string">
Results after this date (`YYYY-MM-DD`).
</ParamField>
<ParamField path="date_before" type="string">
Results before this date (`YYYY-MM-DD`).
</ParamField>
<ParamField path="contents" type="object">
Content extraction options (see below).
</ParamField>
### Content extraction
Pass a `contents` object to control extracted content in results:
```javascript
await web_search({
query: "transformer architecture explained",
type: "neural",
contents: {
text: true, // full page text
highlights: { numSentences: 3 }, // key sentences
summary: true, // AI summary
},
});
```
| 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 |
If `contents` is omitted, Exa defaults to `{ highlights: true }` so results
include key-sentence excerpts. Result descriptions resolve from highlights
first, then summary, then full text -- whichever is available first. Results
also preserve the raw `highlightScores` and `summary` fields from the Exa API
response when available.
### 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
- `count` accepts up to 100, subject to Exa search-type limits.
- Results are cached for 15 minutes by default. Configure the shared
`tools.web.search.cacheTtlMinutes` (minutes) and
`tools.web.search.timeoutSeconds` (default 30s) to change caching and
request timeout for all `web_search` providers, including Exa.
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [Brave Search](/tools/brave-search) -- structured results with country/language filters
- [Perplexity Search](/tools/perplexity-search) -- structured results with domain filtering

View File

@@ -0,0 +1,445 @@
---
summary: "Advanced exec approvals: safe bins, interpreter binding, approval forwarding, native delivery"
read_when:
- Configuring safe bins or custom safe-bin profiles
- Forwarding approvals to Slack/Discord/Telegram or other chat channels
- Implementing a native approval client for a channel
title: "Exec approvals — advanced"
---
Advanced exec-approval topics: the `safeBins` fast-path, interpreter/runtime
binding, and approval-forwarding to chat channels (including native delivery).
For the core policy and approval flow, see [Exec approvals](/tools/exec-approvals).
## Safe bins (stdin-only)
`tools.exec.safeBins` names **stdin-only** binaries (for example `cut`) that
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.
<Warning>
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>`.
</Warning>
Default safe bins:
[//]: # "SAFE_BIN_DEFAULTS:START"
`cut`, `uniq`, `head`, `tail`, `tr`, `wc`
[//]: # "SAFE_BIN_DEFAULTS:END"
`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.
### Argv validation and denied flags
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; long
options validate fail-closed (unknown flags and ambiguous abbreviations are
rejected).
Denied flags by safe-bin profile:
[//]: # "SAFE_BIN_DENIED_FLAGS:START"
- `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_BIN_DENIED_FLAGS:END"
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. `awk`
and `sed` are always denied as safe bins (their semantics cannot be validated
to stdin-only); `jq` can be opted in, but OpenClaw still rejects `env`-style
filters (for example `jq env` or `jq -n env`) in safe-bin mode so `jq` cannot
dump the host process environment without an explicit allowlist path or
approval prompt.
### Trusted binary directories
Safe bins must resolve from trusted binary directories (system defaults plus
optional `tools.exec.safeBinTrustedDirs`). `PATH` entries are never auto-trusted.
Default trusted 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, wrappers, and multiplexers
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, transparent dispatch wrappers
(for example `env`, `flock`, `nice`, `nohup`, `stdbuf`, `timeout`) persist the
inner executable path instead of the wrapper path. Shell multiplexers
(`busybox`, `toybox`) are unwrapped for shell applets (`sh`, `ash`, etc.) the
same way. 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. In strict mode, `allow-always` can still persist benign
interpreter/script invocations, but inline-eval carriers are not persisted
automatically.
### 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, or bare command-name glob for PATH-invoked commands |
| Argument scope | Restricted by safe-bin profile and literal-token rules | Path match by default; optional `argPattern` can restrict parsed argv |
| 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:
- `safeBins` comes from config (`tools.exec.safeBins` or per-agent `agents.list[].tools.exec.safeBins`).
- `safeBinTrustedDirs` comes from config (`tools.exec.safeBinTrustedDirs` or per-agent `agents.list[].tools.exec.safeBinTrustedDirs`).
- `safeBinProfiles` comes from config (`tools.exec.safeBinProfiles` or per-agent `agents.list[].tools.exec.safeBinProfiles`). Per-agent profile keys override global keys.
- allowlist entries live in the host-local approvals file under `agents.<id>.allowlist` (or via Control UI / `openclaw approvals allowlist ...`).
- `openclaw security audit` warns with `tools.exec.safe_bins_interpreter_unprofiled` when interpreter/runtime bins appear in `safeBins` without explicit profiles.
- `openclaw doctor --fix` can scaffold missing custom `safeBinProfiles.<bin>` entries as `{}` (review and tighten afterward). Interpreter/runtime bins are not auto-scaffolded.
Custom profile example:
```json5
{
tools: {
exec: {
safeBins: ["jq", "myfilter"],
safeBinProfiles: {
myfilter: {
minPositional: 0,
maxPositional: 0,
allowedValueFlags: ["-n", "--limit"],
deniedFlags: ["-f", "--file", "-c", "--command"],
},
},
},
},
}
```
## 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 approved-run system events (`Exec finished`, and `Exec running` when configured).
If no decision arrives before the timeout, the request is treated as an approval timeout and
surfaced as a terminal host-command denial. For main-agent async approvals with an originating
session, OpenClaw also resumes that session with an internal followup so the agent observes that
the command did not run instead of later repairing a missing result. Pending exec approvals expire
after 30 minutes by default.
### Followup delivery behavior
After an approved async exec finishes, OpenClaw sends a followup `agent` turn to the same session.
Denied async approvals use the same main-session followup path for the denial status, but they do
not register elevated runtime handoffs and they do not run the command. Denials without a resumable
main session are either suppressed or reported through a safe direct route when one exists.
- If a valid external delivery target exists (deliverable channel plus target `to`), followup delivery uses that channel.
- In webchat-only or internal-session flows with no external target, followup delivery stays session-only (`deliver: false`).
- If a caller explicitly requests strict external delivery with no resolvable external channel, the request fails with `INVALID_REQUEST`.
- If `bestEffortDeliver` is enabled and no external channel can be resolved, delivery is downgraded to session-only instead of failing.
## 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:
```json5
{
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:
```
/approve <id> allow-once
/approve <id> allow-always
/approve <id> deny
```
The `/approve` command handles both exec approvals and plugin approvals. If the ID does not match a pending exec approval, it automatically checks plugin approvals instead. This fallback is bounded to "approval not found" failures; a real exec approval denial/error does not silently retry as a plugin approval.
### Plugin approval forwarding
Plugin approval forwarding uses the same delivery pipeline as exec approvals but has its own
independent config under `approvals.plugin`. Enabling or disabling one does not affect the other.
For plugin-authoring behavior, request fields, and decision semantics, see
[Plugin permission requests](/plugins/plugin-permission-requests).
```json5
{
approvals: {
plugin: {
enabled: true,
mode: "targets",
agentFilter: ["main"],
targets: [
{ channel: "slack", to: "U12345678" },
{ channel: "telegram", to: "123456789" },
],
},
},
}
```
The config shape is identical to `approvals.exec`: `enabled`, `mode`, `agentFilter`,
`sessionFilter`, and `targets` work the same way.
Channels that support shared interactive replies render the same approval buttons for both exec and
plugin approvals. Channels without shared interactive UI fall back to plain text with `/approve`
instructions. Plugin approval requests may restrict the available decisions: approval surfaces use
the request's declared decision set, and the Gateway rejects attempts to submit a decision that was
not offered.
### Same-chat approvals on any channel
When an exec or plugin approval request originates from a deliverable chat surface, that same chat
can approve it with `/approve` by default. This applies to Slack, Matrix, Microsoft Teams, and
similar deliverable chats, in addition to the existing Web UI and terminal UI flows, using the
normal channel auth model for that conversation. If the originating chat can already send commands
and receive replies, approval requests no longer need a separate native delivery adapter just to
stay pending.
Discord, Telegram, and QQ bot also support same-chat `/approve`, but those channels still use their
resolved approver list for authorization even when native approval delivery is disabled.
### Native approval delivery
Some channels can also act as native approval clients: Discord, Slack, Telegram, Matrix, and QQ bot.
Native clients add approver DMs, origin-chat fanout, and channel-specific interactive approval UX on
top of the shared same-chat `/approve` flow.
When native approval cards/buttons are available, that native UI is the primary agent-facing path.
The agent should not also echo a duplicate plain chat `/approve` command unless the tool result says
chat approvals are unavailable or manual approval is the only remaining path.
If a native approval client is configured but no native runtime is active for the originating
channel, OpenClaw keeps the local deterministic `/approve` prompt visible. If the native runtime is
active and attempts delivery but no target receives the card, OpenClaw sends a same-chat fallback
notice with the exact `/approve <id> <decision>` command so the request can still be resolved.
Generic model:
- host exec policy still decides whether exec approval is required
- `approvals.exec` controls forwarding approval prompts to other chat destinations
- `channels.<channel>.execApprovals` controls whether Discord, Slack, Telegram, QQ bot, and similar
channel-specific native clients are enabled
- Slack plugin approvals can use Slack's native approval client when the request comes from Slack
and Slack plugin approvers resolve; `approvals.plugin` can also route plugin approvals to Slack
sessions or targets even when Slack exec approvals are disabled
- Google Chat native approval cards handle exec and plugin approvals that originate from Google
Chat spaces or threads when stable `users/<id>` approvers resolve from `dm.allowFrom` or
`defaultTo`; they do not use reaction events for decisions
- WhatsApp and Signal reaction approval delivery are gated by `approvals.exec` and
`approvals.plugin`; they do not have `channels.<channel>.execApprovals` blocks
Native approval clients auto-enable DM-first delivery when all of these are true:
- the channel supports native approval delivery
- approvers can be resolved from explicit `execApprovals.approvers` or owner
identity such as `commands.ownerAllowFrom`
- `channels.<channel>.execApprovals.enabled` is unset or `"auto"`
Set `enabled: false` to disable a native approval client explicitly. Set `enabled: true` to force
it on when approvers resolve. Public origin-chat delivery stays explicit through
`channels.<channel>.execApprovals.target`. When native `target` enables origin-chat delivery,
approval prompts include the command text.
FAQ: [Why are there two exec approval configs for chat approvals?](/help/faq-first-run)
- Discord: `channels.discord.execApprovals.*`
- Slack: `channels.slack.execApprovals.*`
- Telegram: `channels.telegram.execApprovals.*`
- QQ bot: `channels.qqbot.execApprovals.*`
- Google Chat: configure stable approvers with `channels.googlechat.dm.allowFrom` or
`channels.googlechat.defaultTo`; no `execApprovals` block is required
- WhatsApp: use `approvals.exec` and `approvals.plugin` to route approval prompts to WhatsApp
- Signal: use `approvals.exec` and `approvals.plugin` to route approval prompts to Signal
Native-client-specific routing:
- Telegram defaults to approver DMs (`target: "dm"`). Switch to `channel` or `both` to also show
approval prompts in the originating Telegram chat/topic. For Telegram forum topics, OpenClaw
preserves the topic for the approval prompt and the post-approval follow-up.
- Discord and Telegram approvers can be explicit (`execApprovals.approvers`) or inferred from
`commands.ownerAllowFrom`; only resolved approvers can approve or deny.
- Slack approvers can be explicit (`execApprovals.approvers`) or inferred from
`commands.ownerAllowFrom`. Slack plugin approval DMs use Slack plugin approvers from `allowFrom`
and account default routing, not Slack exec approvers. Slack native buttons preserve approval id
kind, so `plugin:` ids can resolve plugin approvals without a second Slack-local fallback layer.
- Google Chat native cards preserve the manual `/approve` fallback in message text, but card button
callbacks carry only opaque action tokens; the approval id and decision are recovered from
server-side pending state.
- WhatsApp emoji approvals handle both exec and plugin prompts only when the matching top-level
forwarding family is enabled and routes to WhatsApp; target-only WhatsApp forwarding stays on the
shared forwarding path unless it matches the same native origin target.
- Signal reaction approvals handle both exec and plugin prompts only when the matching top-level
forwarding family is enabled and routes to Signal. Direct same-chat Signal exec approvals can
suppress the local `/approve` fallback without explicit approvers; Signal reaction resolution
still requires explicit Signal approvers from `channels.signal.allowFrom` or `defaultTo`.
- Matrix native DM/channel routing and reaction shortcuts handle both exec and plugin approvals;
plugin authorization still comes from `channels.matrix.dm.allowFrom`. Matrix native prompts
include `com.openclaw.approval` custom event content on the first prompt event so OpenClaw-aware
Matrix clients can read structured approval state while stock clients keep the plain-text
`/approve` fallback.
- Native Discord approval buttons route by approval id kind: `plugin:` ids go straight to plugin
approvals, everything else goes to exec approvals. Native Telegram approval buttons follow the
same bounded exec-to-plugin fallback as `/approve`.
- The requester does not need to be an approver.
- If no operator UI or configured approval client can accept the request, the prompt falls back to
`askFallback`.
Sensitive owner-only group commands such as `/diagnostics` and `/export-trajectory` use private
owner routing for approval prompts and final results. OpenClaw first tries a private route on the
same surface where the owner ran the command. If that surface has no private owner route, it falls
back to the first available owner route from `commands.ownerAllowFrom`, so a Discord group command
can still send the approval and result to the owner's Telegram DM when Telegram is the configured
primary private interface. The group chat only gets a short acknowledgement.
See:
- [Discord](/channels/discord)
- [Telegram](/channels/telegram)
- [QQ bot](/channels/qqbot)
### macOS IPC flow
```
Gateway -> Node Service (WS)
| IPC (UDS + token + HMAC + TTL)
v
Mac App (UI + approvals + system.run)
```
Security notes:
- Unix socket mode `0600`, token stored in `exec-approvals.json`.
- Same-UID peer check.
- Challenge/response (nonce + HMAC token + request hash) + short TTL.
## FAQ
### When would `accountId` and `threadId` be used on an approval target?
Use `accountId` when the channel has multiple configured identities and the approval prompt must
leave through one specific account. Use `threadId` when the destination supports topics or
threads and the prompt should stay inside that thread instead of the top-level chat.
A concrete Telegram case is an operations supergroup with forum topics and two Telegram bot
accounts. The `to` value names the supergroup, `accountId` selects the bot account, and `threadId`
selects the forum topic:
```json5
{
approvals: {
exec: {
enabled: true,
mode: "targets",
targets: [
{
channel: "telegram",
to: "-1001234567890",
accountId: "ops-bot",
threadId: "77",
},
],
},
},
channels: {
telegram: {
accounts: {
default: {
name: "Primary bot",
botToken: "env:TELEGRAM_PRIMARY_BOT_TOKEN",
},
"ops-bot": {
name: "Operations bot",
botToken: "env:TELEGRAM_OPS_BOT_TOKEN",
},
},
},
},
}
```
With that setup, forwarded exec approvals are posted by the `ops-bot` Telegram account into topic
`77` of chat `-1001234567890`. A target without `accountId` uses the channel's default account, and
a target without `threadId` posts to the top-level destination.
### When approvals are sent to a session, can anyone in that session approve them?
No. Session delivery only controls where the prompt appears. It does not by itself authorize every
participant in that chat to approve.
For generic same-chat `/approve`, the sender must already be authorized for commands in that
channel session. If the channel exposes explicit approval approvers, those approvers can authorize
the `/approve` action even when they are not otherwise command-authorized in that session.
Some channels are stricter. Discord, Telegram, Matrix, Slack native approval DMs, and similar
native approval clients use their resolved approver lists for approval authorization. For example,
a Telegram forum-topic approval prompt can be visible to everyone in the topic, but only numeric
Telegram user IDs resolved from `channels.telegram.execApprovals.approvers` or
`commands.ownerAllowFrom` can approve or deny it.
## Related
- [Exec approvals](/tools/exec-approvals) — core policy and approval flow
- [Exec tool](/tools/exec)
- [Elevated mode](/tools/elevated)
- [Skills](/tools/skills) — skill-backed auto-allow behavior

View File

@@ -0,0 +1,512 @@
---
summary: "Host exec approvals: policy knobs, allowlists, and the YOLO/strict workflow"
read_when:
- Configuring exec approvals or allowlists
- Implementing exec approval UX in the macOS app
- Reviewing sandbox-escape prompts and their implications
title: "Exec approvals"
sidebarTitle: "Exec approvals"
---
Exec approvals are the **companion app / node host guardrail** for letting a
sandboxed agent run commands on a real host (`gateway` or `node`). Commands
run only when policy + allowlist + (optional) user approval all agree.
Approvals stack **on top of** tool policy and elevated gating (elevated
`full` skips them).
For a mode-first overview of `deny`, `allowlist`, `ask`, `auto`, `full`,
Codex Guardian mapping, and ACPX harness permissions, see
[Permission modes](/tools/permission-modes).
<Note>
Effective policy is the **stricter** of `tools.exec.*` and approvals
defaults: approvals can only tighten config-derived security/ask, never
loosen them. If an approvals field is omitted, the `tools.exec` value is
used. Host exec also uses local approvals state on that machine - a
host-local `ask: "always"` in the execution host approvals file keeps
prompting even if session or config defaults request `ask: "on-miss"`.
</Note>
## Where it applies
Exec approvals are enforced locally on the execution host:
- **Gateway host** -> `openclaw` process on the gateway machine.
- **Node host** -> node runner (macOS companion app or headless node host).
### Trust model
- Gateway-authenticated callers are trusted operators for that Gateway.
- Paired nodes extend that trusted operator capability onto the node host.
- Approvals reduce accidental execution risk, but are **not** a per-user auth boundary or filesystem read-only policy.
- Once approved, a command can mutate files according to the selected host or sandbox filesystem permissions.
- Approved node-host runs bind canonical execution context: cwd, exact argv, env binding when present, and pinned executable path when applicable.
- For shell scripts and direct interpreter/runtime file invocations, OpenClaw also tries to bind one concrete local file operand. If that file changes after approval but before execution, the run is denied instead of executing drifted content.
- File binding is best-effort, not a complete model of every interpreter/runtime loader path. If exactly one concrete local file cannot be identified, OpenClaw refuses to mint an approval-backed run rather than pretend full coverage.
### macOS split
- The **node host service** forwards `system.run` to the **macOS app** over local IPC.
- The **macOS app** enforces approvals and executes the command in UI context.
## Inspecting the effective policy
| Command | What it shows |
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `openclaw approvals get` / `--gateway` / `--node <id\|name\|ip>` | Requested policy, host policy sources, and the effective result. |
| `openclaw exec-policy show` | Local-machine merged view. |
| `openclaw exec-policy set` / `preset` | Synchronize the local requested policy with the local host approvals file in one step. |
Full CLI reference (flags, JSON output, allowlist add/remove): [Approvals CLI](/cli/approvals).
When a local scope requests `host=node`, `exec-policy show` reports that
scope as node-managed at runtime instead of treating the local approvals
file as the source of truth.
If the companion app UI is **not available**, any request that would
normally prompt is resolved by the **ask fallback** (default: `deny`).
<Tip>
Native chat approval clients can seed channel-specific affordances on the
pending approval message. Matrix seeds reaction shortcuts (`✅` allow once,
`♾️` allow always, `❌` deny) while still leaving `/approve ...` in the
message as a fallback.
</Tip>
## Settings and storage
Approvals live in a local JSON file on the execution host. When
`OPENCLAW_STATE_DIR` is set, the file follows that state directory;
otherwise it uses the default OpenClaw state directory:
```text
$OPENCLAW_STATE_DIR/exec-approvals.json
# otherwise
~/.openclaw/exec-approvals.json
```
The default approval socket follows the same root:
`$OPENCLAW_STATE_DIR/exec-approvals.sock`, or
`~/.openclaw/exec-approvals.sock` when the variable is unset.
Example schema:
```json
{
"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",
"source": "allow-always",
"commandText": "rg -n TODO",
"lastUsedAt": 1737150000000,
"lastUsedCommand": "rg -n TODO",
"lastResolvedPath": "/Users/user/Projects/.../bin/rg"
}
]
}
}
}
```
## Policy knobs
### `tools.exec.mode`
`tools.exec.mode` is the preferred normalized policy surface for host exec:
| Value | Behavior |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deny` | Block host exec. |
| `allowlist` | Run only allowlisted commands without asking. |
| `ask` | Use allowlist policy and ask on misses. |
| `auto` | Use allowlist policy, run deterministic matches directly, and send approval misses through OpenClaw's native auto reviewer before falling back to a human approval route. |
| `full` | Run host exec without approval prompts. |
Legacy `tools.exec.security` / `tools.exec.ask` remain supported and still
apply wherever `mode` is unset at that scope.
### `exec.security`
<ParamField path="security" type='"deny" | "allowlist" | "full"'>
- `deny` - block all host exec requests.
- `allowlist` - allow only allowlisted commands.
- `full` - allow everything (equivalent to elevated).
Default is `full` for gateway/node hosts; a `sandbox` host defaults to
`deny` instead.
</ParamField>
### `exec.ask`
<ParamField path="ask" type='"off" | "on-miss" | "always"'>
Configured ask policy for host exec. Controls the baseline approval
prompt behavior from `tools.exec.ask` and host approvals defaults.
Default is `off`. The per-call `ask` tool parameter (see
[Exec tool](/tools/exec#parameters)) can only harden that baseline, and
channel-origin model calls ignore it when the effective host ask is `off`.
- `off` - never prompt.
- `on-miss` - prompt only when the allowlist does not match.
- `always` - prompt on every command. `allow-always` durable trust does **not** suppress prompts when effective ask mode is `always`.
</ParamField>
### `askFallback`
<ParamField path="askFallback" type='"deny" | "allowlist" | "full"'>
Resolution when a prompt is required but no UI is reachable (or the
prompt times out). Defaults to `deny` when omitted.
- `deny` - block.
- `allowlist` - allow only if allowlist matches.
- `full` - allow.
</ParamField>
### `tools.exec.strictInlineEval`
<ParamField path="strictInlineEval" type="boolean">
When `true`, treats inline code-eval forms as approval-only even if the
interpreter binary itself is allowlisted. Defense-in-depth for
interpreter loaders that do not map cleanly to one stable file operand.
</ParamField>
Examples that strict mode catches: `python -c`, `node -e`/`--eval`/`-p`,
`ruby -e`, `perl -e`/`-E`, `php -r`, `lua -e`, `osascript -e` (also `awk`,
`sed`, `make`, `find -exec`, and `xargs` inline forms).
In strict mode these commands still need explicit approval, and
`allow-always` does not persist new allowlist entries for them
automatically.
### `tools.exec.commandHighlighting`
<ParamField path="commandHighlighting" type="boolean" default="false">
Presentation only: when enabled, OpenClaw may attach parser-derived
command spans so Web approval prompts can highlight command tokens. Does
**not** change `security`, `ask`, allowlist matching, strict inline-eval
behavior, approval forwarding, or command execution.
</ParamField>
Set globally under `tools.exec.commandHighlighting` or per agent under
`agents.list[].tools.exec.commandHighlighting`.
## YOLO mode (no-approval)
To run host exec without approval prompts, open **both** policy layers:
requested exec policy in OpenClaw config (`tools.exec.*`) **and**
host-local approvals policy in the execution host approvals file.
Omitted `askFallback` defaults to `deny`. Set host `askFallback` to `full`
explicitly when a no-UI approval prompt should fall back to allow.
| Layer | YOLO setting |
| --------------------- | -------------------------- |
| `tools.exec.security` | `full` on `gateway`/`node` |
| `tools.exec.ask` | `off` |
| Host `askFallback` | `full` |
<Warning>
**Important distinctions:**
- `tools.exec.host=auto` chooses **where** exec runs: sandbox when available, otherwise gateway.
- YOLO chooses **how** host exec is approved: `security=full` plus `ask=off`.
- YOLO does **not** add a separate heuristic command-obfuscation approval gate or script-preflight rejection layer on top of the configured host exec policy.
- `auto` does not make gateway routing a free override from a sandboxed session. A per-call `host=node` request is allowed from `auto`; `host=gateway` is only allowed from `auto` when no sandbox runtime is active. For a stable non-auto default, set `tools.exec.host` or use `/exec host=...` explicitly.
</Warning>
CLI-backed providers that expose their own noninteractive permission mode
can follow this policy. Claude CLI adds
`--permission-mode bypassPermissions` when OpenClaw's effective exec
policy is YOLO. For OpenClaw-managed Claude live sessions, OpenClaw's
effective exec policy is authoritative over Claude's native permission mode:
YOLO normalizes live launches to `--permission-mode bypassPermissions`, and
restrictive effective exec policy normalizes live launches to
`--permission-mode default`, even if raw Claude backend args specify another
mode.
If you want a more conservative setup, tighten OpenClaw exec policy back to
`allowlist` / `on-miss` or `deny`.
### Persistent gateway-host "never prompt" setup
<Steps>
<Step title="Set the requested config policy">
```bash
openclaw config set tools.exec.host gateway
openclaw config set tools.exec.security full
openclaw config set tools.exec.ask off
openclaw gateway restart
```
</Step>
<Step title="Match the host approvals file">
```bash
openclaw approvals set --stdin <<'EOF'
{
version: 1,
defaults: {
security: "full",
ask: "off",
askFallback: "full"
}
}
EOF
```
</Step>
</Steps>
### Local shortcut
```bash
openclaw exec-policy preset yolo
```
Updates both local `tools.exec.host/security/ask` and the local approvals
file defaults (including `askFallback: "full"`). It is intentionally
local-only. To change gateway-host or node-host approvals remotely, use
`openclaw approvals set --gateway` or `openclaw approvals set --node
<id|name|ip>`.
Other built-in presets: `cautious` (`host=gateway`, `security=allowlist`,
`ask=on-miss`, `askFallback=deny`) and `deny-all` (`host=gateway`,
`security=deny`, `ask=off`, `askFallback=deny`). Apply the same way:
`openclaw exec-policy preset cautious`.
To set individual fields instead of a full preset, use
`openclaw exec-policy set --host <auto|sandbox|gateway|node> --security
<deny|allowlist|full> --ask <off|on-miss|always> --ask-fallback
<deny|allowlist|full>` with any subset of those flags.
### Node host
Apply the same approvals file on the node instead:
```bash
openclaw approvals set --node <id|name|ip> --stdin <<'EOF'
{
version: 1,
defaults: {
security: "full",
ask: "off",
askFallback: "full"
}
}
EOF
```
<Note>
**Local-only limitations:**
- `openclaw exec-policy` does not synchronize node approvals.
- `openclaw exec-policy set --host node` is rejected.
- Node exec approvals are fetched from the node at runtime, so node-targeted updates must use `openclaw approvals --node ...`.
</Note>
### Session-only shortcut
- `/exec security=full ask=off` changes only the current session.
- `/elevated full` is a break-glass shortcut that skips exec approvals only
when both the requested policy and the host approvals file resolve to
`security: "full"` and `ask: "off"`. A stricter host file, such as `ask:
"always"`, still prompts.
If the host approvals file stays stricter than config, the stricter host
policy still wins.
## Allowlist (per agent)
Allowlists are **per agent**. If multiple agents exist, switch which agent
you are editing in the macOS app. Patterns are glob matches.
Patterns can be resolved binary path globs or bare command-name globs.
Bare names match only commands invoked through `PATH`, so `rg` can match
`/opt/homebrew/bin/rg` when the command is `rg`, but **not** `./rg` or
`/tmp/rg`. Use a path glob to trust one specific binary location.
Legacy `agents.default` entries are migrated to `agents.main` on load.
Shell chains such as `echo ok && pwd` still need every top-level segment
to satisfy allowlist rules.
Examples:
- `rg`
- `~/Projects/**/bin/peekaboo`
- `~/.local/bin/*`
- `/opt/homebrew/bin/rg`
### Restricting arguments with argPattern
Add `argPattern` when an allowlist entry should match a binary and a
specific argument shape. OpenClaw evaluates the regular expression against
the parsed command arguments, excluding the executable token (`argv[0]`).
For hand-authored entries, arguments are joined with a single space, so
anchor the pattern when you need an exact match.
```json
{
"version": 1,
"agents": {
"main": {
"allowlist": [
{
"pattern": "python3",
"argPattern": "^safe\\.py$"
}
]
}
}
}
```
That entry allows `python3 safe.py`; `python3 other.py` is an allowlist
miss. If a path-only entry for the same binary is also present, unmatched
arguments can still fall back to that path-only entry. Omit the path-only
entry when the goal is to restrict the binary to the declared arguments.
Entries saved by approval flows use an internal separator format for exact
argv matching. Prefer the UI or approval flow to regenerate those entries
instead of hand-editing the encoded value. If OpenClaw cannot parse argv
for a command segment, entries with `argPattern` do not match.
Each allowlist entry supports:
| Field | Meaning |
| ------------------ | ------------------------------------------------------------- |
| `pattern` | Resolved binary path glob or bare command-name glob |
| `argPattern` | Optional argv regex; omitted entries are path-only |
| `id` | Stable UUID used for UI identity |
| `source` | Entry source, such as `allow-always` |
| `commandText` | Command text captured when an approval flow created the entry |
| `lastUsedAt` | Last-used timestamp |
| `lastUsedCommand` | Last command that matched |
| `lastResolvedPath` | Last resolved binary path |
## Auto-allow skill CLIs
When **Auto-allow skill CLIs** (`autoAllowSkills`) 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.
<Warning>
- This is an **implicit convenience allowlist**, separate from manual path allowlist entries.
- It is intended for trusted operator environments where Gateway and node are in the same trust boundary.
- If you require strict explicit trust, keep `autoAllowSkills: false` and use manual path allowlist entries only.
</Warning>
## Safe bins and approval forwarding
For safe bins (the stdin-only fast-path), interpreter binding details, and
how to forward approval prompts to Slack/Discord/Telegram (or run them as
native approval clients), see
[Exec approvals - advanced](/tools/exec-approvals-advanced).
## 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 approvals file directly.
CLI: `openclaw approvals` supports gateway or node editing - see
[Approvals CLI](/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:
- The node exec path prepares one canonical plan up front.
- The approval record stores that plan and its binding metadata.
- Once approved, the final forwarded `system.run` call reuses the stored plan instead of trusting later caller edits.
- If the caller changes `command`, `rawCommand`, `cwd`, `agentId`, or `sessionKey` after the approval request was created, the gateway rejects the forwarded run as an approval mismatch.
## System events and denials
Exec lifecycle posts an `Exec finished` system message to the agent's
session after the node reports completion. OpenClaw can also emit an
in-progress notice once an approval is granted, after
`tools.exec.approvalRunningNoticeMs` elapses (default `10000`, `0` disables
it). Denied exec approvals are terminal for the host command: the command
does not run.
- For main-agent async approvals with an originating session, OpenClaw
posts the denial back into that session as an internal followup so the
agent can stop waiting on the async command and avoid a missing-result
repair.
- If there is no session or the session cannot be resumed, OpenClaw can
still report a concise denial to the operator or direct chat route.
- Denials for subagent and cron sessions are not posted back into that
session.
Gateway-host exec approvals emit the same completion lifecycle event.
Approval-gated execs reuse the approval id to correlate the pending
request with its completion/denial message (`Exec finished (gateway
id=...)` / `Exec denied (gateway id=...)`).
## Implications
- **`full`** is powerful; prefer allowlists when possible.
- **`ask`** keeps you in the loop while still allowing fast approvals.
- Per-agent allowlists prevent one agent's approvals from leaking into others.
- Approvals only apply to host exec requests from **authorized senders**. Unauthorized senders cannot issue `/exec`.
- `/exec security=full` is a session-level convenience for authorized operators and skips approvals by design. To hard-block host exec, set approvals security to `deny` or deny the `exec` tool via tool policy.
## Related
<CardGroup cols={2}>
<Card title="Exec approvals - advanced" href="/tools/exec-approvals-advanced" icon="gear">
Safe bins, interpreter binding, and approval forwarding to chat.
</Card>
<Card title="Exec tool" href="/tools/exec" icon="terminal">
Shell command execution tool.
</Card>
<Card title="Elevated mode" href="/tools/elevated" icon="shield-exclamation">
Break-glass path that also skips approvals.
</Card>
<Card title="Sandboxing" href="/gateway/sandboxing" icon="box">
Sandbox modes and workspace access.
</Card>
<Card title="Security" href="/gateway/security" icon="lock">
Security model and hardening.
</Card>
<Card title="Sandbox vs tool policy vs elevated" href="/gateway/sandbox-vs-tool-policy-vs-elevated" icon="sliders">
When to reach for each control.
</Card>
<Card title="Skills" href="/tools/skills" icon="sparkles">
Skill-backed auto-allow behavior.
</Card>
</CardGroup>

261
docs/tools/exec.md Normal file
View File

@@ -0,0 +1,261 @@
---
summary: "Exec tool usage, stdin modes, and TTY support"
read_when:
- Using or modifying the exec tool
- Debugging stdin or TTY behavior
title: "Exec tool"
---
Run shell commands in the workspace. `exec` is a mutating shell surface: commands can create, edit, or delete files wherever the selected host or sandbox filesystem permits. Disabling OpenClaw filesystem tools such as `write`, `edit`, or `apply_patch` does not make `exec` read-only.
Supports foreground and 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
<ParamField path="command" type="string" required>
Shell command to run.
</ParamField>
<ParamField path="workdir" type="string" default="cwd">
Working directory for the command.
</ParamField>
<ParamField path="env" type="object">
Key/value environment overrides merged on top of the inherited environment.
</ParamField>
<ParamField path="yieldMs" type="number" default="10000">
Auto-background the command after this delay (ms).
</ParamField>
<ParamField path="background" type="boolean" default="false">
Background the command immediately instead of waiting for `yieldMs`.
</ParamField>
<ParamField path="timeout" type="number" default="tools.exec.timeoutSec">
Override the configured exec timeout for this call, in seconds. Applies to foreground, background, `yieldMs`, gateway, sandbox, and node `system.run` execution. `timeout: 0` disables the exec process timeout for that call.
</ParamField>
<ParamField path="pty" type="boolean" default="false">
Run in a pseudo-terminal when available. Use for TTY-only CLIs, coding agents, and terminal UIs.
</ParamField>
<ParamField path="host" type="'auto' | 'sandbox' | 'gateway' | 'node'" default="auto">
Where to execute. `auto` resolves to `sandbox` when a sandbox runtime is active and `gateway` otherwise.
</ParamField>
<ParamField path="security" type="'deny' | 'allowlist' | 'full'">
Ignored for normal tool calls. `gateway`/`node` security is controlled by `tools.exec.security` and the host approvals file; elevated mode can force `security=full` only when the operator explicitly grants elevated access.
</ParamField>
<ParamField path="ask" type="'off' | 'on-miss' | 'always'">
The baseline ask mode comes from `tools.exec.ask` and host approvals. For channel-origin model calls, per-call `ask` is ignored when the effective host ask is `off`; otherwise it can only harden to a stricter mode. Trusted internal/API callers that construct exec tools with an explicit `ask` value are unchanged.
</ParamField>
<ParamField path="node" type="string">
Node id/name when `host=node`.
</ParamField>
<ParamField path="elevated" type="boolean" default="false">
Request elevated mode: escape the sandbox onto the configured host path. `security=full` is forced only when elevated resolves to `full`.
</ParamField>
Notes:
- `host` only accepts `auto`, `sandbox`, `gateway`, or `node`. It is not a hostname selector; hostname-like values are rejected before the command runs.
- Per-call `host=node` is allowed from `auto`; per-call `host=gateway` is only allowed when no sandbox runtime is active.
- With no extra config, `host=auto` still "just works": no sandbox means it resolves to `gateway`; a live sandbox means it stays in the sandbox.
- `elevated` escapes the sandbox onto the configured host path: `gateway` by default, or `node` when `tools.exec.host=node` (or the session default is `host=node`). It is only available when elevated access is enabled for the current session/provider.
- `gateway`/`node` approvals are controlled by the host approvals file.
- `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.
- `exec host=node` is the only shell-execution path for nodes; the legacy `nodes.run` wrapper has been removed.
- On non-Windows hosts, exec uses `SHELL` when set; if `SHELL` is `fish`, it prefers `bash` (or `sh`) from `PATH` to avoid fish-incompatible bashisms, 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.
- On non-Windows gateway hosts, bash and zsh exec commands use a startup snapshot. OpenClaw captures sourceable aliases/functions and a small safe environment set from shell startup files into `$OPENCLAW_STATE_DIR/cache/shell-snapshots/`, then sources that snapshot before each exec command. Secret-looking variables are excluded; sandbox and node exec do not use this snapshot. Set `OPENCLAW_EXEC_SHELL_SNAPSHOT=0` in the Gateway process environment to disable this snapshot path.
- 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.
- For channel-origin runs, OpenClaw also exposes a narrow sender/chat identity JSON payload in `OPENCLAW_CHANNEL_CONTEXT` when the channel provided those ids.
- `exec` cannot run `openclaw channels login` or `/approve` shell commands: `openclaw channels login` is an interactive channel-auth flow, and `/approve` needs to go through the approval command handler, not a shell. Run channel login in a terminal on the gateway host, or use a channel-specific login agent tool when one exists (for example `whatsapp_login`).
- Important: sandboxing is **off by default**. If sandboxing is off, implicit `host=auto` resolves to `gateway`. Explicit `host=sandbox` still 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. Preflight also skips entirely when `host=gateway` and the effective policy is `security=full` with `ask=off`.
- For long-running work that starts now, start it once and rely on automatic completion wake when it is enabled and the command emits output or fails. Use `process` for logs, status, input, or intervention; do not emulate scheduling with sleep loops, timeout loops, or repeated polling.
- For work that should happen later or on a schedule, use cron instead of `exec` sleep/delay patterns.
## Config
| Key | Default | Notes |
| ------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tools.exec.timeoutSec` | `1800` | Default per-command exec timeout in seconds. Per-call `timeout` overrides it; per-call `timeout: 0` disables the exec process timeout. |
| `tools.exec.host` | `auto` | Resolves to `sandbox` when a sandbox runtime is active, `gateway` otherwise. |
| `tools.exec.security` | `deny` for sandbox, `full` for gateway/node when unset | |
| `tools.exec.ask` | `off` | |
| `tools.exec.mode` | unset | Normalized policy knob. See [Modes](#modes) below. Cannot be combined with `tools.exec.security`/`tools.exec.ask`. |
| `tools.exec.node` | unset | |
| `tools.exec.notifyOnExit` | `true` | When true, backgrounded exec sessions enqueue a system event and request a heartbeat on exit. |
| `tools.exec.approvalRunningNoticeMs` | `10000` | Emit a single "running" notice when an approval-gated exec runs longer than this (`0` disables). |
| `tools.exec.strictInlineEval` | `false` | See [Inline eval](#inline-eval-strictinlineeval). |
| `tools.exec.commandHighlighting` | `false` | When true, approval prompts can highlight parser-derived command spans in the command text. Set globally or per agent; does not change approval policy. |
| `tools.exec.pathPrepend` | unset | List of directories to prepend to `PATH` for exec runs (gateway + sandbox only). |
| `tools.exec.safeBins` | unset | Stdin-only safe binaries that can run without explicit allowlist entries. See [Safe bins](/tools/exec-approvals-advanced#safe-bins-stdin-only). |
| `tools.exec.safeBinTrustedDirs` | `/bin`, `/usr/bin` | Additional explicit directories trusted for `safeBins` path checks. `PATH` entries are never auto-trusted. |
| `tools.exec.safeBinProfiles` | unset | Optional custom argv policy per safe bin (`minPositional`, `maxPositional`, `allowedValueFlags`, `deniedFlags`). |
No-approval host exec is the default for gateway and node (`security=full`, `ask=off`) — this comes from the host-policy defaults, not from `host=auto`. If you want approvals/allowlist behavior, tighten both `tools.exec.*` and the host approvals file; see [Exec approvals](/tools/exec-approvals#yolo-mode-no-approval). To force gateway or node routing regardless of sandbox state, set `tools.exec.host` or use `/exec host=...`.
Example:
```json5
{
tools: {
exec: {
pathPrepend: ["~/bin", "/opt/oss/bin"],
},
},
}
```
### Modes
`tools.exec.mode` is the normalized policy knob. Setting it derives `security`/`ask` and cannot be combined with explicit `tools.exec.security`/`tools.exec.ask`.
| Mode | security | ask | Behavior |
| ----------- | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `deny` | `deny` | `off` | Exec is denied. |
| `allowlist` | `allowlist` | `off` | Only allowlisted/safe-bin commands run; nothing else is asked. |
| `ask` | `allowlist` | `on-miss` | Allowlist matches run directly; everything else asks a human. |
| `auto` | `allowlist` | `on-miss` | Allowlist/safe-bin matches run directly; everything else routes through OpenClaw's native auto reviewer before asking a human. |
| `full` | `full` | `off` | No approval gate. |
`ask`/`ask=always` still asks a human every time regardless of mode.
### Inline eval (`strictInlineEval`)
When `tools.exec.strictInlineEval` is `true`, inline interpreter-eval forms require reviewer or explicit approval: `python -c`, `node -e`, `ruby -e`, `perl -e`, `php -r`, `lua -e`, `osascript -e`, and similar forms across other supported interpreters and command carriers (`awk`, `find -exec`, `make`, `sed`, `xargs`, and more). In `mode=auto`, the normal exec approval path may let the native auto reviewer allow a clearly low-risk one-off command; direct node-host `system.run` calls still require an explicit approval because they cannot hand the command to a human approval route. If the reviewer asks, the request goes to a human. `allow-always` can still persist benign interpreter/script invocations, but inline-eval forms do not become durable allow rules.
### PATH handling
- `host=gateway`: merges your login-shell `PATH` into the exec environment. `env.PATH` overrides are rejected for host execution. The daemon itself still runs with a minimal `PATH`:
- macOS: `/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin`, `/bin`
- Linux: `/usr/local/bin`, `/usr/bin`, `/bin`
- To prevent user shell configuration (like `~/.zshenv` or `/etc/zshenv`) from overriding priority paths during startup, `tools.exec.pathPrepend` entries are securely prepended to the final `PATH` inside the shell command right before execution.
- `host=sandbox`: runs `sh -lc` (login shell) inside the container, so `/etc/profile` may reset `PATH`. OpenClaw prepends `env.PATH` after profile sourcing via an internal env var (no shell interpolation); `tools.exec.pathPrepend` applies here too.
- `host=node`: only non-blocked env overrides you pass are sent to the node. `env.PATH` overrides are rejected for host execution and ignored by node hosts. If you need additional PATH entries on a node, configure the node host service environment (systemd/launchd) or install tools in standard locations.
Per-agent node binding (use the agent list index in config):
```bash
openclaw config get agents.list
openclaw config set 'agents.list[0].tools.exec.node' "node-id-or-name"
```
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:
```text
/exec host=auto security=allowlist ask=on-miss node=mac-1
```
`/exec` is only honored for **authorized senders** (channel allowlists/pairing plus `commands.useAccessGroups`). It updates **session state only** and does not write config. Authorized external channel senders may set these session defaults. Internal gateway/webchat clients need `operator.admin` to persist them.
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](/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 command progress and completion system events only for approved runs (`Exec running` / `Exec finished`). Denied or timed-out approvals are terminal and do not wake the agent session with a denial system event.
On channels with native approval cards/buttons, the agent should rely on that native UI first and only include a manual `/approve` command when the tool result explicitly says chat approvals are unavailable or manual approval is the only path.
## Allowlist + safe bins
Manual allowlist enforcement matches resolved binary path globs and bare command-name globs. Bare names match only commands invoked through PATH, so `rg` can match `/opt/homebrew/bin/rg` when the command is `rg`, but not `./rg` or `/tmp/rg`.
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. Durable `allow-always` trust does not bypass that rule: a chained command still requires every top-level segment to match.
`autoAllowSkills` is a separate convenience path in exec approvals, not the same as manual path allowlist entries. For strict explicit trust, keep `autoAllowSkills` disabled.
Use the two controls for different jobs:
- `tools.exec.safeBins`: small, stdin-only stream filters.
- `tools.exec.safeBinTrustedDirs`: explicit extra trusted directories for safe-bin executable paths.
- `tools.exec.safeBinProfiles`: explicit argv policy for custom safe bins.
- allowlist: explicit trust for executable paths.
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` (`jq` supports broad programs and builtins, so prefer explicit allowlist entries or approval-gated runs instead). If you explicitly allowlist interpreters, enable `tools.exec.strictInlineEval` so inline code-eval forms still require reviewer or explicit approval.
For full policy details and examples, see [Exec approvals](/tools/exec-approvals-advanced#safe-bins-stdin-only) and [Safe bins versus allowlist](/tools/exec-approvals-advanced#safe-bins-versus-allowlist).
## Examples
Foreground:
```json
{ "tool": "exec", "command": "ls -la" }
```
Background + poll:
```json
{"tool":"exec","command":"npm run build","yieldMs":1000}
{"tool":"process","action":"poll","sessionId":"<id>"}
```
Polling is for on-demand status, not waiting loops. If automatic completion wake is enabled, the command can wake the session when it emits output or fails.
Send keys (tmux-style):
```json
{"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):
```json
{ "tool": "process", "action": "submit", "sessionId": "<id>" }
```
Paste (bracketed by default):
```json
{ "tool": "process", "action": "paste", "sessionId": "<id>", "text": "line1\nline2\n" }
```
## apply_patch
`apply_patch` is a subtool of `exec` for structured multi-file edits. It is enabled by default and available to any model provider; `allowModels` can restrict it. Use config only when you want to disable it or restrict it to specific models:
```json5
{
tools: {
exec: {
applyPatch: { workspaceOnly: true, allowModels: ["gpt-5.5"] },
},
},
}
```
Notes:
- Tool policy still applies; `allow: ["write"]` implicitly allows `apply_patch`.
- `deny: ["write"]` does not deny `apply_patch`; deny `apply_patch` explicitly or use `deny: ["group:fs"]` when patch writes should also be blocked.
- Config lives under `tools.exec.applyPatch`.
- `tools.exec.applyPatch.enabled` defaults to `true`; set it to `false` to disable the tool.
- `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.
- `tools.exec.applyPatch.allowModels` is an optional allowlist of model ids (raw, like `gpt-5.4`, or full, like `openai/gpt-5.4`). When set, only matching models get the tool; when unset, all models get it.
## Related
- [Exec Approvals](/tools/exec-approvals) — approval gates for shell commands
- [Sandboxing](/gateway/sandboxing) — running commands in sandboxed environments
- [Background Process](/gateway/background-process) — long-running exec and process tool
- [Security](/gateway/security) — tool policy and elevated access

165
docs/tools/firecrawl.md Normal file
View File

@@ -0,0 +1,165 @@
---
summary: "Firecrawl search, scrape, and web_fetch fallback"
read_when:
- You want Firecrawl-backed web extraction
- You want keyless Firecrawl web_fetch
- You need a Firecrawl API key for search or higher limits
- You want Firecrawl as a web_search provider
- You want anti-bot extraction for web_fetch
title: "Firecrawl"
---
OpenClaw can use **Firecrawl** in three ways:
- as the `web_search` provider
- as explicit plugin tools: `firecrawl_search` and `firecrawl_scrape`
- as a fallback extractor for `web_fetch`
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.
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/firecrawl-plugin
openclaw gateway restart
```
## Keyless web_fetch and API keys
The explicitly selected hosted Firecrawl `web_fetch` fallback supports starter access without an API key. Add `FIRECRAWL_API_KEY` in the gateway environment or configure it when you need higher limits. Firecrawl `web_search` and `firecrawl_scrape` require an API key.
## Configure Firecrawl search
```json5
{
tools: {
web: {
search: {
provider: "firecrawl",
},
},
},
plugins: {
entries: {
firecrawl: {
enabled: true,
config: {
webSearch: {
apiKey: "FIRECRAWL_API_KEY_HERE",
baseUrl: "https://api.firecrawl.dev",
},
},
},
},
},
}
```
Notes:
- Choosing Firecrawl in onboarding or `openclaw configure --section web` enables the installed Firecrawl plugin automatically.
- `web_search` with Firecrawl supports `query` and `count`.
- For Firecrawl-specific controls like `sources`, `categories`, or result scraping, use `firecrawl_search`.
- `baseUrl` defaults to hosted Firecrawl at `https://api.firecrawl.dev`. Self-hosted overrides are allowed only for private/internal endpoints; HTTP is accepted only for those private targets.
- `FIRECRAWL_BASE_URL` is the shared env fallback for Firecrawl search and scrape base URLs.
- Firecrawl search requests default to a 30-second timeout; `firecrawl_search`'s `timeoutSeconds` parameter overrides it per call.
## Configure Firecrawl web_fetch fallback
```json5
{
tools: {
web: {
fetch: {
provider: "firecrawl", // explicit selection enables keyless fallback
},
},
},
plugins: {
entries: {
firecrawl: {
enabled: true,
config: {
webFetch: {
baseUrl: "https://api.firecrawl.dev",
onlyMainContent: true,
maxAgeMs: 172800000,
timeoutSeconds: 60,
},
},
},
},
},
}
```
Notes:
- The explicitly selected Firecrawl `web_fetch` fallback works without an API key. When configured, OpenClaw sends `plugins.entries.firecrawl.config.webFetch.apiKey` or `FIRECRAWL_API_KEY` for higher limits.
- Choosing Firecrawl during onboarding or `openclaw configure --section web` enables the plugin and selects Firecrawl for `web_fetch` unless another fetch provider is already configured.
- `firecrawl_scrape` requires an API key.
- `maxAgeMs` controls how old cached results can be (ms). Default is 172,800,000 ms (2 days).
- `onlyMainContent` defaults to `true`; `timeoutSeconds` defaults to 60.
- Legacy `tools.web.fetch.firecrawl.*` and `tools.web.search.firecrawl.*` config is auto-migrated by `openclaw doctor --fix`.
- Firecrawl scrape/base URL overrides follow the same hosted/private rule as search: public hosted traffic uses `https://api.firecrawl.dev`; self-hosted overrides must resolve to private/internal endpoints.
- `firecrawl_scrape` rejects obvious private, loopback, metadata, and non-HTTP(S) target URLs before forwarding them to Firecrawl, matching the `web_fetch` target-safety contract for explicit Firecrawl scrape calls.
`firecrawl_scrape` reuses the same `plugins.entries.firecrawl.config.webFetch.*` settings and env vars, including its required API key.
### Self-hosted Firecrawl
Set `plugins.entries.firecrawl.config.webSearch.baseUrl`, `plugins.entries.firecrawl.config.webFetch.baseUrl`, or `FIRECRAWL_BASE_URL` when you run Firecrawl yourself. OpenClaw accepts `http://` only for loopback, private-network, `.local`, `.internal`, or `.localhost` targets. Public custom hosts are rejected so Firecrawl API keys are not sent to arbitrary endpoints by accident.
## Firecrawl plugin tools
### `firecrawl_search`
Use this when you want Firecrawl-specific search controls instead of generic `web_search`.
Parameters:
- `query`
- `count`
- `sources`
- `categories`
- `scrapeResults`
- `timeoutSeconds`
### `firecrawl_scrape`
Use this for JS-heavy or bot-protected pages where plain `web_fetch` is weak.
Parameters:
- `url`
- `extractMode`
- `maxChars`
- `onlyMainContent`
- `maxAgeMs`
- `proxy`
- `storeInCache`
- `timeoutSeconds`
## Stealth / bot circumvention
`firecrawl_scrape` and the `web_fetch` Firecrawl fallback default to `proxy: "auto"` plus `storeInCache: true` unless the caller overrides those parameters. `firecrawl_search` and the `web_search` Firecrawl provider have no `proxy`/`storeInCache` controls; stealth proxy mode only applies to scrape/fetch requests.
Firecrawl's `proxy` mode controls bot circumvention (`basic`, `stealth`, or `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. Configured fetch provider, such as Firecrawl (when selected, or auto-detected from configured credentials)
3. Basic HTML cleanup (last fallback)
The selection knob is `tools.web.fetch.provider`. If you omit it, OpenClaw auto-detects the first ready web-fetch provider from available credentials. The official Firecrawl plugin provides that fallback.
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [Web Fetch](/tools/web-fetch) -- web_fetch tool with Firecrawl fallback
- [Tavily](/tools/tavily) -- search + extract tools

114
docs/tools/gemini-search.md Normal file
View File

@@ -0,0 +1,114 @@
---
summary: "Gemini web search with Google Search grounding"
read_when:
- You want to use Gemini for web_search
- You need a GEMINI_API_KEY or models.providers.google.apiKey
- You want Google Search grounding
title: "Gemini search"
---
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
<Steps>
<Step title="Create a key">
Go to [Google AI Studio](https://aistudio.google.com/apikey) and create an
API key.
</Step>
<Step title="Store the key">
Set `GEMINI_API_KEY` in the Gateway environment, reuse
`models.providers.google.apiKey`, or configure a dedicated web-search key via:
```bash
openclaw configure --section web
```
</Step>
</Steps>
## Config
```json5
{
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: "AIza...", // optional if GEMINI_API_KEY or models.providers.google.apiKey is set
baseUrl: "https://generativelanguage.googleapis.com/v1beta", // optional; falls back to models.providers.google.baseUrl
model: "gemini-2.5-flash", // default
},
},
},
},
},
tools: {
web: {
search: {
provider: "gemini",
},
},
},
}
```
**Credential precedence:** Gemini web search uses
`plugins.entries.google.config.webSearch.apiKey` first, then `GEMINI_API_KEY`,
then `models.providers.google.apiKey`. For base URLs, the dedicated
`plugins.entries.google.config.webSearch.baseUrl` wins before
`models.providers.google.baseUrl`.
For a gateway install, put env keys 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.
- Citation URLs from Gemini grounding are automatically resolved from Google
redirect URLs to direct URLs via a HEAD request through OpenClaw's SSRF-guarded
fetch path (redirect following, http/https validation).
- Redirect resolution uses strict SSRF defaults, so redirects to
private/internal targets are blocked.
## Supported parameters
Gemini search supports `query`, `freshness`, `date_after`, and `date_before`.
`count` is accepted for shared `web_search` compatibility, but Gemini grounding
still returns one synthesized answer with citations rather than an N-result
list.
`freshness` accepts `day`, `week`, `month`, `year`, and the shared shortcuts
`pd`, `pw`, `pm`, and `py`. `day`/`pd` adds a recency instruction to the Gemini
query instead of a hard 24-hour range. `week`, `month`, `year`, and explicit
`date_after`/`date_before` ranges set Gemini Google Search grounding's
`timeRangeFilter`. `country`, `language`, 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`.
## Base URL overrides
Set `plugins.entries.google.config.webSearch.baseUrl` when Gemini web search
must route through an operator proxy or custom Gemini-compatible endpoint. If
that is unset, Gemini web search reuses `models.providers.google.baseUrl`. A plain
`https://generativelanguage.googleapis.com` value is normalized to
`https://generativelanguage.googleapis.com/v1beta`; custom proxy paths are kept
as provided after trimming trailing slashes.
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [Brave Search](/tools/brave-search) -- structured results with snippets
- [Perplexity Search](/tools/perplexity-search) -- structured results + content extraction

192
docs/tools/goal.md Normal file
View File

@@ -0,0 +1,192 @@
---
doc-schema-version: 1
summary: "Session goals: durable per-session objectives, /goal controls, model goal tools, token budgets, and TUI status"
read_when:
- You want OpenClaw to keep one objective visible across a long session
- You need to pause, resume, block, complete, or clear a session goal
- You want to understand the get_goal, create_goal, and update_goal tools
- You want to see how goals appear in the TUI
title: "Goal"
---
# Goal
A **goal** is one durable objective attached to the current OpenClaw session.
It gives the agent and the operator a shared target for long-running work,
without turning that target into a background task, reminder, cron job, or
standing order.
Goals are session state: they move with the session key, survive process
restarts, and appear in `/goal`, the model-facing goal tools, and the TUI
footer.
## Quick start
```text
/goal start get CI green for PR 87469 and push the fix
/goal
/goal pause waiting for CI
/goal resume
/goal complete pushed and verified
/goal clear
```
`start` is optional: `/goal get CI green for PR 87469` also creates a goal,
since any text after `/goal` that is not a known action word is treated as a
new objective.
## What goals are for
Use a goal when a session has a concrete outcome that should stay visible
across many turns:
- A PR closeout: fix, verify, autoreview, push, and open or update the PR.
- A debug run: reproduce the bug, identify the owning surface, patch, and
prove the fix.
- A docs pass: read the relevant docs, write the new page, cross-link it, and
verify the docs build.
- A maintenance task: inspect current state, make bounded changes, run the
right checks, and report what changed.
A goal is not a task queue. Use [Task Flow](/automation/taskflow),
[tasks](/automation/tasks), [cron jobs](/automation/cron-jobs), or
[standing orders](/automation/standing-orders) when work should run detached,
repeat on a schedule, fan out into managed sub-work, or persist as a policy.
## Command reference
`/goal` with no arguments prints the current goal summary:
```text
Goal
Status: active
Objective: get CI green for PR 87469 and push the fix
Tokens used: 12k
Token budget: 12k/50k
Commands: /goal pause, /goal complete, /goal clear
```
| Command | Effect |
| --------------------------------------------------- | ------------------------------------------------------------------------ |
| `/goal` or `/goal status` | Show the current goal. |
| `/goal start <objective>` | Create a new goal for the current session. |
| `/goal set <objective>`, `/goal create <objective>` | Aliases for `start`. |
| `/goal <objective>` | Also creates a new goal (any text that is not a recognized action word). |
| `/goal pause [note]` | Pause an active goal. |
| `/goal resume [note]` | Resume a paused, blocked, usage-limited, or budget-limited goal. |
| `/goal complete [note]` | Mark the goal achieved. |
| `/goal done [note]` | Alias for `complete`. |
| `/goal block [note]` | Mark the goal blocked. |
| `/goal blocked [note]` | Alias for `block`. |
| `/goal clear` | Remove the goal from the session. |
Only one goal can exist on a session at a time. Starting a second goal fails
with `Goal error: goal already exists` until the current one is cleared.
`/goal start` does not take a token-budget flag; a budget can only be set
through the model-facing `create_goal` tool.
## Statuses
- `active`: the session is pursuing the goal.
- `paused`: the operator paused the goal; `/goal resume` makes it active
again.
- `blocked`: the agent or operator reported a real blocker; `/goal resume`
makes it active again when new information or state is available.
- `budget_limited`: the configured token budget was reached; `/goal resume`
restarts pursuit from the same objective with a fresh budget window.
- `usage_limited`: reserved for a future usage-limit stop state; `/goal
resume` restarts pursuit the same way.
- `complete`: the goal was achieved. Complete goals are terminal; use `/goal
clear` before starting another goal.
`/new` and `/reset` clear the current session goal, since they intentionally
start fresh session context.
## Token budgets
Goals can have an optional positive token budget, set through the
`create_goal` tool's `token_budget` parameter. The budget is measured from the
session's fresh token count at goal-creation time. If the session only has a
stale or unknown token snapshot when the goal starts, OpenClaw waits for the
next fresh snapshot and uses that as the baseline, so tokens spent before the
goal existed are not charged to it.
When usage reaches the budget, the goal moves to `budget_limited`. This does
not delete the goal or erase the objective; it tells the operator and the
agent that the goal is no longer actively being pursued until it is resumed or
cleared. Resuming starts a new budget window at the current fresh token
count.
Token budgets are a session-goal guardrail, not a billing cap. Provider
quota, cost reporting, and context-window behavior still use the normal
OpenClaw usage and model controls.
## Model tools
OpenClaw exposes three goal tools to agent harnesses:
| Tool | Purpose |
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `get_goal` | Read the current session goal: status, objective, token usage, and token budget. |
| `create_goal` | Create a goal only when the user or system instructions explicitly request one. Fails if the session already has a goal. |
| `update_goal` | Mark the goal `complete` or `blocked`. |
The model cannot silently pause, resume, clear, or replace a goal. Those stay
operator/session controls through `/goal` and reset commands, so the agent
can report achievement or a genuine blocker without quietly moving the
target.
`update_goal` should mark a goal `complete` only when the objective is
actually achieved. It should mark a goal `blocked` only after the same
blocking condition recurs for at least three consecutive goal turns, not for
ordinary difficulty or missing polish.
## TUI
The TUI footer keeps the active session's goal visible next to the agent,
session, and model fields, before token/mode indicators.
Footer examples:
- `Pursuing goal (12k/50k)` for an active goal with a token budget.
- `Goal paused (/goal resume)` for a paused goal.
- `Goal blocked (/goal resume)` for a blocked goal.
- `Goal hit usage limits (/goal resume)` for a usage-limited goal.
- `Goal unmet (50k/50k)` for a budget-limited goal.
- `Goal achieved (42k)` for a completed goal.
The footer is intentionally compact. Use `/goal` for the full objective,
note, token budget, and available commands.
## Channel behavior
`/goal` works in command-capable OpenClaw sessions, including the TUI and
chat surfaces that permit text commands. Goal state is attached to the
session key, not the transport, so two surfaces sharing a session key see the
same goal.
Goal state is not a delivery directive: it does not force replies through a
channel, change queue behavior, approve tools, or schedule work.
## Troubleshooting
| Message | Meaning |
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `Goal error: goal already exists` | The session already has a goal. Use `/goal` to inspect it, `/goal complete` if done, or `/goal clear` before starting a different objective. |
| `Goal error: goal not found` | The session has no goal yet. Start one with `/goal start <objective>`. |
| `Goal error: goal is already complete` | The goal is terminal. Clear it before starting or resuming another objective. |
If token usage shows `0` or looks stale, the active session may not have a
fresh token snapshot yet. Usage refreshes as OpenClaw records session usage
and transcript-derived totals.
## Related
- [Slash commands](/tools/slash-commands)
- [TUI](/web/tui)
- [Session tool](/concepts/session-tool)
- [Compaction](/concepts/compaction)
- [Task Flow](/automation/taskflow)
- [Standing orders](/automation/standing-orders)

123
docs/tools/grok-search.md Normal file
View File

@@ -0,0 +1,123 @@
---
summary: "Grok web search via xAI web-grounded responses"
read_when:
- You want to use Grok for web_search
- You want to use xAI OAuth or an XAI_API_KEY for web search
title: "Grok search"
---
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.
Grok web search prefers an existing xAI OAuth sign-in when one is available.
If no OAuth profile exists, the same xAI API key also powers the built-in
`x_search` tool for X (formerly Twitter) post search and the `code_execution`
tool. Storing the key at `plugins.entries.xai.config.webSearch.apiKey` also
lets OpenClaw reuse it as a fallback for the bundled xAI model provider.
For post-level X metrics (reposts, replies, bookmarks, views), use
[`x_search`](/tools/web#x_search) with the exact post URL or status ID
instead of a broad search query.
## Onboarding and configure
Choosing **Grok** during `openclaw onboard` or `openclaw configure --section
web` lets OpenClaw reuse an existing xAI OAuth profile without prompting for
a separate web-search key. Without OAuth, it falls back to xAI API-key setup.
OpenClaw then offers a follow-up step to enable `x_search` with the same xAI
credential. That follow-up:
- only appears after you choose Grok for `web_search`
- is not a separate top-level web-search provider choice
- can optionally set the `x_search` model in the same flow
Skip it to enable or change `x_search` later in config.
## Sign in or get an API key
<Steps>
<Step title="Use xAI OAuth">
If you already signed in with xAI during onboarding or model auth, choose
Grok as the `web_search` provider. No separate API key is required:
```bash
openclaw onboard --auth-choice xai-oauth
openclaw config set tools.web.search.provider grok
```
</Step>
<Step title="Use an API key fallback">
Get an API key from [xAI](https://console.x.ai/) when OAuth is unavailable
or you intentionally want key-backed web-search config.
</Step>
<Step title="Store the key">
Set `XAI_API_KEY` in the Gateway environment, or configure via:
```bash
openclaw configure --section web
```
</Step>
</Steps>
## Config
```json5
{
plugins: {
entries: {
xai: {
config: {
webSearch: {
apiKey: "xai-...", // optional if xAI OAuth or XAI_API_KEY is available
baseUrl: "https://api.x.ai/v1", // optional Responses API proxy/base URL override
},
},
},
},
},
tools: {
web: {
search: {
provider: "grok",
},
},
},
}
```
**Credential alternatives:** `openclaw models auth login --provider xai
--method oauth`, `XAI_API_KEY` in the Gateway environment, or
`plugins.entries.xai.config.webSearch.apiKey`. For a gateway install, put env
vars 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 `query`. `count` is accepted for shared `web_search`
compatibility, but Grok always returns one synthesized answer with citations
rather than an N-result list. Provider-specific filters are not supported.
Grok defaults to a 60 second timeout because xAI Responses web-grounded
searches can run longer than the shared `web_search` default. Override it
with `tools.web.search.timeoutSeconds`.
## Base URL overrides
Set `plugins.entries.xai.config.webSearch.baseUrl` to route Grok web search
through an operator proxy or xAI-compatible Responses endpoint. OpenClaw
posts to `<baseUrl>/responses` after trimming trailing slashes. `x_search`
falls back to the same `webSearch.baseUrl` unless
`plugins.entries.xai.config.xSearch.baseUrl` is set.
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [x_search in Web Search](/tools/web#x_search) -- first-class X search via xAI
- [Gemini Search](/tools/gemini-search) -- AI-synthesized answers via Google grounding

View File

@@ -0,0 +1,554 @@
---
summary: "Generate and edit images via image_generate across OpenAI, Google, fal, Microsoft Foundry, MiniMax, ComfyUI, DeepInfra, OpenRouter, LiteLLM, xAI, Vydra"
read_when:
- Generating or editing images via the agent
- Configuring image-generation providers and models
- Understanding the image_generate tool parameters
title: "Image generation"
sidebarTitle: "Image generation"
---
The `image_generate` tool creates and edits images through your configured
providers. In chat sessions it runs asynchronously: OpenClaw records a
background task, returns the task id immediately, and wakes the agent when
the provider finishes. The completion agent follows the session's normal
visible-reply mode: automatic final reply delivery when configured, or
`message(action="send")` when the session requires the message tool. If the
requester session is inactive or its active wake fails, OpenClaw sends an
idempotent direct fallback with the generated images so the result is not
lost.
<Note>
The tool only appears when at least one image-generation provider is
available. If you do not see `image_generate` in your agent's tools,
configure `agents.defaults.imageGenerationModel`, set up a provider API key,
or sign in with OpenAI ChatGPT/Codex OAuth.
</Note>
## Quick start
<Steps>
<Step title="Configure auth">
Set an API key for at least one provider (for example `OPENAI_API_KEY`,
`GEMINI_API_KEY`, `OPENROUTER_API_KEY`) or sign in with OpenAI Codex OAuth.
</Step>
<Step title="Pick a default model (optional)">
```json5
{
agents: {
defaults: {
imageGenerationModel: {
primary: "openai/gpt-image-2",
timeoutMs: 180_000,
},
},
},
}
```
ChatGPT/Codex OAuth uses the same `openai/gpt-image-2` model ref. When an
`openai` OAuth profile is configured, OpenClaw routes image requests
through that OAuth profile instead of first trying `OPENAI_API_KEY`.
Explicit `models.providers.openai` config (API key, custom/Azure base URL)
opts back into the direct OpenAI Images API route.
</Step>
<Step title="Ask the agent">
_"Generate an image of a friendly robot mascot."_
The agent calls `image_generate` automatically. No tool allow-listing
needed - it is enabled by default when a provider is available. The tool
returns a background task id, then the completion agent sends the
generated attachment through the `message` tool when it is ready.
</Step>
</Steps>
<Warning>
For OpenAI-compatible LAN endpoints such as LocalAI, keep the custom
`models.providers.openai.baseUrl` and explicitly opt in with
`browser.ssrfPolicy.dangerouslyAllowPrivateNetwork: true`. Private and
internal image endpoints remain blocked by default.
</Warning>
## Common routes
| Goal | Model ref | Auth |
| ---------------------------------------------------- | -------------------------------------------------- | -------------------------------------- |
| OpenAI image generation with API billing | `openai/gpt-image-2` | `OPENAI_API_KEY` |
| OpenAI image generation with Codex subscription auth | `openai/gpt-image-2` | OpenAI ChatGPT/Codex OAuth |
| OpenAI transparent-background PNG/WebP | `openai/gpt-image-1.5` | `OPENAI_API_KEY` or OpenAI Codex OAuth |
| DeepInfra image generation | `deepinfra/black-forest-labs/FLUX-1-schnell` | `DEEPINFRA_API_KEY` |
| fal Krea 2 expressive/style-directed generation | `fal/krea/v2/medium/text-to-image` | `FAL_KEY` |
| OpenRouter image generation | `openrouter/google/gemini-3.1-flash-image-preview` | `OPENROUTER_API_KEY` |
| LiteLLM image generation | `litellm/gpt-image-2` | `LITELLM_API_KEY` |
| Microsoft Foundry MAI image generation | `microsoft-foundry/<deployment-name>` | `AZURE_OPENAI_API_KEY` or Entra ID |
| Google Gemini image generation | `google/gemini-3.1-flash-image-preview` | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
The same tool handles text-to-image and reference-image editing. Use `image`
for one reference or `images` for multiple. For Krea 2 models on fal, those
references are sent as style references instead of edit inputs.
Provider-supported output hints such as `quality`, `outputFormat`, and
`background` are forwarded when available and reported as ignored when a
provider does not declare support. Bundled transparent-background support is
OpenAI-specific; other providers may still preserve PNG alpha if their
backend emits it.
## Supported providers
| Provider | Default model | Edit support | Auth |
| ----------------- | --------------------------------------- | ---------------------------------- | ----------------------------------------------------- |
| ComfyUI | `workflow` | Yes (1 image, workflow-configured) | `COMFY_API_KEY` or `COMFY_CLOUD_API_KEY` for cloud |
| DeepInfra | `black-forest-labs/FLUX-1-schnell` | Yes (1 image) | `DEEPINFRA_API_KEY` |
| fal | `fal-ai/flux/dev` | Yes (model-specific limits) | `FAL_KEY` |
| Google | `gemini-3.1-flash-image-preview` | Yes (up to 5 images) | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
| LiteLLM | `gpt-image-2` | Yes (up to 5 input images) | `LITELLM_API_KEY` |
| Microsoft Foundry | `<deployment-name>` | Yes (MAI-Image-2.5 models only) | `AZURE_OPENAI_API_KEY` or Entra ID (`az login`) |
| MiniMax | `image-01` | Yes (subject reference) | `MINIMAX_API_KEY` or MiniMax OAuth (`minimax-portal`) |
| OpenAI | `gpt-image-2` | Yes (up to 5 images) | `OPENAI_API_KEY` or OpenAI ChatGPT/Codex OAuth |
| OpenRouter | `google/gemini-3.1-flash-image-preview` | Yes (up to 5 input images) | `OPENROUTER_API_KEY` |
| Vydra | `grok-imagine` | No | `VYDRA_API_KEY` |
| xAI | `grok-imagine-image` | Yes (up to 5 images) | `XAI_API_KEY` |
Use `action: "list"` to inspect available providers and models at runtime:
```text
/tool image_generate action=list
```
Use `action: "status"` to inspect the active image-generation task for the
current session:
```text
/tool image_generate action=status
```
## Provider capabilities
| Capability | ComfyUI | DeepInfra | fal | Google | Microsoft Foundry | MiniMax | OpenAI | Vydra | xAI |
| --------------------- | ------------------ | --------- | ---------------------------------------------- | -------------- | ----------------- | --------------------- | -------------- | ----- | -------------- |
| Generate (max count) | 1 | 4 | 4 | 4 | 1 | 9 | 4 | 1 | 4 |
| Edit / reference | 1 image (workflow) | 1 image | Flux: 1; GPT: 10; Krea style refs: 10; NB2: 14 | Up to 5 images | 1 image | 1 image (subject ref) | Up to 5 images | - | Up to 5 images |
| Size control | - | ✓ | ✓ | ✓ | ✓ | - | Up to 4K | - | - |
| Aspect ratio | - | - | ✓ | ✓ | - | ✓ | - | - | ✓ |
| Resolution (1K/2K/4K) | - | - | ✓ | ✓ | - | - | - | - | 1K, 2K |
## Tool parameters
<ParamField path="prompt" type="string" required>
Image generation prompt. Required for `action: "generate"`.
</ParamField>
<ParamField path="action" type='"generate" | "status" | "list"' default="generate">
Use `"status"` to inspect the active session task or `"list"` to inspect
available providers and models at runtime.
</ParamField>
<ParamField path="model" type="string">
Provider/model override (e.g. `openai/gpt-image-2`). Use
`openai/gpt-image-1.5` for transparent OpenAI backgrounds.
</ParamField>
<ParamField path="image" type="string">
Single reference image path or URL for edit mode.
</ParamField>
<ParamField path="images" type="string[]">
Multiple reference images for edit mode or style-reference models (up to 14
through the shared tool; provider-specific limits still apply).
</ParamField>
<ParamField path="size" type="string">
Size hint: `1024x1024`, `1536x1024`, `1024x1536`, `2048x2048`, `3840x2160`.
</ParamField>
<ParamField path="aspectRatio" type="string">
Aspect ratio: `1:1`, `2:1`, `20:9`, `19.5:9`, `2:3`, `3:2`, `2.35:1`, `3:4`,
`4:3`, `4:5`, `5:4`, `9:16`, `9:19.5`, `9:20`, `16:9`, `21:9`, `1:2`, `4:1`,
`1:4`, `8:1`, `1:8`. Providers validate their model-specific subset.
</ParamField>
<ParamField path="resolution" type='"1K" | "2K" | "4K"'>Resolution hint.</ParamField>
<ParamField path="quality" type='"low" | "medium" | "high" | "auto"'>
Quality hint when the provider supports it.
</ParamField>
<ParamField path="outputFormat" type='"png" | "jpeg" | "webp"'>
Output format hint when the provider supports it.
</ParamField>
<ParamField path="background" type='"transparent" | "opaque" | "auto"'>
Background hint when the provider supports it. Use `transparent` with
`outputFormat: "png"` or `"webp"` for transparency-capable providers.
</ParamField>
<ParamField path="count" type="number">Number of images to generate (1-4).</ParamField>
<ParamField path="timeoutMs" type="number">
Optional provider request timeout in milliseconds. When Codex calls
`image_generate` through dynamic tools, this per-call value still overrides
the configured default and is capped at 600000 ms.
</ParamField>
<ParamField path="filename" type="string">Output filename hint.</ParamField>
<ParamField path="openai" type="object">
OpenAI-only hints: `background`, `moderation`, `outputCompression`, and `user`.
</ParamField>
<ParamField path="fal.creativity" type='"raw" | "low" | "medium" | "high"'>
fal Krea 2 creativity control. Defaults to `medium`.
</ParamField>
<Note>
Not all providers support all parameters. When a fallback provider supports a
nearby geometry option instead of the exact requested one, OpenClaw remaps to
the closest supported size, aspect ratio, or resolution before submission.
Unsupported output hints are dropped for providers that do not declare
support and reported in the tool result. Tool results report the applied
settings; `details.normalization` captures any requested-to-applied
translation.
</Note>
## Configuration
### Model selection
```json5
{
agents: {
defaults: {
imageGenerationModel: {
primary: "openai/gpt-image-2",
timeoutMs: 180_000,
fallbacks: [
"openrouter/google/gemini-3.1-flash-image-preview",
"google/gemini-3.1-flash-image-preview",
"fal/fal-ai/flux/dev",
],
},
},
},
}
```
### Provider selection order
OpenClaw tries providers in this order:
1. **`model` parameter** from the tool call (if the agent specifies one).
2. **`imageGenerationModel.primary`** from config.
3. **`imageGenerationModel.fallbacks`** in order.
4. **Auto-detection** - auth-backed provider defaults only:
- current default provider first;
- remaining registered image-generation providers in provider-id order.
If a provider fails (auth error, rate limit, etc.), the next configured
candidate is tried automatically. If all fail, the error includes details
from each attempt.
<AccordionGroup>
<Accordion title="Per-call model overrides are exact">
A per-call `model` override tries only that provider/model and does
not continue to configured primary/fallback or auto-detected providers.
</Accordion>
<Accordion title="Auto-detection is auth-aware">
A provider default only enters the candidate list when OpenClaw can
actually authenticate that provider. Set
`agents.defaults.mediaGenerationAutoProviderFallback: false` to use only
explicit `model`, `primary`, and `fallbacks` entries.
</Accordion>
<Accordion title="Timeouts">
Set `agents.defaults.imageGenerationModel.timeoutMs` for slow image
backends. A per-call `timeoutMs` tool parameter overrides the configured
default, and configured defaults override plugin-authored provider
defaults. Google and OpenRouter hosted image providers use 180 second
defaults; Microsoft Foundry MAI, xAI, and Azure OpenAI image generation use
600 seconds. Codex dynamic-tool calls use a 120 second `image_generate`
bridge default and honor the same timeout budget when configured, bounded
by OpenClaw's 600000 ms dynamic-tool bridge maximum.
</Accordion>
<Accordion title="Inspect at runtime">
Use `action: "list"` to inspect the currently registered providers,
their default models, and auth env-var hints.
</Accordion>
</AccordionGroup>
### Image editing
OpenAI, OpenRouter, Google, DeepInfra, fal, Microsoft Foundry, MiniMax,
ComfyUI, and xAI support editing reference images. Krea 2 models on fal use
the same `image` / `images` fields as style references instead of edit
inputs. Pass a reference image path or URL:
```text
"Generate a watercolor version of this photo" + image: "/path/to/photo.jpg"
```
OpenAI, OpenRouter, Google, and xAI support up to 5 reference images via the
`images` parameter. fal supports 1 reference image for Flux image-to-image,
up to 10 for GPT Image 2 edits, up to 10 style references for Krea 2, and up
to 14 for Nano Banana 2 edits. Microsoft Foundry, MiniMax, and ComfyUI
support 1.
## Provider deep dives
<AccordionGroup>
<Accordion title="OpenAI gpt-image-2 (and gpt-image-1.5)">
OpenAI image generation defaults to `openai/gpt-image-2`. If an
`openai` OAuth profile is configured, OpenClaw reuses the same
OAuth profile used by Codex subscription chat models and sends the
image request through the Codex Responses backend. Legacy Codex base
URLs such as `https://chatgpt.com/backend-api` are canonicalized to
`https://chatgpt.com/backend-api/codex` for image requests. OpenClaw
does **not** silently fall back to `OPENAI_API_KEY` for that request -
to force direct OpenAI Images API routing, configure
`models.providers.openai` explicitly with an API key, custom base URL,
or Azure endpoint.
The `openai/gpt-image-1.5`, `openai/gpt-image-1`, and
`openai/gpt-image-1-mini` models can still be selected explicitly. Use
`gpt-image-1.5` for transparent-background PNG/WebP output; the current
`gpt-image-2` API rejects `background: "transparent"`.
`gpt-image-2` supports both text-to-image generation and
reference-image editing through the same `image_generate` tool.
OpenClaw forwards `prompt`, `count`, `size`, `quality`, `outputFormat`,
and reference images to OpenAI. OpenAI does **not** receive
`aspectRatio` or `resolution` directly; when possible OpenClaw maps
those into a supported `size`, otherwise the tool reports them as
ignored overrides.
OpenAI-specific options live under the `openai` object:
```json
{
"quality": "low",
"outputFormat": "jpeg",
"openai": {
"background": "opaque",
"moderation": "low",
"outputCompression": 60,
"user": "end-user-42"
}
}
```
`openai.background` accepts `transparent`, `opaque`, or `auto`;
transparent outputs require `outputFormat` `png` or `webp` and a
transparency-capable OpenAI image model. OpenClaw routes default
`gpt-image-2` transparent-background requests to `gpt-image-1.5`.
`openai.outputCompression` applies to JPEG/WebP outputs and is ignored
for PNG outputs.
The top-level `background` hint is provider-neutral and currently maps
to the same OpenAI `background` request field when the OpenAI provider
is selected. Providers that do not declare background support return
it in `ignoredOverrides` instead of receiving the unsupported parameter.
To route OpenAI image generation through an Azure OpenAI deployment
instead of `api.openai.com`, see
[Azure OpenAI endpoints](/providers/openai#azure-openai-endpoints).
</Accordion>
<Accordion title="Microsoft Foundry MAI image models">
Microsoft Foundry image generation uses deployed MAI image deployment names
under the `microsoft-foundry/` provider prefix. There is no provider-level
default model because the MAI API expects your deployment name in the
`model` field:
```json5
{
agents: {
defaults: {
imageGenerationModel: {
primary: "microsoft-foundry/<deployment-name>",
timeoutMs: 600_000,
},
},
},
}
```
The provider uses Microsoft Foundry's MAI API, not the OpenAI Images API:
- Generation endpoint: `/mai/v1/images/generations`
- Edit endpoint: `/mai/v1/images/edits`
- Auth: `AZURE_OPENAI_API_KEY` / provider API key, or Entra ID through `az login`
- Output: one PNG image
- Size: default `1024x1024`; width and height must each be at least 768 px,
and total pixels must be at most 1,048,576
- Edits: one PNG or JPEG reference image, supported only by
`MAI-Image-2.5-Flash` and `MAI-Image-2.5` deployments
Prompt-only generation can use a custom deployment name with just the
Foundry endpoint configured. Edits with custom deployment names need
onboarding/model metadata so OpenClaw can verify that the deployment is
backed by `MAI-Image-2.5-Flash` or `MAI-Image-2.5`.
Current MAI image models are `MAI-Image-2.5-Flash`, `MAI-Image-2.5`,
`MAI-Image-2e`, and `MAI-Image-2`. See
[Microsoft Foundry plugin](/plugins/reference/microsoft-foundry) for setup
and chat-model behavior.
</Accordion>
<Accordion title="OpenRouter image models">
OpenRouter image generation uses the same `OPENROUTER_API_KEY` and
routes through OpenRouter's chat completions image API. Select
OpenRouter image models with the `openrouter/` prefix:
```json5
{
agents: {
defaults: {
imageGenerationModel: {
primary: "openrouter/google/gemini-3.1-flash-image-preview",
},
},
},
}
```
OpenClaw forwards `prompt`, `count`, reference images, and
Gemini-compatible `aspectRatio` / `resolution` hints to OpenRouter.
Current built-in OpenRouter image model shortcuts include
`google/gemini-3.1-flash-image-preview`,
`google/gemini-3-pro-image-preview`, and `openai/gpt-5.4-image-2`. Use
`action: "list"` to see what your configured plugin exposes.
</Accordion>
<Accordion title="fal Krea 2">
Krea 2 models on fal use fal's native Krea schema instead of the generic
`image_size` schema used by Flux. OpenClaw sends:
- `aspect_ratio` for aspect-ratio hints
- `creativity`, defaulting to `medium`
- `image_style_references` when `image` or `images` are supplied
Select Krea 2 Medium for faster expressive illustration and Krea 2 Large
for slower, more detailed photoreal and textured looks:
```json5
{
agents: {
defaults: {
imageGenerationModel: {
primary: "fal/krea/v2/medium/text-to-image",
},
},
},
}
```
Krea 2 currently returns one image per request. Prefer `aspectRatio` for
Krea; OpenClaw maps `size` to the closest supported Krea aspect ratio and
rejects `resolution` for Krea rather than dropping it. Use `fal.creativity`
when you want a native Krea creativity level:
```json
{
"model": "fal/krea/v2/medium/text-to-image",
"prompt": "A cyber zine portrait with risograph texture",
"aspectRatio": "9:16",
"fal": {
"creativity": "high"
}
}
```
</Accordion>
<Accordion title="MiniMax dual-auth">
MiniMax image generation is available through both bundled MiniMax
auth paths:
- `minimax/image-01` for API-key setups
- `minimax-portal/image-01` for OAuth setups
</Accordion>
<Accordion title="xAI grok-imagine-image">
The bundled xAI provider uses `/v1/images/generations` for prompt-only
requests and `/v1/images/edits` when `image` or `images` is present.
- Models: `xai/grok-imagine-image`, `xai/grok-imagine-image-quality`
- Count: up to 4
- References: one `image` or up to five `images`
- Aspect ratios: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2:3`, `3:2`
- Resolutions: `1K`, `2K`
- Outputs: returned as OpenClaw-managed image attachments
OpenClaw intentionally does not expose xAI-native `quality`, `mask`,
`user`, or extra native-only aspect ratios until those controls exist
in the shared cross-provider `image_generate` contract.
</Accordion>
</AccordionGroup>
## Examples
<Tabs>
<Tab title="Generate (4K landscape)">
```text
/tool image_generate action=generate model=openai/gpt-image-2 prompt="A clean editorial poster for OpenClaw image generation" size=3840x2160 count=1
```
</Tab>
<Tab title="Generate (transparent PNG)">
```text
/tool image_generate action=generate model=openai/gpt-image-1.5 prompt="A simple red circle sticker on a transparent background" outputFormat=png background=transparent
```
Equivalent CLI:
```bash
openclaw infer image generate \
--model openai/gpt-image-1.5 \
--output-format png \
--background transparent \
--prompt "A simple red circle sticker on a transparent background" \
--json
```
</Tab>
<Tab title="Generate (OpenAI low quality)">
```text
/tool image_generate action=generate model=openai/gpt-image-2 prompt="Low-cost draft poster for a quiet productivity app" quality=low openai='{"moderation":"low"}'
```
Equivalent CLI:
```bash
openclaw infer image generate \
--model openai/gpt-image-2 \
--quality low \
--openai-moderation low \
--prompt "Low-cost draft poster for a quiet productivity app" \
--json
```
</Tab>
<Tab title="Generate (two square)">
```text
/tool image_generate action=generate model=openai/gpt-image-2 prompt="Two visual directions for a calm productivity app icon" size=1024x1024 count=2
```
</Tab>
<Tab title="Edit (one reference)">
```text
/tool image_generate action=generate model=openai/gpt-image-2 prompt="Keep the subject, replace the background with a bright studio setup" image=/path/to/reference.png size=1024x1536
```
</Tab>
<Tab title="Edit (multiple references)">
```text
/tool image_generate action=generate model=openai/gpt-image-2 prompt="Combine the character identity from the first image with the color palette from the second" images='["/path/to/character.png","/path/to/palette.jpg"]' size=1536x1024
```
</Tab>
<Tab title="Krea style references">
```text
/tool image_generate action=generate model=fal/krea/v2/medium/text-to-image prompt="An expressive editorial portrait using this color palette and print texture" images='["/path/to/palette.png","/path/to/texture.jpg"]' aspectRatio=9:16 fal='{"creativity":"high"}'
```
</Tab>
</Tabs>
The same `--output-format`, `--background`, `--quality`, and
`--openai-moderation` flags are available on `openclaw infer image edit`;
`--openai-background` remains as an OpenAI-specific alias. Bundled providers
other than OpenAI do not declare explicit background control today, so
`background: "transparent"` is reported as ignored for them.
## Related
- [Tools overview](/tools) - all available agent tools
- [ComfyUI](/providers/comfy) - local ComfyUI and Comfy Cloud workflow setup
- [fal](/providers/fal) - fal image and video provider setup
- [Google (Gemini)](/providers/google) - Gemini image provider setup
- [Microsoft Foundry plugin](/plugins/reference/microsoft-foundry) - Microsoft Foundry chat and MAI image setup
- [MiniMax](/providers/minimax) - MiniMax image provider setup
- [OpenAI](/providers/openai) - OpenAI Images provider setup
- [Vydra](/providers/vydra) - Vydra image, video, and speech setup
- [xAI](/providers/xai) - Grok image, video, search, code execution, and TTS setup
- [Configuration reference](/gateway/config-agents#agent-defaults) - `imageGenerationModel` config
- [Models](/concepts/models) - model configuration and failover

188
docs/tools/index.md Normal file
View File

@@ -0,0 +1,188 @@
---
doc-schema-version: 1
summary: "OpenClaw tools, skills, and plugins overview: what agents can call and how to extend them"
read_when:
- You want to understand what tools OpenClaw provides
- You are deciding between built-in tools, skills, and plugins
- You need the right docs entry point for tool policy, automation, or agent coordination
title: "Overview"
---
Use this page to choose the right Capabilities surface. **Tools** are
callable actions, **skills** teach agents how to work, and **plugins** add
runtime capabilities such as tools, providers, channels, hooks, and packaged
skills.
This is an overview and routing page. For exhaustive tool policy, defaults,
group membership, provider restrictions, and configuration fields, use
[Tools and custom providers](/gateway/config-tools).
## Start here
For most agents, start with the built-in tool categories, then adjust policy
only when the agent should see fewer tools or needs explicit host access.
| If you need to... | Use this first | Then read |
| ------------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Let an agent act with existing capabilities | [Built-in tools](#built-in-tool-categories) | [Tool categories](#built-in-tool-categories) |
| Control what an agent can call | [Tool policy](#configure-access-and-approvals) | [Tools and custom providers](/gateway/config-tools) |
| Teach an agent a workflow | [Skills](#choose-tools-skills-or-plugins) | [Skills](/tools/skills), [Creating skills](/tools/creating-skills), and [Skill Workshop](/tools/skill-workshop) |
| Add a new integration or runtime surface | [Plugins](#extend-capabilities) | [Plugins](/tools/plugin) and [Build plugins](/plugins/building-plugins) |
| Run work later or in the background | [Automation](/automation) | [Automation overview](/automation) |
| Coordinate multiple agents or harnesses | [Sub-agents](/tools/subagents) | [ACP agents](/tools/acp-agents) and [Agent send](/tools/agent-send) |
| Search a large OpenClaw tool catalog | [Tool Search](/tools/tool-search) | [Tool Search](/tools/tool-search) |
## Choose tools, skills, or plugins
<Steps>
<Step title="Use a tool when the agent needs to act">
A tool is a typed function the agent can call, such as `exec`, `browser`,
`web_search`, `message`, or `image_generate`. Use tools when the agent
needs to read data, change files, send messages, call a provider, or
operate another system. Visible tools are sent to the model as structured
function definitions.
The model only sees tools that survive the active profile, allow/deny
policy, provider restrictions, sandbox state, channel permissions, and
plugin availability.
</Step>
<Step title="Use a skill when the agent needs instructions">
A skill is a `SKILL.md` instruction pack loaded into the agent prompt. Use
a skill when the agent already has the tools it needs, but needs a
repeatable workflow, review rubric, command sequence, or operating
constraint.
Skills can live in a workspace, shared skill directory, managed OpenClaw
skill root, or plugin package.
[Skills](/tools/skills) | [Skill Workshop](/tools/skill-workshop) | [Creating skills](/tools/creating-skills) | [Skills config](/tools/skills-config)
</Step>
<Step title="Use a plugin when OpenClaw needs a new capability">
A plugin can add tools, skills, channels, model providers, speech,
realtime voice, media generation, web search, web fetch, hooks, and other
runtime capabilities. Use a plugin when the capability has code,
credentials, lifecycle hooks, manifest metadata, or installable
packaging. Existing plugins can be installed from ClawHub, npm, git,
local directories, or archives.
[Install and configure plugins](/tools/plugin) | [Build plugins](/plugins/building-plugins) | [Plugin SDK](/plugins/sdk-overview)
</Step>
</Steps>
## Built-in tool categories
The table lists representative tools so you can recognize the surface. It is
not the full policy reference. For exact groups, defaults, and allow/deny
semantics, use [Tools and custom providers](/gateway/config-tools).
| Category | Use when the agent needs to... | Representative tools | Read next |
| ----------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Runtime | Run commands, manage processes, or use provider-backed Python analysis | `exec`, `process`, `code_execution` | [Exec](/tools/exec), [Code execution](/tools/code-execution) |
| Files | Read and change workspace files | `read`, `write`, `edit`, `apply_patch` | [Apply patch](/tools/apply-patch) |
| Web | Search the web, search X posts, or fetch readable page content | `web_search`, `x_search`, `web_fetch` | [Web tools](/tools/web), [Web fetch](/tools/web-fetch) |
| Browser | Operate a browser session | `browser` | [Browser](/tools/browser) |
| Messaging and channels | Send replies or channel actions | `message` | [Agent send](/tools/agent-send) |
| Sessions and agents | Inspect sessions, delegate work, steer another run, or report status | `sessions_*`, `subagents`, `agents_list`, `session_status`, `get_goal`, `create_goal`, `update_goal` | [Goal](/tools/goal), [Sub-agents](/tools/subagents), [Session tool](/concepts/session-tool) |
| Automation | Schedule work or respond to background events | `cron`, `heartbeat_respond` | [Automation](/automation) |
| Gateway and nodes | Inspect Gateway state or paired target devices | `gateway`, `nodes` | [Gateway configuration](/gateway/configuration), [Nodes](/nodes) |
| Media | Analyze, generate, or speak media | `image`, `image_generate`, `music_generate`, `video_generate`, `tts` | [Media overview](/tools/media-overview) |
| Large OpenClaw catalogs | Search and call many eligible tools without sending every schema to the model | `tool_search_code`, `tool_search`, `tool_describe` | [Tool Search](/tools/tool-search) |
<Note>
Tool Search is an experimental OpenClaw agent surface. Codex harness runs use
Codex-native code mode, native tool search, deferred dynamic tools, and
nested tool calls instead of `tools.toolSearch`.
</Note>
## Plugin-provided tools
Plugins can register additional tools. Plugin authors wire tools through
`api.registerTool(...)` and the manifest's `contracts.tools`; use
[Plugin SDK](/plugins/sdk-overview) and [Plugin manifest](/plugins/manifest)
for contract details.
Common plugin-provided tools include:
- [Diffs](/tools/diffs) for rendering file and markdown diffs
- [LLM Task](/tools/llm-task) for JSON-only workflow steps
- [Lobster](/tools/lobster) for typed workflows with resumable approvals
- [Tokenjuice](/tools/tokenjuice) for compacting noisy `exec` and `bash` tool
output
- [Tool Search](/tools/tool-search) for discovering and calling large tool
catalogs without putting every schema in the prompt
- [Canvas](/plugins/reference/canvas) for node Canvas control and A2UI
rendering
## Configure access and approvals
Tool policy is enforced before the model call. If policy removes a tool, the
model does not receive that tool's schema for the turn. A run can lose tools
because of global config, per-agent config, channel policy, provider
restrictions, sandbox rules, channel/runtime policy, or plugin availability.
- [Tools and custom providers](/gateway/config-tools) documents tool profiles,
allow/deny lists, provider-specific restrictions, loop detection, and
provider-backed tool settings.
- [Exec approvals](/tools/exec-approvals) documents host command approval
policy.
- [Elevated exec](/tools/elevated) documents controlled execution outside the
sandbox.
- [Sandbox vs tool policy vs elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)
explains which layer controls file and process access.
- [Per-agent sandbox and tool restrictions](/tools/multi-agent-sandbox-tools)
documents agent-specific restrictions for delegated runs.
## Extend capabilities
Choose the extension path by the job you need OpenClaw to do:
- Install or manage an existing plugin with [Plugins](/tools/plugin).
- Build a new integration, provider, channel, tool, or hook with
[Build plugins](/plugins/building-plugins).
- Add or tune reusable agent instructions with [Skills](/tools/skills) and
[Creating skills](/tools/creating-skills).
- Use [Plugin SDK](/plugins/sdk-overview) and
[Plugin manifest](/plugins/manifest) when you need implementation
contracts.
## Troubleshoot missing tools
If the model cannot see or call a tool, start with the effective policy for
the current turn:
1. Check the active profile, `tools.allow`, and `tools.deny` in
[Tools and custom providers](/gateway/config-tools).
2. Check provider-specific restrictions in
[Tools and custom providers](/gateway/config-tools) and confirm the
selected [model provider](/concepts/model-providers) supports the tool
shape.
3. Check channel permissions, sandbox state, and elevated access with
[Sandbox vs tool policy vs elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)
and [Elevated exec](/tools/elevated).
4. Check whether the owning plugin is installed and enabled in
[Plugins](/tools/plugin).
5. For delegated runs, check per-agent restrictions in
[Per-agent sandbox and tool restrictions](/tools/multi-agent-sandbox-tools).
6. For large OpenClaw catalogs, confirm whether the run uses direct tool
exposure or [Tool Search](/tools/tool-search).
## Related
- [Automation](/automation) for cron, tasks, heartbeat, commitments, hooks,
standing orders, and Task Flow
- [Agents](/concepts/agent) for the agent model, sessions, memory, and
multi-agent coordination
- [Tools and custom providers](/gateway/config-tools) for the canonical tool
policy reference
- [Plugins](/tools/plugin) for plugin installation and management
- [Plugin SDK](/plugins/sdk-overview) for plugin author reference
- [Skills](/tools/skills) for skill load order, gating, and config
- [Skill Workshop](/tools/skill-workshop) for generated and reviewed skill
creation
- [Tool Search](/tools/tool-search) for compact OpenClaw tool catalog
discovery

101
docs/tools/kimi-search.md Normal file
View File

@@ -0,0 +1,101 @@
---
summary: "Kimi web search via Moonshot web search"
read_when:
- You want to use Kimi for web_search
- You need a KIMI_API_KEY or MOONSHOT_API_KEY
title: "Kimi search"
---
Kimi is a `web_search` provider backed by Moonshot's native web search. Moonshot
synthesizes one answer with inline citations, similar to Gemini and Grok's
grounded-response providers, rather than returning a ranked result list.
## Setup
<Steps>
<Step title="Create a key">
Get an API key from [Moonshot AI](https://platform.moonshot.cn/).
</Step>
<Step title="Store the key">
Set `KIMI_API_KEY` or `MOONSHOT_API_KEY` in the Gateway environment (for a
gateway install, add it to `~/.openclaw/.env`), or configure via:
```bash
openclaw configure --section web
```
</Step>
</Steps>
Choosing **Kimi** during `openclaw onboard` or `openclaw configure --section web`
also prompts for:
- the Moonshot API region: `https://api.moonshot.ai/v1` or `https://api.moonshot.cn/v1`
- the web-search model (defaults to `kimi-k2.6`)
## Config
```json5
{
plugins: {
entries: {
moonshot: {
config: {
webSearch: {
apiKey: "sk-...", // optional if KIMI_API_KEY or MOONSHOT_API_KEY is set
baseUrl: "https://api.moonshot.ai/v1",
model: "kimi-k2.6",
},
},
},
},
},
tools: {
web: {
search: {
provider: "kimi",
},
},
},
}
```
`tools.web.search.provider` is auto-detected from available API keys when omitted;
set it to `kimi` explicitly if multiple search credentials are configured.
Equivalent scoped form under `tools.web.search.kimi` (`apiKey`, `baseUrl`, `model`)
also works; both shapes merge into the same resolved config.
Defaults: `baseUrl` defaults to `https://api.moonshot.ai/v1` when omitted, `model`
defaults to `kimi-k2.6`.
If chat traffic uses the China host (`models.providers.moonshot.baseUrl`:
`https://api.moonshot.cn/v1`), Kimi `web_search` reuses that host automatically
when its own `baseUrl` is unset, so `.cn` keys do not accidentally hit the
international endpoint (which returns HTTP 401 for those keys). Set an explicit
Kimi `baseUrl` to override this inheritance.
## Grounding requirement
OpenClaw only returns a Kimi `web_search` result after Moonshot's response
includes native web-search grounding evidence, such as a `$web_search` tool-call
replay, `search_results`, or citation URLs. If Kimi answers directly with no
grounding (for example "I cannot browse the internet"), OpenClaw returns a
`kimi_web_search_ungrounded` error instead of treating that text as a search
result. Retry the query, switch to a structured provider such as Brave, or use
`web_fetch` / the browser tool when you already have a target URL.
## Tool parameters
| Parameter | Supported |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `query` | Yes |
| `count` | Accepted for cross-provider compatibility, but ignored: Kimi always returns one synthesized answer, not an N-result list |
| `country`, `language`, `freshness`, `date_after`, `date_before` | No |
## Related
- [Web Search overview](/tools/web) - all providers and auto-detection
- [Moonshot AI](/providers/moonshot) - Moonshot model + Kimi Coding provider docs
- [Gemini Search](/tools/gemini-search) - AI-synthesized answers via Google grounding
- [Grok Search](/tools/grok-search) - AI-synthesized answers via xAI grounding

143
docs/tools/llm-task.md Normal file
View File

@@ -0,0 +1,143 @@
---
summary: "JSON-only LLM tasks for workflows (optional plugin tool)"
read_when:
- You want a JSON-only LLM step inside workflows
- You need schema-validated LLM output for automation
title: "LLM task"
---
`llm-task` is a bundled **optional plugin tool** that runs a single JSON-only
LLM call and returns structured output, optionally validated against a JSON
Schema. It gives workflow engines like Lobster an LLM step without custom
OpenClaw code per workflow.
## Enable
1. Enable the plugin:
```json
{
"plugins": {
"entries": {
"llm-task": { "enabled": true }
}
}
}
```
2. Allow the tool:
```json
{
"tools": {
"alsoAllow": ["llm-task"]
}
}
```
`alsoAllow` adds `llm-task` on top of the active tool profile without
restricting other core tools. Use `tools.allow` only if you want a restrictive
allowlist mode instead.
## Config (optional)
```json
{
"plugins": {
"entries": {
"llm-task": {
"enabled": true,
"config": {
"defaultProvider": "openai",
"defaultModel": "gpt-5.5",
"defaultAuthProfileId": "main",
"allowedModels": ["openai/gpt-5.5"],
"maxTokens": 800,
"timeoutMs": 30000
}
}
}
}
}
```
`allowedModels` is an allowlist of `provider/model` strings; a request for any
other model is rejected. All other keys are per-call fallbacks used when the
tool call omits that parameter.
## Tool parameters
| Parameter | Type | Notes |
| --------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `prompt` | string | Required. Task instruction for the LLM. |
| `input` | any | Optional payload; serialized to JSON and appended to the prompt. |
| `schema` | object | Optional JSON Schema the parsed output must validate against. |
| `provider` | string | Overrides `defaultProvider` / the agent's default provider. |
| `model` | string | Overrides `defaultModel`; accepts bare model ids, aliases, or a `provider/model` ref (a duplicate provider prefix is stripped automatically). |
| `thinking` | string | Reasoning level (e.g. `low`, `medium`); must be one supported by the resolved model. |
| `authProfileId` | string | Overrides `defaultAuthProfileId`. |
| `temperature` | number | Best-effort; not all providers honor it. |
| `maxTokens` | number | Best-effort cap on output tokens. |
| `timeoutMs` | number | Run timeout; default `30000`. |
## Output
Returns `details.json` (the parsed, schema-validated JSON) plus `details.provider`
and `details.model` naming what actually ran.
## Example: Lobster workflow step
### Important limitation
The example below assumes the **standalone Lobster CLI** is running where
`openclaw.invoke` already has the correct gateway URL/auth context.
For the bundled **embedded** Lobster runner inside OpenClaw, this nested CLI
pattern is **not currently reliable**:
```lobster
openclaw.invoke --tool llm-task --action json --args-json '{ ... }'
```
Until embedded Lobster has a supported bridge for this flow, prefer either:
- direct `llm-task` tool calls outside Lobster, or
- Lobster steps that do not rely on nested `openclaw.invoke` calls.
Standalone Lobster CLI example:
```lobster
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
- **JSON-only**: the model is instructed to return only a JSON value, no code
fences, no commentary.
- **No tools**: the underlying run has tools disabled, so the model cannot call
out mid-task.
- Treat output as untrusted unless you validate it with `schema`.
- Put approvals before any side-effecting step (send, post, exec) that consumes
this output.
## Related
- [Thinking levels](/tools/thinking)
- [Sub-agents](/tools/subagents)
- [Slash commands](/tools/slash-commands)

380
docs/tools/lobster.md Normal file
View File

@@ -0,0 +1,380 @@
---
summary: "Typed workflow runtime for OpenClaw with resumable approval gates."
title: Lobster
read_when:
- You want deterministic multi-step workflows with explicit approvals
- You need to resume a workflow without re-running earlier steps
---
Lobster runs multi-step tool pipelines as one deterministic tool call, with
explicit approval checkpoints and resume tokens. It sits one layer above
detached background work: for orchestrating flows across many detached tasks,
see [Task Flow](/automation/taskflow) (`openclaw tasks flow`); for the task
activity ledger, see [Background Tasks](/automation/tasks).
## Why
Without Lobster, a multi-step job means many round-trip tool calls, with the
model orchestrating every step. Lobster moves that orchestration into a typed
runtime:
- **One call instead of many**: a single Lobster tool call returns a structured
result for the whole pipeline.
- **Approvals built in**: side effects (send, post, delete) halt the workflow
until explicitly approved.
- **Resumable**: a halted workflow returns a token; approve and resume without
re-running earlier steps.
Lobster is a small, constrained DSL rather than a general scripting language:
approve/resume is a durable, built-in primitive; pipelines are data (easy to
log, diff, replay, review); the tiny grammar limits "creative" code paths so
validation stays realistic; timeouts, output caps, sandbox checks, and
allowlists are enforced by the runtime, not by each script. Each step can still
call any CLI or script - generate `.lobster` files from other tooling if you
want a richer authoring language.
Without Lobster, a recurring email triage looks like:
```text
User: "Check my email and draft replies"
→ openclaw calls gmail.list
→ LLM summarizes
→ User: "draft replies to #2 and #5"
→ LLM drafts
→ User: "send #2"
→ openclaw calls gmail.send
(repeat daily, no memory of what was triaged)
```
With Lobster, the same job is one call that halts for approval and resumes:
```json
{ "action": "run", "pipeline": "email.triage --limit 20", "timeoutMs": 30000 }
```
```json
{
"ok": true,
"status": "needs_approval",
"output": [{ "summary": "5 need replies, 2 need action" }],
"requiresApproval": {
"type": "approval_request",
"prompt": "Send 2 draft replies?",
"items": [],
"resumeToken": "..."
}
}
```
## How it works
OpenClaw runs Lobster workflows **in-process** using the bundled
`@clawdbot/lobster` package as an embedded runner. No external `lobster`
subprocess is spawned; the tool call returns a JSON envelope directly. If the
pipeline halts for approval, the envelope carries a resume token (or a short
approval ID) so you can continue later.
## Enable
Lobster is an **optional** plugin tool, not enabled by default. It ships
bundled, so no separate install step is required - just allow the tool:
```json
{
"tools": {
"alsoAllow": ["lobster"]
}
}
```
Or per-agent:
```json
{
"agents": {
"list": [
{
"id": "main",
"tools": {
"alsoAllow": ["lobster"]
}
}
]
}
}
```
<Note>
`alsoAllow` adds `lobster` on top of the active tool profile without
restricting other core tools. Use `tools.allow` only if you want a restrictive
allowlist mode instead.
</Note>
The tool is disabled entirely for sandboxed tool contexts.
If you need the standalone Lobster CLI for development or external pipelines
(outside the embedded gateway runner), install it from the
[Lobster repo](https://github.com/openclaw/lobster) and put `lobster` on
`PATH`.
## Pattern: small CLI + JSON pipes + approvals
Build tiny commands that speak JSON, then chain them into one Lobster call.
(Example command names below - swap in your own.)
```bash
inbox list --json
inbox categorize --json
inbox apply --json
```
```json
{
"action": "run",
"pipeline": "exec --json --shell 'inbox list --json' | exec --stdin json --shell 'inbox categorize --json' | exec --stdin json --shell 'inbox apply --json' | approve --preview-from-stdin --limit 5 --prompt 'Apply changes?'",
"timeoutMs": 30000
}
```
If the pipeline requests approval, resume with the token:
```json
{
"action": "resume",
"token": "<resumeToken>",
"approve": true
}
```
Example: map input items into tool calls:
```bash
gog.gmail.search --query 'newer_than:1d' \
| openclaw.invoke --tool message --action send --each --item-key message --args-json '{"provider":"telegram","to":"..."}'
```
## JSON-only LLM steps (llm-task)
For a **structured LLM step** inside a workflow, enable the optional
`llm-task` plugin tool and call it from Lobster:
```json
{
"plugins": {
"entries": {
"llm-task": { "enabled": true }
}
},
"agents": {
"list": [
{
"id": "main",
"tools": { "alsoAllow": ["llm-task"] }
}
]
}
}
```
### Important limitation: embedded Lobster vs `openclaw.invoke`
The bundled Lobster plugin runs workflows **in-process** inside the gateway.
In that embedded mode, `openclaw.invoke` does **not** automatically inherit a
gateway URL/auth context for nested OpenClaw CLI tool calls.
That means this pattern is **not currently reliable in the embedded runner**:
```lobster
openclaw.invoke --tool llm-task --action json --args-json '{ ... }'
```
Use the example below only when running the **standalone Lobster CLI** in an
environment where `openclaw.invoke` is already configured with the correct
gateway/auth context.
```lobster
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
}
}'
```
If you are using the embedded Lobster plugin today, prefer either:
- a direct `llm-task` tool call outside Lobster, or
- non-`openclaw.invoke` steps inside the Lobster pipeline until a supported
embedded bridge is added.
See [LLM Task](/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. Set `pipeline` to the file path in the tool
call.
```yaml
name: inbox-triage
args:
tag:
default: "family"
steps:
- id: collect
command: inbox list --json
- id: categorize
command: inbox categorize --json
stdin: $collect.stdout
- id: approve
command: inbox apply --approve
stdin: $categorize.stdout
approval: required
- id: execute
command: inbox apply --execute
stdin: $categorize.stdout
condition: $approve.approved
```
Notes:
- `stdin: $step.stdout` and `stdin: $step.json` pass a prior step's output.
- `condition` (or `when`) can gate steps on `$step.approved`.
## Tool parameters
### `run`
```json
{
"action": "run",
"pipeline": "gog.gmail.search --query 'newer_than:1d' | email.triage",
"cwd": "workspace",
"timeoutMs": 30000,
"maxStdoutBytes": 512000
}
```
Run a workflow file with args:
```json
{
"action": "run",
"pipeline": "/path/to/inbox-triage.lobster",
"argsJson": "{\"tag\":\"family\"}"
}
```
| Field | Default | Notes |
| ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------ |
| `pipeline` | required | Inline pipeline string, or a path ending in `.lobster`/`.yaml`/`.yml`/`.json` for a workflow file. |
| `cwd` | gateway cwd | Relative working directory; must resolve inside the gateway working directory (absolute paths are rejected). |
| `timeoutMs` | `20000` | Aborts the run if exceeded. |
| `maxStdoutBytes` | `512000` | Aborts the run if captured stdout or stderr exceeds this size. |
| `argsJson` | - | JSON string of args for a workflow file (ignored for inline pipelines). |
### `resume`
```json
{
"action": "resume",
"token": "<resumeToken>",
"approve": true
}
```
`resume` accepts either `token` (the full resume token from `requiresApproval`)
or `approvalId` (the short id from the same object) - use whichever the halted
run returned. `approve` is required.
### Managed Task Flow mode
Passing `flowControllerId` and `flowGoal` on `run` (or `flowId` and
`flowExpectedRevision` on `resume`) drives the call through the plugin
runtime's managed [Task Flow](/automation/taskflow) API instead of returning
a bare envelope: OpenClaw creates or resumes a durable flow record, applies the
Lobster envelope to it (`waiting` on approval, `succeeded`/`failed` on
completion), and returns `{ ok, envelope, flow, mutation }`. This mode requires
a bound Task Flow runtime and is intended for plugin/controller code that needs
durable flow state across gateway restarts, not typical ad hoc agent use.
## Output envelope
Lobster returns a JSON envelope with one of three statuses:
- `ok` - finished successfully
- `needs_approval` - paused; `requiresApproval` carries a `resumeToken` and a
short `approvalId`, either of which can resume the run
- `cancelled` - explicitly denied or cancelled
The tool surfaces the envelope in both `content` (pretty JSON) and `details`
(raw object).
## Approvals
If `requiresApproval` is present, inspect the prompt and decide:
- `approve: true` - resume and continue side effects
- `approve: false` - cancel and finalize the workflow
Use `approve --preview-from-stdin --limit N` to attach a JSON preview to
approval requests without custom jq/heredoc glue. Resume state is stored as
small JSON files under the Lobster state directory (`~/.lobster/state` by
default, override with `LOBSTER_STATE_DIR`); the token itself only encodes a
pointer to that state, not the full pipeline state.
## 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](/prose).
## Safety
- **Local in-process only** - workflows execute inside the gateway process; no
network calls from the plugin itself.
- **No secrets** - Lobster doesn't manage OAuth; it calls OpenClaw tools that
do.
- **Sandbox-aware** - disabled when the tool context is sandboxed.
- **Hardened** - timeouts and output caps enforced by the embedded runner.
## Troubleshooting
| Error | Cause / fix |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `lobster runtime timed out` | Pipeline exceeded `timeoutMs`. Increase it or split the pipeline. |
| `lobster stdout exceeded maxStdoutBytes` (or `stderr`) | Captured output exceeded the cap. Raise `maxStdoutBytes` or reduce output. |
| `run --args-json must be valid JSON` | `argsJson` (workflow-file runs) failed to parse. Fix the JSON string. |
| `lobster runtime failed` (or another `runtime_error` message) | The embedded runtime returned an error envelope. Check gateway logs for details. |
## Learn more
- [Plugins](/tools/plugin)
- [Plugin tool authoring](/plugins/building-plugins#registering-agent-tools)
## Case study: community workflows
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.
- Thread: [https://x.com/plattenschieber/status/2014508656335770033](https://x.com/plattenschieber/status/2014508656335770033)
- Repo: [https://github.com/bloomedai/brain-cli](https://github.com/bloomedai/brain-cli)
## Related
- [Automation](/automation) - all automation mechanisms
- [Tools Overview](/tools) - all available agent tools

View File

@@ -0,0 +1,193 @@
---
summary: "How to enable and tune guardrails that detect repetitive tool-call loops"
title: "Tool-loop detection"
read_when:
- A user reports agents getting stuck repeating tool calls
- You need to tune repetitive-call protection
- You are editing agent tool/runtime policies
- You hit `compaction_loop_persisted` aborts after a context-overflow retry
---
OpenClaw has two cooperating guardrails against repetitive tool-call patterns,
both configured under `tools.loopDetection`:
1. **Loop detection** (`enabled`) - disabled by default. Watches the rolling
tool-call history for repeated patterns and unknown-tool retries.
2. **Post-compaction guard** (`postCompactionGuard`) - enabled whenever
`enabled` is not explicitly `false`. Arms after every compaction-retry and
aborts the run if the agent repeats the same `(tool, args, result)` triple
within the window.
Set `tools.loopDetection.enabled: false` to silence both guardrails.
## Why this exists
- Detect repetitive sequences that make no progress.
- Detect high-frequency no-result loops (same tool, same inputs, repeated
errors).
- Detect specific repeated-call patterns for known polling tools.
- Break context-overflow -> compaction -> same-loop cycles instead of letting
them run indefinitely.
## Configuration block
Global defaults, with every documented field shown:
```json5
{
tools: {
loopDetection: {
enabled: false, // master switch for the rolling-history detectors
historySize: 30,
warningThreshold: 10,
criticalThreshold: 20,
unknownToolThreshold: 10,
globalCircuitBreakerThreshold: 30,
detectors: {
genericRepeat: true,
knownPollNoProgress: true,
pingPong: true,
},
postCompactionGuard: {
windowSize: 3, // armed after compaction-retry; runs unless enabled is explicitly false
},
},
},
}
```
Per-agent override (optional, at `agents.list[].tools.loopDetection`):
```json5
{
agents: {
list: [
{
id: "safe-runner",
tools: {
loopDetection: {
enabled: true,
warningThreshold: 8,
criticalThreshold: 16,
},
},
},
],
},
}
```
Per-agent settings overlay the global block field by field (including nested
`detectors` and `postCompactionGuard`), so an agent only needs to set the
fields it wants to change.
### Field behavior
| Field | Default | Effect |
| -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `enabled` | `false` | Master switch for the rolling-history detectors. `false` also disables the post-compaction guard. |
| `historySize` | `30` | Number of recent tool calls kept for analysis. |
| `warningThreshold` | `10` | Repeat count before a pattern is classified as warning-only. |
| `criticalThreshold` | `20` | Repeat count for blocking a no-progress loop pattern. Runtime clamps this above `warningThreshold` if misconfigured. |
| `unknownToolThreshold` | `10` | Blocks repeated calls to the same unavailable tool after this many misses. Not gated by `detectors`. |
| `globalCircuitBreakerThreshold` | `30` | Global no-progress breaker across all detectors. Runtime clamps this above `criticalThreshold` if misconfigured. Not gated by `detectors`. |
| `detectors.genericRepeat` | `true` | Warns on repeated same-tool + same-args calls; blocks once those calls also return identical outcomes. |
| `detectors.knownPollNoProgress` | `true` | Detects known no-progress polling patterns (`process` with `action: "poll"`/`"log"`, `command_status`). |
| `detectors.pingPong` | `true` | Detects alternating no-progress ping-pong patterns between two calls. |
| `postCompactionGuard.windowSize` | `3` | Attempts the guard stays armed after compaction, and the count of identical triples that aborts the run. |
For `exec`, no-progress hashing compares stable command outcomes (status,
exit code, timed-out flag, output) and ignores volatile runtime metadata such
as duration, PID, session ID, and working directory. Outbound message-send
results are hashed with volatile per-call ids (message id, file id, timestamp)
stripped, so a "sent" result does not look identical to a different "sent"
result. When a run id is available, history is evaluated only within that run,
so scheduled heartbeat cycles and fresh runs do not inherit stale loop counts
from earlier runs.
## Recommended setup
- For smaller models, set `enabled: true` and leave thresholds at their
defaults. Flagship models rarely need rolling-history detection and can
leave the master switch `false` while still benefiting from the
post-compaction guard.
- Keep thresholds ordered `warningThreshold < criticalThreshold <
globalCircuitBreakerThreshold`; the runtime nudges `criticalThreshold` and
`globalCircuitBreakerThreshold` upward if you set them at or below the
threshold they must exceed.
- If false positives occur:
- Raise `warningThreshold` and/or `criticalThreshold`.
- Optionally raise `globalCircuitBreakerThreshold`.
- Disable only the specific detector causing issues (`detectors.<name>: false`).
- Reduce `historySize` for a shorter historical window.
- To disable everything, including the post-compaction guard, set
`tools.loopDetection.enabled: false` explicitly.
## Post-compaction guard
After a compaction-retry following a context-overflow, the runner arms a
short-window guard on the next few tool calls. If the agent emits the same
`(toolName, argsHash, resultHash)` triple `postCompactionGuard.windowSize`
times within that window, the guard concludes compaction did not break the
loop and aborts the run with a `compaction_loop_persisted` error.
The guard is gated by the master `tools.loopDetection.enabled` flag with one
twist: it stays **enabled when the flag is unset or `true`**, and only turns
off when the flag is explicitly `false`. This is intentional - the guard
exists to escape compaction loops that would otherwise burn unbounded tokens,
so a no-config user still gets the protection.
```json5
{
tools: {
loopDetection: {
// master switch; set false to disable the guard along with the rolling detectors
enabled: true,
postCompactionGuard: {
windowSize: 3, // default
},
},
},
}
```
- Lower `windowSize` is stricter (fewer attempts before abort).
- Higher `windowSize` gives the agent more recovery attempts.
- The guard never aborts while results are changing; only byte-identical
results across the window trigger it.
- It only arms in the immediate aftermath of a compaction-retry, not at other
points in a run.
<Note>
The post-compaction guard runs whenever the master flag is not explicitly `false`, even if you never wrote a `tools.loopDetection` block. To verify, look for `post-compaction guard armed for N attempts` in the gateway log immediately after a compaction event.
</Note>
## Logs and expected behavior
When a loop is detected, OpenClaw logs a loop event and either warns or blocks
the next tool-cycle depending on severity, protecting against runaway token
spend and lockups while preserving normal tool access.
- Warnings come first.
- Blocking follows once a pattern persists past the warning threshold.
- Critical thresholds block the next tool-cycle and surface a clear
loop-detection reason in the run record.
- The post-compaction guard emits `compaction_loop_persisted` errors naming
the offending tool and identical-call count.
## Related
<CardGroup cols={2}>
<Card title="Exec approvals" href="/tools/exec-approvals" icon="shield">
Allow/deny policy for shell execution.
</Card>
<Card title="Thinking levels" href="/tools/thinking" icon="brain">
Reasoning effort levels and provider-policy interaction.
</Card>
<Card title="Sub-agents" href="/tools/subagents" icon="users">
Spawning isolated agents to bound runaway behavior.
</Card>
<Card title="Configuration reference" href="/gateway/config-tools#toolsloopdetection" icon="gear">
Full `tools.loopDetection` schema and merging semantics.
</Card>
</CardGroup>

View File

@@ -0,0 +1,176 @@
---
summary: "Image, video, music, speech, and media-understanding capabilities at a glance"
read_when:
- Looking for an overview of OpenClaw's media capabilities
- Deciding which media provider to configure
- Understanding how async media generation works
title: "Media overview"
sidebarTitle: "Media overview"
---
OpenClaw generates images, videos, and music, understands inbound media
(images, audio, video), and speaks replies aloud with text-to-speech. All
media capabilities are tool-driven: the agent decides when to use them based
on the conversation, and each tool only appears when at least one backing
provider is configured.
Live speech uses the Talk session contract instead of the one-shot media tool
path. Talk has three modes: provider-native `realtime`, local or streaming
`stt-tts`, and `transcription` for observe-only speech capture. Those modes
share provider catalogs, event envelopes, and cancellation semantics with
telephony, meetings, browser realtime, and native push-to-talk clients.
## Capabilities
<CardGroup cols={2}>
<Card title="Image generation" href="/tools/image-generation" icon="image">
Create and edit images from text prompts or reference images via
`image_generate`. Async in chat sessions — runs in the background and
posts the result when ready.
</Card>
<Card title="Video generation" href="/tools/video-generation" icon="video">
Text-to-video, image-to-video, and video-to-video via `video_generate`.
Async — runs in the background and posts the result when ready.
</Card>
<Card title="Music generation" href="/tools/music-generation" icon="music">
Generate music or audio tracks via `music_generate`. Async in chat
sessions on the shared media-generation task lifecycle.
</Card>
<Card title="Text-to-speech" href="/tools/tts" icon="microphone">
Convert outbound replies to spoken audio via the `tts` tool plus
`messages.tts` config. Synchronous.
</Card>
<Card title="Media understanding" href="/nodes/media-understanding" icon="eye">
Summarize inbound images, audio, and video using vision-capable model
providers and dedicated media-understanding plugins.
</Card>
<Card title="Speech-to-text" href="/nodes/audio" icon="ear-listen">
Transcribe inbound voice messages through batch STT or Voice Call
streaming STT providers.
</Card>
</CardGroup>
## Provider capability matrix
<Note>
This table covers the dedicated media-generation, TTS, and STT plugins. Many
chat-model providers (Anthropic, Google, OpenAI, and others) also understand
inbound media through their reply model; see the full provider list in
[Media understanding](/nodes/media-understanding#provider-support-matrix).
</Note>
| Provider | Image | Video | Music | TTS | STT | Realtime voice | Media understanding |
| ----------------- | :---: | :---: | :---: | :-: | :-: | :------------: | :-----------------: |
| Alibaba | | ✓ | | | | | |
| Azure Speech | | | | ✓ | | | |
| BytePlus | | ✓ | | | | | |
| ComfyUI | ✓ | ✓ | ✓ | | | | |
| Deepgram | | | | | ✓ | | |
| DeepInfra | ✓ | ✓ | | ✓ | ✓ | | ✓ |
| ElevenLabs | | | | ✓ | ✓ | | |
| fal | ✓ | ✓ | ✓ | | | | |
| Google | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Gradium | | | | ✓ | | | |
| Inworld | | | | ✓ | | | |
| LiteLLM | ✓ | | | | | | |
| Local CLI | | | | ✓ | | | |
| Microsoft | | | | ✓ | | | |
| Microsoft Foundry | ✓ | | | | | | |
| MiniMax | ✓ | ✓ | ✓ | ✓ | | | |
| Mistral | | | | | ✓ | | |
| OpenAI | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ |
| OpenRouter | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ |
| PixVerse | | ✓ | | | | | |
| Qwen | | ✓ | | | | | ✓ |
| Runway | | ✓ | | | | | |
| SenseAudio | | | | | ✓ | | |
| Together | | ✓ | | | | | |
| Volcengine | | | | ✓ | | | |
| Vydra | ✓ | ✓ | | ✓ | | | |
| xAI | ✓ | ✓ | | ✓ | ✓ | | ✓ |
| Xiaomi MiMo | | | | ✓ | | | |
<Note>
**Realtime voice** here means provider-native bidirectional realtime (Talk
`realtime` mode, e.g. Gemini Live or the OpenAI Realtime API) — only Google
and OpenAI register it today. Deepgram, ElevenLabs, Mistral, OpenAI, and xAI
separately register Voice Call streaming STT (one-way audio-to-text); see
[Speech-to-text and Voice Call](#speech-to-text-and-voice-call) below.
xAI Realtime voice is an upstream capability but is not registered in
OpenClaw until the shared realtime-voice contract can represent it.
</Note>
## Async vs synchronous
| Capability | Mode | Why |
| -------------- | ------------ | ---------------------------------------------------------------------------------------------------- |
| Image | Asynchronous | Provider processing can outlive a chat turn; generated attachments use the shared completion path. |
| Text-to-speech | Synchronous | Provider responses return in seconds; attached to the reply audio. |
| Video | Asynchronous | Provider processing takes 30 s to several minutes; slow queues can run up to the configured timeout. |
| Music | Asynchronous | Same provider-processing characteristic as video. |
For async tools, OpenClaw submits the request to the provider, returns a task
id immediately, and tracks the job in the task ledger. The agent continues
responding to other messages while the job runs. When the provider finishes,
OpenClaw wakes the agent with the generated media paths so it can tell the
user through the session's normal visible-reply mode: automatic final reply
delivery when configured, or `message(action="send")` when the session requires
the message tool. If the requester session is inactive or its active wake
fails, and some generated media is still missing from the completion reply,
OpenClaw sends an idempotent direct fallback with only the missing media. Media
already delivered by the completion reply is not posted again.
## Speech-to-text and Voice Call
Deepgram, DeepInfra, ElevenLabs, Google, Groq, Mistral, OpenAI, OpenRouter,
SenseAudio, and xAI can all transcribe inbound audio through the batch
`tools.media.audio` path when configured. Channel plugins that preflight a
voice note for mention gating or command parsing mark the transcribed
attachment on the inbound context, so the shared media-understanding pass
reuses that transcript instead of making a second STT call for the same
audio.
Deepgram, ElevenLabs, Mistral, OpenAI, and xAI also register Voice Call
streaming STT providers, so live phone audio can be forwarded to the selected
vendor without waiting for a completed recording.
For live user conversations, prefer [Talk mode](/nodes/talk). Batch audio
attachments stay on the media path; browser realtime, native push-to-talk,
telephony, and meeting audio should use Talk events and the session-scoped
catalogs returned by the Gateway.
## Provider mappings (how vendors split across surfaces)
<AccordionGroup>
<Accordion title="Google">
Image, video, music, batch TTS, batch STT, backend realtime voice, and
media-understanding surfaces.
</Accordion>
<Accordion title="OpenAI">
Image, video, batch TTS, batch STT, Voice Call streaming STT, backend
realtime voice, and memory-embedding surfaces.
</Accordion>
<Accordion title="DeepInfra">
Chat/model routing, image generation/editing, text-to-video, batch TTS,
batch STT, image media understanding, and memory-embedding surfaces.
DeepInfra also exposes reranking, classification, object-detection, and
other native model types; OpenClaw has no provider contract for those
categories yet, so this plugin does not register them.
</Accordion>
<Accordion title="xAI">
Image, video, search, code-execution, batch TTS, batch STT, and Voice
Call streaming STT. xAI Realtime voice is an upstream capability but is
not registered in OpenClaw until the shared realtime-voice contract can
represent it.
</Accordion>
</AccordionGroup>
## Related
- [Image generation](/tools/image-generation)
- [Video generation](/tools/video-generation)
- [Music generation](/tools/music-generation)
- [Text-to-speech](/tools/tts)
- [Media understanding](/nodes/media-understanding)
- [Audio nodes](/nodes/audio)
- [Talk mode](/nodes/talk)

View File

@@ -0,0 +1,103 @@
---
summary: "MiniMax Search via the Token Plan search API"
read_when:
- You want to use MiniMax for web_search
- You need a MiniMax Token Plan key or OAuth token
- You want MiniMax CN/global search host guidance
title: "MiniMax search"
---
OpenClaw supports MiniMax as a `web_search` provider through the MiniMax
Token Plan search API. It returns structured search results with titles, URLs,
snippets, and related queries.
## Get a Token Plan credential
<Steps>
<Step title="Create a key">
Create or copy a MiniMax Token Plan key from
[MiniMax Platform](https://platform.minimax.io/user-center/basic-information/interface-key).
OAuth setups can reuse `MINIMAX_OAUTH_TOKEN` instead.
</Step>
<Step title="Store the key">
Set `MINIMAX_CODE_PLAN_KEY` in the Gateway environment, or configure via:
```bash
openclaw configure --section web
```
</Step>
</Steps>
OpenClaw also accepts `MINIMAX_CODING_API_KEY`, `MINIMAX_OAUTH_TOKEN`, and
`MINIMAX_API_KEY` as env aliases, checked in that order after
`MINIMAX_CODE_PLAN_KEY`. `MINIMAX_API_KEY` should point at a search-enabled
Token Plan credential; ordinary MiniMax model API keys may not be accepted by
the Token Plan search endpoint.
## Config
```json5
{
plugins: {
entries: {
minimax: {
config: {
webSearch: {
apiKey: "sk-cp-...", // optional if a MiniMax Token Plan env var is set
region: "global", // or "cn"
},
},
},
},
},
tools: {
web: {
search: {
provider: "minimax",
},
},
},
}
```
**Environment alternative:** set `MINIMAX_CODE_PLAN_KEY`, `MINIMAX_CODING_API_KEY`,
`MINIMAX_OAUTH_TOKEN`, or `MINIMAX_API_KEY` in the Gateway environment.
For a gateway install, put it in `~/.openclaw/.env`.
## Region selection
MiniMax Search uses these endpoints:
- Global: `https://api.minimax.io/v1/coding_plan/search`
- CN: `https://api.minimaxi.com/v1/coding_plan/search`
If `plugins.entries.minimax.config.webSearch.region` is unset, OpenClaw resolves
the region in this order:
1. `tools.web.search.minimax.region` / plugin-owned `webSearch.region`
2. `MINIMAX_API_HOST`
3. `models.providers.minimax.baseUrl`
4. `models.providers.minimax-portal.baseUrl`
That means CN onboarding or `MINIMAX_API_HOST=https://api.minimaxi.com/...`
automatically keeps MiniMax Search on the CN host too.
Even when you authenticated MiniMax through the OAuth `minimax-portal` path,
web search still registers as provider id `minimax`; the OAuth provider base URL
is used as a region hint for CN/global host selection, and `MINIMAX_OAUTH_TOKEN`
can satisfy the MiniMax Search bearer credential.
## Supported parameters
| Parameter | Type | Constraints | Description |
| --------- | ------- | --------------- | --------------------------------------------------------------------------- |
| `query` | string | required | Search query string. |
| `count` | integer | 1-10, default 5 | Number of results to return. OpenClaw trims the returned list to this size. |
Provider-specific filters are not currently supported.
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [MiniMax](/providers/minimax) -- model, image, speech, and auth setup

View File

@@ -0,0 +1,409 @@
---
summary: "Per-agent sandbox + tool restrictions, precedence, and examples"
title: "Multi-agent sandbox and tools"
sidebarTitle: "Multi-agent sandbox and tools"
read_when: "You want per-agent sandboxing or per-agent tool allow/deny policies in a multi-agent gateway."
status: active
---
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.
<CardGroup cols={3}>
<Card title="Sandboxing" href="/gateway/sandboxing">
Backends and modes — full sandbox reference.
</Card>
<Card title="Sandbox vs tool policy vs elevated" href="/gateway/sandbox-vs-tool-policy-vs-elevated">
Debug "why is this blocked?"
</Card>
<Card title="Elevated mode" href="/tools/elevated">
Elevated exec for trusted senders.
</Card>
</CardGroup>
<Warning>
Auth is scoped by agent: each agent has its own `agentDir` auth store in `~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite`. Never reuse `agentDir` across agents. Agents can read through to the default/main agent's auth profiles when they do not have a local profile, but OAuth refresh tokens are not cloned into secondary agent stores. If you copy credentials manually, copy only portable static `api_key` or `token` profiles.
</Warning>
---
## Configuration examples
<AccordionGroup>
<Accordion title="Example 1: Personal + restricted family agent">
```json
{
"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", "message"],
"deny": ["exec", "write", "edit", "apply_patch", "process", "browser"],
"message": {
"crossContext": {
"allowWithinProvider": false,
"allowAcrossProviders": false
}
}
}
}
]
},
"bindings": [
{
"agentId": "family",
"match": {
"provider": "whatsapp",
"accountId": "*",
"peer": {
"kind": "group",
"id": "120363424282127706@g.us"
}
}
}
]
}
```
**Result:**
- `main` agent: runs on host, full tool access.
- `family` agent: runs in Docker (one container per agent), only `read` and current-conversation message sends.
</Accordion>
<Accordion title="Example 2: Work agent with shared sandbox">
```json
{
"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"]
}
}
]
}
}
```
</Accordion>
<Accordion title="Example 2b: Global coding profile + messaging-only agent">
```json
{
"tools": { "profile": "coding" },
"agents": {
"list": [
{
"id": "support",
"tools": { "profile": "messaging", "allow": ["slack"] }
}
]
}
}
```
**Result:**
- default agents get coding tools.
- `support` agent is messaging-only (+ Slack tool).
</Accordion>
<Accordion title="Example 3: Different sandbox modes per agent">
```json
{
"agents": {
"defaults": {
"sandbox": {
"mode": "non-main",
"scope": "session"
}
},
"list": [
{
"id": "main",
"workspace": "~/.openclaw/workspace",
"sandbox": {
"mode": "off"
}
},
{
"id": "public",
"workspace": "~/.openclaw/workspace-public",
"sandbox": {
"mode": "all",
"scope": "agent"
},
"tools": {
"allow": ["read"],
"deny": ["exec", "write", "edit", "apply_patch"]
}
}
]
}
}
```
</Accordion>
</AccordionGroup>
---
## Configuration precedence
When both global (`agents.defaults.*`) and agent-specific (`agents.list[].*`) configs exist:
### Sandbox config
Agent-specific settings override global:
```text
agents.list[].sandbox.mode > agents.defaults.sandbox.mode
agents.list[].sandbox.scope > agents.defaults.sandbox.scope
agents.list[].sandbox.workspaceRoot > agents.defaults.sandbox.workspaceRoot
agents.list[].sandbox.workspaceAccess > agents.defaults.sandbox.workspaceAccess
agents.list[].sandbox.docker.* > agents.defaults.sandbox.docker.*
agents.list[].sandbox.browser.* > agents.defaults.sandbox.browser.*
agents.list[].sandbox.prune.* > agents.defaults.sandbox.prune.*
```
<Note>
`agents.list[].sandbox.{docker,browser,prune}.*` overrides `agents.defaults.sandbox.{docker,browser,prune}.*` for that agent (ignored when sandbox scope resolves to `"shared"`).
</Note>
### Tool restrictions
The filtering order is:
<Steps>
<Step title="Tool profile">
`tools.profile` or `agents.list[].tools.profile`.
</Step>
<Step title="Provider tool profile">
`tools.byProvider[provider].profile` or `agents.list[].tools.byProvider[provider].profile`.
</Step>
<Step title="Global tool policy">
`tools.allow` / `tools.deny`.
</Step>
<Step title="Provider tool policy">
`tools.byProvider[provider].allow/deny`.
</Step>
<Step title="Agent-specific tool policy">
`agents.list[].tools.allow/deny`.
</Step>
<Step title="Agent provider policy">
`agents.list[].tools.byProvider[provider].allow/deny`.
</Step>
<Step title="Sandbox tool policy">
`tools.sandbox.tools` or `agents.list[].tools.sandbox.tools`.
</Step>
<Step title="Subagent tool policy">
`tools.subagents.tools`, if applicable.
</Step>
</Steps>
<AccordionGroup>
<Accordion title="Precedence rules">
- 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.4`).
</Accordion>
<Accordion title="Empty allowlist behavior">
If any explicit allowlist in that chain leaves the run with no callable tools, OpenClaw stops before submitting the prompt to the model. This is intentional: an agent configured with a missing tool such as `agents.list[].tools.allow: ["query_db"]` should fail loudly until the plugin that registers `query_db` is enabled, not continue as a text-only agent.
</Accordion>
</AccordionGroup>
Tool policies support `group:*` shorthands that expand to multiple tools. See [Tool groups](/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](/tools/elevated) for details.
---
## Migration from single agent
<Tabs>
<Tab title="Before (single agent)">
```json
{
"agents": {
"defaults": {
"workspace": "~/.openclaw/workspace",
"sandbox": {
"mode": "non-main"
}
}
},
"tools": {
"sandbox": {
"tools": {
"allow": ["read", "write", "apply_patch", "exec"],
"deny": []
}
}
}
}
```
</Tab>
<Tab title="After (multi-agent)">
```json
{
"agents": {
"list": [
{
"id": "main",
"default": true,
"workspace": "~/.openclaw/workspace",
"sandbox": { "mode": "off" }
}
]
}
}
```
</Tab>
</Tabs>
<Note>
Legacy `agents.defaults.*`/`agents.list[].*` config keys (such as `sandbox.perSession`, `agentRuntime`, `embeddedPi`) are migrated by `openclaw doctor`; prefer `agents.defaults` + `agents.list` going forward.
</Note>
---
## Tool restriction examples
<Tabs>
<Tab title="Read-only agent">
```json
{
"tools": {
"allow": ["read"],
"deny": ["exec", "write", "edit", "apply_patch", "process"]
}
}
```
</Tab>
<Tab title="Shell execution with filesystem tools disabled">
```json
{
"tools": {
"allow": ["read", "exec", "process"],
"deny": ["write", "edit", "apply_patch", "browser", "gateway"]
}
}
```
<Warning>
This policy disables OpenClaw filesystem tools, but `exec` is still a shell and can write files wherever the selected host or sandbox filesystem allows. For a read-only agent, deny `exec` and `process`, or combine shell access with sandbox filesystem controls such as `agents.defaults.sandbox.workspaceAccess: "ro"` or `"none"`.
</Warning>
</Tab>
<Tab title="Communication-only">
```json
{
"tools": {
"sessions": { "visibility": "tree" },
"allow": ["sessions_list", "sessions_send", "sessions_history", "session_status"],
"deny": ["exec", "write", "edit", "apply_patch", "read", "browser"]
}
}
```
`sessions_history` in this profile still returns a bounded, sanitized recall view rather than a raw transcript dump. Assistant recall strips thinking tags, `<relevant-memories>` scaffolding, plain-text tool-call XML payloads (including `<tool_call>...</tool_call>`, `<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`, `<function_calls>...</function_calls>`, and truncated tool-call blocks), downgraded tool-call scaffolding, leaked ASCII/full-width model control tokens, and malformed MiniMax tool-call XML before redaction/truncation.
</Tab>
</Tabs>
---
## Common pitfall: "non-main"
<Warning>
`agents.defaults.sandbox.mode: "non-main"` checks the session key against the main session key (always `"main"`; `session.mainKey` is not user-configurable, and OpenClaw warns and ignores any other value), 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"`.
</Warning>
---
## Testing
After configuring multi-agent sandbox and tools:
<Steps>
<Step title="Check agent resolution">
```bash
openclaw agents list --bindings
```
</Step>
<Step title="Verify sandbox containers">
```bash
docker ps --filter "name=openclaw-sbx-"
```
</Step>
<Step title="Test tool restrictions">
- Send a message requiring restricted tools.
- Verify the agent cannot use denied tools.
</Step>
<Step title="Monitor logs">
```bash
openclaw logs --follow | grep -E "routing|sandbox|tools"
```
</Step>
</Steps>
---
## Troubleshooting
<AccordionGroup>
<Accordion title="Agent not sandboxed despite `mode: 'all'`">
- Check if there's a global `agents.defaults.sandbox.mode` that overrides it.
- Agent-specific config takes precedence, so set `agents.list[].sandbox.mode: "all"`.
</Accordion>
<Accordion title="Tools still available despite deny list">
- Check the [full filtering order](#tool-restrictions): profile → provider profile → global policy → provider policy → agent policy → agent provider policy → sandbox → subagent.
- Each level can only further restrict, not grant back.
- See [Sandbox vs tool policy vs elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) for step-by-step debugging.
</Accordion>
<Accordion title="Container not isolated per agent">
- Default `scope` is `"agent"` (one container per agent id).
- Set `scope: "session"` for one container per session, or `scope: "shared"` to reuse one container across agents.
</Accordion>
</AccordionGroup>
---
## Related
- [Elevated mode](/tools/elevated)
- [Multi-agent routing](/concepts/multi-agent)
- [Sandbox configuration](/gateway/config-agents#agentsdefaultssandbox)
- [Sandbox vs tool policy vs elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) — debugging "why is this blocked?"
- [Sandboxing](/gateway/sandboxing) — full sandbox reference (modes, scopes, backends, images)
- [Session management](/concepts/session)

View File

@@ -0,0 +1,390 @@
---
summary: "Generate music via music_generate across ComfyUI, fal, Google Lyria, MiniMax, and OpenRouter workflows"
read_when:
- Generating music or audio via the agent
- Configuring music-generation providers and models
- Understanding the music_generate tool parameters
title: "Music generation"
sidebarTitle: "Music generation"
---
The `music_generate` tool creates music or audio through the shared
music-generation capability, backed by ComfyUI, fal, Google, MiniMax, and
OpenRouter.
<Note>
`music_generate` only appears when at least one music-generation provider is
available: an explicit `agents.defaults.musicGenerationModel` config, or an
auth-configured provider (a set API key, for example).
</Note>
For session-backed agent runs, `music_generate` starts as a background task,
tracks progress in the task ledger, then wakes the agent when the track is
ready so it can tell the user and attach the finished audio. The completion
agent follows the session's visible-reply contract: automatic final reply
when configured, or `message(action="send")` when the session requires the
message tool. If the requester session is inactive or its wake fails and
generated audio is still missing from the reply, OpenClaw sends an
idempotent direct fallback with just the missing audio.
## Quick start
<Tabs>
<Tab title="Shared provider-backed">
<Steps>
<Step title="Configure auth">
Set an API key for at least one provider — for example
`GEMINI_API_KEY` or `MINIMAX_API_KEY`.
</Step>
<Step title="Pick a default model (optional)">
```json5
{
agents: {
defaults: {
musicGenerationModel: {
primary: "google/lyria-3-clip-preview",
},
},
},
}
```
</Step>
<Step title="Ask the agent">
_"Generate an upbeat synthpop track about a night drive through a
neon city."_
The agent calls `music_generate` automatically. No tool
allow-listing needed.
</Step>
</Steps>
Without a session-backed agent run (direct/local contexts), the tool
runs inline and returns the final media path in the same tool result.
</Tab>
<Tab title="ComfyUI workflow">
<Steps>
<Step title="Configure the workflow">
Configure `plugins.entries.comfy.config.music` with a workflow
JSON and prompt/output nodes.
</Step>
<Step title="Cloud auth (optional)">
For Comfy Cloud, set `COMFY_API_KEY` or `COMFY_CLOUD_API_KEY`.
</Step>
<Step title="Call the tool">
```text
/tool music_generate prompt="Warm ambient synth loop with soft tape texture"
```
</Step>
</Steps>
</Tab>
</Tabs>
Example prompts:
```text
Generate a cinematic piano track with soft strings and no vocals.
```
```text
Generate an energetic chiptune loop about launching a rocket at sunrise.
```
Use `action: "list"` to inspect available providers/models, and
`action: "status"` to inspect the active session-backed music task:
```text
/tool music_generate action=list
/tool music_generate action=status
```
Direct generation example:
```text
/tool music_generate prompt="Dreamy lo-fi hip hop with vinyl texture and gentle rain" instrumental=true
```
## Supported providers
| Provider | Default model | Reference inputs | Supported controls | Auth |
| ---------- | ---------------------------- | ---------------- | ----------------------------------------------------- | -------------------------------------- |
| ComfyUI | `workflow` | Up to 1 image | Workflow-defined music or audio | `COMFY_API_KEY`, `COMFY_CLOUD_API_KEY` |
| fal | `fal-ai/minimax-music/v2.6` | None | `lyrics`, `instrumental`, `durationSeconds`, `format` | `FAL_KEY` or `FAL_API_KEY` |
| Google | `lyria-3-clip-preview` | Up to 10 images | `lyrics`, `instrumental`, `format` | `GEMINI_API_KEY`, `GOOGLE_API_KEY` |
| MiniMax | `music-2.6` | None | `lyrics`, `instrumental`, `format` (mp3 only) | `MINIMAX_API_KEY` or MiniMax OAuth |
| OpenRouter | `google/lyria-3-pro-preview` | Up to 1 image | `lyrics`, `instrumental`, `durationSeconds`, `format` | `OPENROUTER_API_KEY` |
MiniMax registers two provider ids sharing the same models: `minimax` for
API-key auth and `minimax-portal` for OAuth. Model refs follow the auth path
(`minimax/music-2.6` vs `minimax-portal/music-2.6`); see
[MiniMax](/providers/minimax#music-generation).
fal also exposes `fal-ai/ace-step/prompt-to-audio` (wav, no lyrics, no
instrumental toggle) and `fal-ai/stable-audio-25/text-to-audio` (wav,
prompt-only) alongside its default MiniMax-backed model. Google's default
`lyria-3-clip-preview` outputs mp3 only; `lyria-3-pro-preview` also supports
wav. MiniMax also exposes `music-2.6-free`, `music-cover`, and
`music-cover-free`. OpenRouter also exposes `google/lyria-3-clip-preview`.
### Capability matrix
The explicit mode contract used by `music_generate`, contract tests, and the
shared live sweep:
| Provider | `generate` | `edit` | Edit limit | Shared live lanes |
| ---------- | :--------: | :----: | ---------- | ------------------------------------------------------------------------- |
| ComfyUI | ✓ | ✓ | 1 image | Not in the shared sweep; covered by `extensions/comfy/comfy.live.test.ts` |
| fal | ✓ | — | None | `generate` |
| Google | ✓ | ✓ | 10 images | `generate`, `edit` |
| MiniMax | ✓ | — | None | `generate` |
| OpenRouter | ✓ | ✓ | 1 image | `generate`, `edit` |
## Tool parameters
<ParamField path="prompt" type="string" required>
Music generation prompt. Required for `action: "generate"`.
</ParamField>
<ParamField path="action" type='"generate" | "status" | "list"' default="generate">
`"status"` returns the current session task; `"list"` inspects providers.
</ParamField>
<ParamField path="model" type="string">
Provider/model override (e.g. `google/lyria-3-pro-preview`,
`comfy/workflow`).
</ParamField>
<ParamField path="lyrics" type="string">
Optional lyrics when the provider supports explicit lyric input.
</ParamField>
<ParamField path="instrumental" type="boolean">
Request instrumental-only output when the provider supports it.
</ParamField>
<ParamField path="image" type="string">
Single reference image path or URL.
</ParamField>
<ParamField path="images" type="string[]">
Multiple reference images (up to 10 on supporting providers).
</ParamField>
<ParamField path="durationSeconds" type="number">
Target duration in seconds when the provider supports duration hints.
</ParamField>
<ParamField path="format" type='"mp3" | "wav"'>
Output format hint when the provider supports it.
</ParamField>
<ParamField path="filename" type="string">Output filename hint.</ParamField>
<Note>
Not all providers support all parameters. OpenClaw still validates hard
limits such as input counts before submission. When a provider supports
duration but uses a shorter maximum than the requested value, OpenClaw
clamps to the closest supported duration. Truly unsupported optional hints
are ignored with a warning when the selected provider or model cannot honor
them. Tool results report applied settings; `details.normalization`
captures any requested-to-applied mapping.
</Note>
Provider request timeouts are operator configuration only. OpenClaw uses
`agents.defaults.musicGenerationModel.timeoutMs` when configured, raises
values below 120000ms to 120000ms, and otherwise defaults provider requests
to 300000ms.
## Async behavior
Session-backed music generation runs as a background task:
- **Background task:** `music_generate` creates a background task, returns a
started/task response immediately, and posts the finished track later in
a follow-up agent message.
- **Duplicate prevention:** while a task is `queued` or `running`, later
`music_generate` calls in the same session return task status instead of
starting another generation. Use `action: "status"` to check explicitly.
A recently completed matching request is also deduplicated for 2 minutes.
- **Status lookup:** `openclaw tasks list` or `openclaw tasks show <taskId>`
inspects queued, running, and terminal status.
- **Completion wake:** OpenClaw injects an internal completion event back
into the same session so the model can write the user-facing follow-up
itself.
- **Prompt hint:** later user/manual turns in the same session get a small
runtime hint when a music task is already in flight, so the model does
not blindly call `music_generate` again.
- **No-session fallback:** direct/local contexts without a real agent
session run inline and return the final audio result in the same turn.
### Task lifecycle
The music task surfaces the same states as the general task registry (see
[Background tasks](/automation/tasks#task-lifecycle) for the full state
machine, including `timed_out`, `cancelled`, and `lost`). Most music runs
move through:
| State | Meaning |
| ----------- | ---------------------------------------------------------------------------------------------- |
| `queued` | Task created, waiting for the provider to accept it. |
| `running` | Provider is processing (typically 30 seconds to 3 minutes depending on provider and duration). |
| `succeeded` | Track ready; the agent wakes and posts it to the conversation. |
| `failed` | Provider error or timeout; the agent wakes with error details. |
Check status from the CLI:
```bash
openclaw tasks list
openclaw tasks show <taskId>
openclaw tasks cancel <taskId>
```
## Configuration
### Model selection
```json5
{
agents: {
defaults: {
musicGenerationModel: {
primary: "google/lyria-3-clip-preview",
fallbacks: ["fal/fal-ai/minimax-music/v2.6", "minimax/music-2.6"],
},
},
},
}
```
### Provider selection order
OpenClaw tries providers in this order:
1. `model` parameter from the tool call (if the agent specifies one).
2. `musicGenerationModel.primary` from config.
3. `musicGenerationModel.fallbacks` in order.
4. Auto-detection using auth-backed provider defaults only:
- current default text-model provider first, if it also offers music
generation;
- remaining registered music-generation providers, alphabetically by
provider id.
If a provider fails, the next candidate is tried automatically. If all
fail, the error includes details from each attempt.
Set `agents.defaults.mediaGenerationAutoProviderFallback: false` to use only
explicit `model`, `primary`, and `fallbacks` entries.
## Provider notes
<AccordionGroup>
<Accordion title="ComfyUI">
Workflow-driven and depends on the configured graph plus node mapping
for prompt/output fields. The bundled `comfy` plugin plugs into the
shared `music_generate` tool through the music-generation provider
registry.
</Accordion>
<Accordion title="fal">
Uses fal model endpoints through the shared provider auth path. The
bundled provider defaults to `fal-ai/minimax-music/v2.6` and also exposes
`fal-ai/ace-step/prompt-to-audio` and
`fal-ai/stable-audio-25/text-to-audio` for prompt-to-audio requests.
Lyrics and instrumental mode are MiniMax-model-only; the other two
models are prompt-only.
</Accordion>
<Accordion title="Google (Lyria 3)">
Uses Lyria 3 batch generation. The current bundled flow supports
prompt, optional lyrics text, and optional reference images. The
default `lyria-3-clip-preview` model outputs mp3 only; the
`lyria-3-pro-preview` model also supports wav.
</Accordion>
<Accordion title="MiniMax">
Uses the batch `music_generation` endpoint. Supports prompt, optional
lyrics, instrumental mode, and mp3 output through either `minimax`
API-key auth or `minimax-portal` OAuth. Also exposes `music-2.6-free`,
`music-cover`, and `music-cover-free` models.
</Accordion>
<Accordion title="OpenRouter">
Uses OpenRouter chat completions audio output with streaming enabled. The
bundled provider defaults to `google/lyria-3-pro-preview` and also exposes
`openrouter/google/lyria-3-clip-preview`.
</Accordion>
</AccordionGroup>
## Choosing the right path
- **Shared provider-backed** when you want model selection, provider
failover, and the built-in async task/status flow.
- **Plugin path (ComfyUI)** when you need a custom workflow graph or a
provider that is not part of the shared bundled music capability.
If you are debugging ComfyUI-specific behavior, see
[ComfyUI](/providers/comfy). If you are debugging shared provider
behavior, start with [fal](/providers/fal), [Google (Gemini)](/providers/google),
[MiniMax](/providers/minimax), or [OpenRouter](/providers/openrouter).
## Provider capability modes
The shared music-generation contract supports explicit mode declarations:
- `generate` for prompt-only generation.
- `edit` when the request includes one or more reference images.
New provider implementations should prefer explicit mode blocks:
```typescript
capabilities: {
generate: {
maxTracks: 1,
supportsLyrics: true,
supportsFormat: true,
},
edit: {
enabled: true,
maxTracks: 1,
maxInputImages: 1,
supportsFormat: true,
},
}
```
Legacy flat fields such as `maxInputImages`, `supportsLyrics`, and
`supportsFormat` are **not** enough to advertise edit support. Providers
should declare `generate` and `edit` explicitly so live tests, contract
tests, and the shared `music_generate` tool can validate mode support
deterministically.
## Live tests
Opt-in live coverage for the shared bundled providers (fal, Google, MiniMax,
OpenRouter):
```bash
OPENCLAW_LIVE_TEST=1 pnpm test:live -- extensions/music-generation-providers.live.test.ts
```
Equivalent repo wrapper, which drives the same test file:
```bash
pnpm test:live:media:music
```
This live file uses already-exported provider env vars ahead of stored auth
profiles by default, and runs both `generate` and declared `edit` coverage when
the provider enables edit mode. Coverage today:
- `google`: `generate` plus `edit`
- `fal`: `generate` only
- `minimax`: `generate` only
- `openrouter`: `generate` plus `edit`
- `comfy`: separate Comfy live coverage, not the shared provider sweep
Opt-in live coverage for the bundled ComfyUI music path:
```bash
OPENCLAW_LIVE_TEST=1 COMFY_LIVE_TEST=1 pnpm test:live -- extensions/comfy/comfy.live.test.ts
```
The Comfy live file also covers comfy image and video workflows when those
sections are configured.
## Related
- [Background tasks](/automation/tasks) — task tracking for detached `music_generate` runs
- [ComfyUI](/providers/comfy)
- [Configuration reference](/gateway/config-agents#agent-defaults) — `musicGenerationModel` config
- [Google (Gemini)](/providers/google)
- [MiniMax](/providers/minimax)
- [Models](/concepts/models) — model configuration and failover
- [Tools overview](/tools)

143
docs/tools/ollama-search.md Normal file
View File

@@ -0,0 +1,143 @@
---
summary: "Ollama Web Search via a local Ollama host or the hosted Ollama API"
read_when:
- You want to use Ollama for web_search
- You want a key-free web_search provider
- You want to use hosted Ollama Web Search with OLLAMA_API_KEY
- You need Ollama Web Search setup guidance
title: "Ollama web search"
---
OpenClaw supports **Ollama Web Search** as a bundled `web_search` provider,
returning titles, URLs, and snippets from Ollama's web-search API.
Local/self-hosted Ollama needs no API key by default; it requires a reachable
Ollama host plus `ollama signin`. Direct hosted search (no local Ollama) needs
`baseUrl: "https://ollama.com"` and a real `OLLAMA_API_KEY`.
## Setup
<Steps>
<Step title="Start Ollama">
Make sure Ollama is installed and running.
</Step>
<Step title="Sign in">
```bash
ollama signin
```
</Step>
<Step title="Choose Ollama Web Search">
```bash
openclaw configure --section web
```
Select **Ollama Web Search** as the provider.
</Step>
</Steps>
If you already use Ollama for models, Ollama Web Search reuses the same
configured host.
<Note>
OpenClaw never auto-selects Ollama Web Search over a higher-priority
credentialed provider; you must choose it explicitly with
`tools.web.search.provider: "ollama"`.
</Note>
## Config
```json5
{
tools: {
web: {
search: {
provider: "ollama",
},
},
},
}
```
Optional host override, scoped to web search only:
```json5
{
plugins: {
entries: {
ollama: {
config: {
webSearch: {
baseUrl: "http://ollama-host:11434",
},
},
},
},
},
}
```
Or reuse the host already configured for the Ollama model provider:
```json5
{
models: {
providers: {
ollama: {
baseUrl: "http://ollama-host:11434",
},
},
},
}
```
`models.providers.ollama.baseUrl` is the canonical key; the web-search
provider also accepts `baseURL` there for compatibility with OpenAI SDK-style
config examples. If nothing is set, OpenClaw defaults to
`http://127.0.0.1:11434`.
Direct hosted Ollama Web Search (no local Ollama):
```json5
{
models: {
providers: {
ollama: {
baseUrl: "https://ollama.com",
apiKey: "OLLAMA_API_KEY",
},
},
},
tools: {
web: {
search: {
provider: "ollama",
},
},
},
}
```
## Auth and request routing
- No web-search-specific API key field exists; the provider reuses
`models.providers.ollama.apiKey` (or the matching env-backed provider auth)
when the configured host is auth-protected.
- Host resolution order: `plugins.entries.ollama.config.webSearch.baseUrl` →
`models.providers.ollama.baseUrl` (or `baseURL`) → `http://127.0.0.1:11434`.
- If the resolved host is `https://ollama.com`, OpenClaw calls
`https://ollama.com/api/web_search` directly with the API key as bearer
auth.
- Otherwise OpenClaw calls the local proxy endpoint
`/api/experimental/web_search` first (which signs and forwards to Ollama
Cloud), then falls back to `/api/web_search` on the same host. If both fail
and `OLLAMA_API_KEY` is set, it retries once against
`https://ollama.com/api/web_search` with that key — without sending it to
the local host.
- OpenClaw warns during setup if Ollama is unreachable or not signed in, but
does not block selecting the provider.
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [Ollama](/providers/ollama) -- Ollama model setup and cloud/local modes

View File

@@ -0,0 +1,166 @@
---
summary: "Parallel Search -- LLM-optimized dense excerpts from web sources"
read_when:
- You want web search without an API key
- You want Parallel's paid Search API
- You want dense excerpts ranked for LLM context efficiency
title: "Parallel search"
---
The Parallel plugin provides two [Parallel](https://parallel.ai/) `web_search`
providers, both returning ranked, LLM-optimized excerpts from a web index
built for AI agents:
| Provider | id | Auth |
| ---------------------- | --------------- | ------------------------------------------------------------------------------------------ |
| Parallel Search (Free) | `parallel-free` | None -- Parallel's free [Search MCP](https://docs.parallel.ai/integrations/mcp/search-mcp) |
| Parallel Search | `parallel` | `PARALLEL_API_KEY` -- paid Search API, higher rate limits and objective tuning |
Set `tools.web.search.provider` to `parallel-free` or `parallel` to select
one explicitly; neither is auto-detected.
<Note>
Direct OpenAI Responses models (`api: "openai-responses"`, provider
`openai`, official API base URL) use OpenAI's hosted native web search
automatically when `tools.web.search.provider` is unset, empty, `"auto"`,
or `"openai"` -- so they bypass Parallel by default. Set
`tools.web.search.provider` to `parallel-free` or `parallel` to route them
through Parallel instead. See [Web Search overview](/tools/web).
</Note>
## Install plugin
```bash
openclaw plugins install @openclaw/parallel-plugin
openclaw gateway restart
```
## API key (paid provider)
`parallel-free` needs no key but still must be selected explicitly. The paid
`parallel` provider needs an API key:
<Steps>
<Step title="Create an account">
Sign up at [platform.parallel.ai](https://platform.parallel.ai) and
generate an API key from your dashboard.
</Step>
<Step title="Store the key">
Set `PARALLEL_API_KEY` in the Gateway environment, or configure via:
```bash
openclaw configure --section web
```
</Step>
</Steps>
## Config
```json5
{
plugins: {
entries: {
parallel: {
config: {
webSearch: {
apiKey: "par-...", // optional if PARALLEL_API_KEY is set
baseUrl: "https://api.parallel.ai", // optional; OpenClaw appends /v1/search
},
},
},
},
},
tools: {
web: {
search: {
// "parallel-free" for the free Search MCP, or "parallel" for the
// paid API-backed provider shown here.
provider: "parallel",
},
},
},
}
```
**Environment alternative:** set `PARALLEL_API_KEY` in the Gateway
environment. For a gateway install, put it in `~/.openclaw/.env`.
## Base URL override
Applies to the paid `parallel` provider only; `parallel-free` always uses
`https://search.parallel.ai/mcp` and ignores this setting.
Set `plugins.entries.parallel.config.webSearch.baseUrl` to route paid
requests through a compatible proxy or alternate endpoint (for example, the
Cloudflare AI Gateway). OpenClaw normalizes bare hosts by prepending
`https://` and appends `/v1/search` unless the path already ends there. The
resolved endpoint is part of the search cache key, so results from different
endpoints are never shared.
## Tool parameters
Both providers expose Parallel's native search shape so the model fills in a
natural-language goal plus a few short keyword queries -- the pairing
Parallel [recommends](https://docs.parallel.ai/search/best-practices) for
best results.
<ParamField path="objective" type="string" required>
Natural-language description of the underlying question or goal (max 5000
chars). Should be self-contained.
</ParamField>
<ParamField path="search_queries" type="string[]" required>
Concise keyword search queries, 3-6 words each (1-5 entries, max 200 chars
each). Provide 2-3 diverse queries for best results.
</ParamField>
<ParamField path="count" type="number">
Results to return (1-40).
</ParamField>
<ParamField path="session_id" type="string">
Optional Parallel session id from a previous result's `sessionId`. Pass it on
follow-up searches in the same task so Parallel groups related calls and
improves subsequent results. Max 1000 chars on `parallel`; the free
`parallel-free` Search MCP caps it at 100. An id past the limit is dropped
(paid) or a fresh one is minted (free).
</ParamField>
<ParamField path="client_model" type="string">
Optional identifier of the model making the call (e.g. `claude-opus-4-7`,
`gpt-5.5`), max 100 chars. Lets Parallel tailor default settings for your
model's capabilities. Pass the exact active model slug; do not shorten to a
family alias.
</ParamField>
## Notes
- Parallel ranks and compresses results for LLM reasoning utility, not human
click-through; expect dense excerpts per result rather than full-page
content.
- Result excerpts come back as the `excerpts` array and are also joined into
`description` for compatibility with the generic `web_search` contract.
- Both providers return a `session_id`; OpenClaw surfaces it as `sessionId` in
the tool payload so callers can group follow-up searches. A
Parallel-generated session id (one the caller did not supply) is excluded
from the cache entry, since unrelated tasks with identical queries should
not inherit it.
- `searchId`, `warnings`, and `usage` from Parallel are passed through when
present.
- OpenClaw always forwards a resolved result count to Parallel as
`advanced_settings.max_results` (`parallel`) or applies `count`
client-side after Parallel's fixed-size response (`parallel-free`). The
caller's `count` arg wins, then `tools.web.search.maxResults`, otherwise
OpenClaw's generic `web_search` default (5) -- Parallel's own API defaults
to 10.
- Results are cached for 15 minutes by default (`cacheTtlMinutes`).
- `parallel-free` mints a fresh `session_id` per call via its MCP handshake
when the caller does not supply one; `parallel` leaves it unset in that
case.
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [Exa search](/tools/exa-search) -- neural search with content extraction
- [Perplexity Search](/tools/perplexity-search) -- structured results with domain filtering

189
docs/tools/pdf.md Normal file
View File

@@ -0,0 +1,189 @@
---
summary: "Analyze one or more PDF documents with native provider support and extraction fallback"
title: "PDF tool"
read_when:
- You want to analyze PDFs from agents
- You need exact pdf tool parameters and limits
- You are debugging native PDF mode vs extraction fallback
---
`pdf` analyzes one or more PDF documents and returns text. It uses native document input on Anthropic and Google models, and falls back to text/image extraction for every other provider.
## Availability
The tool registers only when OpenClaw can resolve a PDF-capable model for the agent. Resolution order:
1. `agents.defaults.pdfModel` (explicit primary/fallbacks)
2. `agents.defaults.imageModel` (explicit primary/fallbacks)
3. The agent's resolved session/default model, if its provider supports native PDF input (Anthropic, Google) or already has a configured vision model
4. Auto-detected image/vision-capable providers with usable auth, preferring native-PDF providers first
Every fallback candidate is auth-checked before use, so a configured `provider/model` only counts if OpenClaw can authenticate that provider for the agent. If no usable model resolves, the `pdf` tool is not exposed.
## Input reference
<ParamField path="pdf" type="string">
One PDF path or URL.
</ParamField>
<ParamField path="pdfs" type="string[]">
Multiple PDF paths or URLs, up to 10 total.
</ParamField>
<ParamField path="prompt" type="string" default="Analyze this PDF document.">
Analysis prompt.
</ParamField>
<ParamField path="pages" type="string">
Page filter like `1-5` or `1,3,7-9`. Not supported in native provider mode.
</ParamField>
<ParamField path="password" type="string">
Password for encrypted PDFs. Applies to every PDF in the request; only used by extraction fallback mode.
</ParamField>
<ParamField path="model" type="string">
Optional model override in `provider/model` form.
</ParamField>
<ParamField path="maxBytesMb" type="number">
Per-PDF size cap in MB. Defaults to `agents.defaults.pdfMaxBytesMb`, or `10` if unset.
</ParamField>
Notes:
- `pdf` and `pdfs` are merged and deduplicated before loading; at least one is required.
- `pages` is parsed as 1-based page numbers, deduped, sorted, and clamped to `agents.defaults.pdfMaxPages` (default `20`). A range that matches no in-bounds pages errors before the model call.
## Supported PDF references
- Local file path (including `~` expansion)
- `file://` URL
- `http://` and `https://` URL
- OpenClaw-managed inbound refs such as `media://inbound/<id>`
Other URI schemes (for example `ftp://`) return `details.error = "unsupported_pdf_reference"`. Remote `http(s)` URLs are rejected when the tool runs sandboxed. With workspace-only file policy enabled, local paths outside allowed roots are rejected; managed inbound refs and replayed paths under OpenClaw's inbound media store are still allowed.
## Execution modes
### Native provider mode
Used for provider `anthropic` and `google` (the only providers that currently declare native PDF document support). Raw PDF bytes go directly to the provider API as a native document/inline-PDF part per file.
Limits:
- `pages` is not supported; if set, the tool throws `pages is not supported with native PDF providers`.
- `password` is not supported; if set, the tool throws `password is not supported with native PDF providers`. Use a non-native model for encrypted PDFs.
### Extraction fallback mode
Used for every other provider.
1. Extract text from the selected pages (up to `agents.defaults.pdfMaxPages`, default `20`) via the bundled `document-extract` plugin, which uses the `clawpdf` package (PDFium WebAssembly) for text and image extraction.
2. If the extracted text is shorter than `200` characters, render the same pages to PNG images. The render budget is `4,000,000` pixels total, shared across all pages needing images (allocated proportionally per remaining page, not per page), so text pages that already have enough text skip rendering entirely.
3. Send the extracted text (and any rendered images) plus the prompt to the selected model.
Details:
- Encrypted PDFs open with the top-level `password` parameter.
- If the model has no image input and there is no extractable text, the tool errors.
- If image rendering fails, OpenClaw drops the images and continues with the extracted text.
- If the target model is text-only and extraction produced images, OpenClaw drops the images and sends text only.
## Config
```json5
{
agents: {
defaults: {
pdfModel: {
primary: "anthropic/claude-opus-4-6",
fallbacks: ["openai/gpt-5.4-mini"],
},
pdfMaxBytesMb: 10,
pdfMaxPages: 20,
},
},
}
```
| Key | Default | Meaning |
| ------------------------------- | ------- | ----------------------------------------------------------------------------------------- |
| `agents.defaults.pdfModel` | unset | Explicit primary/fallback PDF models; falls back to `imageModel`, then the session model. |
| `agents.defaults.pdfMaxBytesMb` | `10` | Per-PDF size cap in MB. |
| `agents.defaults.pdfMaxPages` | `20` | Max pages processed per PDF. |
See [Configuration Reference](/gateway/config-agents#agent-defaults) for full field 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
| Condition | Result |
| --------------------------------- | -------------------------------------------------------------- |
| No PDF input | Throws `pdf required: provide a path or URL to a PDF document` |
| More than 10 PDFs | `details.error = "too_many_pdfs"` |
| Unsupported reference scheme | `details.error = "unsupported_pdf_reference"` |
| `pages` with a native provider | Throws `pages is not supported with native PDF providers` |
| `password` with a native provider | Throws `password is not supported with native PDF providers` |
## Examples
Single PDF:
```json
{
"pdf": "/tmp/report.pdf",
"prompt": "Summarize this report in 5 bullets"
}
```
Multiple PDFs:
```json
{
"pdfs": ["/tmp/q1.pdf", "/tmp/q2.pdf"],
"prompt": "Compare risks and timeline changes across both documents"
}
```
Page-filtered fallback model:
```json
{
"pdf": "https://example.com/report.pdf",
"pages": "1-3,7",
"model": "openai/gpt-5.4-mini",
"prompt": "Extract only customer-impacting incidents"
}
```
Encrypted PDF with extraction fallback:
```json
{
"pdf": "/tmp/locked.pdf",
"password": "example-password",
"model": "openai/gpt-5.4-mini",
"prompt": "Summarize this contract"
}
```
## Related
- [Tools Overview](/tools) - all available agent tools
- [Configuration Reference](/gateway/config-agents#agent-defaults) - pdfMaxBytesMb and pdfMaxPages config

View File

@@ -0,0 +1,111 @@
---
summary: "Permission modes for host exec, Codex Guardian approvals, and ACPX harness sessions"
read_when:
- Choosing auto, ask, allowlist, full, or deny for command permissions
- Configuring Codex Guardian-reviewed approvals through tools.exec.mode
- Comparing OpenClaw exec approvals with ACPX harness permissions
title: "Permission modes"
---
Permission modes decide how much authority an agent has before it runs host commands, writes files, or asks a backend harness for extra access.
<Note>
Permission mode is separate from `tools.exec.host=auto`. `tools.exec.host`
chooses where a command runs. `tools.exec.mode` chooses how host exec is
approved.
</Note>
## Recommended default
Use `auto` for coding agents that need useful host access without making every miss a human prompt:
```bash
openclaw config set tools.exec.mode auto
openclaw approvals get
openclaw gateway restart
```
Then verify the effective policy:
```bash
openclaw exec-policy show
```
## OpenClaw host exec modes
`tools.exec.mode` is the normalized policy surface for host `exec`. Each mode resolves to an underlying `security` (allowlist strictness) and `ask` (prompt-on-miss) pair:
| Mode | security / ask | Behavior | Use when |
| ----------- | ----------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `deny` | `deny` / `off` | Block host exec entirely. | No host commands are allowed. |
| `allowlist` | `allowlist` / `off` | Run only allowlisted commands; silently deny misses. | You have a known-safe command set. |
| `ask` | `allowlist` / `on-miss` | Run allowlist matches; ask a human on misses. | A human should review every new command. |
| `auto` | `allowlist` / `on-miss` | Run allowlist matches; send misses through auto-review before falling back to human approval. | Coding sessions need practical guarded access. |
| `full` | `full` / `off` | Run host exec without prompts. | This trusted host/session should skip approval gates. |
`ask` and `auto` share the same allowlist/ask settings; `auto` additionally enables the native auto-reviewer, which decides misses itself and only defers to the configured human approval route when it cannot safely approve.
For the full host exec policy, local approvals file, allowlist schema, safe bins, and forwarding behavior, see [Exec approvals](/tools/exec-approvals).
## Codex Guardian mapping
For native Codex app-server sessions, `tools.exec.mode: "auto"` drives Codex toward Guardian-reviewed approvals when the local Codex requirements allow it. Typical resulting values:
| Codex field | Typical value |
| ------------------- | ----------------- |
| `approvalPolicy` | `on-request` |
| `approvalsReviewer` | `auto_review` |
| `sandbox` | `workspace-write` |
`auto` mode forces this policy over any configured Codex sandbox/approval overrides, so it does not preserve legacy unsafe combinations such as `approvalPolicy: "never"` with `sandbox: "danger-full-access"`. `tools.exec.mode: "deny"` and `"allowlist"` block Codex app-server local execution entirely. Use `tools.exec.mode: "full"` only when you intentionally want the no-approval posture.
For app-server setup, auth order, and native Codex runtime details, see [Codex harness](/plugins/codex-harness).
## ACPX harness permissions
ACPX sessions are non-interactive, so they cannot click a TTY permission prompt. ACPX uses separate harness-level settings under `plugins.entries.acpx.config`:
| Setting | Values | Meaning |
| --------------------------- | --------------- | ------------------------------------------- |
| `permissionMode` | `approve-reads` | Auto-approve reads only. |
| `permissionMode` | `approve-all` | Auto-approve writes and shell commands. |
| `permissionMode` | `deny-all` | Deny all permission prompts. |
| `nonInteractivePermissions` | `fail` | Abort when a prompt would be required. |
| `nonInteractivePermissions` | `deny` | Deny the prompt and continue when possible. |
Set ACPX permissions separately from OpenClaw exec approvals:
```bash
openclaw config set plugins.entries.acpx.config.permissionMode approve-all
openclaw config set plugins.entries.acpx.config.nonInteractivePermissions fail
openclaw gateway restart
```
Use `approve-all` as the ACPX break-glass equivalent of a no-prompt harness session. For setup details and failure modes, see [ACP agents setup](/tools/acp-agents-setup#permission-configuration).
## Choosing a mode
| Goal | Configure |
| --------------------------------------------- | ----------------------------------------------------------- |
| Block host commands completely | `tools.exec.mode: "deny"` |
| Let known-safe commands run only | `tools.exec.mode: "allowlist"` |
| Ask a human for every new command shape | `tools.exec.mode: "ask"` |
| Use Codex/OpenClaw auto-review before humans | `tools.exec.mode: "auto"` |
| Skip host exec approvals entirely | `tools.exec.mode: "full"` plus matching host approvals file |
| Make non-interactive ACPX sessions write/exec | `plugins.entries.acpx.config.permissionMode: "approve-all"` |
If a command still prompts or fails after changing mode, inspect both layers:
```bash
openclaw approvals get
openclaw exec-policy show
```
Host exec uses the stricter result of OpenClaw config and the host-local approvals file. ACPX harness permissions do not loosen host exec approvals, and host exec approvals do not loosen ACPX harness prompts.
## Related
- [Exec approvals](/tools/exec-approvals)
- [Exec approvals - advanced](/tools/exec-approvals-advanced)
- [Codex harness](/plugins/codex-harness)
- [ACP agents setup](/tools/acp-agents-setup#permission-configuration)

View File

@@ -0,0 +1,220 @@
---
summary: "Perplexity Search API and Sonar/OpenRouter compatibility for web_search"
read_when:
- You want to use Perplexity Search for web search
- You need PERPLEXITY_API_KEY or OPENROUTER_API_KEY setup
title: "Perplexity search"
---
OpenClaw supports the 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.
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/perplexity-plugin
openclaw gateway restart
```
## 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:
- `plugins.entries.perplexity.config.webSearch.baseUrl`
- `plugins.entries.perplexity.config.webSearch.model`
## Config examples
### Native Perplexity Search API
```json5
{
plugins: {
entries: {
perplexity: {
config: {
webSearch: {
apiKey: "pplx-...",
},
},
},
},
},
tools: {
web: {
search: {
provider: "perplexity",
},
},
},
}
```
### OpenRouter / Sonar compatibility
```json5
{
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](/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.
<ParamField path="query" type="string" required>
Search query.
</ParamField>
<ParamField path="count" type="number" default="5">
Number of results to return (1-10).
</ParamField>
<ParamField path="country" type="string">
2-letter ISO country code (e.g. `US`, `DE`).
</ParamField>
<ParamField path="language" type="string">
ISO 639-1 language code (e.g. `en`, `de`, `fr`).
</ParamField>
<ParamField path="freshness" type="'day' | 'week' | 'month' | 'year'">
Time filter - `day` is 24 hours.
</ParamField>
<ParamField path="date_after" type="string">
Only results published after this date (`YYYY-MM-DD`).
</ParamField>
<ParamField path="date_before" type="string">
Only results published before this date (`YYYY-MM-DD`).
</ParamField>
<ParamField path="domain_filter" type="string[]">
Domain allowlist/denylist array (max 20).
</ParamField>
<ParamField path="max_tokens" type="number" default="25000">
Total content budget (max 1000000).
</ParamField>
<ParamField path="max_tokens_per_page" type="number" default="2048">
Per-page token limit.
</ParamField>
For the legacy Sonar/OpenRouter compatibility path:
- `query`, `count`, and `freshness` are accepted.
- `count` is compatibility-only there; the response is still one synthesized answer with citations rather than an N-result list.
- Search API-only filters (`country`, `language`, `date_after`, `date_before`, `domain_filter`, `max_tokens`, `max_tokens_per_page`) return explicit errors.
**Examples:**
```javascript
// 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
- Maximum 20 domains per filter.
- Cannot mix allowlist and denylist entries in the same request.
- Use a `-` prefix for denylist entries (e.g., `["-reddit.com"]`).
## Notes
- Perplexity Search API returns structured web search results (`title`, `url`, `snippet`).
- OpenRouter, or an explicit `plugins.entries.perplexity.config.webSearch.baseUrl` / `model`, switches Perplexity back to Sonar chat completions for compatibility.
- Sonar/OpenRouter compatibility returns one synthesized answer with citations, not structured result rows.
- Results are cached for 15 minutes by default (configurable via `cacheTtlMinutes`).
## Related
<CardGroup cols={2}>
<Card title="Web search overview" href="/tools/web" icon="globe">
All providers and auto-detection rules.
</Card>
<Card title="Brave search" href="/tools/brave-search" icon="shield">
Structured results with country and language filters.
</Card>
<Card title="Exa search" href="/tools/exa-search" icon="magnifying-glass">
Neural search with content extraction.
</Card>
<Card title="Perplexity Search API docs" href="https://docs.perplexity.ai/docs/search/quickstart" icon="arrow-up-right-from-square">
Official Perplexity Search API quickstart and reference.
</Card>
</CardGroup>

387
docs/tools/plugin.md Normal file
View File

@@ -0,0 +1,387 @@
---
summary: "Install, configure, and manage OpenClaw plugins"
read_when:
- Installing or configuring plugins
- Understanding plugin discovery and load rules
- Working with Codex/Claude-compatible plugin bundles
title: "Plugins"
sidebarTitle: "Getting Started"
doc-schema-version: 1
---
Plugins extend OpenClaw with channels, model providers, agent harnesses, tools,
skills, speech, realtime transcription, voice, media understanding, generation,
web fetch, web search, and other runtime capabilities.
Use this page to install a plugin, restart the Gateway, verify the runtime
loaded it, and route common setup failures. For command-only examples, see
[Manage plugins](/plugins/manage-plugins). For the generated inventory of
bundled, official external, and source-only plugins, see
[Plugin inventory](/plugins/plugin-inventory).
## Requirements
- an OpenClaw checkout or installation with the `openclaw` CLI available
- network access to the selected source (ClawHub, npm, or a git host)
- any plugin-specific credentials, config keys, or OS tools named by that
plugin's setup docs
- permission for the Gateway that serves your channels to reload or restart
## Quick start
<Steps>
<Step title="Find the plugin">
Search [ClawHub](/clawhub) for public plugin packages:
```bash
openclaw plugins search "calendar"
```
ClawHub is the primary discovery surface for community plugins. During the
launch cutover, ordinary bare package specs still install from npm unless
they match an official plugin id. Raw `@openclaw/*` specs that match a
bundled plugin resolve to that bundled copy. Use an explicit source prefix
when you need one source specifically.
</Step>
<Step title="Install the plugin">
```bash
# From ClawHub.
openclaw plugins install clawhub:<package>
# From npm.
openclaw plugins install npm:<package>
# From git.
openclaw plugins install git:github.com/<owner>/<repo>@<ref>
# From a local development checkout.
openclaw plugins install ./my-plugin
openclaw plugins install --link ./my-plugin
```
Treat plugin installs like running code. Prefer pinned versions for
reproducible production installs.
</Step>
<Step title="Configure and enable it">
Configure plugin-specific settings under `plugins.entries.<id>.config`.
Enable the plugin if it is not already enabled:
```bash
openclaw plugins enable <plugin-id>
```
If `plugins.allow` is set, the installed plugin id must be in that list
before the plugin can load. `openclaw plugins install` adds the installed
id to an existing `plugins.allow` list and removes the same id from
`plugins.deny` so the explicit install can load after restart.
</Step>
<Step title="Let the Gateway reload">
Installing, updating, or uninstalling plugin code requires a Gateway
restart. A managed Gateway with config reload enabled detects the changed
plugin install record and restarts automatically. Otherwise, restart it
yourself:
```bash
openclaw gateway restart
```
Enable/disable update config and the cold registry. A runtime inspect is
still the clearest proof of live runtime surfaces.
</Step>
<Step title="Verify runtime registration">
```bash
openclaw plugins inspect <plugin-id> --runtime --json
```
Use `--runtime` to prove registered tools, hooks, services, Gateway
methods, or plugin-owned CLI commands. Plain `inspect` is a cold manifest
and registry check only.
</Step>
</Steps>
## Configuration
### Choose an install source
| Source | Use when | Example |
| ----------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| ClawHub | You want OpenClaw-native discovery, scans, version metadata, and install hints | `openclaw plugins install clawhub:<package>` |
| npm | You need direct npm registry or dist-tag workflows | `openclaw plugins install npm:<package>` |
| git | You need a branch, tag, or commit from a repository | `openclaw plugins install git:github.com/<owner>/<repo>@<ref>` |
| local path | You are developing or testing a plugin on the same machine | `openclaw plugins install --link ./my-plugin` |
| marketplace | You are installing a Claude-compatible marketplace plugin | `openclaw plugins install <plugin> --marketplace <source>` |
Bare package specs have special compatibility behavior: a bare name that
matches a bundled plugin id uses that bundled source; a bare name that matches
an official external plugin id uses the official package catalog; any other
bare spec installs through npm during the launch cutover. Raw `@openclaw/*`
specs that match bundled plugins also resolve to the bundled copy before npm
fallback. Use `npm:@openclaw/<plugin>@<version>` to deliberately install the
external npm package instead of the bundled copy. Use `clawhub:`, `npm:`,
`git:`, or `npm-pack:` for deterministic source selection. See
[`openclaw plugins`](/cli/plugins#install) for the full command contract.
For npm installs, unpinned specs and `@latest` choose the newest stable
package that advertises compatibility with this OpenClaw build. If npm's
current latest release declares a newer `openclaw.compat.pluginApi` or
`openclaw.install.minHostVersion` than this build supports, OpenClaw scans
older stable versions and installs the newest one that fits. Exact versions
and explicit channel tags such as `@beta` stay pinned to the selected package
and fail when incompatible.
### Operator install policy
Configure `security.installPolicy` to run a trusted local policy command
before a plugin install or update proceeds. The policy receives metadata plus
the staged source path and can allow or block the install. It covers both CLI
and Gateway-backed install/update paths. Plugin `before_install` hooks run
later, and only in OpenClaw processes where plugin hooks are loaded, so use
`security.installPolicy` for operator-owned install decisions instead. The
deprecated `--dangerously-force-unsafe-install` flag is accepted for
compatibility but is a no-op: it does not bypass install policy or OpenClaw's
built-in plugin dependency denylist.
See [Skills config](/tools/skills-config#operator-install-policy-securityinstallpolicy)
for the shared `security.installPolicy` exec schema used by both skills and
plugins.
### Configure plugin policy
The common plugin config shape is:
```json5
{
plugins: {
enabled: true,
allow: ["voice-call"],
deny: ["untrusted-plugin"],
load: { paths: ["~/Projects/oss/voice-call-plugin"] },
slots: { memory: "memory-core" },
entries: {
"voice-call": { enabled: true, config: { provider: "twilio" } },
},
},
}
```
Key policy rules:
- `plugins.enabled: false` disables all plugins and skips discovery/load
work. Stale plugin references stay inert while this is active; re-enable
plugins before running doctor cleanup if you want stale ids removed.
- `plugins.deny` wins over allow and per-plugin enablement.
- `plugins.allow` is an exclusive allowlist. Plugin-owned tools outside the
allowlist stay unavailable even when `tools.allow` includes `"*"`.
- `plugins.entries.<id>.enabled: false` disables one plugin while keeping its
config.
- `plugins.load.paths` adds explicit local plugin files or directories.
Managed `plugins install` local paths must be plugin directories or
archives; use `plugins.load.paths` for standalone plugin files.
- Workspace-origin plugins are disabled by default; explicitly enable or
allowlist them before using local workspace code.
- Bundled plugins follow their built-in default-on/default-off metadata
unless config explicitly overrides it.
- `plugins.slots.<slot>` (`memory` or `contextEngine`) picks one plugin for an
exclusive category. Slot selection counts as explicit activation and
force-enables the selected plugin for that slot, even if it would otherwise
be opt-in. `plugins.deny` and `plugins.entries.<id>.enabled: false` still
block it.
- Bundled opt-in plugins can auto-activate when config names one of their
owned surfaces, such as a provider/model ref, channel config, CLI backend,
or agent harness runtime.
- OpenAI-family Codex routing keeps provider and runtime plugin boundaries
separate: legacy Codex model refs are legacy config that doctor repairs,
while the bundled `codex` plugin owns Codex app-server runtime for
canonical `openai/*` agent refs, explicit `agentRuntime.id: "codex"`, and
legacy `codex/*` refs.
When `plugins.allow` is unset and non-bundled plugins are auto-discovered from
the workspace or global plugin roots, startup logs
`plugins.allow is empty; discovered non-bundled plugins may auto-load: ...`
with the discovered plugin ids and, for short lists, a minimal `plugins.allow`
snippet. Run [`openclaw plugins list --enabled --verbose`](/cli/plugins#list)
or [`openclaw plugins inspect <id>`](/cli/plugins#inspect) on the listed
plugin id before copying trusted plugins into `openclaw.json`. The same
trust-pinning applies when diagnostics say a plugin loaded
`without install/load-path provenance`: inspect that plugin id, then pin it in
`plugins.allow` or reinstall from a trusted source so OpenClaw records install
provenance.
Run `openclaw doctor` or `openclaw doctor --fix` when config validation
reports stale plugin ids, allowlist/tool mismatches, or legacy bundled plugin
paths.
## Understand plugin formats
OpenClaw recognizes two plugin formats:
| Format | How it loads | Use when |
| ---------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Native OpenClaw plugin | `openclaw.plugin.json` plus a runtime module loaded in process | You are installing or building OpenClaw-specific runtime capabilities |
| Compatible bundle | Codex, Claude, or Cursor plugin layout mapped into OpenClaw plugin inventory | You are reusing compatible skills, commands, hooks, or bundle metadata |
Both formats appear in `openclaw plugins list`, `openclaw plugins inspect`,
`openclaw plugins enable`, and `openclaw plugins disable`. See
[Plugin bundles](/plugins/bundles) for the bundle compatibility boundary and
[Building plugins](/plugins/building-plugins) for native plugin authoring.
## Plugin hooks
Plugins can register hooks at runtime through two different APIs:
- `api.on(...)` typed hooks for runtime lifecycle events. This is the
preferred surface for middleware, policy, message rewriting, prompt
shaping, and tool control.
- `api.registerHook(...)` for the internal hook system described in
[Hooks](/automation/hooks). This is mainly for coarse command/lifecycle side
effects and compatibility with existing HOOK-style automation.
Quick rule: if the handler needs priority, merge semantics, or
block/cancel behavior, use typed hooks. If it just reacts to `command:new`,
`command:reset`, `message:sent`, or similar coarse events, `api.registerHook`
is fine.
Plugin-managed internal hooks show up in `openclaw hooks list` with
`plugin:<id>`. You cannot enable or disable them through `openclaw hooks`;
enable or disable the plugin instead.
## Verify the active Gateway
`openclaw plugins list` and plain `openclaw plugins inspect` read cold config,
manifest, and registry state. They do not prove that an already-running
Gateway has imported the same plugin code.
When a plugin appears installed but live chat traffic does not use it:
```bash
openclaw gateway status --deep --require-rpc
openclaw plugins inspect <plugin-id> --runtime --json
openclaw gateway restart
```
Managed Gateways restart automatically after plugin install, update, and
uninstall changes that alter plugin source. On VPS or container installs, make
sure any manual restart targets the actual `openclaw gateway run` child that
serves your channels, not only a wrapper or supervisor.
## Troubleshooting
| Symptom | Check | Fix |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| Plugin appears in `plugins list` but runtime hooks do not run | Use `openclaw plugins inspect <id> --runtime --json` and confirm the active Gateway with `gateway status --deep --require-rpc` | Restart the live Gateway after install, update, config, or source changes |
| Duplicate channel or tool ownership diagnostics appear | Run `openclaw plugins list --enabled --verbose`, inspect each suspected plugin with `--runtime --json`, and compare channel/tool ownership | Disable one owner, remove stale installs, or use manifest `preferOver` for intentional replacement |
| Config says a plugin is missing | Check [Plugin inventory](/plugins/plugin-inventory) for whether it is bundled, official external, or source-only | Install the external package, enable the bundled plugin, or remove stale config |
| Config is invalid during install | Read the validation message and run `openclaw doctor --fix` if it points to stale plugin state | Doctor can quarantine invalid plugin config by disabling the entry and removing the invalid payload |
| Plugin path is blocked for suspicious ownership or permissions | Inspect the diagnostic before the config error | Fix filesystem ownership/permissions, then run `openclaw plugins registry --refresh` |
| `OPENCLAW_NIX_MODE=1` blocks lifecycle commands | Confirm the install is managed by Nix | Change plugin selection in the Nix source instead of using plugin mutator commands |
| Dependency import fails at runtime | Check whether the plugin was installed through npm/git/ClawHub or loaded from a local path | Run `openclaw plugins update <id>`, reinstall the source, or install local plugin dependencies yourself |
When stale plugin config still names a no-longer-discoverable channel plugin,
config validation downgrades that channel key to a warning instead of a hard
failure, so Gateway startup can still serve every other channel. Run
`openclaw doctor --fix` to remove stale plugin and channel entries. Unknown
channel keys without stale-plugin evidence still fail validation so typos
stay visible.
For intentional channel replacement, the preferred plugin should declare
`channelConfigs.<channel-id>.preferOver` with the legacy or lower-priority
plugin id. If both plugins are explicitly enabled, OpenClaw keeps that request
and reports duplicate channel/tool diagnostics instead of silently choosing
one owner.
If an installed package reports that it `requires compiled runtime output for
TypeScript entry ...`, the package was published without the JavaScript files
OpenClaw needs at runtime. Update or reinstall after the publisher ships
compiled JavaScript, or disable/uninstall the plugin until then.
### Blocked plugin path ownership
If diagnostics say
`blocked plugin candidate: suspicious ownership (... uid=1000, expected uid=0 or root)`
and validation follows with `plugin present but blocked`, OpenClaw found
plugin files owned by a different Unix user than the process loading them.
Keep the plugin config in place; fix the filesystem ownership or run OpenClaw
as the same user that owns the state directory.
For Docker installs, the official image runs as `node` (uid `1000`), so the
host bind-mounted OpenClaw config and workspace directories should normally be
owned by uid `1000`:
```bash
sudo chown -R 1000:1000 /path/to/openclaw-config /path/to/openclaw-workspace
```
If you intentionally run OpenClaw as root, repair the managed plugin root to
root ownership instead:
```bash
sudo chown -R root:root /path/to/openclaw-config/npm
```
After fixing ownership, rerun `openclaw doctor --fix` or
`openclaw plugins registry --refresh` so the persisted plugin registry
matches the repaired files.
### Slow plugin tool setup
If agent turns appear to stall while preparing tools, enable trace logging
and check for plugin tool factory timing lines:
```bash
openclaw config set logging.level trace
openclaw logs --follow
```
Look for:
```text
[trace:plugin-tools] factory timings ...
```
The summary lists total factory time and the slowest plugin tool factories,
including plugin id, declared tool names, result shape, and whether the tool
is optional. Slow lines are promoted to warnings when a single factory takes
at least 1s or total plugin tool factory prep takes at least 5s.
OpenClaw caches successful plugin tool factory results for repeated
resolutions with the same effective request context. The cache key includes
the effective runtime config, workspace and agent id, sandbox policy, browser
settings, delivery context, requester identity, and ownership state, so
factories that depend on those trusted fields re-run when the context
changes. If timings stay high, the plugin may be doing expensive work before
returning its tool definitions.
If one plugin dominates the timing, inspect its runtime registrations:
```bash
openclaw plugins inspect <plugin-id> --runtime --json
```
Then update, reinstall, or disable that plugin. Plugin authors should move
expensive dependency loading behind the tool execution path instead of doing
it inside the tool factory.
For dependency roots, package metadata validation, registry records, startup
reload behavior, and legacy cleanup, see
[Plugin dependency resolution](/plugins/dependency-resolution).
## Related
- [Manage plugins](/plugins/manage-plugins) - command examples for list, install, update, uninstall, and publish
- [`openclaw plugins`](/cli/plugins) - full CLI reference
- [Plugin inventory](/plugins/plugin-inventory) - generated bundled and external plugin list
- [Plugin reference](/plugins/reference) - generated per-plugin reference pages
- [Community plugins](/plugins/community) - ClawHub discovery and docs PR policy
- [Plugin dependency resolution](/plugins/dependency-resolution) - install roots, registry records, and runtime boundaries
- [Building plugins](/plugins/building-plugins) - native plugin authoring guide
- [Plugin SDK overview](/plugins/sdk-overview) - runtime registration, hooks, and API fields
- [Plugin manifest](/plugins/manifest) - manifest and package metadata

104
docs/tools/reactions.md Normal file
View File

@@ -0,0 +1,104 @@
---
summary: "Reaction tool semantics across all supported channels"
read_when:
- Working on reactions in any channel
- Understanding how emoji reactions differ across platforms
title: "Reactions"
---
The agent adds and removes emoji reactions with the `message` tool's `react`
action. Behavior varies by channel.
## How it works
```json
{
"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) on
channels that support it.
- Set `remove: true` to remove one specific emoji (requires non-empty
`emoji`).
- On channels with status reactions, `trackToolCalls: true` on a reaction lets
the runtime reuse that reacted message for subsequent tool-progress
reactions during the same turn.
## Channel behavior
<AccordionGroup>
<Accordion title="Discord and Slack">
- Empty `emoji` removes all of the bot's reactions on the message.
- `remove: true` removes just the specified emoji.
</Accordion>
<Accordion title="Google Chat">
- Empty `emoji` (or `remove: true`) removes the bot's own reactions on the message, filtered to `emoji` when set.
- `remove: true` removes just the specified emoji.
</Accordion>
<Accordion title="Nextcloud Talk">
- Adding reactions only: `emoji` is required and must be non-empty.
- Reaction removal is not wired to a delete call yet; `remove: true` is rejected with an explicit error instead of silently no-oping.
- Requires the Talk bot registered with the `reaction` feature (see [Nextcloud Talk channel docs](/channels/nextcloud-talk)).
</Accordion>
<Accordion title="Telegram">
- Empty `emoji` removes the bot's reactions.
- `remove: true` also removes reactions but still requires a non-empty `emoji` for tool validation.
</Accordion>
<Accordion title="WhatsApp">
- Empty `emoji` removes the bot reaction.
- `remove: true` maps to empty emoji internally (still requires `emoji` in the tool call).
- WhatsApp has one bot reaction slot per message; sending a new reaction replaces it rather than stacking multiple emoji.
</Accordion>
<Accordion title="Zalo Personal (zalouser)">
- Requires non-empty `emoji` for both add and remove.
- `remove: true` removes that specific emoji reaction.
</Accordion>
<Accordion title="Feishu/Lark">
- Uses the same `react` action as other channels (add/remove/list via message reaction IDs), not a separate tool.
- Adding requires non-empty `emoji` (mapped to a Feishu `emoji_type`, e.g. `SMILE`, `THUMBSUP`, `HEART`).
- `remove: true` requires non-empty `emoji` and removes the bot's own reaction matching that emoji type.
- Empty `emoji` with `clearAll: true` removes all of the bot's reactions on the message.
</Accordion>
<Accordion title="Signal">
- Inbound reaction notifications are controlled by `channels.signal.reactionNotifications`: `"off"` disables them, `"own"` (default) emits events when users react to bot messages, `"all"` emits events for all reactions, and `"allowlist"` emits events only for senders in `channels.signal.reactionAllowlist`.
</Accordion>
<Accordion title="iMessage">
- Outbound reactions are iMessage tapbacks (`love`, `like`, `dislike`, `laugh`, `emphasize`, and `question`); `emoji` must map to one of these kinds to add a reaction.
- `remove: true` without a recognized tapback kind removes all tapback kinds; with a recognized kind it removes just that one.
</Accordion>
</AccordionGroup>
## Reaction level
Per-channel `reactionLevel` throttles how often the agent sends its own
reactions. Values: `off`, `ack`, `minimal`, or `extensive`.
- [Telegram reaction notifications](/channels/telegram#feature-reference) - `channels.telegram.reactionLevel` (default `minimal`)
- [WhatsApp reaction level](/channels/whatsapp#reaction-level) - `channels.whatsapp.reactionLevel` (default `minimal`)
- [Signal reactions](/channels/signal#reactions-message-tool) - `channels.signal.reactionLevel` (default `minimal`)
## Related
- [Agent Send](/tools/agent-send) - the `message` tool that includes `react`
- [Channels](/channels) - channel-specific configuration

View File

@@ -0,0 +1,146 @@
---
summary: "SearXNG web search -- self-hosted, key-free meta-search provider"
read_when:
- You want a self-hosted web search provider
- You want to use SearXNG for web_search
- You need a privacy-focused or air-gapped search option
title: "SearXNG search"
---
OpenClaw supports [SearXNG](https://docs.searxng.org/) as a **self-hosted,
key-free** `web_search` provider. SearXNG is an open-source meta-search engine
that aggregates results from Google, Bing, DuckDuckGo, and other sources.
Advantages:
- **Free and unlimited** -- no API key or commercial subscription required
- **Privacy / air-gap** -- queries never leave your network
- **Works anywhere** -- no region restrictions on commercial search APIs
## Setup
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/searxng-plugin
```
</Step>
<Step title="Run a SearXNG instance">
```bash
docker run -d -p 8888:8080 searxng/searxng
```
Or use any existing SearXNG deployment you have access to. See the
[SearXNG documentation](https://docs.searxng.org/) for production setup.
</Step>
<Step title="Configure">
```bash
openclaw configure --section web
# Select "searxng" as the provider
```
Or set the env var and let auto-detection find it:
```bash
export SEARXNG_BASE_URL="http://localhost:8888"
```
</Step>
</Steps>
## Config
```json5
{
tools: {
web: {
search: {
provider: "searxng",
},
},
},
}
```
Plugin-level settings for the SearXNG instance:
```json5
{
plugins: {
entries: {
searxng: {
config: {
webSearch: {
baseUrl: "http://localhost:8888",
categories: "general,news", // optional
language: "en", // optional
},
},
},
},
},
}
```
`baseUrl` also accepts a SecretRef object (for example `{ source: "env", id: "SEARXNG_BASE_URL" }`).
## Environment variable
Set `SEARXNG_BASE_URL` as an alternative to config:
```bash
export SEARXNG_BASE_URL="http://localhost:8888"
```
Resolution order: configured `baseUrl` string, then an inline env SecretRef on
`baseUrl`, then `SEARXNG_BASE_URL`. When none of the config paths are set and
`SEARXNG_BASE_URL` is present with no explicit provider chosen, auto-detection
picks SearXNG.
## Plugin config reference
| Field | Description |
| ------------ | ------------------------------------------------------------------ |
| `baseUrl` | Base URL of your SearXNG instance (required) |
| `categories` | Comma-separated categories such as `general`, `news`, or `science` |
| `language` | Language code for results such as `en`, `de`, or `fr` |
The `web_search` tool call also accepts `count` (1-10 results), `categories`,
and `language` as per-call overrides.
## Notes
- **JSON API** -- uses SearXNG's native `format=json` endpoint, not HTML scraping
- **Image result URLs** -- image-category results include `img_src` when SearXNG
returns a direct image URL
- **No API key** -- works with any SearXNG instance out of the box
- **Base URL validation** -- `baseUrl` must be a valid `http://` or `https://`
URL
- **Network guard** -- `http://` base URLs must target a trusted private or
loopback host (public hosts must use `https://`); `https://` base URLs that
resolve to a private/internal address get the same self-hosted allowance,
while `https://` base URLs that resolve publicly keep strict SSRF protection
- **Auto-detection order** -- SearXNG requires a configured `baseUrl` (order
200 among providers that already have their required credential). Key-free
providers such as DuckDuckGo or Ollama Web Search never win auto-detection
implicitly; they only activate on an explicit `provider` choice
- **Self-hosted** -- you control the instance, queries, and upstream search engines
- **Categories** default to `general` when not configured
- **Category fallback** -- if a non-`general` category request succeeds but
returns zero results, OpenClaw retries the same query once with `general`
before returning an empty result set
- **Result caching** -- identical queries (same query, count, categories,
language, and base URL) are cached in-process for a short TTL
- **Version requirement** -- the plugin declares `minHostVersion: >=2026.6.9`
<Tip>
For SearXNG JSON API to work, make sure your SearXNG instance has the `json`
format enabled in its `settings.yml` under `search.formats`.
</Tip>
## Related
- [Web Search overview](/tools/web) -- all providers and auto-detection
- [DuckDuckGo Search](/tools/duckduckgo-search) -- another key-free provider
- [Brave Search](/tools/brave-search) -- structured results with free tier

View File

@@ -0,0 +1,278 @@
---
summary: "Create and update workspace skills through Skill Workshop review"
read_when:
- You want the agent to create or update a skill from chat
- You need to review, apply, reject, or quarantine a generated skill draft
- You are configuring Skill Workshop approval, autonomy, storage, or limits
title: "Skill Workshop"
sidebarTitle: "Skill Workshop"
---
Skill Workshop is OpenClaw's governed path for creating and updating workspace
skills. Agents and operators never write `SKILL.md` directly through this
path — they create a **proposal** (pending draft with content, target
binding, scanner state, hashes, and rollback metadata) that becomes a live
skill only when applied.
Skill Workshop writes workspace skills only. It never touches bundled,
plugin, ClawHub, extra-root, managed, personal-agent, or system skills.
## How it works
- **Proposal first:** generated content is stored as `PROPOSAL.md`, not
`SKILL.md`.
- **Apply is the only live write:** create, update, and revise never change
active skills.
- **Workspace scoped:** creates target the workspace `skills/` root; updates
are allowed only for writable workspace skills.
- **No clobber:** create fails if the target skill already exists.
- **Hash bound:** update proposals bind to the current target hash and go
`stale` if the live skill changes before apply.
- **Scanner gated:** apply reruns the security scanner before writing.
- **Recoverable:** apply writes rollback metadata before touching live files.
- **Consistent surfaces:** chat, CLI, and Gateway all call the same service.
## Lifecycle
```text
create/update -> pending
revise -> pending
apply -> applied
reject -> rejected
quarantine -> quarantined
target change -> stale
```
Only a `pending` proposal can be revised, applied, rejected, or quarantined.
## Chat
Ask the agent for the skill you want; it calls `skill_workshop` and returns a
proposal id.
Create:
```text
Make a skill called morning-catchup that runs my Monday inbox routine.
```
Update an existing workspace skill:
```text
Update trip-planning to also check seat maps before booking.
```
Iterate on a pending proposal:
```text
Show me the morning-catchup proposal.
Revise it to also flag anything marked urgent.
Apply the morning-catchup proposal.
```
Agent-initiated `apply`, `reject`, and `quarantine` show an approval prompt by
default. Set `skills.workshop.approvalPolicy` to `"auto"` to skip it in
trusted environments.
## CLI
```bash
# Create
openclaw skills workshop propose-create \
--name morning-catchup \
--description "Daily inbox catch-up: triage, archive, surface, draft, plan" \
--proposal ./PROPOSAL.md
# Update an existing workspace skill
openclaw skills workshop propose-update trip-planning --proposal ./PROPOSAL.md
# List and inspect
openclaw skills workshop list
openclaw skills workshop inspect <proposal-id>
# Revise before approval
openclaw skills workshop revise <proposal-id> --proposal ./PROPOSAL.md
# Close out
openclaw skills workshop apply <proposal-id>
openclaw skills workshop reject <proposal-id> --reason "Duplicate"
openclaw skills workshop quarantine <proposal-id> --reason "Needs security review"
```
Every subcommand takes `--agent <id>` (target workspace; defaults to
cwd-inferred, then the default agent) and `--json` (structured output).
`propose-create`, `propose-update`, and `revise` also take `--goal <text>` and
`--evidence <text>` to record proposal context alongside `--proposal`.
## Proposal content
While pending, the proposal is stored as `PROPOSAL.md` with proposal-only
frontmatter:
```markdown
---
name: "morning-catchup"
description: "Daily inbox catch-up: triage, archive, surface, draft, plan"
status: proposal
version: "v1"
date: "2026-05-30T00:00:00.000Z"
---
```
On apply, Skill Workshop writes the active `SKILL.md` and removes the
proposal-only fields: `status`, proposal `version`, and proposal `date`.
## Support files
Use `--proposal-dir` when the proposed skill needs files beside
`PROPOSAL.md`:
```bash
openclaw skills workshop propose-create \
--name weekly-update \
--description "Friday wrap-up: stats, highlights, next week's top three" \
--proposal-dir ./weekly-update-proposal
```
The directory must contain `PROPOSAL.md`. Support files must live under
`assets/`, `examples/`, `references/`, `scripts/`, or `templates/`. Skill
Workshop scans, hashes, and stores them with the proposal, then writes them
beside the live `SKILL.md` only on apply.
Rejected support-file paths: absolute paths, hidden path segments, path
traversal, overlapping paths, executable files, non-UTF-8 text, null bytes,
and paths outside the standard support folders.
## Agent tool
The model uses `skill_workshop` with one required `action`:
`create | update | revise | list | inspect | apply | reject | quarantine`.
Other parameters apply depending on the action:
| Parameter | Used by | Notes |
| -------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------- |
| `name` | `create`, `inspect`, `revise` | Required for `create`; resolves a pending proposal by name otherwise |
| `description` | `create`, `update`, `revise` | Max 160 bytes |
| `skill_name` | `update` | Existing skill name or key |
| `proposal_content` | `create`, `update`, `revise` | Stored as `PROPOSAL.md`; capped by `skills.workshop.maxSkillBytes` |
| `support_files` | `create`, `update`, `revise` | Array of `{ path, content }` |
| `goal`, `evidence` | `create`, `update`, `revise` | Free-text context |
| `proposal_id` | `inspect`, `revise`, `apply`, `reject`, `quarantine` | Target proposal |
| `reason` | `apply`, `reject`, `quarantine` | Optional |
| `query`, `status`, `limit` | `list` | Filter/paginate; `limit` max 50, default 20 |
Agents must use `skill_workshop` for generated skill work. They must not
create or change proposal files through `write`, `edit`, `exec`, shell
commands, or direct filesystem operations.
<Note>
`skill_workshop` is a built-in agent tool and is included in
`tools.profile: "coding"`. If a stricter policy hides it, add
`skill_workshop` to the active `tools.allow` list, or use
`tools.alsoAllow: ["skill_workshop"]` when the scope uses a profile without an
explicit `tools.allow`. Sandboxed runs do not construct the host-side
Skill Workshop tool, so run proposal review actions from a normal host-side
agent session or the CLI.
</Note>
## Approval and autonomy
```json5
{
skills: {
workshop: {
autonomous: {
enabled: false,
},
allowSymlinkTargetWrites: false,
approvalPolicy: "pending",
maxPending: 50,
maxSkillBytes: 40000,
},
},
}
```
| Setting | Default | Effect |
| -------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `autonomous.enabled` | `false` | Lets OpenClaw create pending proposals from durable conversation signals after a successful turn. |
| `allowSymlinkTargetWrites` | `false` | Lets apply write through workspace skill symlinks whose real target is listed in `skills.load.allowSymlinkTargets`. |
| `approvalPolicy` | `"pending"` | `"pending"` requires an approval prompt before agent-initiated `apply`, `reject`, or `quarantine`. `"auto"` skips the prompt (the agent still has to call the action). |
| `maxPending` | `50` | Caps pending and quarantined proposals per workspace (1-200). |
| `maxSkillBytes` | `40000` | Caps proposal body size in bytes (1024-200000). |
Proposal descriptions are always capped at 160 bytes, independent of
`maxSkillBytes`.
## Gateway methods
| Method | Scope |
| ---------------------------------- | ---------------- |
| `skills.proposals.list` | `operator.read` |
| `skills.proposals.inspect` | `operator.read` |
| `skills.proposals.create` | `operator.admin` |
| `skills.proposals.update` | `operator.admin` |
| `skills.proposals.revise` | `operator.admin` |
| `skills.proposals.requestRevision` | `operator.admin` |
| `skills.proposals.apply` | `operator.admin` |
| `skills.proposals.reject` | `operator.admin` |
| `skills.proposals.quarantine` | `operator.admin` |
`requestRevision` is Gateway-only (no CLI or agent-tool equivalent): it
forwards free-text revision instructions to the owning agent's chat session
instead of replacing `PROPOSAL.md` directly, for UIs that ask the agent to
revise rather than submit literal new content.
## Storage
```text
<OPENCLAW_STATE_DIR>/skill-workshop/
proposals.json
proposals/<proposal-id>/
proposal.json
PROPOSAL.md
rollback.json
assets/
examples/
references/
scripts/
templates/
```
Default state directory: `~/.openclaw`.
- `proposal.json`: canonical proposal record.
- `proposals.json`: fast listing index, rebuildable from proposal folders.
- `PROPOSAL.md`: pending skill proposal.
- `rollback.json`: recovery metadata written before apply changes live files.
## Limits
| Limit | Value |
| ------------------------------- | -------------------------------------------------------------------- |
| Description | 160 bytes |
| Proposal body | `skills.workshop.maxSkillBytes` (default 40,000; hard ceiling 1 MiB) |
| Support files | 64 per proposal |
| Support file size | 256 KiB each, 2 MiB total |
| Pending + quarantined proposals | `skills.workshop.maxPending` per workspace (default 50) |
## Troubleshooting
| Problem | Resolution |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Skill proposal description is too large` | Shorten `description` to 160 bytes or less. |
| `Skill proposal content is too large` | Shorten the proposal body or raise `skills.workshop.maxSkillBytes`. |
| `Target skill changed after proposal creation` | Revise the proposal against the current target, or create a new proposal. |
| `Proposal scan failed` | Inspect scanner findings, then revise or quarantine the proposal. |
| `untrusted symlink target` | Configure `skills.load.allowSymlinkTargets` and enable `skills.workshop.allowSymlinkTargetWrites` only for intentional shared skill roots. |
| `Support file paths must be under one of...` | Move support files under `assets/`, `examples/`, `references/`, `scripts/`, or `templates/`. |
| Proposal does not show in list | Check the selected `--agent` workspace and `OPENCLAW_STATE_DIR`. |
| Agent cannot call `skill_workshop` | Check the active tool policy and run mode. `coding` includes the tool; restrictive `tools.allow` policies must list it explicitly, and sandboxed runs must use a normal host-side agent session or the CLI. |
## Related
- [Skills](/tools/skills) for load order, precedence, and visibility
- [Creating skills](/tools/creating-skills) for hand-written `SKILL.md`
basics
- [Skills config](/tools/skills-config) for the full `skills.workshop` schema
- [Skills CLI](/cli/skills) for `openclaw skills` commands

484
docs/tools/skills-config.md Normal file
View File

@@ -0,0 +1,484 @@
---
title: "Skills config"
sidebarTitle: "Skills config"
summary: "Full reference for the skills.* config schema, agent allowlists, workshop settings, and sandbox env var handling."
read_when:
- Configuring skill loading, install, or gating behavior
- Setting per-agent skill visibility
- Adjusting Skill Workshop limits or approval policy
---
Most skills configuration lives under `skills` in
`~/.openclaw/openclaw.json`. Agent-specific visibility lives under
`agents.defaults.skills` and `agents.list[].skills`.
```json5
{
skills: {
allowBundled: ["gemini", "peekaboo"],
load: {
extraDirs: ["~/Projects/agent-scripts/skills"],
allowSymlinkTargets: ["~/Projects/manager/skills"],
watch: true,
watchDebounceMs: 250,
},
install: {
preferBrew: true,
nodeManager: "npm",
allowUploadedArchives: false,
},
workshop: {
autonomous: { enabled: false },
allowSymlinkTargetWrites: false,
approvalPolicy: "pending",
maxPending: 50,
maxSkillBytes: 40000,
},
entries: {
"image-lab": {
enabled: true,
apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" },
env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
},
peekaboo: { enabled: true },
sag: { enabled: false },
},
},
}
```
<Note>
For built-in image generation, use `agents.defaults.imageGenerationModel`
plus the core `image_generate` tool instead of `skills.entries`. Skill
entries are for custom or third-party skill workflows only.
</Note>
## Loading (`skills.load`)
<ParamField path="skills.load.extraDirs" type="string[]">
Additional skill directories to scan, at the lowest precedence (below
bundled and plugin skills). Paths are expanded with `~` support.
</ParamField>
<ParamField path="skills.load.allowSymlinkTargets" type="string[]">
Trusted real target directories that symlinked skill folders may resolve
into, even when the symlink lives outside the configured root. Use this for
intentional sibling-repo layouts such as
`<workspace>/skills/manager -> ~/Projects/manager/skills`. Keep this list
narrow — do not point at broad roots like `~` or `~/Projects`.
</ParamField>
<ParamField path="skills.load.watch" type="boolean" default="true">
Watch skill folders and refresh the skills snapshot when `SKILL.md` files
change. Covers nested files under grouped skill roots.
</ParamField>
<ParamField path="skills.load.watchDebounceMs" type="number" default="250">
Debounce window for skill watcher events in milliseconds.
</ParamField>
## Install (`skills.install`)
<ParamField path="skills.install.preferBrew" type="boolean" default="true">
Prefer Homebrew installers when `brew` is available.
</ParamField>
<ParamField path="skills.install.nodeManager" type='"npm" | "pnpm" | "yarn" | "bun"' default='"npm"'>
Node package manager preference for skill installs. This only affects skill
installs — the Gateway runtime should still use Node (Bun is not
recommended for WhatsApp/Telegram). `openclaw setup --node-manager` and
`openclaw onboard --node-manager` accept `npm`, `pnpm`, or `bun`; set
`"yarn"` directly in config for Yarn-backed skill installs.
</ParamField>
<ParamField path="skills.install.allowUploadedArchives" type="boolean" default="false">
Allow trusted `operator.admin` Gateway clients to install private zip
archives staged through `skills.upload.*`. Normal ClawHub installs do not
need this setting.
</ParamField>
## Operator Install Policy (`security.installPolicy`)
Use `security.installPolicy` when operators need a trusted local command to
approve or block skill and plugin installs with host-specific policy. The
policy runs after OpenClaw has staged source material and before the install
or update continues. It applies to ClawHub skills, uploaded skills, Git/local
skills, skill dependency installers, and plugin install/update sources.
```json5
{
security: {
installPolicy: {
enabled: true,
// Omit targets to cover every supported target.
targets: ["skill", "plugin"],
exec: {
source: "exec",
command: "/usr/local/bin/openclaw-install-policy",
args: ["--json"],
timeoutMs: 10000,
noOutputTimeoutMs: 10000,
maxOutputBytes: 1048576,
passEnv: ["OPENCLAW_STATE_DIR", "PATH"],
env: { POLICY_MODE: "strict" },
trustedDirs: ["/usr/local/bin"],
},
},
},
}
```
<ParamField path="security.installPolicy.enabled" type="boolean" default="false">
Enables operator-owned install policy. When enabled without a valid `exec`
command, installs fail closed.
</ParamField>
<ParamField path="security.installPolicy.targets" type='("skill" | "plugin")[]'>
Optional target filter. When omitted, policy applies to every supported
target so new installs do not unexpectedly fail open.
</ParamField>
<ParamField path="security.installPolicy.exec.command" type="string">
Absolute path to the trusted policy executable. OpenClaw runs it without a
shell and validates the path before use.
</ParamField>
<ParamField path="security.installPolicy.exec.args" type="string[]">
Static arguments passed after `command`.
</ParamField>
<ParamField path="security.installPolicy.exec.timeoutMs" type="number" default="10000">
Maximum wall-clock runtime for one policy decision.
</ParamField>
<ParamField path="security.installPolicy.exec.noOutputTimeoutMs" type="number" default="timeoutMs">
Maximum time without stdout or stderr output before the policy fails
closed.
</ParamField>
<ParamField path="security.installPolicy.exec.maxOutputBytes" type="number" default="1048576">
Maximum combined stdout and stderr bytes accepted from the policy process.
</ParamField>
<ParamField path="security.installPolicy.exec.env" type="Record<string, string>">
Literal environment variables provided to the policy process.
</ParamField>
<ParamField path="security.installPolicy.exec.passEnv" type="string[]">
Environment variable names copied from the OpenClaw process into the
policy process. Only named variables are passed.
</ParamField>
<ParamField path="security.installPolicy.exec.trustedDirs" type="string[]">
Optional allowlist of directories that may contain the policy executable.
</ParamField>
<ParamField path="security.installPolicy.exec.allowInsecurePath" type="boolean" default="false">
Bypasses command path ownership and permission checks. Use only when the
path is protected by another mechanism.
</ParamField>
<ParamField path="security.installPolicy.exec.allowSymlinkCommand" type="boolean" default="false">
Allows the configured command path to be a symlink. The resolved target
must still satisfy the other path checks. Interpreter script arguments must
be direct regular files, not symlinks.
</ParamField>
The policy receives one JSON object on stdin with `protocolVersion: 1`,
`openclawVersion`, `targetType`, `targetName`, `sourcePath`, `sourcePathKind`,
optional structured `source`, structured `origin`, and `request`. It must
write one JSON object on stdout: `{ "protocolVersion": 1, "decision": "allow" }`
or `{ "protocolVersion": 1, "decision": "block", "reason": "..." }`. Non-zero
exit, timeout, malformed JSON, missing fields, or unsupported protocol
versions fail closed.
OpenClaw does not execute install policy during normal Gateway startup.
Installs and updates fail closed when policy is enabled but unavailable.
`openclaw doctor` performs static validation; `openclaw doctor --deep`
executes a synthetic install probe against the configured command.
Bulk updates apply policy per target: a blocked skill or plugin update fails
that target without disabling the policy or skipping later targets in the
batch.
Example stdin:
```json
{
"protocolVersion": 1,
"openclawVersion": "2026.6.1",
"targetType": "skill",
"targetName": "weather",
"sourcePath": "/var/folders/.../openclaw-skill-clawhub/root",
"sourcePathKind": "directory",
"source": {
"kind": "clawhub",
"authority": "openclaw",
"mutable": false,
"network": true
},
"origin": {
"type": "clawhub",
"registry": "https://clawhub.openclaw.ai",
"slug": "weather",
"version": "1.0.0"
},
"request": {
"kind": "skill-install",
"mode": "install",
"requestedSpecifier": "clawhub:weather@1.0.0"
},
"skill": {
"installId": "clawhub"
}
}
```
Minimal policy command:
```js
#!/usr/bin/env node
let input = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
input += chunk;
});
process.stdin.on("end", () => {
const request = JSON.parse(input);
if (request.targetType === "plugin" && request.source?.kind === "local-path") {
process.stdout.write(
JSON.stringify({
protocolVersion: 1,
decision: "block",
reason: "local plugin paths are not approved on this host",
}),
);
return;
}
process.stdout.write(JSON.stringify({ protocolVersion: 1, decision: "allow" }));
});
```
## Bundled skill allowlist
<ParamField path="skills.allowBundled" type="string[]">
Optional allowlist for **bundled** skills only. When set, only bundled
skills in the list are eligible. Managed, agent-level, and workspace
skills are unaffected.
</ParamField>
## Per-skill entries (`skills.entries`)
Keys under `entries` match the skill `name` by default. If a skill defines
`metadata.openclaw.skillKey`, use that key instead. Quote hyphenated names
(JSON5 allows quoted keys).
<ParamField path="skills.entries.<key>.enabled" type="boolean">
`false` disables the skill even when bundled or installed. The
`coding-agent` bundled skill is opt-in — set it to `true` and ensure one of
`claude`, `codex`, `opencode`, or another supported CLI is installed and
authenticated.
</ParamField>
<ParamField path="skills.entries.<key>.apiKey" type='string | { source, provider, id }'>
Convenience field for skills that declare `metadata.openclaw.primaryEnv`.
Supports a plaintext string or a SecretRef: `{ source: "env", provider: "default", id: "VAR_NAME" }`.
</ParamField>
<ParamField path="skills.entries.<key>.env" type="Record<string, string>">
Environment variables injected for the agent run. Only injected when the
variable is not already set in the process.
</ParamField>
<ParamField path="skills.entries.<key>.config" type="object">
Optional bag for custom per-skill configuration fields.
</ParamField>
## Agent allowlists (`agents`)
Use agent config when you want the same machine/workspace skill roots but a
different visible skill set per agent.
```json5
{
agents: {
defaults: {
skills: ["github", "weather"], // shared baseline
},
list: [
{ id: "writer" }, // inherits github, weather
{ id: "docs", skills: ["docs-search"] }, // replaces defaults entirely
{ id: "locked-down", skills: [] }, // no skills
],
},
}
```
<ParamField path="agents.defaults.skills" type="string[]">
Shared baseline allowlist inherited by agents that omit
`agents.list[].skills`. Omit entirely to leave skills unrestricted by
default.
</ParamField>
<ParamField path="agents.list[].skills" type="string[]">
Explicit final skill set for that agent. Explicit lists **replace**
inherited defaults — they do not merge. Set to `[]` to expose no skills for
that agent.
</ParamField>
<Warning>
Agent skill allowlists are a visibility and loading filter for OpenClaw
skill discovery, prompts, slash-command discovery, sandbox sync, and skill
snapshots. They are not a shell-time authorization boundary. If an agent
can run host `exec`, that shell can still run external clients or read
host files that are visible to the execution user, including MCP client
registries such as `~/.openclaw/skills/config/mcporter.json`. For
per-agent MCP isolation, combine skill allowlists with sandbox/OS-user
isolation, deny or tightly allowlist host exec, and prefer per-agent
credentials at the MCP server.
</Warning>
## Workshop (`skills.workshop`)
<ParamField path="skills.workshop.autonomous.enabled" type="boolean" default="false">
When `true`, agents can create pending proposals from durable conversation
signals after successful turns. User-prompted skill creation always goes
through Skill Workshop regardless of this setting.
</ParamField>
<ParamField path="skills.workshop.approvalPolicy" type='"pending" | "auto"' default='"pending"'>
`pending` requires operator approval before agent-initiated apply, reject,
or quarantine. `auto` allows those actions without approval.
</ParamField>
<ParamField path="skills.workshop.allowSymlinkTargetWrites" type="boolean" default="false">
Allow Skill Workshop apply to write through workspace skill symlinks whose
real target is already trusted by `skills.load.allowSymlinkTargets`. Keep
this disabled unless generated proposal applies should mutate that shared
skill root.
</ParamField>
<ParamField path="skills.workshop.maxPending" type="number" default="50">
Maximum pending and quarantined proposals retained per workspace (allowed
range: 1-200).
</ParamField>
<ParamField path="skills.workshop.maxSkillBytes" type="number" default="40000">
Maximum proposal body size in bytes (allowed range: 1024-200000). Proposal
descriptions are hard-capped at 160 bytes separately, because they appear
in discovery and listing output.
</ParamField>
See [Skill Workshop](/tools/skill-workshop) for the proposal lifecycle, CLI
commands, agent tool parameters, and Gateway methods this config controls.
## Symlinked skill roots
By default, workspace, project-agent, extra-dir, and bundled skill roots are
containment boundaries. A symlinked skill folder under `<workspace>/skills`
that resolves outside the root is skipped with a log message.
To allow an intentional symlink layout, declare the trusted target:
```json5
{
skills: {
load: {
extraDirs: ["~/Projects/manager/skills"],
allowSymlinkTargets: ["~/Projects/manager/skills"],
},
},
}
```
With this config, `<workspace>/skills/manager -> ~/Projects/manager/skills`
is accepted after realpath resolution. `extraDirs` scans the sibling repo
directly; `allowSymlinkTargets` preserves the symlinked path for existing
layouts.
Skill Workshop apply does not write through those symlinks by default. To
let Workshop apply mutate skills under already-trusted symlink targets, opt
in separately:
```json5
{
skills: {
load: {
allowSymlinkTargets: ["~/Projects/manager/skills"],
},
workshop: {
allowSymlinkTargetWrites: true,
},
},
}
```
Managed `~/.openclaw/skills` and personal `~/.agents/skills` directories
already accept skill-directory symlinks unconditionally (per-skill
`SKILL.md` containment still applies) — `allowSymlinkTargets` is only needed
for workspace, extra-dir, and project-agent (`<workspace>/.agents/skills`)
roots.
## Sandboxed skills and env vars
<Warning>
`skills.entries.<skill>.env` and `apiKey` apply to **host** runs only.
Inside a sandbox they have no effect — a skill that depends on
`GEMINI_API_KEY` will fail with `apiKey not configured` unless the sandbox
is given the variable separately.
</Warning>
Pass secrets into a Docker sandbox with:
```json5
{
agents: {
defaults: {
sandbox: {
docker: {
env: { GEMINI_API_KEY: "your-key-here" },
},
},
},
},
}
```
<Note>
Users with Docker daemon access can inspect `sandbox.docker.env` values
through Docker metadata. Use a mounted secret file, a custom image, or
another delivery path when that exposure is not acceptable.
</Note>
## Loading order reminder
```text
workspace/skills (highest)
workspace/.agents/skills
~/.agents/skills
~/.openclaw/skills
bundled skills
skills.load.extraDirs (lowest)
```
Changes to skills and config take effect on the next new session when the
watcher is enabled, or on the next agent turn when the watcher detects a
change.
## Related
<CardGroup cols={2}>
<Card title="Skills reference" href="/tools/skills" icon="puzzle-piece">
What skills are, loading order, gating, and SKILL.md format.
</Card>
<Card title="Creating skills" href="/tools/creating-skills" icon="hammer">
Authoring custom workspace skills.
</Card>
<Card title="Skill Workshop" href="/tools/skill-workshop" icon="flask">
Proposal queue for agent-drafted skills.
</Card>
<Card title="Slash commands" href="/tools/slash-commands" icon="terminal">
Native slash-command catalog and chat directives.
</Card>
</CardGroup>

609
docs/tools/skills.md Normal file
View File

@@ -0,0 +1,609 @@
---
title: "Skills"
sidebarTitle: "Skills"
summary: "Skills teach your agent how to use tools. Learn how they load, how precedence works, and how to configure gating, allowlists, and environment injection."
read_when:
- Adding or modifying skills
- Changing skill gating, allowlists, or load rules
- Understanding skill precedence and snapshot behavior
---
Skills are markdown instruction files that teach the agent how and when to use
tools. Each skill lives in a directory containing a `SKILL.md` file with YAML
frontmatter and a markdown body. OpenClaw loads bundled skills plus any local
overrides, and filters them at load time based on environment, config, and
binary presence.
<CardGroup cols={2}>
<Card title="Creating skills" href="/tools/creating-skills" icon="hammer">
Build and test a custom skill from scratch.
</Card>
<Card title="Skill Workshop" href="/tools/skill-workshop" icon="flask">
Review and approve agent-drafted skill proposals.
</Card>
<Card title="Skills config" href="/tools/skills-config" icon="gear">
Full `skills.*` config schema and agent allowlists.
</Card>
<Card title="ClawHub" href="/clawhub" icon="cloud">
Browse and install community skills.
</Card>
</CardGroup>
## Loading order
OpenClaw loads from these sources, **highest precedence first**. When the same
skill name appears in multiple places, the highest source wins.
| Priority | Source | Path |
| ----------- | ---------------------- | --------------------------------------- |
| 1 — highest | Workspace skills | `<workspace>/skills` |
| 2 | Project agent skills | `<workspace>/.agents/skills` |
| 3 | Personal agent skills | `~/.agents/skills` |
| 4 | Managed / local skills | `~/.openclaw/skills` |
| 5 | Bundled skills | shipped with the install |
| 6 — lowest | Extra directories | `skills.load.extraDirs` + plugin skills |
Skill roots support grouped layouts. OpenClaw discovers a skill whenever
`SKILL.md` appears anywhere under a configured root (up to 6 levels deep):
```text
<workspace>/skills/research/SKILL.md ✓ found as "research"
<workspace>/skills/personal/research/SKILL.md ✓ also found as "research"
```
The folder path is for organization only. The skill's name and slash command
come from the `name` frontmatter field (or the directory name when `name` is
missing). Agent allowlists (below) also match on this `name`.
<Note>
Codex CLI's native `$CODEX_HOME/skills` directory is **not** an OpenClaw
skill root. Use `openclaw migrate plan codex` to inventory those skills, then
`openclaw migrate codex` to copy them into your OpenClaw workspace.
</Note>
## Per-agent vs shared skills
In multi-agent setups, each agent has its own workspace. Use the path that
matches your desired visibility:
| Scope | Path | Visible to |
| -------------- | ---------------------------- | --------------------------- |
| Per-agent | `<workspace>/skills` | Only that agent |
| Project-agent | `<workspace>/.agents/skills` | Only that workspace's agent |
| Personal-agent | `~/.agents/skills` | All agents on this machine |
| Shared managed | `~/.openclaw/skills` | All agents on this machine |
| Extra dirs | `skills.load.extraDirs` | All agents on this machine |
## Agent allowlists
Skill **location** (precedence) and skill **visibility** (which agent can use
it) are separate controls. Use allowlists to restrict which skills an agent sees,
regardless of where they are loaded from.
```json5
{
agents: {
defaults: {
skills: ["github", "weather"], // shared baseline
},
list: [
{ id: "writer" }, // inherits github, weather
{ id: "docs", skills: ["docs-search"] }, // replaces defaults entirely
{ id: "locked-down", skills: [] }, // no skills
],
},
}
```
<AccordionGroup>
<Accordion title="Allowlist rules">
- Omit `agents.defaults.skills` to leave all skills unrestricted by default.
- Omit `agents.list[].skills` to inherit `agents.defaults.skills`.
- Set `agents.list[].skills: []` to expose no skills for that agent.
- A non-empty `agents.list[].skills` list is the **final** set — it does not
merge with defaults.
- The effective allowlist applies across prompt building, slash-command
discovery, sandbox sync, and skill snapshots.
- This is not a host shell authorization boundary. If the same agent can
use `exec`, constrain that shell separately with sandboxing, OS-user
isolation, exec deny/allowlists, and per-resource credentials.
</Accordion>
</AccordionGroup>
## Plugins and 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 — for example, the browser plugin ships a
`browser-automation` skill for multi-step browser control.
Plugin skill directories merge at the same low-precedence level as
`skills.load.extraDirs`, so a same-named bundled, managed, agent, or workspace
skill overrides them. Gate a plugin skill's own eligibility via
`metadata.openclaw.requires` in its frontmatter, same as any other skill.
See [Plugins](/tools/plugin) and [Tools](/tools) for the full plugin system.
## Skill Workshop
[Skill Workshop](/tools/skill-workshop) is a proposal queue between the agent
and your active skill files. When the agent spots reusable work, it drafts a
proposal instead of writing directly to `SKILL.md`. You review and approve
before anything changes.
```bash
openclaw skills workshop list
openclaw skills workshop inspect <proposal-id>
openclaw skills workshop apply <proposal-id>
```
See [Skill Workshop](/tools/skill-workshop) for the full lifecycle, CLI
reference, and configuration.
## Installing from ClawHub
[ClawHub](https://clawhub.ai) is the public skills registry. Use
`openclaw skills` commands for install and update, or the `clawhub` CLI for
publish and sync.
| Action | Command |
| ---------------------------------- | ------------------------------------------------------ |
| Install a skill into the workspace | `openclaw skills install @owner/<slug>` |
| Install from a Git repository | `openclaw skills install git:owner/repo@ref` |
| Install a local skill directory | `openclaw skills install ./path/to/skill --as my-tool` |
| Install for all local agents | `openclaw skills install @owner/<slug> --global` |
| Update all workspace skills | `openclaw skills update --all` |
| Update a shared managed skill | `openclaw skills update @owner/<slug> --global` |
| Update all shared managed skills | `openclaw skills update --all --global` |
| Verify a skill's trust envelope | `openclaw skills verify @owner/<slug>` |
| Print the generated Skill Card | `openclaw skills verify @owner/<slug> --card` |
| Publish / sync via ClawHub CLI | `clawhub sync --all` |
<AccordionGroup>
<Accordion title="Install details">
`openclaw skills install` installs into the active workspace `skills/`
directory by default. Add `--global` to install into the shared
`~/.openclaw/skills` directory, visible to all local agents unless agent
allowlists narrow it.
Git and local installs expect `SKILL.md` at the source root. The slug comes
from `SKILL.md` frontmatter `name` when valid, then falls back to the
directory or repository name. Use `--as <slug>` to override.
`openclaw skills update` tracks ClawHub installs only — reinstall Git or
local sources to refresh them.
</Accordion>
<Accordion title="Verification and security scanning">
`openclaw skills verify @owner/<slug>` asks ClawHub for the skill's
`clawhub.skill.verify.v1` trust envelope. Installed ClawHub skills verify
against the version and registry recorded in `.clawhub/origin.json`.
Bare slugs remain accepted for existing installed or unambiguous skills, but
owner-qualified refs avoid publisher ambiguity.
ClawHub skill pages expose the latest security scan state before install,
with detail pages for VirusTotal, ClawScan, and static analysis. The
command exits non-zero when ClawHub marks verification as failed. Publishers
recover false positives through the ClawHub dashboard or
`clawhub skill rescan @owner/<slug>`.
</Accordion>
<Accordion title="Private archive installs">
Gateway clients that need non-ClawHub delivery can stage a zip skill archive
with `skills.upload.begin`, `skills.upload.chunk`, and `skills.upload.commit`,
then install with `skills.install({ source: "upload", ... })`. This path is
off by default and requires `skills.install.allowUploadedArchives: true` in
`openclaw.json`. Normal ClawHub installs never need that setting.
</Accordion>
</AccordionGroup>
## Security
<Warning>
Treat third-party skills as **untrusted code**. Read them before enabling.
Prefer sandboxed runs for untrusted inputs and risky tools. See
[Sandboxing](/gateway/sandboxing) for agent-side controls.
</Warning>
<AccordionGroup>
<Accordion title="Path containment">
Workspace, project-agent, and extra-dir skill discovery only accepts skill
roots whose resolved realpath stays inside the configured root, unless
`skills.load.allowSymlinkTargets` explicitly trusts a target root.
Skill Workshop writes through those trusted targets only when
`skills.workshop.allowSymlinkTargetWrites` is enabled.
Managed `~/.openclaw/skills` and personal `~/.agents/skills` may contain
symlinked skill folders, but every `SKILL.md` realpath must still stay
inside its resolved skill directory.
</Accordion>
<Accordion title="Operator install policy">
Configure `security.installPolicy` to run a trusted local policy command
before skill installs continue. The policy receives metadata and the staged
source path, applies to ClawHub, uploaded, Git, local, update, and
dependency-installer paths, and fails closed when the command cannot return
a valid decision.
</Accordion>
<Accordion title="Secret injection scope">
`skills.entries.*.env` and `skills.entries.*.apiKey` inject secrets into the
**host** process for that agent turn only — not into the sandbox. Keep
secrets out of prompts and logs.
</Accordion>
</AccordionGroup>
For the broader threat model and security checklists, see
[Security](/gateway/security).
## SKILL.md format
Every skill needs at minimum a `name` and `description` in the frontmatter:
```markdown
---
name: image-lab
description: Generate or edit images via a provider-backed image workflow
---
When the user asks to generate an image, use the `image_generate` tool...
```
<Note>
OpenClaw follows the [AgentSkills](https://agentskills.io) spec. Frontmatter
is parsed as YAML first; if that fails, it falls back to a single-line-only
parser. Nested `metadata` blocks (including multi-line YAML mappings) are
flattened to a JSON string and re-parsed as JSON5, so the block form shown
under [Gating](#gating) works. Use `{baseDir}` in the body to reference the
skill folder path.
</Note>
### Optional frontmatter keys
<ParamField path="homepage" type="string">
URL shown as "Website" in the macOS Skills UI. Also supported via
`metadata.openclaw.homepage`.
</ParamField>
<ParamField path="user-invocable" type="boolean" default="true">
When `true`, the skill is exposed as a user-invocable slash command.
</ParamField>
<ParamField path="disable-model-invocation" type="boolean" default="false">
When `true`, OpenClaw keeps the skill's instructions out of the agent's normal
prompt. The skill is still available as a slash command when `user-invocable`
is also `true`.
</ParamField>
<ParamField path="command-dispatch" type='"tool"'>
When set to `tool`, the slash command bypasses the model and dispatches
directly to a registered tool.
</ParamField>
<ParamField path="command-tool" type="string">
Tool name to invoke when `command-dispatch: tool` is set.
</ParamField>
<ParamField path="command-arg-mode" type='"raw"' default="raw">
For tool dispatch, forwards the raw args string to the tool with no
core parsing. The tool receives
`{ command: "<raw args>", commandName: "<slash command>", skillName: "<skill name>" }`.
</ParamField>
## Gating
OpenClaw filters skills at load time using `metadata.openclaw` (JSON5 object
embedded in the frontmatter, see the parsing note above). A skill with no
`metadata.openclaw` block is always eligible unless explicitly disabled.
```markdown
---
name: image-lab
description: Generate or edit images via a provider-backed image workflow
metadata:
{
"openclaw":
{
"requires": { "bins": ["uv"], "env": ["GEMINI_API_KEY"], "config": ["browser.enabled"] },
"primaryEnv": "GEMINI_API_KEY",
},
}
---
```
<ParamField path="always" type="boolean">
When `true`, always include the skill and skip all other gates.
</ParamField>
<ParamField path="emoji" type="string">
Optional emoji shown in the macOS Skills UI.
</ParamField>
<ParamField path="homepage" type="string">
Optional URL shown as "Website" in the macOS Skills UI.
</ParamField>
<ParamField path="os" type='("darwin" | "linux" | "win32")[]'>
Platform filter. When set, the skill is only eligible on a listed OS.
</ParamField>
<ParamField path="requires.bins" type="string[]">
Each binary must exist on `PATH`.
</ParamField>
<ParamField path="requires.anyBins" type="string[]">
At least one binary must exist on `PATH`.
</ParamField>
<ParamField path="requires.env" type="string[]">
Each env var must exist in the process or be provided via config.
</ParamField>
<ParamField path="requires.config" type="string[]">
Each `openclaw.json` path must be truthy.
</ParamField>
<ParamField path="primaryEnv" type="string">
Env var name associated with `skills.entries.<name>.apiKey`.
</ParamField>
<ParamField path="install" type="object[]">
Optional installer specs used by the macOS Skills UI (brew / node / go / uv / download).
</ParamField>
<Note>
Legacy `metadata.clawdbot` blocks are still accepted when
`metadata.openclaw` is absent, so older installed skills keep their
dependency gates and installer hints. New skills should use
`metadata.openclaw`.
</Note>
### Installer specs
Installer specs tell the macOS Skills UI how to install a dependency:
```markdown
---
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)",
},
],
},
}
---
```
<AccordionGroup>
<Accordion title="Installer selection rules">
- When multiple installers are listed, the gateway picks one preferred
option (brew when available, otherwise node).
- If all installers are `download`, OpenClaw lists each entry so you can
see all available artifacts.
- Specs can include `os: ["darwin"|"linux"|"win32"]` to filter by platform.
- Node installs honor `skills.install.nodeManager` in `openclaw.json`
(default: npm; options: npm / pnpm / yarn / bun). This only affects skill
installs; the Gateway runtime should still be Node.
- Gateway installer preference: Homebrew → uv → configured node manager →
go → download.
</Accordion>
<Accordion title="Per-installer details">
- **Homebrew:** OpenClaw does not auto-install Homebrew or translate brew
formulas into system package commands. In Linux containers without
`brew`, brew-only installers are hidden; use a custom image or install
the dependency manually.
- **Go:** OpenClaw requires Go 1.21 or newer for automatic skill installs.
If `go` is missing and Homebrew is available, OpenClaw installs Go via
Homebrew first; on Linux without Homebrew it can instead use `apt-get`
as root or through passwordless `sudo` when the refreshed `golang-go`
candidate meets the minimum version. The actual `go install` for the
dependency always targets a dedicated OpenClaw-managed bin directory
(Homebrew's `bin` on a fresh install, else `~/.local/bin`) rather than
your configured `GOBIN` — your own `GOBIN`, `GOPATH`, and `GOTOOLCHAIN`
env vars are read but never overwritten.
- **Download:** `url` (required), `archive` (`tar.gz` | `tar.bz2` | `zip`),
`extract` (default: auto when archive detected), `stripComponents`,
`targetDir` (default: `~/.openclaw/tools/<skillKey>`).
</Accordion>
<Accordion title="Sandboxing notes">
`requires.bins` is checked on the **host** at skill load time. If an agent
runs in a sandbox, the binary must also exist **inside the container**.
Install it via `agents.defaults.sandbox.docker.setupCommand` or a custom
image. `setupCommand` runs once after container creation and requires
network egress, a writable root FS, and a root user in the sandbox.
</Accordion>
</AccordionGroup>
## Config overrides
Toggle and configure bundled or managed skills under `skills.entries` in
`~/.openclaw/openclaw.json`:
```json5
{
skills: {
entries: {
"image-lab": {
enabled: true,
apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" },
env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
config: {
endpoint: "https://example.invalid",
model: "nano-pro",
},
},
peekaboo: { enabled: true },
sag: { enabled: false },
},
},
}
```
<ParamField path="enabled" type="boolean">
`false` disables the skill even when bundled or installed. The `coding-agent`
bundled skill is opt-in — set `skills.entries.coding-agent.enabled: true`
and ensure one of `claude`, `codex`, `opencode`, or another supported CLI
is installed and authenticated.
</ParamField>
<ParamField path="apiKey" type='string | { source, provider, id }'>
Convenience field for skills that declare `metadata.openclaw.primaryEnv`.
Supports a plaintext string or a SecretRef object.
</ParamField>
<ParamField path="env" type="Record<string, string>">
Environment variables injected for the agent run. Only injected when the
variable is not already set in the process.
</ParamField>
<ParamField path="config" type="object">
Optional bag for custom per-skill configuration fields.
</ParamField>
<ParamField path="allowBundled" type="string[]">
Optional allowlist for **bundled** skills only. When set, only bundled skills
in the list are eligible. Managed and workspace skills are unaffected.
</ParamField>
<Note>
Config keys match the **skill name** by default. If a skill defines
`metadata.openclaw.skillKey`, use that key under `skills.entries` instead.
Quote hyphenated names: JSON5 allows quoted keys.
</Note>
## Environment injection
When an agent run starts, OpenClaw:
<Steps>
<Step title="Reads skill metadata">
OpenClaw resolves the effective skill list for the agent, applying gating
rules, allowlists, and config overrides.
</Step>
<Step title="Injects env and API keys">
`skills.entries.<key>.env` and `skills.entries.<key>.apiKey` are applied to
`process.env` for the duration of the run.
</Step>
<Step title="Builds the system prompt">
Eligible skills are compiled into a compact XML block and injected into the
system prompt.
</Step>
<Step title="Restores the environment">
After the run ends, the original environment is restored.
</Step>
</Steps>
<Warning>
Env injection is scoped to the **host** agent run, not the sandbox. Inside a
sandbox, `env` and `apiKey` have no effect. See
[Skills config](/tools/skills-config#sandboxed-skills-and-env-vars) for how
to pass secrets into sandboxed runs.
</Warning>
For the bundled `claude-cli` backend, OpenClaw also materializes the same
eligible skill snapshot as a temporary Claude Code plugin and passes it via
`--plugin-dir`. Other CLI backends use the prompt catalog only.
## Snapshots and refresh
OpenClaw snapshots eligible skills **when a session starts** and reuses that
list for all subsequent turns in the session. Changes to skills or config take
effect on the next new session.
Skills refresh mid-session in two cases:
- The skills watcher detects a `SKILL.md` change.
- A new eligible remote node connects.
The refreshed list is picked up on the next agent turn. If the effective agent
allowlist changes, OpenClaw refreshes the snapshot to keep visible skills
aligned.
<AccordionGroup>
<Accordion title="Skills watcher">
By default, OpenClaw watches skill folders and bumps the snapshot when
`SKILL.md` files change. Configure under `skills.load`:
```json5
{
skills: {
load: {
extraDirs: ["~/Projects/agent-scripts/skills"],
allowSymlinkTargets: ["~/Projects/manager/skills"],
watch: true, // default
watchDebounceMs: 250, // default
},
},
}
```
Use `allowSymlinkTargets` for intentional symlinked layouts where a skill
root symlink points outside the configured root, for example
`<workspace>/skills/manager -> ~/Projects/manager/skills`.
Enable `skills.workshop.allowSymlinkTargetWrites` only when Skill Workshop
should also apply proposals through those trusted symlinked paths.
</Accordion>
<Accordion title="Remote macOS nodes (Linux gateway)">
If the Gateway runs on Linux but a **macOS node** is connected with
`system.run` allowed, OpenClaw can treat macOS-only skills as eligible when
the required binaries are present on that node. The agent should run those
skills via the `exec` tool with `host=node`.
Offline nodes do **not** make remote-only skills visible. If a node stops
answering bin probes, OpenClaw clears its cached bin matches.
</Accordion>
</AccordionGroup>
## Token impact
When skills are eligible, OpenClaw injects a compact XML block into the system
prompt. The cost is deterministic and scales linearly per skill:
- **Base overhead** (only when 1+ skills are eligible): a fixed block of intro
prose plus the `<available_skills>` wrapper.
- **Per skill:** ~97 characters + your `name`, `description`, and `location`
field lengths.
- XML escaping expands `& < > " '` into entities, adding a few characters per
occurrence.
- At ~4 chars/token, 97 chars ≈ 24 tokens per skill before field lengths.
If the rendered block would exceed the configured prompt budget
(`skills.limits.maxSkillsPromptChars`), OpenClaw first drops descriptions
(compact format: name + location only), then truncates the skill list and adds
a note pointing at `openclaw skills check`.
Keep descriptions short and descriptive to minimize prompt overhead.
## Related
<CardGroup cols={2}>
<Card title="Creating skills" href="/tools/creating-skills" icon="hammer">
Step-by-step guide to authoring a custom skill.
</Card>
<Card title="Skill Workshop" href="/tools/skill-workshop" icon="flask">
Proposal queue for agent-drafted skills.
</Card>
<Card title="Skills config" href="/tools/skills-config" icon="gear">
Full `skills.*` config schema and agent allowlists.
</Card>
<Card title="Slash commands" href="/tools/slash-commands" icon="terminal">
How skill slash commands are registered and routed.
</Card>
<Card title="ClawHub" href="/clawhub" icon="cloud">
Browse and publish skills on the public registry.
</Card>
<Card title="Plugins" href="/tools/plugin" icon="plug">
Plugins can ship skills alongside the tools they document.
</Card>
</CardGroup>

View File

@@ -0,0 +1,524 @@
---
title: "Slash commands"
sidebarTitle: "Slash commands"
summary: "All available slash commands, directives, and inline shortcuts — configuration, routing, and per-surface behavior."
read_when:
- Using or configuring chat commands
- Debugging command routing or permissions
- Understanding how skill commands are registered
---
The Gateway handles commands sent as standalone messages starting with `/`.
Host-only bash commands use `! <cmd>` (with `/bash <cmd>` as an alias).
When a conversation is bound to an ACP session, normal text routes to the ACP
harness. Gateway management commands remain local: `/acp ...` always reaches
the OpenClaw command handler, and `/status` plus `/unfocus` stay local whenever
command handling is enabled for the surface.
## Three command types
<CardGroup cols={3}>
<Card title="Commands" icon="terminal">
Standalone `/...` messages handled by the Gateway. Must be sent as the
only content in the message.
</Card>
<Card title="Directives" icon="sliders">
`/think`, `/fast`, `/verbose`, `/trace`, `/reasoning`, `/elevated`,
`/exec`, `/model`, `/queue` — stripped from the message before the model
sees it. Persist session settings when sent alone; act as inline hints
when sent with other text.
</Card>
<Card title="Inline shortcuts" icon="bolt">
`/help`, `/commands`, `/status`, `/whoami` — run immediately and are
stripped before the model sees the remaining text. Authorized senders only.
</Card>
</CardGroup>
<AccordionGroup>
<Accordion title="Directive behavior details">
- Directives are stripped from the message before the model sees it.
- In **directive-only** messages (the message is only directives), they
persist to the session and reply with an acknowledgement.
- In **normal chat** messages with other text, they act as inline hints and
do **not** persist session settings.
- Directives only apply for **authorized senders**. If `commands.allowFrom`
is set, it is the only allowlist used; otherwise authorization comes from
channel allowlists/pairing plus `commands.useAccessGroups`. Unauthorized
senders see directives treated as plain text.
</Accordion>
</AccordionGroup>
## Configuration
```json5
{
commands: {
native: "auto",
nativeSkills: "auto",
text: true,
bash: false,
bashForegroundMs: 2000,
config: false,
mcp: false,
plugins: false,
debug: false,
restart: true,
ownerAllowFrom: ["discord:123456789012345678"],
ownerDisplay: "raw",
ownerDisplaySecret: "${OWNER_ID_HASH_SECRET}",
allowFrom: {
"*": ["user1"],
discord: ["user:123"],
},
useAccessGroups: true,
},
}
```
<ParamField path="commands.text" type="boolean" default="true">
Enables parsing `/...` in chat messages. On surfaces without native commands
(WhatsApp, WebChat, Signal, iMessage, Google Chat, Microsoft Teams), text
commands work even when set to `false`.
</ParamField>
<ParamField path="commands.native" type='boolean | "auto"' default='"auto"'>
Registers native commands. Auto: on for Discord/Telegram; off for Slack;
ignored for providers without native support. Override per-channel with
`channels.<provider>.commands.native`. On Discord, `false` skips slash-command
registration; previously registered commands may stay visible until removed.
</ParamField>
<ParamField path="commands.nativeSkills" type='boolean | "auto"' default='"auto"'>
Registers skill commands natively when supported. Auto: on for
Discord/Telegram; off for Slack. Override with
`channels.<provider>.commands.nativeSkills`.
</ParamField>
<ParamField path="commands.bash" type="boolean" default="false">
Enables `! <cmd>` to run host shell commands (`/bash <cmd>` alias). Requires
`tools.elevated` allowlists.
</ParamField>
<ParamField path="commands.bashForegroundMs" type="number" default="2000">
How long bash waits before switching to background mode (`0` backgrounds
immediately).
</ParamField>
<ParamField path="commands.config" type="boolean" default="false">
Enables `/config` (reads/writes `openclaw.json`). Owner-only.
</ParamField>
<ParamField path="commands.mcp" type="boolean" default="false">
Enables `/mcp` (reads/writes OpenClaw-managed MCP config under `mcp.servers`). Owner-only.
</ParamField>
<ParamField path="commands.plugins" type="boolean" default="false">
Enables `/plugins` (plugin discovery/status plus install + enable/disable). Owner-only for writes.
</ParamField>
<ParamField path="commands.debug" type="boolean" default="false">
Enables `/debug` (runtime-only config overrides). Owner-only.
</ParamField>
<ParamField path="commands.restart" type="boolean" default="true">
Enables `/restart` and gateway restart tool actions.
</ParamField>
<ParamField path="commands.ownerAllowFrom" type="string[]">
Explicit owner allowlist for owner-only command surfaces. Separate from
`commands.allowFrom` and DM pairing access.
</ParamField>
<ParamField path="channels.<channel>.commands.enforceOwnerForCommands" type="boolean" default="false">
Per-channel: requires owner identity for owner-only commands. When `true`,
sender must match `commands.ownerAllowFrom` or hold internal `operator.admin`
scope. A wildcard `allowFrom` entry is **not** sufficient.
</ParamField>
<ParamField path="commands.ownerDisplay" type='"raw" | "hash"'>
Controls how owner ids appear in the system prompt.
</ParamField>
<ParamField path="commands.ownerDisplaySecret" type="string">
HMAC secret used when `commands.ownerDisplay: "hash"`.
</ParamField>
<ParamField path="commands.allowFrom" type="object">
Per-provider allowlist for command authorization. When configured, it is the
**only** authorization source for commands and directives. Use `"*"` for a
global default; provider-specific keys override it.
</ParamField>
<ParamField path="commands.useAccessGroups" type="boolean" default="true">
Enforces allowlists/policies for commands when `commands.allowFrom` is not set.
</ParamField>
## Command list
Commands come from three sources:
- **Core built-ins:** `src/auto-reply/commands-registry.shared.ts`
- **Generated dock commands:** `src/auto-reply/commands-registry.data.ts`
- **Plugin commands:** plugin `registerCommand()` calls
Availability depends on config flags, channel surface, and installed/enabled
plugins.
### Core commands
<AccordionGroup>
<Accordion title="Sessions and runs">
| Command | Description |
| --- | --- |
| `/new [model]` | Archive the current session and start a fresh one |
| `/reset [soft [message]]` | Reset the current session in place. `soft` keeps the transcript, drops reused CLI backend session ids, and reruns startup |
| `/name <title>` | Name or rename the current session. Omit the title to see the current name and a suggestion |
| `/compact [instructions]` | Compact the session context. See [Compaction](/concepts/compaction) |
| `/stop` | Abort the current run |
| `/session idle <duration\|off>` | Manage thread-binding idle expiry |
| `/session max-age <duration\|off>` | Manage thread-binding max-age expiry |
| `/export-session [path]` | Export the current session to HTML. Alias: `/export` |
| `/export-trajectory [path]` | Export a JSONL trajectory bundle for the current session. Alias: `/trajectory` |
<Note>
Control UI intercepts typed `/new` to create and switch to a fresh
dashboard session, except when `session.dmScope: "main"` is configured
and the current parent is the agent's main session — in that case `/new`
resets the main session in place. Typed `/reset` still runs the Gateway's
in-place reset. Use `/model default` when you want to clear a pinned
session model selection.
</Note>
</Accordion>
<Accordion title="Model and run controls">
| Command | Description |
| --- | --- |
| `/think <level\|default>` | Set the thinking level or clear the session override. Aliases: `/thinking`, `/t` |
| `/verbose on\|off\|full` | Toggle verbose output. Alias: `/v` |
| `/trace on\|off` | Toggle plugin trace output for the current session |
| `/fast [status\|auto\|on\|off\|default]` | Show, set, or clear fast mode |
| `/reasoning [on\|off\|stream]` | Toggle reasoning visibility. Alias: `/reason` |
| `/elevated [on\|off\|ask\|full]` | Toggle elevated mode. Alias: `/elev` |
| `/exec host=<auto\|sandbox\|gateway\|node> security=<deny\|allowlist\|full> ask=<off\|on-miss\|always> node=<id>` | Show or set exec defaults |
| `/login [codex\|openai\|openai-codex]` | Pair Codex/OpenAI login from a private chat or Web UI session. Owner/admin only |
| `/model [name\|#\|status]` | Show or set the model |
| `/models [provider] [page] [limit=<n>\|all]` | List configured/auth-available providers or models |
| `/queue <mode>` | Manage active-run queue behavior. See [Queue](/concepts/queue) and [Queue steering](/concepts/queue-steering) |
| `/steer <message>` | Inject guidance into the active run. Alias: `/tell`. See [Steer](/tools/steer) |
<AccordionGroup>
<Accordion title="verbose / trace / fast / reasoning safety">
- `/verbose` is for debugging — keep it **off** in normal use.
- `/trace` reveals only plugin-owned trace/debug lines; normal verbose chatter stays off.
- `/fast auto|on|off` persists a session override; use the Sessions UI `inherit` option to clear it.
- `/fast` is provider-specific: OpenAI/Codex map it to `service_tier=priority`; direct Anthropic requests map it to `service_tier=auto` or `standard_only`.
- `/reasoning`, `/verbose`, and `/trace` are risky in group settings — they may reveal internal reasoning or plugin diagnostics. Keep them off in group chats.
</Accordion>
<Accordion title="Model switching details">
- `/model` persists the new model immediately to the session.
- If the agent is idle, the next run uses it right away.
- If a run is active, the switch is marked pending and applied at the next clean retry point.
</Accordion>
</AccordionGroup>
</Accordion>
<Accordion title="Discovery and status">
| Command | Description |
| --- | --- |
| `/help` | Show the short help summary |
| `/commands` | Show the generated command catalog |
| `/tools [compact\|verbose]` | Show what the current agent can use right now |
| `/status` | Show execution/runtime status, Gateway and system uptime, plugin health, plus provider usage/quota |
| `/status plugins` | Show detailed plugin health: load errors, quarantines, channel plugin failures, dependency issues, compatibility notices. Requires `commands.plugins: true` |
| `/goal [status\|start\|pause\|resume\|complete\|block\|clear] ...` | Manage the current session's durable [goal](/tools/goal) |
| `/diagnostics [note]` | Owner-only support-report flow. Asks for exec approval every time |
| `/crestodian <request>` | Run the Crestodian setup and repair helper from an owner DM |
| `/tasks` | List active/recent background tasks for the current session |
| `/context [list\|detail\|map\|json]` | Explain how context is assembled |
| `/whoami` | Show your sender id. Alias: `/id` |
| `/usage off\|tokens\|full\|reset\|cost` | Control the per-response usage footer (`reset`/`inherit`/`clear`/`default` clears the session override to re-inherit the configured default) or print a local cost summary |
</Accordion>
<Accordion title="Skills, allowlists, approvals">
| Command | Description |
| --- | --- |
| `/skill <name> [input]` | Run a skill by name |
| `/allowlist [list\|add\|remove] ...` | Manage allowlist entries. Text-only |
| `/approve <id> <decision>` | Resolve exec or plugin approval prompts |
| `/btw <question>` | Ask a side question without changing session context. Alias: `/side`. See [BTW](/tools/btw) |
</Accordion>
<Accordion title="Subagents and ACP">
| Command | Description |
| --- | --- |
| `/subagents list\|log\|info` | Inspect sub-agent runs for the current session |
| `/acp spawn\|cancel\|steer\|close\|sessions\|status\|set-mode\|set\|cwd\|permissions\|timeout\|model\|reset-options\|doctor\|install\|help` | Manage ACP sessions and runtime options. Runtime controls require external owner or internal Gateway admin identity |
| `/focus <target>` | Bind the current Discord thread or Telegram topic to a session target |
| `/unfocus` | Remove the current thread binding |
| `/agents` | List thread-bound agents for the current session |
</Accordion>
<Accordion title="Owner-only writes and admin">
| Command | Requires | Description |
| --- | --- | --- |
| `/config show\|get\|set\|unset` | `commands.config: true` | Read or write `openclaw.json`. Owner-only |
| `/mcp show\|get\|set\|unset` | `commands.mcp: true` | Read or write OpenClaw-managed MCP server config. Owner-only |
| `/plugins list\|inspect\|show\|get\|install\|enable\|disable` | `commands.plugins: true` | Inspect or mutate plugin state. Owner-only for writes. Alias: `/plugin` |
| `/debug show\|set\|unset\|reset` | `commands.debug: true` | Runtime-only config overrides. Owner-only |
| `/restart` | `commands.restart: true` (default) | Restart OpenClaw |
| `/send on\|off\|inherit` | owner | Set send policy |
</Accordion>
<Accordion title="Voice, TTS, channel control">
| Command | Description |
| --- | --- |
| `/tts on\|off\|status\|chat\|latest\|provider\|limit\|summary\|audio\|help` | Control TTS. See [TTS](/tools/tts) |
| `/activation mention\|always` | Set group activation mode |
| `/bash <command>` | Run a host shell command. Alias: `! <command>`. Requires `commands.bash: true` |
| `!poll [sessionId]` | Check a background bash job |
| `!stop [sessionId]` | Stop a background bash job |
</Accordion>
</AccordionGroup>
### Dock commands
Dock commands switch the active session's reply route to another linked channel.
See [Channel docking](/concepts/channel-docking) for setup and troubleshooting.
Generated from channel plugins with native-command support:
- `/dock-discord` (alias: `/dock_discord`)
- `/dock-mattermost` (alias: `/dock_mattermost`)
- `/dock-slack` (alias: `/dock_slack`)
- `/dock-telegram` (alias: `/dock_telegram`)
Dock commands require `session.identityLinks`. The source sender and target peer
must be in the same identity group.
### Bundled plugin commands
| Command | Description |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/dreaming [on\|off\|status\|help]` | Toggle memory dreaming (owner or Gateway admin). See [Dreaming](/concepts/dreaming) |
| `/pair [qr\|status\|pending\|approve\|cleanup\|notify]` | Manage device pairing. See [Pairing](/channels/pairing) |
| `/phone status\|arm ...\|disarm` | Temporarily arm high-risk phone node commands |
| `/voice status\|list\|set <voiceId>` | Manage Talk voice config. Discord native name: `/talkvoice` |
| `/card ...` | Send LINE rich card presets. See [LINE](/channels/line) |
| `/codex <action> ...` | Bind, steer, and inspect the Codex app-server harness (status, threads, resume, model, fast, permissions, compact, review, mcp, skills, and more). See [Codex harness](/plugins/codex-harness) |
QQBot-only: `/bot-ping`, `/bot-version`, `/bot-help`, `/bot-upgrade`, `/bot-logs`
### Skill commands
User-invocable skills are exposed as slash commands:
- `/skill <name> [input]` always works as the generic entrypoint.
- Skills may register as direct commands (e.g. `/prose` for OpenProse).
- Native skill-command registration is controlled by `commands.nativeSkills` and
`channels.<provider>.commands.nativeSkills`.
- Names are sanitized to `a-z0-9_` (max 32 chars); collisions get numeric suffixes.
<AccordionGroup>
<Accordion title="Skill command dispatch">
By default, skill commands route to the model as a normal request.
Skills can declare `command-dispatch: tool` to route directly to a tool
(deterministic, no model involvement). Example: `/prose` (OpenProse plugin)
— see [OpenProse](/prose).
</Accordion>
<Accordion title="Native command arguments">
Discord uses autocomplete for dynamic options and button menus when required
args are omitted. Telegram and Slack show a button menu for commands with
choices. Dynamic choices resolve against the target session model, so model-
specific options like `/think` levels follow the session's `/model` override.
</Accordion>
</AccordionGroup>
## `/tools`: what the agent can use now
`/tools` answers a runtime question: **what this agent can use right now in this
conversation** — not a static config catalog.
```text
/tools # compact view
/tools verbose # with short descriptions
```
Results are session-scoped. Changing agent, channel, thread, sender
authorization, or model can change the output. For profile and override editing,
use the Control UI Tools panel or config surfaces.
## `/model`: model selection
```text
/model # show model picker
/model list # same
/model 3 # select by number from picker
/model openai/gpt-5.4
/model opus@anthropic:default
/model default # clear the session model selection
/model status # detailed view with endpoint and API mode
```
On Discord, `/model` and `/models` open an interactive picker with provider and
model dropdowns. The picker respects `agents.defaults.models`, including
`provider/*` entries.
## `/config`: on-disk config writes
<Note>
Owner-only. Disabled by default — enable with `commands.config: true`.
</Note>
```text
/config show
/config show messages.responsePrefix
/config get messages.responsePrefix
/config set messages.responsePrefix="[openclaw]"
/config unset messages.responsePrefix
```
Config is validated before write. Invalid changes are rejected. `/config`
updates persist across restarts.
## `/mcp`: MCP server config
<Note>
Owner-only. Disabled by default — enable with `commands.mcp: true`.
</Note>
```text
/mcp show
/mcp show context7
/mcp set context7={"command":"uvx","args":["context7-mcp"]}
/mcp unset context7
```
`/mcp` stores config in OpenClaw config, not embedded-agent project settings.
## `/debug`: runtime-only overrides
<Note>
Owner-only. Disabled by default — enable with `commands.debug: true`.
Overrides apply immediately to new config reads but do **not** write to disk.
</Note>
```text
/debug show
/debug set messages.responsePrefix="[openclaw]"
/debug set channels.whatsapp.allowFrom=["+1555","+4477"]
/debug unset messages.responsePrefix
/debug reset
```
## `/plugins`: plugin management
<Note>
Owner-only for writes. Disabled by default — enable with `commands.plugins: true`.
</Note>
```text
/plugins
/plugins list
/plugin show context7
/plugins enable context7
/plugins disable context7
/plugins install ./path/to/plugin
```
`/plugins enable|disable` updates plugin config and hot-reloads the Gateway
plugin runtime for new agent turns. `/plugins install` restarts managed
Gateways automatically because plugin source modules changed.
## `/trace`: plugin trace output
```text
/trace # show current trace state
/trace on
/trace off
```
`/trace` reveals session-scoped plugin trace/debug lines without full verbose
mode. It does not replace `/debug` (runtime overrides) or `/verbose` (normal
tool output).
## `/btw`: side questions
`/btw` is a quick side question about the current session context. Alias: `/side`.
```text
/btw what are we doing right now?
/side what changed while the main run continued?
```
Unlike a normal message:
- Uses the current session as background context.
- In Codex harness sessions, runs as an ephemeral Codex side thread.
- Does **not** change future session context.
- Is not written to transcript history.
See [BTW side questions](/tools/btw) for the full behavior.
## Surface notes
<AccordionGroup>
<Accordion title="Session scoping per surface">
- **Text commands:** run in the normal chat session (DMs share `main`, groups have their own session).
- **Native Discord commands:** `agent:<agentId>:discord:slash:<userId>`
- **Native Slack commands:** `agent:<agentId>:slack:slash:<userId>` (prefix configurable via `channels.slack.slashCommand.sessionPrefix`)
- **Native Telegram commands:** `telegram:slash:<userId>` (targets the chat session via `CommandTargetSessionKey`)
- **`/login codex`** sends device pairing codes only through private chat or Web UI response paths. Telegram group/topic invocations ask the owner to DM the bot instead.
- **`/stop`** targets the active chat session to abort the current run.
</Accordion>
<Accordion title="Slack specifics">
`channels.slack.slashCommand` supports a single `/openclaw`-style command.
With `commands.native: true`, create one Slack slash command per built-in
command. Register `/agentstatus` (not `/status`) because Slack reserves
`/status`. Text `/status` still works in Slack messages.
</Accordion>
<Accordion title="Fast path and inline shortcuts">
- Command-only messages from allowlisted senders are handled immediately (bypass queue + model).
- Inline shortcuts (`/help`, `/commands`, `/status`, `/whoami`) also work embedded in normal messages and are stripped before the model sees the remaining text.
- Unauthorized command-only messages are silently ignored; inline `/...` tokens are treated as plain text.
</Accordion>
<Accordion title="Argument notes">
- Commands accept an optional `:` between the command and args (`/think: high`, `/send: on`).
- `/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.
- `/allowlist add|remove` requires `commands.config: true` and honors channel `configWrites`.
</Accordion>
</AccordionGroup>
## Provider usage and status
- **Provider usage/quota** (e.g., "Claude 80% left") shows in `/status` for the current model provider when usage tracking is enabled.
- **Token/cache lines** in `/status` can fall back to the latest transcript usage entry when the live session snapshot is sparse.
- **Execution vs runtime:** `/status` reports `Execution` for the effective sandbox path and `Runtime` for who is running the session: `OpenClaw Default`, `OpenAI Codex`, a CLI backend, or an ACP backend.
- **Per-response tokens/cost:** controlled by `/usage off|tokens|full`.
- `/model status` is about models/auth/endpoints, not usage.
## Related
<CardGroup cols={2}>
<Card title="Skills" href="/tools/skills" icon="puzzle-piece">
How skill slash commands are registered and gated.
</Card>
<Card title="Creating skills" href="/tools/creating-skills" icon="hammer">
Build a skill that registers its own slash command.
</Card>
<Card title="BTW" href="/tools/btw" icon="comments">
Side questions without changing session context.
</Card>
<Card title="Steer" href="/tools/steer" icon="compass">
Guide the agent mid-run with `/steer`.
</Card>
</CardGroup>

77
docs/tools/steer.md Normal file
View File

@@ -0,0 +1,77 @@
---
summary: "Steer an active run without changing queue mode"
read_when:
- Using /steer or /tell while an agent is already running
- Comparing /steer with /queue modes
- Deciding whether to steer the current run or an ACP session
title: "Steer"
sidebarTitle: "Steer"
---
`/steer` first tries to send guidance to an already-active run. It is for
"adjust this run while it is still working" moments. If the current runtime
cannot accept steering, OpenClaw sends the message as a normal prompt instead
of dropping it.
## Current session
Use top-level `/steer` to target the active run for the current session:
```text
/steer prefer the smaller patch and keep the tests focused
/tell summarize before making the next tool call
```
Behavior:
- Targets only the current session's active run.
- Works independently of the session's `/queue` mode.
- Starts a normal turn with the same message when the session is idle or the
active run cannot accept steering.
- Uses the active runtime's steering path, so the model sees the guidance at
the next supported runtime boundary.
## Steer vs queue
`/queue steer` makes normal inbound messages try to steer the active run when
they arrive while a run is active. `/steer <message>` is an explicit command
that tries to inject that command's message into the active run at the next
supported runtime boundary, regardless of the stored `/queue` setting. When
that injection is not available, the command prefix is stripped and `<message>`
continues as a normal prompt.
Use:
- `/steer <message>` when you want to guide the active run right now.
- `/queue steer` when you want future normal messages to steer active runs by
default.
- `/queue collect` or `/queue followup` when future normal messages should wait
for a later turn instead of steering the active run.
- `/queue interrupt` when the newest message should replace the active run
instead of steering it.
For queue modes and steering boundaries, see [Command queue](/concepts/queue) and
[Steering queue](/concepts/queue-steering).
## Sub-agents
Top-level `/steer` targets the current session's active run. Sub-agents report
back to their parent/requester session; `/subagents` is for visibility only.
## ACP sessions
Use `/acp steer` when the target is an ACP harness session:
```text
/acp steer --session agent:main:acp:codex tighten the repro
```
See [ACP agents](/tools/acp-agents) for ACP session selection and runtime
behavior.
## Related
- [Slash commands](/tools/slash-commands)
- [Command queue](/concepts/queue)
- [Steering queue](/concepts/queue-steering)
- [Sub-agents](/tools/subagents)

658
docs/tools/subagents.md Normal file
View File

@@ -0,0 +1,658 @@
---
summary: "Spawn isolated background agent runs that announce results back to the requester chat"
read_when:
- You want background or parallel work via the agent
- You are changing sessions_spawn or sub-agent tool policy
- You are implementing or troubleshooting thread-bound subagent sessions
title: "Sub-agents"
sidebarTitle: "Sub-agents"
---
Sub-agents are background agent runs spawned from an existing agent run.
Each one runs in its own session (`agent:<agentId>:subagent:<uuid>`) and,
when finished, **announces** its result back to the requester chat channel.
Every sub-agent run is tracked as a [background task](/automation/tasks).
Goals:
- Parallelize research, long tasks, and slow tool work without blocking the main run.
- Keep sub-agents isolated by default (session separation, optional sandboxing).
- Keep the tool surface hard to misuse: sub-agents do **not** get session or message tools by default.
- Support configurable nesting depth for orchestrator patterns.
<Note>
**Cost note:** each sub-agent has its own context and token usage by
default. For heavy or repetitive tasks, set a cheaper model for sub-agents
and keep your main agent on a higher-quality model via
`agents.defaults.subagents.model` or per-agent overrides. When a child
genuinely needs the requester's current transcript, spawn it with
`context: "fork"`. Thread-bound subagent sessions default to
`context: "fork"` because they branch the current conversation into a
follow-up thread.
</Note>
## Slash command
`/subagents` inspects sub-agent runs for the **current session**:
```text
/subagents list
/subagents log <id|#> [limit] [tools]
/subagents info <id|#>
```
`/subagents info` shows run metadata (status, timestamps, session id,
transcript path, cleanup). `/subagents log` prints recent chat turns for a
run; add the `tools` token to include tool-call/result messages (omitted
by default). Use `sessions_history` for a bounded, safety-filtered recall
view from within an agent turn, or inspect the transcript path on disk for
the raw full transcript.
### Thread binding controls
These commands work on channels with persistent thread bindings. See
[Thread supporting channels](#thread-supporting-channels) below.
```text
/focus <subagent-label|session-key|session-id|session-label>
/unfocus
/agents
/session idle <duration|off>
/session max-age <duration|off>
```
### Spawn behavior
Agents start background sub-agents with the `sessions_spawn` tool.
Completions return as internal parent-session events; the parent/requester
agent decides whether a user-facing update is needed.
<AccordionGroup>
<Accordion title="Non-blocking, push-based completion">
- `sessions_spawn` is non-blocking; it returns a run id immediately.
- On completion, the sub-agent reports back to the parent/requester session.
- Agent turns that need child results should call `sessions_yield` after spawning required work. That ends the current turn and lets the completion event arrive as the next model-visible message.
- Completion is push-based. Once spawned, do **not** poll `/subagents list`, `sessions_list`, or `sessions_history` in a loop just to wait for it to finish; check status on-demand only when debugging.
- Child output is a report/evidence for the requester agent to synthesize. It is not user-authored instruction text and cannot override system, developer, or user policy.
- On completion, OpenClaw best-effort closes tracked browser tabs/processes opened by that sub-agent session before the announce cleanup flow continues.
</Accordion>
<Accordion title="Completion delivery">
- OpenClaw hands completions back to the requester session through an `agent` turn with a stable idempotency key.
- If the requester run is still active, OpenClaw first tries to wake/steer that run instead of starting a second visible reply path.
- If an active requester cannot be woken, OpenClaw falls back to a requester-agent handoff with the same completion context instead of dropping the announce.
- A successful parent handoff completes sub-agent delivery even when the parent decides no visible user update is needed.
- Native sub-agents do not get the message tool. They return plain assistant text to the parent/requester agent; human-visible replies stay owned by the parent/requester agent's normal delivery policy.
- If direct handoff cannot be used, delivery falls back to queue routing, then to a short exponential-backoff retry of the announce before final give-up.
- Delivery keeps the resolved requester route: thread-bound or conversation-bound completion routes win when available. If the completion origin only provides a channel, OpenClaw fills the missing target/account from the requester session's resolved route (`lastChannel` / `lastTo` / `lastAccountId`) so direct delivery still works.
</Accordion>
<Accordion title="Completion handoff metadata">
The completion handoff to the requester session is runtime-generated
internal context (not user-authored text) and includes:
- `Result` — the latest visible `assistant` reply text from the child. Tool/toolResult output is not promoted into child results. Terminal failed runs do not reuse captured reply text.
- `Status``completed; ready for parent review` / `failed` / `timed out` / `unknown`.
- Compact runtime/token stats.
- A review instruction telling the requester agent to verify the result before deciding whether the original task is done.
- Follow-up guidance telling the requester agent to continue the task or record a follow-up when the child result leaves more action.
- A final-update instruction for the no-more-action path, written in normal assistant voice without forwarding raw internal metadata.
</Accordion>
<Accordion title="Modes and ACP runtime">
- `--model` and `--thinking` override defaults for that specific run.
- Use `info`/`log` to inspect details and output after completion.
- For persistent thread-bound sessions, use `sessions_spawn` with `thread: true` and `mode: "session"`.
- If the requester channel does not support thread bindings, use `mode: "run"` instead of retrying an impossible thread-bound combination.
- For ACP harness sessions (Claude Code, Gemini CLI, OpenCode, or explicit Codex ACP/acpx), use `sessions_spawn` with `runtime: "acp"` when the tool advertises that runtime. See [ACP delivery model](/tools/acp-agents#delivery-model) when debugging completions or agent-to-agent loops. When the `codex` plugin is enabled, Codex chat/thread control should prefer `/codex ...` over ACP unless the user explicitly asks for ACP/acpx.
- OpenClaw hides `runtime: "acp"` until ACP is enabled, the requester is not sandboxed, and a backend plugin such as `acpx` is loaded. `runtime: "acp"` expects an external ACP harness id, or an `agents.list[]` entry with `runtime.type="acp"`; use the default sub-agent runtime for normal OpenClaw config agents from `agents_list`.
</Accordion>
</AccordionGroup>
## Context modes
Native sub-agents start isolated unless the caller explicitly asks to fork
the current transcript.
| Mode | When to use it | Behavior |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `isolated` | Fresh research, independent implementation, slow tool work, or anything that can be briefed in the task text | Creates a clean child transcript. This is the default and keeps token use lower. |
| `fork` | Work that depends on the current conversation, prior tool results, or nuanced instructions already present in the requester transcript | Branches the requester transcript into the child session before the child starts. |
Use `fork` sparingly. It is for context-sensitive delegation, not a
replacement for writing a clear task prompt.
## Tool: `sessions_spawn`
Starts a sub-agent run with `deliver: false` on the global `subagent` lane,
then runs an announce step and posts the announce reply to the requester
chat channel.
Availability depends on the caller's effective tool policy. The built-in
`coding` profile includes `sessions_spawn`; `messaging` and `minimal` do
not. `full` allows every tool. Add `tools.alsoAllow: ["sessions_spawn",
"sessions_yield", "subagents"]`, or use `tools.profile: "coding"`, for
agents on a narrower profile that should still delegate work.
Channel/group, provider, sandbox, and per-agent allow/deny policies can
still remove the tool after the profile stage. Use `/tools` from the same
session to confirm the effective tool list.
**Defaults:**
- **Model:** native sub-agents inherit the caller unless you set `agents.defaults.subagents.model` (or per-agent `agents.list[].subagents.model`). ACP runtime spawns use the same configured subagent model when present; otherwise the ACP harness keeps its own default. An explicit `sessions_spawn.model` still wins.
- **Thinking:** native sub-agents inherit the caller unless you set `agents.defaults.subagents.thinking` (or per-agent `agents.list[].subagents.thinking`). ACP runtime spawns also apply `agents.defaults.models["provider/model"].params.thinking` for the selected model. An explicit `sessions_spawn.thinking` still wins.
- **Run timeout:** OpenClaw uses `agents.defaults.subagents.runTimeoutSeconds` when set; otherwise it falls back to `0` (no timeout). `sessions_spawn` does not accept per-call timeout overrides.
- **Task delivery:** native sub-agents receive the delegated task in their first visible `[Subagent Task]` message. The sub-agent system prompt carries runtime rules and routing context, not a hidden duplicate of the task.
Accepted native sub-agent spawns include the resolved child model metadata
in the tool result: `resolvedModel` contains the applied model ref and
`resolvedProvider` contains the provider prefix when the ref has one.
### Delegation prompt mode
`agents.defaults.subagents.delegationMode` controls prompt guidance only; it does not change tool policy or enforce delegation.
- `suggest` (default): keep the standard prompt nudge to use sub-agents for larger or slower work.
- `prefer`: tell the main agent to stay responsive and delegate anything more involved than a direct reply through `sessions_spawn`.
Per-agent override: `agents.list[].subagents.delegationMode`.
```json5
{
agents: {
defaults: {
subagents: {
delegationMode: "prefer",
maxConcurrent: 4,
},
},
list: [
{
id: "coordinator",
subagents: { delegationMode: "prefer" },
},
],
},
}
```
### Tool parameters
<ParamField path="task" type="string" required>
The task description for the sub-agent.
</ParamField>
<ParamField path="taskName" type="string">
Optional stable handle for identifying a specific child in later status output. Must match `[a-z][a-z0-9_-]{0,63}` and cannot be a reserved target such as `last` or `all`.
</ParamField>
<ParamField path="label" type="string">
Optional human-readable label.
</ParamField>
<ParamField path="agentId" type="string">
Spawn under another configured agent id when allowed by `subagents.allowAgents`.
</ParamField>
<ParamField path="cwd" type="string">
Optional task working directory for the child run. Native sub-agents still load bootstrap files from the target agent workspace; `cwd` only changes where runtime tools and CLI harnesses do the delegated work.
</ParamField>
<ParamField path="runtime" type='"subagent" | "acp"' default="subagent">
`acp` is only for external ACP harnesses (`claude`, `droid`, `gemini`, `opencode`, or explicitly requested Codex ACP/acpx) and for `agents.list[]` entries whose `runtime.type` is `acp`.
</ParamField>
<ParamField path="resumeSessionId" type="string">
ACP-only. Resumes an existing ACP harness session when `runtime: "acp"`; ignored for native sub-agent spawns.
</ParamField>
<ParamField path="streamTo" type='"parent"'>
ACP-only. Streams ACP run output to the parent session when `runtime: "acp"`; omit for native sub-agent spawns.
</ParamField>
<ParamField path="model" type="string">
Override the sub-agent model. Invalid values are skipped and the sub-agent runs on the default model with a warning in the tool result.
</ParamField>
<ParamField path="thinking" type="string">
Override thinking level for the sub-agent run.
</ParamField>
<ParamField path="thread" type="boolean" default="false">
When `true`, requests channel thread binding for this sub-agent session.
</ParamField>
<ParamField path="mode" type='"run" | "session"' default="run">
If `thread: true` and `mode` is omitted, default becomes `session`. `mode: "session"` requires `thread: true`.
If thread binding is unavailable for the requester channel, use `mode: "run"` instead.
</ParamField>
<ParamField path="cleanup" type='"delete" | "keep"' default="keep">
`"delete"` archives the session immediately after announce (still keeps the transcript via rename).
</ParamField>
<ParamField path="sandbox" type='"inherit" | "require"' default="inherit">
`require` rejects the spawn unless the target child runtime is sandboxed.
</ParamField>
<ParamField path="context" type='"isolated" | "fork"' default="isolated">
`fork` branches the requester's current transcript into the child session. Native sub-agents only. Thread-bound spawns default to `fork`; non-thread spawns default to `isolated`.
</ParamField>
<Warning>
`sessions_spawn` does **not** accept channel-delivery params (`target`,
`channel`, `to`, `threadId`, `replyTo`, `transport`). Native sub-agents report
their latest assistant turn back to the requester; external delivery stays with
the parent/requester agent.
</Warning>
### Task names and targeting
`taskName` is a model-facing handle for orchestration, not a session key.
Use it for stable child names such as `review_subagents`,
`linux_validation`, or `docs_update` when a coordinator may need to inspect
that child later.
Target resolution accepts exact `taskName` matches and unambiguous
prefixes. Matching is scoped to the same active/recent target window used
by numbered `/subagents` targets, so a stale completed child does not make
a reused handle ambiguous. If two active or recent children share the same
`taskName`, the target is ambiguous; use the list index, session key, or
run id instead.
The reserved targets `last` and `all` are not valid `taskName` values
because they already have control meanings.
## Tool: `sessions_yield`
Ends the current model turn and waits for runtime events, primarily
sub-agent completion events, to arrive as the next message. Use it after
spawning required child work when the requester cannot produce a final
answer until those completions arrive.
`sessions_yield` is the waiting primitive. Do not replace it with polling
loops over `subagents`, `sessions_list`, `sessions_history`, shell
`sleep`, or process polling just to detect child completion.
Only use `sessions_yield` when the session's effective tool list includes
it. Some minimal or custom tool profiles may expose `sessions_spawn` and
`subagents` without exposing `sessions_yield`; in that case, do not invent
a polling loop just to wait for completion.
When active children exist, OpenClaw injects a compact runtime-generated
`Active Subagents` prompt block into normal turns so the requester can see
the current child sessions, run ids, statuses, labels, tasks, and
`taskName` aliases without polling. The task and label fields in that
block are quoted as data, not instructions, because they can originate
from user/model-provided spawn arguments.
## Tool: `subagents`
Lists spawned sub-agent runs owned by the requester session. It is scoped
to the current requester; a child can only see its own controlled children.
Use `subagents` for on-demand status and debugging. Use `sessions_yield` to
wait for completion events.
## 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
A channel supports persistent thread-bound subagent sessions
(`sessions_spawn` with `thread: true`) when it registers a conversation
binding adapter. Bundled channels with that support: **Discord**,
**iMessage**, **Matrix**, and **Telegram**. Discord and Matrix default to
creating a child thread; Telegram and iMessage default to binding the
current conversation. Use the per-channel `threadBindings` config keys for
enablement, timeouts, and `spawnSessions`.
### Quick flow
<Steps>
<Step title="Spawn">
`sessions_spawn` with `thread: true` (and optionally `mode: "session"`).
</Step>
<Step title="Bind">
OpenClaw creates or binds a thread to that session target in the active channel.
</Step>
<Step title="Route follow-ups">
Replies and follow-up messages in that thread route to the bound session.
</Step>
<Step title="Inspect timeouts">
Use `/session idle` to inspect/update inactivity auto-unfocus and
`/session max-age` to control the hard cap.
</Step>
<Step title="Detach">
Use `/unfocus` to detach manually.
</Step>
</Steps>
### Manual controls
| Command | Effect |
| ------------------ | ----------------------------------------------------------------------------------------- |
| `/focus <target>` | Bind the current thread (or create one) to a sub-agent/session target |
| `/unfocus` | Remove the binding for the current bound thread |
| `/agents` | List active runs and binding state (`binding:<id>`, `unbound`, or `bindings unavailable`) |
| `/session idle` | Inspect/update idle auto-unfocus (focused bound threads only) |
| `/session max-age` | Inspect/update hard cap (focused bound threads only) |
### Config switches
- **Global default:** `session.threadBindings.enabled`, `session.threadBindings.idleHours`, `session.threadBindings.maxAgeHours`.
- **Channel override and spawn auto-bind keys** are adapter-specific. See [Thread supporting channels](#thread-supporting-channels) above.
See [Configuration reference](/gateway/configuration-reference) and
[Slash commands](/tools/slash-commands) for current adapter details.
### Allowlist
<ParamField path="agents.list[].subagents.allowAgents" type="string[]">
List of configured agent ids that can be targeted via explicit `agentId` (`["*"]` allows any configured target). Default: only the requester agent. If you set a list and still want the requester to spawn itself with `agentId`, include the requester id in the list.
</ParamField>
<ParamField path="agents.defaults.subagents.allowAgents" type="string[]">
Default configured target-agent allowlist used when the requester agent does not set its own `subagents.allowAgents`.
</ParamField>
<ParamField path="agents.defaults.subagents.requireAgentId" type="boolean" default="false">
Block `sessions_spawn` calls that omit `agentId` (forces explicit profile selection). Per-agent override: `agents.list[].subagents.requireAgentId`.
</ParamField>
<ParamField path="agents.defaults.subagents.announceTimeoutMs" type="number" default="120000">
Per-call timeout for gateway `agent` announce delivery attempts. Values are positive integer milliseconds and are clamped to the platform-safe timer maximum. Transient retries can make the total announce wait longer than one configured timeout.
</ParamField>
If the requester session is sandboxed, `sessions_spawn` rejects targets
that would run unsandboxed.
### Discovery
Use `agents_list` to see which agent ids are currently allowed for
`sessions_spawn`. The response includes each listed agent's effective
model and embedded runtime metadata so callers can distinguish OpenClaw, Codex
app-server, and other configured native runtimes.
`allowAgents` entries must point at configured agent ids in `agents.list[]`.
`["*"]` means any configured target agent plus the requester. If an agent config
is deleted but its id remains in `allowAgents`, `sessions_spawn` rejects that id
and `agents_list` omits it. Run `openclaw doctor --fix` to clean stale
allowlist entries, or add a minimal `agents.list[]` entry when the target should
remain spawnable while inheriting defaults.
### Auto-archive
- Sub-agent sessions are automatically archived after `agents.defaults.subagents.archiveAfterMinutes` (default `60`).
- Archive uses `sessions.delete` and renames the transcript to `*.deleted.<timestamp>` (same folder).
- `cleanup: "delete"` archives immediately after announce (still keeps the transcript via rename).
- Auto-archive is best-effort; pending timers are lost if the gateway restarts.
- Configured run timeouts do **not** auto-archive; they only stop the run. The session remains until auto-archive.
- Auto-archive applies equally to depth-1 and depth-2 sessions.
- Browser cleanup is separate from archive cleanup: tracked browser tabs/processes are best-effort closed when the run finishes, even if the transcript/session record is kept.
## Nested sub-agents
By default, sub-agents cannot spawn their own sub-agents
(`maxSpawnDepth: 1`). Set `maxSpawnDepth: 2` to enable one level of
nesting — the **orchestrator pattern**: main → orchestrator sub-agent →
worker sub-sub-agents.
```json5
{
agents: {
defaults: {
subagents: {
maxSpawnDepth: 2, // allow sub-agents to spawn children (default: 1, range 1-5)
maxChildrenPerAgent: 5, // max active children per agent session (default: 5, range 1-20)
maxConcurrent: 8, // global concurrency lane cap (default: 8)
runTimeoutSeconds: 900, // default timeout for sessions_spawn (0 = no timeout)
announceTimeoutMs: 120000, // per-call gateway announce timeout
},
},
},
}
```
### 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.
<Note>
**Operational guidance:** start child work once and wait for completion
events instead of building poll loops around `sessions_list`,
`sessions_history`, `/subagents list`, or `exec` sleep commands.
`sessions_list` and `/subagents list` keep child-session relationships
focused on live work — live children remain attached, ended children stay
visible for a short recent window, and stale store-only child links are
ignored after their freshness window. This prevents old `spawnedBy` /
`parentSessionKey` metadata from resurrecting ghost children after
restart. If a child completion event arrives after you already sent the
final answer, the correct follow-up is the exact silent token
`NO_REPLY` / `no_reply`.
</Note>
### Tool policy by depth
- Role and control scope are written into session metadata at spawn time. That keeps flat or restored session keys from accidentally regaining orchestrator privileges.
- **Depth 1 (orchestrator, when `maxSpawnDepth >= 2`):** gets `sessions_spawn`, `subagents`, `sessions_list`, `sessions_history` so it can spawn children and inspect their status. Other session/system tools remain denied.
- **Depth 1 (leaf, when `maxSpawnDepth == 1`):** no session tools (current default behavior).
- **Depth 2 (leaf worker):** no session tools — `sessions_spawn` is always denied at depth 2. Cannot spawn further children.
### 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:
- `/stop` in the main chat stops all depth-1 agents and cascades to their depth-2 children.
## Authentication
Sub-agent auth is resolved by **agent id**, not by session type:
- The sub-agent session key is `agent:<agentId>:subagent:<uuid>`.
- The auth store is loaded from that agent's `agentDir`.
- The main agent's auth profiles are merged in as a **fallback**; agent profiles override main profiles on conflicts.
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:
- The announce step runs inside the sub-agent session (not the requester session).
- If the sub-agent replies exactly `ANNOUNCE_SKIP`, nothing is posted.
- If the latest assistant text is the exact silent token `NO_REPLY` / `no_reply`, announce output is suppressed even if earlier visible progress existed.
Delivery depends on requester depth:
- Top-level requester sessions use a follow-up `agent` call with external delivery (`deliver=true`).
- Nested requester subagent sessions receive an internal follow-up injection (`deliver=false`) so the orchestrator can synthesize child results in-session.
- If a nested requester subagent session is gone, OpenClaw falls back to that session's requester when available.
For top-level requester sessions, completion-mode direct delivery first
resolves any bound conversation/thread route and hook override, then fills
missing channel-target fields from the requester session's stored route.
That keeps completions on the right chat/topic even when the completion
origin only identifies the channel.
Child completion aggregation is scoped to the current requester run when
building nested completion findings, preventing stale prior-run child
outputs from leaking into the current announce. Announce replies preserve
thread/topic routing when available on channel adapters.
### Announce context
Announce context is normalized to a stable internal event block:
| Field | Source |
| -------------- | -------------------------------------------------------------------------------------------------------- |
| Source | `subagent` or `cron` |
| Session ids | Child session key/id |
| Type | Announce type + task label |
| Status | Derived from runtime outcome (`ok`, `error`, `timeout`, or `unknown`) — **not** inferred from model text |
| Result content | Latest visible assistant text from the child |
| Follow-up | Instruction describing when to reply vs stay silent |
Terminal failed runs report failure status without replaying captured
reply text. Tool/toolResult output is not promoted into child result text.
### Stats line
Announce payloads include a stats line at the end (even when wrapped):
- Runtime (e.g. `runtime 5m12s`).
- Token usage (input/output/total).
- Estimated cost when model pricing is configured (`models.providers.*.models[].cost`).
- `sessionKey`, `sessionId`, and transcript path so the main agent can fetch history via `sessions_history` or inspect the file on disk.
Internal metadata is meant for orchestration only; user-facing replies
should be rewritten in normal assistant voice.
### Why prefer `sessions_history`
`sessions_history` is the safer orchestration path for reading a child's
transcript from within an agent turn:
- Redacts credential/token-like text even when general-purpose log redaction is disabled.
- Truncates long text blocks (4000 chars per block) and drops thinking signatures, reasoning replay payloads, and inline image data.
- Enforces an 80 KB response cap; oversized rows are replaced with `[sessions_history omitted: message too large]`.
- Use `nextOffset` when present to page backward through older transcript windows.
- `sessions_history` does **not** strip reasoning tags, `<relevant-memories>` scaffolding, or tool-call XML from message text — it returns structured content blocks close to the raw transcript shape, just redacted and size-bounded. `/subagents log` applies the heavier prose sanitizer (strips reasoning tags, memory scaffolding, and tool-call XML) because it renders plain chat lines instead of structured blocks.
- Raw on-disk transcript inspection is the fallback when you need the full byte-for-byte transcript.
## Tool policy
Sub-agents use the same profile and tool-policy pipeline as the parent or
target agent first. After that, OpenClaw applies the sub-agent restriction
layer.
Sub-agents always lose `gateway`, `agents_list`, `session_status`, and
`cron` regardless of depth or role (system-level/interactive tools, or
tools the main agent should coordinate). Leaf sub-agents (default depth-1
behavior, and always at depth 2) additionally lose `subagents`,
`sessions_list`, `sessions_history`, and `sessions_spawn`. Sub-agents never
get the `message` tool — it is disabled at spawn time, not filtered by
this deny list — and `sessions_send` stays denied so sub-agents
communicate only through the announce chain.
`sessions_history` remains a bounded, sanitized recall view here too — it
is not a raw transcript dump.
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
```json5
{
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"]
},
},
},
}
```
`tools.subagents.tools.allow` is a final allow-only filter. It can narrow
the already-resolved tool set, but it cannot **add back** a tool removed
by `tools.profile`. For example, `tools.profile: "coding"` includes
`web_search`/`web_fetch` but not the `browser` tool. To let
coding-profile sub-agents use browser automation, add browser at the
profile stage:
```json5
{
tools: {
profile: "coding",
alsoAllow: ["browser"],
},
}
```
Use per-agent `agents.list[].tools.alsoAllow: ["browser"]` when only one
agent should get browser automation.
## Concurrency
Sub-agents use a dedicated in-process queue lane:
- **Lane name:** `subagent`
- **Concurrency:** `agents.defaults.subagents.maxConcurrent` (default `8`)
## Liveness and recovery
OpenClaw does not treat `endedAt` absence as permanent proof that a
sub-agent is still alive. Unended runs older than the stale-run window
(2 hours, or the configured run timeout plus a short grace period,
whichever is longer) stop counting as active/pending in `/subagents list`,
status summaries, descendant completion gating, and per-session
concurrency checks.
After a gateway restart, stale unended restored runs are pruned unless
their child session is marked `abortedLastRun: true`. Those
restart-aborted child sessions remain recoverable through the sub-agent
orphan recovery flow, which sends a synthetic resume message before
clearing the aborted marker.
Automatic restart recovery is bounded per child session. If the same
sub-agent child is accepted for orphan recovery repeatedly inside the
rapid re-wedge window, OpenClaw persists a recovery tombstone on that
session and stops auto-resuming it on later restarts. Run
`openclaw tasks maintenance --apply` to reconcile the task record, or
`openclaw doctor --fix` to clear stale aborted recovery flags on
tombstoned sessions.
<Note>
If a sub-agent spawn fails with Gateway `PAIRING_REQUIRED` /
`scope-upgrade`, check the RPC caller before editing pairing state.
Internal `sessions_spawn` coordination dispatches in process when the
caller is already running inside the gateway request context, so it does
not open a loopback WebSocket or depend on the CLI's paired-device scope
baseline. Callers outside the gateway process still use the WebSocket
fallback as `client.id: "gateway-client"` with `client.mode: "backend"`
over direct loopback shared-token/password auth. Remote callers, explicit
`deviceIdentity`, explicit device-token paths, and browser/node clients
still need normal device approval for scope upgrades.
</Note>
## Stopping
- Sending `/stop` in the requester chat aborts the requester session and stops any active sub-agent runs spawned from it, cascading to nested children.
## Limitations
- Sub-agent announce is **best-effort**. If the gateway restarts, pending "announce back" work is lost.
- Sub-agents still share the same gateway process resources; treat `maxConcurrent` as a safety valve.
- `sessions_spawn` is always non-blocking: it returns `{ status: "accepted", runId, childSessionKey }` immediately.
- Sub-agent context only injects `AGENTS.md` and `TOOLS.md` (no `SOUL.md`, `IDENTITY.md`, `USER.md`, `MEMORY.md`, `HEARTBEAT.md`, or `BOOTSTRAP.md`). Codex-native subagents follow the same boundary: `TOOLS.md` stays in inherited Codex thread instructions, while parent-only persona, identity, and user files are injected as turn-scoped collaboration instructions so children do not clone them.
- Maximum nesting depth is 5 (`maxSpawnDepth` range: 1-5). Depth 2 is recommended for most use cases.
- `maxChildrenPerAgent` caps active children per session (default `5`, range `1-20`).
## Related
- [ACP agents](/tools/acp-agents)
- [Agent send](/tools/agent-send)
- [Background tasks](/automation/tasks)
- [Multi-agent sandbox tools](/tools/multi-agent-sandbox-tools)

169
docs/tools/tavily.md Normal file
View File

@@ -0,0 +1,169 @@
---
summary: "Tavily search and extract tools"
read_when:
- You want Tavily-backed web search
- You need a Tavily API key
- You want Tavily as a web_search provider
- You want content extraction from URLs
title: "Tavily"
---
[Tavily](https://tavily.com) is a search API designed for AI applications. OpenClaw exposes it in two ways:
- as the `web_search` provider for the generic search tool
- as explicit plugin tools: `tavily_search` and `tavily_extract`
Tavily returns structured results optimized for LLM consumption, with configurable search depth, topic filtering, domain filters, AI-generated answer summaries, and content extraction from URLs (including JavaScript-rendered pages).
| Property | Value |
| --------- | --------------------------------------------------------------------------------------------- |
| Plugin id | `tavily` |
| Package | `@openclaw/tavily-plugin` |
| Auth | `TAVILY_API_KEY` env var or config `apiKey` |
| Base URL | `https://api.tavily.com` (default); `TAVILY_BASE_URL` env var or config `baseUrl` to override |
| Timeouts | 30s search, 60s extract (default) |
| Tools | `tavily_search`, `tavily_extract` |
## Getting started
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/tavily-plugin
```
</Step>
<Step title="Get an API key">
Create a Tavily account at [tavily.com](https://tavily.com), then generate an API key in the dashboard.
</Step>
<Step title="Configure the plugin and provider">
```json5
{
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",
},
},
},
}
```
</Step>
<Step title="Verify search runs">
Trigger a `web_search` from any agent, or call `tavily_search` directly.
</Step>
</Steps>
<Tip>
Choosing Tavily in onboarding or `openclaw configure --section web` installs and enables the official Tavily plugin when needed.
</Tip>
## Tool reference
### `tavily_search`
Use this when you want Tavily-specific search controls instead of generic `web_search`.
| Parameter | Type | Constraints / default | Description |
| ----------------- | ------------ | -------------------------------------- | --------------------------------------------- |
| `query` | string | required | Search query string. |
| `search_depth` | enum | `basic` (default), `advanced` | `advanced` is slower but higher relevance. |
| `topic` | enum | `general` (default), `news`, `finance` | Filter by topic family. |
| `max_results` | integer | 1-20, default `5` | Number of results. |
| `include_answer` | boolean | default `false` | Include a Tavily AI-generated answer summary. |
| `time_range` | enum | `day`, `week`, `month`, `year` | Filter results by recency. |
| `include_domains` | string array | (none) | Only include results from these domains. |
| `exclude_domains` | string array | (none) | Exclude results from these domains. |
Search depth tradeoff:
| Depth | Speed | Relevance | Best for |
| ---------- | ------ | --------- | ------------------------------------ |
| `basic` | Faster | High | General-purpose queries (default). |
| `advanced` | Slower | Highest | Precision research and fact-finding. |
### `tavily_extract`
Use this to extract clean content from one or more URLs. Handles JavaScript-rendered pages and supports query-focused chunking for targeted extraction.
| Parameter | Type | Constraints / default | Description |
| ------------------- | ------------ | ----------------------------- | ----------------------------------------------------------- |
| `urls` | string array | required, 1-20 | URLs to extract content from. |
| `query` | string | (optional) | Rerank extracted chunks by relevance to this query. |
| `extract_depth` | enum | `basic` (default), `advanced` | Use `advanced` for JS-heavy pages, SPAs, or dynamic tables. |
| `chunks_per_source` | integer | 1-5; **requires `query`** | Chunks returned per URL. Errors if set without `query`. |
| `include_images` | boolean | default `false` | Include image URLs in results. |
Extract depth tradeoff:
| Depth | When to use |
| ---------- | ------------------------------------------ |
| `basic` | Simple pages. Try this first. |
| `advanced` | JS-rendered SPAs, dynamic content, tables. |
<Tip>
Batch larger URL lists into multiple `tavily_extract` calls (max 20 per request). Use `query` plus `chunks_per_source` to get only relevant content instead of full pages.
</Tip>
## 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` |
<Note>
The generic `web_search` tool with Tavily as provider supports `query` and `count` (up to 20 results). For Tavily-specific controls (`search_depth`, `topic`, `include_answer`, domain filters, time range), use `tavily_search` instead.
</Note>
## Advanced configuration
<AccordionGroup>
<Accordion title="API key resolution order">
The Tavily client looks up its API key in this order:
1. `plugins.entries.tavily.config.webSearch.apiKey` (resolved through SecretRefs).
2. `TAVILY_API_KEY` from the gateway environment.
`tavily_search` and `tavily_extract` both raise a setup error if neither is present.
</Accordion>
<Accordion title="Custom base URL">
Override `plugins.entries.tavily.config.webSearch.baseUrl`, or set `TAVILY_BASE_URL`, if you front Tavily through a proxy. Config takes priority over the env var. The default is `https://api.tavily.com`.
</Accordion>
<Accordion title="`chunks_per_source` requires `query`">
`tavily_extract` rejects calls that pass `chunks_per_source` without a `query`. Tavily ranks chunks by query relevance, so the parameter is meaningless without one.
</Accordion>
</AccordionGroup>
## Related
<CardGroup cols={2}>
<Card title="Web Search overview" href="/tools/web" icon="magnifying-glass">
All providers and auto-detection rules.
</Card>
<Card title="Firecrawl" href="/tools/firecrawl" icon="fire">
Search plus scraping with content extraction.
</Card>
<Card title="Exa Search" href="/tools/exa-search" icon="binoculars">
Neural search with content extraction.
</Card>
<Card title="Configuration" href="/gateway/configuration" icon="gear">
Full config schema for plugin entries and tool routing.
</Card>
</CardGroup>

143
docs/tools/thinking.md Normal file
View File

@@ -0,0 +1,143 @@
---
summary: "Directive syntax for /think, /fast, /verbose, /trace, and reasoning visibility"
read_when:
- Adjusting thinking, fast-mode, or verbose directive parsing or defaults
title: "Thinking levels"
---
## 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 | max`, roughly mirroring Anthropic's classic "think" < "think hard" < "think harder" < "ultrathink" magic-word ladder:
- minimal ~ "think"
- low ~ "think hard"
- medium ~ "think harder"
- high ~ "ultrathink" (max budget)
- xhigh ~ "ultrathink+" (GPT-5.2+ and Codex models, plus Anthropic Claude Opus 4.7+ effort)
- adaptive → provider-managed adaptive thinking (supported for Claude 4.6 on Anthropic/Bedrock, Anthropic Claude Opus 4.7+, and Google Gemini dynamic thinking)
- max → provider max reasoning (Anthropic Claude Opus 4.7+; Ollama maps this to its highest native `think` effort)
- `x-high`, `x_high`, `extra-high`, `extra high`, and `extra_high` map to `xhigh`.
- `highest` maps to `high`.
- Provider notes:
- Thinking menus and pickers are provider-profile driven. Provider plugins declare the exact level set for the selected model, including labels such as binary `on`.
- `adaptive`, `xhigh`, and `max` are only advertised for provider/model profiles that support them. Typed directives for unsupported levels are rejected with that model's valid options.
- Existing stored unsupported levels are remapped by provider profile rank. `adaptive` falls back to `medium` on non-adaptive models, while `xhigh` and `max` fall back to the largest supported non-off level for the selected model.
- Anthropic Claude 4.6 models default to `adaptive` when no explicit thinking level is set.
- Anthropic Claude Opus 4.8 and Opus 4.7 keep thinking off unless you explicitly set a thinking level. Opus 4.8's provider-owned effort default is `high` after adaptive thinking is enabled.
- Anthropic Claude Opus 4.7+ maps `/think xhigh` to adaptive thinking plus `output_config.effort: "xhigh"`, because `/think` is a thinking directive and `xhigh` is the Opus effort setting.
- Anthropic Claude Opus 4.7+ also exposes `/think max`; it maps to the same provider-owned max effort path.
- Direct DeepSeek V4 models expose `/think xhigh|max`; both map to DeepSeek `reasoning_effort: "max"` while lower non-off levels map to `high`.
- OpenRouter-routed DeepSeek V4 models expose `/think xhigh` and send OpenRouter-supported `reasoning.effort` values instead of DeepSeek-native top-level `reasoning_effort`. Lower non-off levels map to `high`, and stored `max` overrides fall back to `xhigh`.
- Ollama thinking-capable models expose `/think low|medium|high|max`; `max` maps to native `think: "high"` because Ollama's native API accepts `low`, `medium`, and `high` effort strings.
- OpenAI GPT models map `/think` through model-specific Responses API effort support. `/think off` sends `reasoning.effort: "none"` only when the target model supports it; otherwise OpenClaw omits the disabled reasoning payload instead of sending an unsupported value.
- Custom OpenAI-compatible catalog entries can opt into `/think xhigh` by setting `models.providers.<provider>.models[].compat.supportedReasoningEfforts` to include `"xhigh"`. This uses the same compat metadata that maps outbound OpenAI reasoning effort payloads, so menus, session validation, agent CLI, and `llm-task` agree with transport behavior.
- Stale configured OpenRouter Hunter Alpha refs skip proxy reasoning injection because that retired route could return final answer text through reasoning fields.
- Google Gemini maps `/think adaptive` to Gemini's provider-owned dynamic thinking. Gemini 3 requests omit a fixed `thinkingLevel`, while Gemini 2.5 requests send `thinkingBudget: -1`; fixed levels still map to the closest Gemini `thinkingLevel` or budget for that model family.
- MiniMax M2.x (`minimax/MiniMax-M2*`) on the Anthropic-compatible streaming path defaults to `thinking: { type: "disabled" }` unless you explicitly set thinking in model params or request params. This avoids leaked `reasoning_content` deltas from M2.x's non-native Anthropic stream format. MiniMax-M3 (and M3.x) is exempt: M3 emits proper Anthropic thinking blocks and returns empty content when thinking is disabled, so OpenClaw keeps M3 on the provider's omitted/adaptive thinking path.
- Z.AI (`zai/*`) is binary (`on`/`off`) for most GLM models. GLM-5.2 is the exception: it exposes `/think off|low|high|max`, maps `low` and `high` to Z.AI `reasoning_effort: "high"`, and maps `max` to `reasoning_effort: "max"`.
- Moonshot Kimi K2.7 Code (`moonshot/kimi-k2.7-code`) always thinks. Its profile exposes only `on`, and OpenClaw omits the outbound `thinking` field as required by Moonshot. Other `moonshot/*` models map `/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
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: provider-declared default when available; otherwise reasoning-capable models resolve to `medium` or the nearest supported non-`off` level for that model, and non-reasoning models stay `off`.
## 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). Use `/think default` to clear the session override and inherit the configured/provider default; aliases include `inherit`, `clear`, `reset`, and `unpin`.
- `/think off` stores an explicit off override. It disables thinking until you change or clear the session override.
- 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
- **Embedded OpenClaw**: the resolved level is passed to the in-process OpenClaw agent runtime.
- **Claude CLI backend**: non-off levels are passed to Claude Code as `--effort` when using `claude-cli`; see [CLI backends](/gateway/cli-backends).
## Fast mode (/fast)
- Levels: `auto|on|off|default`.
- Directive-only message toggles a session fast-mode override and replies `Fast mode set to auto.`, `Fast mode enabled.`, or `Fast mode disabled.`. Use `/fast default` to clear the session override and inherit the configured default; aliases include `inherit`, `clear`, `reset`, and `unpin`.
- 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 auto|on|off` override (`/fast default` clears this layer)
2. Session override
3. Per-agent default (`agents.list[].fastModeDefault`)
4. Per-model config: `agents.defaults.models["<provider>/<model>"].params.fastMode`
5. Fallback: `off`
- `auto` keeps the session/config mode as auto but resolves each new model call independently. Calls that start before the auto cutoff have fast mode enabled; later retry, fallback, tool-result, or continuation calls start with fast mode disabled. The cutoff defaults to 60 seconds; set `agents.defaults.models["<provider>/<model>"].params.fastAutoOnSeconds` on the active model to change it.
- For `openai/*`, fast mode maps to OpenAI priority processing by sending `service_tier=priority` on supported Responses requests.
- For Codex-backed `openai/*` / `openai-codex/*` models, fast mode sends the same `service_tier=priority` flag on Codex Responses. Native Codex app-server turns receive the tier only on `turn/start` or thread start/resume, so `auto` cannot retier one already-running app-server turn; it applies to the next model turn OpenClaw starts.
- For direct public `anthropic/*` requests, including OAuth-authenticated traffic sent to `api.anthropic.com`, fast mode maps to Anthropic service tiers: `/fast on` sets `service_tier=auto`, `/fast off` sets `service_tier=standard_only`.
- For `minimax/*` on the Anthropic-compatible path, `/fast on` (or `params.fastMode: true`) rewrites `MiniMax-M2.7` to `MiniMax-M2.7-highspeed`.
- Explicit Anthropic `serviceTier` / `service_tier` model params override the fast-mode default when both are set. OpenClaw still skips Anthropic service-tier injection for non-Anthropic proxy base URLs.
- `/status` shows `Fast` when fast mode is enabled and `Fast:auto` when the configured mode is auto.
## 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`.
- Authorized external channel senders may persist the session verbose override. Internal gateway/webchat clients need `operator.admin` to persist it.
- 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 send each tool call back as its own metadata-only message, prefixed with `<emoji> <tool-name>: <arg>` when available. 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 `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.
- `agents.defaults.toolProgressDetail` controls the shape of `/verbose` tool summaries and progress-draft tool lines. Use `"explain"` (default) for compact human labels such as `🛠️ Exec: checking JS syntax`; use `"raw"` when you also want the raw command/detail appended for debugging. Per-agent `agents.list[].toolProgressDetail` overrides the default.
- `explain`: `🛠️ Exec: check JS syntax for /tmp/app.js`
- `raw`: `🛠️ Exec: check JS syntax for /tmp/app.js, node --check /tmp/app.js`
## Plugin trace directives (/trace)
- Levels: `on` | `off` (default).
- Directive-only message toggles session plugin trace output and replies `Plugin trace enabled.` / `Plugin trace disabled.`.
- Inline directive affects only that message; session/global defaults apply otherwise.
- Send `/trace` (or `/trace:`) with no argument to see the current trace level.
- `/trace` is narrower than `/verbose`: it only exposes plugin-owned trace/debug lines such as Active Memory debug summaries.
- Trace lines can appear in `/status` and as a follow-up diagnostic message after the normal assistant reply.
## 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 `Thinking`.
- `stream`: streams reasoning while the reply is generating when the active channel supports reasoning previews, 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 global default (`agents.defaults.reasoningDefault`), then fallback (`off`).
Malformed local-model reasoning tags are handled conservatively. Closed `<think>...</think>` blocks stay hidden on normal replies, and unclosed reasoning after already visible text is also hidden. If a reply is fully wrapped in a single unclosed opening tag and would otherwise deliver as empty text, OpenClaw removes the malformed opening tag and delivers the remaining text.
## Related
- Elevated mode docs live in [Elevated mode](/tools/elevated).
## 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 `Thinking` message (when available), set `agents.defaults.heartbeat.includeReasoning: true` or per-agent `agents.list[].heartbeat.includeReasoning: true`.
## 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 writes the session override immediately via `sessions.patch`; it does not wait for the next send and it is not a one-shot `thinkingOnce` override.
- The first option is always the clear-override choice. It shows `Inherited: <resolved level>`, including `Inherited: Off` when inherited thinking is disabled.
- Explicit picker choices use their direct level labels while preserving provider labels when present (for example `Maximum` for a provider-labeled `max` option).
- The picker uses `thinkingLevels` returned by the gateway session row/defaults, with `thinkingOptions` kept as a legacy label list. The browser UI does not keep its own provider regex list; plugins own model-specific level sets.
- `/think:<level>` still works and updates the same stored session level, so chat directives and the picker stay in sync.
## Provider profiles
- Provider plugins can expose `resolveThinkingProfile(ctx)` to define the model's supported levels and default.
- Provider plugins that proxy Claude models should reuse `resolveClaudeThinkingProfile(modelId)` from `openclaw/plugin-sdk/provider-model-shared` so direct Anthropic and proxy catalogs stay aligned.
- Each profile level has a stored canonical `id` (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `adaptive`, or `max`) and may include a display `label`. Binary providers use `{ id: "low", label: "on" }`.
- Profile hooks receive merged catalog facts when available, including `reasoning`, `compat.thinkingFormat`, and `compat.supportedReasoningEfforts`. Use those facts to expose binary or custom profiles only when the configured request contract supports the matching payload.
- Tool plugins that need to validate an explicit thinking override should use `api.runtime.agent.resolveThinkingPolicy({ provider, model })` plus `api.runtime.agent.normalizeThinkingLevel(...)`; they should not keep their own provider/model level lists.
- Tool plugins with access to configured custom model metadata can pass `catalog` into `resolveThinkingPolicy` so `compat.supportedReasoningEfforts` opt-ins are reflected in plugin-side validation.
- Published legacy hooks (`supportsXHighThinking`, `isBinaryThinking`, and `resolveDefaultThinkingLevel`) remain as compatibility adapters, but new custom level sets should use `resolveThinkingProfile`.
- Gateway rows/defaults expose `thinkingLevels`, `thinkingOptions`, and `thinkingDefault` so ACP/chat clients render the same profile ids and labels that runtime validation uses.

84
docs/tools/tokenjuice.md Normal file
View File

@@ -0,0 +1,84 @@
---
summary: "Compact noisy exec and bash tool results with the optional Tokenjuice plugin"
title: "Tokenjuice"
read_when:
- You want shorter `exec` or `bash` tool results in OpenClaw
- You want to install or enable the Tokenjuice plugin
- You need to understand what tokenjuice changes and what it leaves raw
---
`tokenjuice` is an optional external plugin that compacts noisy `exec` and `bash`
tool results after the command has already run.
It changes the returned `tool_result`, not the command itself. Tokenjuice does
not rewrite shell input, rerun commands, or change exit codes.
Today this applies to OpenClaw embedded runs and OpenClaw dynamic tools in the Codex
app-server harness. Tokenjuice hooks OpenClaw's tool-result middleware and
trims the output before it goes back into the active harness session.
## Enable the plugin
Install once:
```bash
openclaw plugins install clawhub:@openclaw/tokenjuice
```
Then enable it:
```bash
openclaw config set plugins.entries.tokenjuice.enabled true
```
Equivalent:
```bash
openclaw plugins enable tokenjuice
```
If you prefer editing config directly:
```json5
{
plugins: {
entries: {
tokenjuice: {
enabled: true,
},
},
},
}
```
## What tokenjuice changes
- Compacts noisy `exec` and `bash` results before they are fed back into the session.
- Keeps the original command execution untouched.
- Applies a safe-inventory policy: exact file-content reads stay raw, standalone repository-inventory commands can compact, and unsafe mixed command sequences stay raw.
- Stays opt-in: disable the plugin if you want verbatim output everywhere.
## Verify it is working
1. Enable the plugin.
2. Start a session that can call `exec`.
3. Run a noisy command such as `git status`.
4. Check that the returned tool result is shorter and more structured than the raw shell output.
## Disable the plugin
```bash
openclaw config set plugins.entries.tokenjuice.enabled false
```
Or:
```bash
openclaw plugins disable tokenjuice
```
## Related
- [Exec tool](/tools/exec)
- [Thinking levels](/tools/thinking)
- [Context engine](/concepts/context-engine)

309
docs/tools/tool-search.md Normal file
View File

@@ -0,0 +1,309 @@
---
summary: "Tool Search: compact large OpenClaw tool catalogs behind search, describe, and call"
title: "Tool Search"
read_when:
- You want OpenClaw agents to use a large tool catalog without adding every tool schema to the prompt
- You want OpenClaw tools, MCP tools, and client tools exposed through one compact runtime surface
- You are implementing or debugging tool discovery for OpenClaw runs
---
Tool Search is an experimental OpenClaw agent runtime feature. It gives agents one
compact way to discover and call large tool catalogs. It is useful when the run
has many available tools but the model is likely to need only a few of them.
This page documents OpenClaw Tool Search. It is not the Codex-native tool
search or dynamic-tools surface. Codex-native code mode, tool search, deferred
dynamic tools, and nested tool calls are stable Codex harness surfaces and do
not depend on `tools.toolSearch`.
When enabled for OpenClaw runs, the model receives one `tool_search_code` tool
by default. That tool runs a short JavaScript body in an isolated Node
subprocess with an `openclaw.tools` bridge:
```js
const hits = await openclaw.tools.search("create a GitHub issue");
const tool = await openclaw.tools.describe(hits[0].id);
return await openclaw.tools.call(tool.id, {
title: "Crash on startup",
body: "Steps to reproduce...",
});
```
The catalog can include OpenClaw tools, plugin tools, MCP tools, and
client-provided tools. The model does not see every full schema up front.
Instead, it searches compact descriptors, describes one selected tool when it
needs the exact schema, and calls that tool through OpenClaw.
Codex harness runs do not receive these experimental OpenClaw Tool Search
controls. OpenClaw passes product capabilities to Codex as dynamic tools, and
Codex owns the stable native code mode, native tool search, deferred dynamic
tools, and nested tool calls.
## How a turn runs
At planning time the OpenClaw embedded runner builds the effective catalog for the
run:
1. Resolve the active tool policy for the agent, profile, sandbox, and session.
2. List eligible OpenClaw and plugin tools.
3. List eligible MCP tools through the session MCP runtime.
4. Add eligible client tools supplied for the current run.
5. Index compact descriptors for search.
6. Expose the OpenClaw code bridge, the structured fallback tools, or the
compact directory surface to the model.
At execution time every real tool call returns to OpenClaw. The isolated Node
runtime does not hold plugin implementations, MCP client objects, or secrets.
`openclaw.tools.call(...)` crosses the bridge back into the Gateway, where the
normal policy, approval, hook, logging, and result handling still apply.
## Modes
`tools.toolSearch` has three model-facing modes:
- `code`: exposes `tool_search_code`, the default compact JavaScript bridge.
- `tools`: exposes `tool_search`, `tool_describe`, and `tool_call` as plain
structured tools for providers that should not receive code.
- `directory`: exposes `tool_search`, `tool_describe`, and `tool_call` plus a
bounded prompt directory of available tool names and descriptions for
providers that should see tool names without every full schema. OpenClaw can
also expose a small bounded set of likely or required tool schemas directly
for the current turn.
All modes use the same policy-filtered catalog and normal OpenClaw execution
path. If the current runtime cannot launch the isolated Node code-mode child
process, the default `code` mode falls back to `tools` before catalog
compaction. In `directory` mode, client-provided tools stay directly visible
for the current run while OpenClaw tools, plugin tools, and MCP tools can be
compacted behind the directory catalog. A direct call to an exact hidden
directory name is hydrated from that same authorized catalog before execution.
All modes are experimental. Prefer direct tool exposure for small OpenClaw tool
catalogs, and prefer the Codex-native stable surfaces for Codex harness runs.
There is no separate source-selection config. When Tool Search is enabled, the
catalog includes eligible OpenClaw, MCP, and client tools after normal policy
filtering.
## Why this exists
Large catalogs are useful but expensive. Sending every tool schema to the model
makes the request larger, slows planning, and increases accidental tool
selection.
Tool Search changes the shape:
- direct tools: the model sees every selected schema before the first token
- Tool Search code mode: the model sees one compact code tool and a short API
contract
- Tool Search tools mode: the model sees three compact structured fallback
tools
- Tool Search directory mode: the model sees a bounded directory plus
search/describe/call controls and a small bounded set of likely or required
schemas
- during the turn: the model can load remaining schemas as needed
Direct tool exposure is still the right default for small catalogs. Tool Search
is best when one run can see many tools, especially from MCP servers or
client-provided app tools.
## API
`openclaw.tools.search(query, options?)`
Searches the effective catalog for the current run. Results are compact and safe
to put back into prompt context.
```js
const hits = await openclaw.tools.search("calendar event", { limit: 5 });
```
`openclaw.tools.describe(id)`
Loads full metadata for one search result, including the exact input schema.
```js
const calendarCreate = await openclaw.tools.describe("mcp:calendar:create_event");
```
`openclaw.tools.call(id, args)`
Calls a selected tool through OpenClaw.
```js
await openclaw.tools.call(calendarCreate.id, {
summary: "Planning",
start: "2026-05-09T14:00:00Z",
});
```
The structured fallback mode exposes the same operations as tools:
- `tool_search`
- `tool_describe`
- `tool_call`
Directory mode exposes:
- `tool_search`
- `tool_describe`
- `tool_call`
It also keeps client-provided tools directly visible and may expose a small
bounded set of likely or required catalog tool schemas directly for the current
turn. If the bounded directory omits entries, use `tool_search` to find them. If
the model requests an exact hidden directory tool name directly, OpenClaw
hydrates it from the authorized catalog before normal execution.
Directory-mode client tool names must not collide with OpenClaw, plugin, or MCP
tool names because exact deferred dispatch uses those names.
## Runtime boundary
The code bridge runs in a short-lived Node subprocess. The subprocess starts
with Node permission mode enabled, an empty environment, no filesystem or
network grants, and no child-process or worker grants. OpenClaw enforces a
parent-process wall-clock timeout and kills the subprocess on timeout, including
after async continuations.
The runtime exposes only:
- `console.log`, `console.warn`, and `console.error`
- `openclaw.tools.search`
- `openclaw.tools.describe`
- `openclaw.tools.call`
Normal OpenClaw behavior still applies to final calls:
- tool allow and deny policies
- per-agent and per-sandbox tool restrictions
- channel/runtime tool policy
- approval hooks
- plugin `before_tool_call` hooks
- session identity, logs, and telemetry
## Config
Enable Tool Search for OpenClaw runs with the default code bridge:
```bash
openclaw config set tools.toolSearch true
```
Equivalent JSON:
```json5
{
tools: {
toolSearch: true,
},
}
```
Use the structured fallback tools instead for OpenClaw runs:
```json5
{
tools: {
toolSearch: {
mode: "tools",
},
},
}
```
Use the compact directory surface instead for OpenClaw runs:
```json5
{
tools: {
toolSearch: {
mode: "directory",
},
},
}
```
Tune code-mode timeout and search result limits (values shown are the defaults):
```json5
{
tools: {
toolSearch: {
mode: "code",
codeTimeoutMs: 10000,
searchDefaultLimit: 8,
maxSearchLimit: 20,
},
},
}
```
The runtime clamps `codeTimeoutMs` to 1000-60000, `maxSearchLimit` to 1-50, and
`searchDefaultLimit` to 1..`maxSearchLimit`.
Disable it:
```json5
{
tools: {
toolSearch: false,
},
}
```
## Prompt and telemetry
Tool Search records enough telemetry to compare it with direct tool exposure:
- total serialized tool and prompt bytes sent to the harness
- catalog size and source breakdown
- search, describe, and call counts
- final tool calls executed through OpenClaw
- selected tool ids and sources
Session logs should make it possible to answer:
- how many tool schemas the model saw up front
- how many search and describe operations it performed
- which final tool was called
- whether the result came from OpenClaw, MCP, or a client tool
## E2E validation
The QA Lab gateway scenario proves both paths with the OpenClaw runtime:
```bash
pnpm openclaw qa suite --provider-mode mock-openai --scenario tool-search-gateway-e2e
```
It creates a temporary fake plugin with a large tool catalog, starts the mock
OpenAI provider, starts a Gateway once in direct mode and once with Tool Search
enabled, then compares provider request payloads and session logs.
The regression proves:
1. Direct mode can call the fake plugin tool.
2. Tool Search can call the same fake plugin tool.
3. Direct mode exposes the fake plugin tool schemas directly to the provider.
4. Tool Search exposes only the compact bridge.
5. The Tool Search request payload is smaller for the large fake catalog.
6. Session logs show the expected tool-call counts and bridged call telemetry.
## Failure behavior
Tool Search should fail closed:
- if a tool is not in the effective policy, search should not return it
- if a selected tool becomes unavailable, `tool_call` should fail
- if policy or approval blocks execution, the call result should report that
block instead of bypassing it
- if the code bridge cannot create an isolated runtime, use `mode: "tools"` or
disable Tool Search for that deployment
## Related
- [Tools and plugins](/tools)
- [Multi-agent sandbox and tools](/tools/multi-agent-sandbox-tools)
- [Exec tool](/tools/exec)
- [ACP agents setup](/tools/acp-agents-setup)
- [Building plugins](/plugins/building-plugins)

214
docs/tools/trajectory.md Normal file
View File

@@ -0,0 +1,214 @@
---
summary: "Export redacted trajectory bundles for debugging an OpenClaw agent session"
read_when:
- Debugging why an agent answered, failed, or called tools a certain way
- Exporting a support bundle for an OpenClaw session
- Investigating prompt context, tool calls, runtime errors, or usage metadata
- Disabling or relocating trajectory capture
title: "Trajectory bundles"
---
Trajectory capture is OpenClaw's per-session flight recorder. It records a
structured timeline for each agent run, then `/export-trajectory` packages the
current session into a redacted support bundle covering:
- The prompt, system prompt, and tools sent to the model
- Which transcript messages and tool calls led to an answer
- Whether the run timed out, aborted, compacted, or hit a provider error
- Which model, plugins, skills, and runtime settings were active
- Usage and prompt-cache metadata the provider returned
For a broad Gateway support report, start with
[`/diagnostics`](/gateway/diagnostics#chat-command) instead; it collects the
sanitized Gateway bundle and, for OpenAI Codex harness sessions, can send Codex
feedback to OpenAI after approval. Use `/export-trajectory` when you need the
detailed per-session prompt, tool, and transcript timeline.
## Quick start
Send in the active session (alias `/trajectory`):
```text
/export-trajectory
```
OpenClaw writes the bundle under the workspace:
```text
.openclaw/trajectory-exports/openclaw-trajectory-<session>-<timestamp>/
```
Pass a relative output directory name to override it:
```text
/export-trajectory bug-1234
```
The name resolves inside `.openclaw/trajectory-exports/`. Absolute paths and
`~` paths are rejected.
Trajectory bundles can contain prompts, model messages, tool schemas, tool
results, runtime events, and local paths, so the chat command always runs
through exec approval. Approve the export once when you intend to create the
bundle; do not use allow-all. In group chats, OpenClaw sends the approval
prompt and export result to the owner privately instead of posting trajectory
details back to the shared room.
For local inspection or support workflows, run the underlying CLI command
directly:
```bash
openclaw sessions export-trajectory --session-key "agent:main:telegram:direct:123" --workspace .
```
Other flags: `--output <path>` (directory name inside
`.openclaw/trajectory-exports`), `--store <path>` (session store override),
`--agent <id>` (agent id for store resolution), `--json` (structured output).
## Access
Trajectory export is an owner command. The sender must pass the normal command
authorization checks plus the owner check for the channel.
## What gets recorded
Trajectory capture is on by default for OpenClaw agent runs.
Runtime events include:
- `session.started`
- `trace.metadata`
- `context.compiled`
- `prompt.submitted`
- `model.fallback_step`, including the source model, next model, failure reason/detail, chain position, and whether the chain advanced, succeeded, or was exhausted
- `model.completed`
- `trace.artifacts`
- `session.ended`
Transcript events are reconstructed from the active session branch: user
messages, assistant messages, tool calls, tool results, compactions, model
changes, labels, and custom session entries.
Events are written as JSON Lines with this schema marker:
```json
{
"traceSchema": "openclaw-trajectory",
"schemaVersion": 1
}
```
## Bundle files
| File | Contents |
| --------------------- | ---------------------------------------------------------------------------------------------- |
| `manifest.json` | Bundle schema, source files, event counts, and generated file list |
| `events.jsonl` | Ordered runtime and transcript timeline |
| `session-branch.json` | Redacted active transcript branch and session header |
| `metadata.json` | OpenClaw version, OS/runtime, model, config snapshot, plugins, skills, and prompt metadata |
| `artifacts.json` | Final status, errors, usage, prompt cache, compaction count, assistant text, and tool metadata |
| `prompts.json` | Submitted prompts and selected prompt-building details |
| `system-prompt.txt` | Latest compiled system prompt, when captured |
| `tools.json` | Tool definitions sent to the model, when captured |
`manifest.json` lists the files present in a given bundle; some files are
omitted when the session did not capture the corresponding runtime data.
## Capture location
By default, runtime trajectory events are written beside the session file:
```text
<session>.trajectory.jsonl
```
OpenClaw also writes a best-effort pointer file beside the session:
```text
<session>.trajectory-path.json
```
Set `OPENCLAW_TRAJECTORY_DIR` to store runtime trajectory sidecars in a
dedicated directory instead, one JSONL file per session id:
```bash
export OPENCLAW_TRAJECTORY_DIR=/var/lib/openclaw/trajectories
```
Session maintenance removes trajectory sidecars when their owning session
entry is pruned, capped, or evicted by the sessions disk budget. Runtime files
outside the sessions directory are removed only when the pointer target still
proves it belongs to that session.
## Disable capture
```bash
export OPENCLAW_TRAJECTORY=0
```
This disables runtime trajectory capture before starting OpenClaw.
`/export-trajectory` can still export the transcript branch, but runtime-only
files such as compiled context, provider artifacts, and prompt metadata may be
missing.
## Tune flush timeout
OpenClaw flushes runtime trajectory sidecars during agent cleanup. The default
cleanup timeout is 10,000 ms. On slow disks or large stores, set
`OPENCLAW_TRAJECTORY_FLUSH_TIMEOUT_MS` before starting OpenClaw:
```bash
export OPENCLAW_TRAJECTORY_FLUSH_TIMEOUT_MS=30000
```
This controls when OpenClaw logs an `openclaw-trajectory-flush` timeout and
continues; it does not change the trajectory size caps. To tune all agent
cleanup steps that do not pass an explicit timeout, set
`OPENCLAW_AGENT_CLEANUP_TIMEOUT_MS`.
## Privacy and limits
Trajectory bundles are for support and debugging, not public posting. OpenClaw
redacts sensitive values before writing export files:
- credentials and known secret-like payload fields
- image data
- local state paths
- workspace paths, replaced with `$WORKSPACE_DIR`
- home directory paths, where detected
The exporter also bounds input size:
- runtime sidecar files: the live capture file is a rolling window capped at 10 MiB, dropping the oldest events to make room for new ones; export accepts existing runtime sidecar files up to 50 MiB
- session files: 50 MiB
- runtime events per export: 200,000
- total exported events: 250,000
- individual runtime event lines are truncated above 256 KiB
Review bundles before sharing them outside your team. Redaction is best-effort
and cannot know every application-specific secret.
## Troubleshooting
If the export has no runtime events:
- confirm OpenClaw was started without `OPENCLAW_TRAJECTORY=0`
- check whether `OPENCLAW_TRAJECTORY_DIR` points to a writable directory
- run another message in the session, then export again
- inspect `manifest.json` for `runtimeEventCount`
If the command rejects the output path:
- use a relative name like `bug-1234`
- do not pass `/tmp/...` or `~/...`
- keep the export inside `.openclaw/trajectory-exports/`
If the export fails with a size error, the session or sidecar exceeded the
export safety limits above. Start a new session or export a smaller
reproduction.
## Related
- [Diffs](/tools/diffs)
- [Session management](/concepts/session)
- [Exec tool](/tools/exec)

998
docs/tools/tts.md Normal file
View File

@@ -0,0 +1,998 @@
---
summary: "Text-to-speech for outbound replies — providers, personas, slash commands, and per-channel output"
read_when:
- Enabling text-to-speech for replies
- Configuring a TTS provider, fallback chain, or persona
- Using /tts commands or directives
title: "Text-to-speech"
sidebarTitle: "Text to speech (TTS)"
---
OpenClaw converts outbound replies into audio across **14 speech providers**:
native voice messages on Feishu, Matrix, Telegram, and WhatsApp; audio
attachments everywhere else; and PCM/Ulaw streams for telephony and Talk.
TTS is the speech-output half of Talk's `stt-tts` mode (`talk.speak` calls this
same synthesis path). Provider-native `realtime` Talk sessions synthesize
speech inside the realtime provider instead; `transcription` sessions never
synthesize an assistant voice reply.
## Quick start
<Steps>
<Step title="Pick a provider">
OpenAI and ElevenLabs are the most reliable hosted options. Microsoft and
Local CLI work without an API key. See the [provider matrix](#supported-providers)
for the full list.
</Step>
<Step title="Set the API key">
Export the env var for your provider (for example `OPENAI_API_KEY`,
`ELEVENLABS_API_KEY`). Microsoft and Local CLI need no key.
</Step>
<Step title="Enable in config">
Set `messages.tts.auto: "always"` and `messages.tts.provider`:
```json5
{
messages: {
tts: {
auto: "always",
provider: "elevenlabs",
},
},
}
```
</Step>
<Step title="Try it in chat">
`/tts status` shows the current state. `/tts audio Hello from OpenClaw`
sends a one-off audio reply.
</Step>
</Steps>
<Note>
Auto-TTS is **off** by default. When `messages.tts.provider` is unset,
OpenClaw picks the first configured provider in registry auto-select order.
The built-in `tts` agent tool is explicit-intent only: ordinary chat stays
text unless the user asks for audio, uses `/tts`, or enables Auto-TTS/directive
speech.
</Note>
## Supported providers
| Provider | Auth | Notes |
| ----------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| **Azure Speech** | `AZURE_SPEECH_KEY` + `AZURE_SPEECH_REGION` (also `AZURE_SPEECH_API_KEY`, `SPEECH_KEY`, `SPEECH_REGION`) | Native Ogg/Opus voice-note output and telephony. |
| **DeepInfra** | `DEEPINFRA_API_KEY` | OpenAI-compatible TTS. Defaults to `hexgrad/Kokoro-82M`. |
| **ElevenLabs** | `ELEVENLABS_API_KEY` or `XI_API_KEY` | Voice cloning, multilingual, deterministic via `seed`; streamed for Discord voice playback. |
| **Google Gemini** | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | Gemini API batch TTS; persona-aware via `promptTemplate: "audio-profile-v1"`. |
| **Gradium** | `GRADIUM_API_KEY` | Voice-note and telephony output. |
| **Inworld** | `INWORLD_API_KEY` | Streaming TTS API. Native Opus voice-note and PCM telephony. |
| **Local CLI** | none | Runs a configured local TTS command. |
| **Microsoft** | none | Public Edge neural TTS via `node-edge-tts`. Best-effort, no SLA. |
| **MiniMax** | `MINIMAX_API_KEY` (or Token Plan: `MINIMAX_OAUTH_TOKEN`, `MINIMAX_CODE_PLAN_KEY`, `MINIMAX_CODING_API_KEY`) | T2A v2 API. Defaults to `speech-2.8-hd`. |
| **OpenAI** | `OPENAI_API_KEY` | Also used for auto-summary; supports persona `instructions`. |
| **OpenRouter** | `OPENROUTER_API_KEY` (can reuse `models.providers.openrouter.apiKey`) | Default model `hexgrad/kokoro-82m`. |
| **Volcengine** | `VOLCENGINE_TTS_API_KEY` or `BYTEPLUS_SEED_SPEECH_API_KEY` (legacy AppID/token: `VOLCENGINE_TTS_APPID`/`_TOKEN`) | BytePlus Seed Speech HTTP API. |
| **Vydra** | `VYDRA_API_KEY` | Shared image, video, and speech provider. |
| **xAI** | `XAI_API_KEY` | xAI batch TTS. Native Opus voice-note is **not** supported. |
| **Xiaomi MiMo** | `XIAOMI_API_KEY` | MiMo TTS through Xiaomi chat completions. |
If multiple providers are configured, the selected one is used first and the
others are fallback options. Auto-summary uses `summaryModel` (or
`agents.defaults.model.primary`), so that provider must also be authenticated
if you keep summaries enabled.
<Warning>
The bundled **Microsoft** provider uses Microsoft Edge's online neural TTS
service via `node-edge-tts`. It is a public web service without a published
SLA or quota — treat it as best-effort. The legacy provider id `edge` is
normalized to `microsoft` and `openclaw doctor --fix` rewrites persisted
config; new configs should always use `microsoft`.
</Warning>
## Configuration
TTS config lives under `messages.tts` in `~/.openclaw/openclaw.json`. Pick a
preset and adapt the provider block. The `speakerVoice`/`speakerVoiceId`
fields shown below are canonical; each provider's own `voice`/`voiceId`/
`voiceName` field names still work as legacy aliases.
<Tabs>
<Tab title="Azure Speech">
```json5
{
messages: {
tts: {
auto: "always",
provider: "azure-speech",
providers: {
"azure-speech": {
apiKey: "${AZURE_SPEECH_KEY}",
region: "eastus",
speakerVoice: "en-US-JennyNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
voiceNoteOutputFormat: "ogg-24khz-16bit-mono-opus",
},
},
},
},
}
```
</Tab>
<Tab title="ElevenLabs">
```json5
{
messages: {
tts: {
auto: "always",
provider: "elevenlabs",
providers: {
elevenlabs: {
apiKey: "${ELEVENLABS_API_KEY}",
model: "eleven_multilingual_v2",
speakerVoiceId: "EXAVITQu4vr4xnSDxMaL",
},
},
},
},
}
```
</Tab>
<Tab title="Google Gemini">
```json5
{
messages: {
tts: {
auto: "always",
provider: "google",
providers: {
google: {
apiKey: "${GEMINI_API_KEY}",
model: "gemini-3.1-flash-tts-preview",
speakerVoice: "Kore",
// Optional natural-language style prompts:
// audioProfile: "Speak in a calm, podcast-host tone.",
// speakerName: "Alex",
},
},
},
},
}
```
</Tab>
<Tab title="Gradium">
```json5
{
messages: {
tts: {
auto: "always",
provider: "gradium",
providers: {
gradium: {
apiKey: "${GRADIUM_API_KEY}",
speakerVoiceId: "YTpq7expH9539ERJ",
},
},
},
},
}
```
</Tab>
<Tab title="Inworld">
```json5
{
messages: {
tts: {
auto: "always",
provider: "inworld",
providers: {
inworld: {
apiKey: "${INWORLD_API_KEY}",
modelId: "inworld-tts-1.5-max",
speakerVoiceId: "Sarah",
temperature: 0.7,
},
},
},
},
}
```
</Tab>
<Tab title="Local CLI">
```json5
{
messages: {
tts: {
auto: "always",
provider: "tts-local-cli",
providers: {
"tts-local-cli": {
command: "say",
args: ["-o", "{{OutputPath}}", "{{Text}}"],
outputFormat: "wav",
timeoutMs: 120000,
},
},
},
},
}
```
</Tab>
<Tab title="Microsoft (no key)">
```json5
{
messages: {
tts: {
auto: "always",
provider: "microsoft",
providers: {
microsoft: {
enabled: true,
speakerVoice: "en-US-MichelleNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
rate: "+0%",
pitch: "+0%",
},
},
},
},
}
```
</Tab>
<Tab title="MiniMax">
```json5
{
messages: {
tts: {
auto: "always",
provider: "minimax",
providers: {
minimax: {
apiKey: "${MINIMAX_API_KEY}",
model: "speech-2.8-hd",
speakerVoiceId: "English_expressive_narrator",
speed: 1.0,
vol: 1.0,
pitch: 0,
},
},
},
},
}
```
</Tab>
<Tab title="OpenAI + ElevenLabs">
```json5
{
messages: {
tts: {
auto: "always",
provider: "openai",
summaryModel: "openai/gpt-4.1-mini",
modelOverrides: { enabled: true },
providers: {
openai: {
apiKey: "${OPENAI_API_KEY}",
model: "gpt-4o-mini-tts",
speakerVoice: "alloy",
},
elevenlabs: {
apiKey: "${ELEVENLABS_API_KEY}",
model: "eleven_multilingual_v2",
speakerVoiceId: "EXAVITQu4vr4xnSDxMaL",
voiceSettings: { stability: 0.5, similarityBoost: 0.75, style: 0.0, useSpeakerBoost: true, speed: 1.0 },
applyTextNormalization: "auto",
languageCode: "en",
},
},
},
},
}
```
</Tab>
<Tab title="OpenRouter">
```json5
{
messages: {
tts: {
auto: "always",
provider: "openrouter",
providers: {
openrouter: {
apiKey: "${OPENROUTER_API_KEY}",
model: "hexgrad/kokoro-82m",
speakerVoice: "af_alloy",
responseFormat: "mp3",
},
},
},
},
}
```
</Tab>
<Tab title="Volcengine">
```json5
{
messages: {
tts: {
auto: "always",
provider: "volcengine",
providers: {
volcengine: {
apiKey: "${VOLCENGINE_TTS_API_KEY}",
resourceId: "seed-tts-1.0",
speakerVoice: "en_female_anna_mars_bigtts",
},
},
},
},
}
```
</Tab>
<Tab title="xAI">
```json5
{
messages: {
tts: {
auto: "always",
provider: "xai",
providers: {
xai: {
apiKey: "${XAI_API_KEY}",
speakerVoiceId: "eve",
language: "en",
responseFormat: "mp3",
},
},
},
},
}
```
</Tab>
<Tab title="Xiaomi MiMo">
```json5
{
messages: {
tts: {
auto: "always",
provider: "xiaomi",
providers: {
xiaomi: {
apiKey: "${XIAOMI_API_KEY}",
model: "mimo-v2.5-tts",
speakerVoice: "mimo_default",
format: "mp3",
},
},
},
},
}
```
</Tab>
</Tabs>
For Xiaomi `mimo-v2.5-tts-voicedesign`, omit `speakerVoice` and set `style` to
the voice-design prompt. OpenClaw sends that prompt as the TTS `user` message
and does not send `audio.voice` for the voicedesign model.
### Per-agent voice overrides
Use `agents.list[].tts` when one agent should speak with a different provider,
voice, model, persona, or auto-TTS mode. The agent block deep-merges over
`messages.tts`, so provider credentials can stay in the global provider config:
```json5
{
messages: {
tts: {
auto: "always",
provider: "elevenlabs",
providers: {
elevenlabs: { apiKey: "${ELEVENLABS_API_KEY}", model: "eleven_multilingual_v2" },
},
},
},
agents: {
list: [
{
id: "reader",
tts: {
providers: {
elevenlabs: { speakerVoiceId: "EXAVITQu4vr4xnSDxMaL" },
},
},
},
],
},
}
```
To pin a per-agent persona, set `agents.list[].tts.persona` alongside provider
config — it overrides the global `messages.tts.persona` for that agent only.
Precedence order for automatic replies, `/tts audio`, `/tts status`, and the
`tts` agent tool:
1. `messages.tts`
2. active `agents.list[].tts`
3. channel override, when the channel supports `channels.<channel>.tts`
4. account override, when the channel passes `channels.<channel>.accounts.<id>.tts`
5. local `/tts` preferences for this host
6. inline `[[tts:...]]` directives when [model overrides](#model-driven-directives) are enabled
Channel and account overrides use the same shape as `messages.tts` and
deep-merge over the earlier layers, so shared provider credentials can stay in
`messages.tts` while a channel or bot account changes only speaker voice, model, persona,
or auto mode:
```json5
{
messages: {
tts: {
provider: "openai",
providers: {
openai: { apiKey: "${OPENAI_API_KEY}", model: "gpt-4o-mini-tts" },
},
},
},
channels: {
feishu: {
accounts: {
english: {
tts: {
providers: {
openai: { speakerVoice: "shimmer" },
},
},
},
},
},
},
}
```
## Personas
A **persona** is a stable spoken identity that can be applied deterministically
across providers. It can prefer one provider, define provider-neutral prompt
intent, and carry provider-specific bindings for voices, models, prompt
templates, seeds, and voice settings.
### Minimal persona
```json5
{
messages: {
tts: {
auto: "always",
persona: "narrator",
personas: {
narrator: {
label: "Narrator",
provider: "elevenlabs",
providers: {
elevenlabs: {
speakerVoiceId: "EXAVITQu4vr4xnSDxMaL",
modelId: "eleven_multilingual_v2",
},
},
},
},
},
},
}
```
### Full persona (provider-neutral prompt)
```json5
{
messages: {
tts: {
auto: "always",
persona: "alfred",
personas: {
alfred: {
label: "Alfred",
description: "Dry, warm British butler narrator.",
provider: "google",
fallbackPolicy: "preserve-persona",
prompt: {
profile: "A brilliant British butler. Dry, witty, warm, charming, emotionally expressive, never generic.",
scene: "A quiet late-night study. Close-mic narration for a trusted operator.",
sampleContext: "The speaker is answering a private technical request with concise confidence and dry warmth.",
style: "Refined, understated, lightly amused.",
accent: "British English.",
pacing: "Measured, with short dramatic pauses.",
constraints: ["Do not read configuration values aloud.", "Do not explain the persona."],
},
providers: {
google: {
model: "gemini-3.1-flash-tts-preview",
speakerVoice: "Algieba",
promptTemplate: "audio-profile-v1",
},
openai: { model: "gpt-4o-mini-tts", speakerVoice: "cedar" },
elevenlabs: {
speakerVoiceId: "voice_id",
modelId: "eleven_multilingual_v2",
seed: 42,
voiceSettings: {
stability: 0.65,
similarityBoost: 0.8,
style: 0.25,
useSpeakerBoost: true,
speed: 0.95,
},
},
},
},
},
},
},
}
```
### Persona resolution
The active persona is selected deterministically:
1. `/tts persona <id>` local preference, if set.
2. `messages.tts.persona`, if set.
3. No persona.
Provider selection runs explicit-first:
1. Direct overrides (CLI, gateway, Talk, allowed TTS directives).
2. `/tts provider <id>` local preference.
3. Active persona's `provider`.
4. `messages.tts.provider`.
5. Registry auto-select.
For each provider attempt, OpenClaw merges configs in this order:
1. `messages.tts.providers.<id>`
2. `messages.tts.personas.<persona>.providers.<id>`
3. Trusted request overrides
4. Allowed model-emitted TTS directive overrides
### How providers use persona prompts
Persona prompt fields (`profile`, `scene`, `sampleContext`, `style`, `accent`,
`pacing`, `constraints`) are **provider-neutral**. Each provider decides how
to use them:
<AccordionGroup>
<Accordion title="Google Gemini">
Wraps persona prompt fields in a Gemini TTS prompt structure **only when**
the effective Google provider config sets `promptTemplate: "audio-profile-v1"`
or `personaPrompt`. The older `audioProfile` and `speakerName` fields are
still prepended as Google-specific prompt text. Inline audio tags such as
`[whispers]` or `[laughs]` inside a `[[tts:text]]` block are preserved
inside the Gemini transcript; OpenClaw does not generate these tags.
</Accordion>
<Accordion title="OpenAI">
Maps persona prompt fields to the request `instructions` field **only when**
no explicit OpenAI `instructions` is configured. Explicit `instructions`
always wins.
</Accordion>
<Accordion title="Other providers">
Use only the provider-specific persona bindings under
`personas.<id>.providers.<provider>`. Persona prompt fields are ignored
unless the provider implements its own persona-prompt mapping.
</Accordion>
</AccordionGroup>
### Fallback policy
`fallbackPolicy` controls behavior when a persona has **no binding** for the
attempted provider:
| Policy | Behavior |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `preserve-persona` | **Default.** Provider-neutral prompt fields stay available; the provider may use them or ignore them. |
| `provider-defaults` | Persona is omitted from prompt preparation for that attempt; the provider uses its neutral defaults while fallback to other providers continues. |
| `fail` | Skip that provider attempt with `reasonCode: "not_configured"` and `personaBinding: "missing"`. Fallback providers are still tried. |
The whole TTS request only fails when **every** attempted provider is skipped
or fails.
Talk session provider selection is session-scoped. A Talk client should choose
provider ids, model ids, voice ids, and locales from `talk.catalog` and pass
them through the Talk session or handoff request. Opening a voice session should
not mutate `messages.tts` or global Talk provider defaults.
## Model-driven directives
By default, the assistant **can** emit `[[tts:...]]` directives to override
voice, model, or speed for a single reply, plus an optional
`[[tts:text]]...[[/tts:text]]` block for expressive cues that should appear in
audio only:
```text
Here you go.
[[tts:speakerVoiceId=pMsXgVXv3BLzUgSXRplE model=eleven_v3 speed=1.1]]
[[tts:text]](laughs) Read the song once more.[[/tts:text]]
```
When `messages.tts.auto` is `"tagged"`, **directives are required** to trigger
audio. Streaming block delivery strips directives from visible text before the
channel sees them, even when split across adjacent blocks.
`provider=...` is ignored unless `modelOverrides.allowProvider: true`. When a
reply declares `provider=...`, the other keys in that directive are parsed
only by that provider; unsupported keys are stripped and reported as TTS
directive warnings.
**Available directive keys:**
- `provider` (registered provider id; requires `allowProvider: true`)
- `speakerVoice` / `speakerVoiceId` (legacy aliases: `voice`, `voiceName`, `voice_name`, `google_voice`, `voiceId`)
- `model` / `google_model`
- `stability`, `similarityBoost`, `style`, `speed`, `useSpeakerBoost`
- `vol` / `volume` (MiniMax volume, 010)
- `pitch` (MiniMax integer pitch, 12 to 12; fractional values are truncated)
- `emotion` (Volcengine emotion tag)
- `applyTextNormalization` (`auto|on|off`)
- `languageCode` (ISO 639-1)
- `seed`
**Disable model overrides entirely:**
```json5
{ messages: { tts: { modelOverrides: { enabled: false } } } }
```
**Allow provider switching while keeping other knobs configurable:**
```json5
{ messages: { tts: { modelOverrides: { enabled: true, allowProvider: true, allowSeed: false } } } }
```
## Slash commands
Single command `/tts`. On Discord, OpenClaw also registers `/voice` because
`/tts` is a built-in Discord command — text `/tts ...` still works.
```text
/tts off | on | status
/tts chat on | off | default
/tts latest
/tts provider <id>
/tts persona <id> | off
/tts limit <chars>
/tts summary off
/tts audio <text>
```
<Note>
Commands require an authorized sender (allowlist/owner rules apply) and either
`commands.text` or native command registration must be enabled.
</Note>
Behavior notes:
- `/tts on` writes the local TTS preference to `always`; `/tts off` writes it to `off`.
- `/tts chat on|off|default` writes a session-scoped auto-TTS override for the current chat.
- `/tts persona <id>` writes the local persona preference; `/tts persona off` clears it.
- `/tts latest` reads the latest assistant reply from the current session transcript and sends it as audio once. It stores only a hash of that reply on the session entry to suppress duplicate voice sends.
- `/tts audio` generates a one-off audio reply (does **not** toggle TTS on).
- `/tts limit <chars>` accepts **1004096** (4096 is the Telegram caption/message max); values outside that range are rejected.
- `limit` and `summary` are stored in **local prefs**, not the main config.
- `/tts status` includes fallback diagnostics for the latest attempt — `Fallback: <primary> -> <used>`, `Attempts: ...`, and per-attempt detail (`provider:outcome(reasonCode) latency`).
- `/status` shows the active TTS mode plus configured provider, model, voice, and sanitized custom endpoint metadata when TTS is enabled.
## Per-user preferences
Slash commands write local overrides to `prefsPath`. The default is
`~/.openclaw/settings/tts.json`; override with the `OPENCLAW_TTS_PREFS` env var
or `messages.tts.prefsPath`.
| Stored field | Effect |
| ------------ | -------------------------------------------------------------------------------- |
| `auto` | Local auto-TTS override (`always`, `off`, …) |
| `provider` | Local primary provider override |
| `persona` | Local persona override |
| `maxLength` | Summary/truncation threshold (default `1500` chars, `/tts limit` range 1004096) |
| `summarize` | Summary toggle (default `true`) |
These override the effective config from `messages.tts` plus the active
`agents.list[].tts` block for that host.
## Output formats
TTS voice delivery is channel-capability driven. Channel plugins advertise
whether voice-style TTS should ask providers for a native `voice-note` target or
keep normal `audio-file` synthesis, and whether the channel transcodes
non-native output before sending.
| Target | Format |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Feishu / Matrix / Telegram / WhatsApp | Voice-note replies prefer **Opus** (`opus_48000_64` from ElevenLabs, `opus` from OpenAI). 48 kHz / 64 kbps balances clarity and size. |
| Other channels | **MP3** (`mp3_44100_128` from ElevenLabs, `mp3` from OpenAI). 44.1 kHz / 128 kbps is the default balance for speech. |
| Talk / telephony | Provider-native **PCM** (Inworld 22050 Hz, Google 24 kHz), or `ulaw_8000` from Gradium for telephony. |
Per-provider notes:
- **Feishu / WhatsApp transcoding:** when a voice-note reply lands as MP3/WebM/WAV/M4A or another likely audio file, the channel plugin transcodes it to 48 kHz Ogg/Opus with `ffmpeg` (`libopus`, 64 kbps) before sending the native voice message. WhatsApp sends the result through the Baileys `audio` payload with `ptt: true` and `audio/ogg; codecs=opus`. On transcode failure: Feishu catches the error and falls back to sending the original file as a plain attachment; WhatsApp has no fallback, so the send itself fails rather than posting an incompatible PTT payload.
- **MiniMax:** MP3 (`speech-2.8-hd` model, 32 kHz sample rate) for normal audio attachments; transcoded to 48 kHz Opus with `ffmpeg` for channel-advertised voice-note targets.
- **Xiaomi MiMo:** MP3 by default, or WAV when configured; transcoded to 48 kHz Opus with `ffmpeg` for channel-advertised voice-note targets.
- **Local CLI:** uses the configured `outputFormat`. Voice-note targets are converted to Ogg/Opus and telephony output is converted to raw 16 kHz mono PCM with `ffmpeg`.
- **Google Gemini:** returns raw 24 kHz PCM. OpenClaw wraps it as WAV for audio attachments, transcodes it to 48 kHz Opus for voice-note targets, and returns PCM directly for Talk/telephony.
- **Gradium:** WAV for audio attachments, Opus for voice-note targets, and `ulaw_8000` at 8 kHz for telephony.
- **Inworld:** MP3 for normal audio attachments, native `OGG_OPUS` for voice-note targets, and raw `PCM` at 22050 Hz for Talk/telephony.
- **xAI:** MP3 by default; `responseFormat` may be `mp3`, `wav`, `pcm`, `mulaw`, or `alaw`. Uses xAI's batch REST TTS endpoint and returns a complete audio attachment; xAI's streaming TTS WebSocket is not used by this provider path. Native Opus voice-note format is not supported.
- **Microsoft:** uses `microsoft.outputFormat` (default `audio-24khz-48kbitrate-mono-mp3`).
- The bundled transport accepts an `outputFormat`, but not all formats are available from the service.
- Output format values follow Microsoft Speech output formats (including Ogg/WebM Opus).
- Telegram `sendVoice` accepts OGG/MP3/M4A; use OpenAI/ElevenLabs if you need guaranteed Opus voice messages.
- If the configured Microsoft output format fails, OpenClaw retries with MP3.
- When no explicit voice override is set and the default English voice is used, OpenClaw auto-switches to a Chinese neural voice (`zh-CN-XiaoxiaoNeural`, `zh-CN` locale) if the reply text is CJK-dominant.
OpenAI and ElevenLabs output formats are fixed per channel as listed above.
## Auto-TTS behavior
When `messages.tts.auto` is enabled, OpenClaw:
- Skips TTS if the reply already contains structured media.
- Skips very short replies (under 10 chars).
- Summarizes long replies when summaries are enabled, using
`summaryModel` (or `agents.defaults.model.primary`).
- Attaches the generated audio to the reply.
- In `mode: "final"`, still sends audio-only TTS for streamed final replies
after the text stream completes; the generated media goes through the same
channel media normalization as normal reply attachments.
If the reply exceeds `maxLength`, OpenClaw never skips audio outright:
- **Summary on** (default) and a summary model is available: summarizes the
text to roughly `maxLength` chars, then synthesizes the summary.
- **Summary off**, summarization fails, or no API key is available for the
summary model: truncates the text to `maxLength` chars and synthesizes the
truncated text.
```text
Reply -> TTS enabled?
no -> send text
yes -> has media / short?
yes -> send text
no -> length > limit?
no -> TTS -> attach audio
yes -> summary enabled and available?
no -> truncate -> TTS -> attach audio
yes -> summarize -> TTS -> attach audio
```
## Field reference
<AccordionGroup>
<Accordion title="Top-level messages.tts.*">
<ParamField path="auto" type='"off" | "always" | "inbound" | "tagged"'>
Auto-TTS mode. `inbound` only sends audio after an inbound voice message; `tagged` only sends audio when the reply includes `[[tts:...]]` directives or a `[[tts:text]]` block.
</ParamField>
<ParamField path="enabled" type="boolean" deprecated>
Legacy toggle. `openclaw doctor --fix` migrates this to `auto`.
</ParamField>
<ParamField path="mode" type='"final" | "all"' default="final">
`"all"` includes tool/block replies in addition to final replies.
</ParamField>
<ParamField path="provider" type="string">
Speech provider id. When unset, OpenClaw uses the first configured provider in registry auto-select order. Legacy `provider: "edge"` is rewritten to `"microsoft"` by `openclaw doctor --fix`.
</ParamField>
<ParamField path="persona" type="string">
Active persona id from `personas`. Normalized to lowercase.
</ParamField>
<ParamField path="personas.<id>" type="object">
Stable spoken identity. Fields: `label`, `description`, `provider`, `fallbackPolicy`, `prompt`, `providers.<provider>`. See [Personas](#personas).
</ParamField>
<ParamField path="summaryModel" type="string">
Cheap model for auto-summary; defaults to `agents.defaults.model.primary`. Accepts `provider/model` or a configured model alias.
</ParamField>
<ParamField path="modelOverrides" type="object">
Allow the model to emit TTS directives. `enabled` defaults to `true`; `allowProvider` defaults to `false`.
</ParamField>
<ParamField path="providers.<id>" type="object">
Provider-owned settings keyed by speech provider id. Legacy direct blocks (`messages.tts.openai`, `.elevenlabs`, `.microsoft`, `.edge`) are rewritten by `openclaw doctor --fix`; commit only `messages.tts.providers.<id>`.
</ParamField>
<ParamField path="maxTextLength" type="number" default="4096">
Hard cap for TTS input characters. `/tts audio` and `tts.convert` fail if exceeded.
</ParamField>
<ParamField path="timeoutMs" type="number" default="30000">
Request timeout in milliseconds. A per-call `timeoutMs` (agent tool, gateway) wins when set; otherwise an explicitly configured `messages.tts.timeoutMs` wins over any plugin-authored provider default.
</ParamField>
<ParamField path="prefsPath" type="string">
Override the local prefs JSON path (provider/limit/summary). Default `~/.openclaw/settings/tts.json`.
</ParamField>
</Accordion>
<Accordion title="Azure Speech">
<ParamField path="apiKey" type="string">Env: `AZURE_SPEECH_KEY`, `AZURE_SPEECH_API_KEY`, or `SPEECH_KEY`.</ParamField>
<ParamField path="region" type="string">Azure Speech region (e.g. `eastus`). Env: `AZURE_SPEECH_REGION` or `SPEECH_REGION`.</ParamField>
<ParamField path="endpoint" type="string">Optional Azure Speech endpoint override (alias `baseUrl`).</ParamField>
<ParamField path="speakerVoice" type="string">Azure voice ShortName. Default `en-US-JennyNeural`. Legacy alias: `voice`.</ParamField>
<ParamField path="lang" type="string">SSML language code. Default `en-US`.</ParamField>
<ParamField path="outputFormat" type="string">Azure `X-Microsoft-OutputFormat` for standard audio. Default `audio-24khz-48kbitrate-mono-mp3`.</ParamField>
<ParamField path="voiceNoteOutputFormat" type="string">Azure `X-Microsoft-OutputFormat` for voice-note output. Default `ogg-24khz-16bit-mono-opus`.</ParamField>
</Accordion>
<Accordion title="ElevenLabs">
<ParamField path="apiKey" type="string">Falls back to `ELEVENLABS_API_KEY` or `XI_API_KEY`.</ParamField>
<ParamField path="model" type="string">Model id. Default `eleven_multilingual_v2`. Legacy ids `eleven_turbo_v2_5`/`eleven_turbo_v2` are normalized to the matching `flash` model.</ParamField>
<ParamField path="speakerVoiceId" type="string">ElevenLabs voice id. Default `pMsXgVXv3BLzUgSXRplE`. Legacy alias: `voiceId`.</ParamField>
<ParamField path="voiceSettings" type="object">
`stability`, `similarityBoost`, `style` (each `0..1`, defaults `0.5`/`0.75`/`0`), `useSpeakerBoost` (`true|false`, default `true`), `speed` (`0.5..2.0`, default `1.0`).
</ParamField>
<ParamField path="applyTextNormalization" type='"auto" | "on" | "off"'>Text normalization mode.</ParamField>
<ParamField path="languageCode" type="string">2-letter ISO 639-1 (e.g. `en`, `de`).</ParamField>
<ParamField path="seed" type="number">Integer `0..4294967295` for best-effort determinism.</ParamField>
<ParamField path="baseUrl" type="string">Override ElevenLabs API base URL.</ParamField>
</Accordion>
<Accordion title="Google Gemini">
<ParamField path="apiKey" type="string">Falls back to `GEMINI_API_KEY` / `GOOGLE_API_KEY`. If omitted, TTS can reuse `models.providers.google.apiKey` before env fallback.</ParamField>
<ParamField path="model" type="string">Gemini TTS model. Default `gemini-3.1-flash-tts-preview`.</ParamField>
<ParamField path="speakerVoice" type="string">Gemini prebuilt voice name. Default `Kore`. Legacy aliases: `voiceName`, `voice`.</ParamField>
<ParamField path="audioProfile" type="string">Natural-language style prompt prepended before spoken text.</ParamField>
<ParamField path="speakerName" type="string">Optional speaker label prepended before spoken text when your prompt uses a named speaker.</ParamField>
<ParamField path="promptTemplate" type='"audio-profile-v1"'>Set to `audio-profile-v1` to wrap active persona prompt fields in a deterministic Gemini TTS prompt structure.</ParamField>
<ParamField path="personaPrompt" type="string">Google-specific extra persona prompt text appended to the template's Director's Notes.</ParamField>
<ParamField path="baseUrl" type="string">Only `https://generativelanguage.googleapis.com` is accepted.</ParamField>
</Accordion>
<Accordion title="Gradium">
<ParamField path="apiKey" type="string">Env: `GRADIUM_API_KEY`.</ParamField>
<ParamField path="baseUrl" type="string">Default `https://api.gradium.ai`.</ParamField>
<ParamField path="speakerVoiceId" type="string">Default Emma (`YTpq7expH9539ERJ`). Legacy alias: `voiceId`.</ParamField>
</Accordion>
<Accordion title="Inworld">
### Inworld primary
<ParamField path="apiKey" type="string">Env: `INWORLD_API_KEY`.</ParamField>
<ParamField path="baseUrl" type="string">Default `https://api.inworld.ai`.</ParamField>
<ParamField path="modelId" type="string">Default `inworld-tts-1.5-max`. Also: `inworld-tts-1.5-mini`, `inworld-tts-1-max`, `inworld-tts-1`.</ParamField>
<ParamField path="speakerVoiceId" type="string">Default `Sarah`. Legacy alias: `voiceId`.</ParamField>
<ParamField path="temperature" type="number">Sampling temperature `0..2` (exclusive of 0).</ParamField>
</Accordion>
<Accordion title="Local CLI (tts-local-cli)">
<ParamField path="command" type="string">Local executable or command string for CLI TTS.</ParamField>
<ParamField path="args" type="string[]">Command arguments. Supports `{{Text}}`, `{{OutputPath}}`, `{{OutputDir}}`, `{{OutputBase}}` placeholders.</ParamField>
<ParamField path="outputFormat" type='"mp3" | "opus" | "wav"'>Expected CLI output format. Default `mp3` for audio attachments.</ParamField>
<ParamField path="timeoutMs" type="number">Command timeout in milliseconds. Default `120000`.</ParamField>
<ParamField path="cwd" type="string">Optional command working directory.</ParamField>
<ParamField path="env" type="Record<string, string>">Optional environment overrides for the command.</ParamField>
</Accordion>
<Accordion title="Microsoft (no API key)">
<ParamField path="enabled" type="boolean" default="true">Allow Microsoft speech usage.</ParamField>
<ParamField path="speakerVoice" type="string">Microsoft neural voice name (e.g. `en-US-MichelleNeural`). Legacy alias: `voice`. If the default English voice is in effect and reply text is CJK-dominant, OpenClaw auto-switches to `zh-CN-XiaoxiaoNeural`.</ParamField>
<ParamField path="lang" type="string">Language code (e.g. `en-US`).</ParamField>
<ParamField path="outputFormat" type="string">Microsoft output format. Default `audio-24khz-48kbitrate-mono-mp3`. Not all formats are supported by the bundled Edge-backed transport.</ParamField>
<ParamField path="rate / pitch / volume" type="string">Percent strings (e.g. `+10%`, `-5%`).</ParamField>
<ParamField path="saveSubtitles" type="boolean">Write JSON subtitles alongside the audio file.</ParamField>
<ParamField path="proxy" type="string">Proxy URL for Microsoft speech requests.</ParamField>
<ParamField path="timeoutMs" type="number">Request timeout override (ms).</ParamField>
<ParamField path="edge.*" type="object" deprecated>Legacy alias. Run `openclaw doctor --fix` to rewrite persisted config to `providers.microsoft`.</ParamField>
</Accordion>
<Accordion title="MiniMax">
<ParamField path="apiKey" type="string">Falls back to `MINIMAX_API_KEY`. Token Plan auth via `MINIMAX_OAUTH_TOKEN`, `MINIMAX_CODE_PLAN_KEY`, or `MINIMAX_CODING_API_KEY`.</ParamField>
<ParamField path="baseUrl" type="string">Default `https://api.minimax.io`. Env: `MINIMAX_API_HOST`.</ParamField>
<ParamField path="model" type="string">Default `speech-2.8-hd`. Env: `MINIMAX_TTS_MODEL`.</ParamField>
<ParamField path="speakerVoiceId" type="string">Default `English_expressive_narrator`. Env: `MINIMAX_TTS_VOICE_ID`. Legacy alias: `voiceId`.</ParamField>
<ParamField path="speed" type="number">`0.5..2.0`. Default `1.0`.</ParamField>
<ParamField path="vol" type="number">`(0, 10]`. Default `1.0`.</ParamField>
<ParamField path="pitch" type="number">Integer `-12..12`. Default `0`. Fractional values are truncated before the request.</ParamField>
</Accordion>
<Accordion title="OpenAI">
<ParamField path="apiKey" type="string">Falls back to `OPENAI_API_KEY`.</ParamField>
<ParamField path="model" type="string">OpenAI TTS model id. Default `gpt-4o-mini-tts`.</ParamField>
<ParamField path="speakerVoice" type="string">Voice name (e.g. `alloy`, `cedar`). Default `coral`. Legacy alias: `voice`.</ParamField>
<ParamField path="instructions" type="string">Explicit OpenAI `instructions` field. When set, persona prompt fields are **not** auto-mapped.</ParamField>
<ParamField path="extraBody / extra_body" type="Record<string, unknown>">Extra JSON fields merged into `/audio/speech` request bodies after generated OpenAI TTS fields. Use this for OpenAI-compatible endpoints such as Kokoro that require provider-specific keys like `lang`; unsafe prototype keys are ignored.</ParamField>
<ParamField path="baseUrl" type="string">
Override the OpenAI TTS endpoint. Resolution order: config → `OPENAI_TTS_BASE_URL` → `https://api.openai.com/v1`. Non-default values are treated as OpenAI-compatible TTS endpoints, so custom model and voice names are accepted, and `speed` loses its `0.25..4.0` range check.
</ParamField>
</Accordion>
<Accordion title="OpenRouter">
<ParamField path="apiKey" type="string">Env: `OPENROUTER_API_KEY`. Can reuse `models.providers.openrouter.apiKey`.</ParamField>
<ParamField path="baseUrl" type="string">Default `https://openrouter.ai/api/v1`. Legacy `https://openrouter.ai/v1` is normalized.</ParamField>
<ParamField path="model" type="string">Default `hexgrad/kokoro-82m`. Alias: `modelId`.</ParamField>
<ParamField path="speakerVoice" type="string">Default `af_alloy`. Legacy aliases: `voice`, `voiceId`.</ParamField>
<ParamField path="responseFormat" type='"mp3" | "pcm"'>Default `mp3`.</ParamField>
<ParamField path="speed" type="number">Provider-native speed override.</ParamField>
</Accordion>
<Accordion title="Volcengine (BytePlus Seed Speech)">
<ParamField path="apiKey" type="string">Env: `VOLCENGINE_TTS_API_KEY` or `BYTEPLUS_SEED_SPEECH_API_KEY`.</ParamField>
<ParamField path="resourceId" type="string">Default `seed-tts-1.0`. Env: `VOLCENGINE_TTS_RESOURCE_ID`. Use `seed-tts-2.0` when your project has TTS 2.0 entitlement.</ParamField>
<ParamField path="appKey" type="string">App key header. Default `aGjiRDfUWi`. Env: `VOLCENGINE_TTS_APP_KEY`.</ParamField>
<ParamField path="baseUrl" type="string">Override the Seed Speech TTS HTTP endpoint. Env: `VOLCENGINE_TTS_BASE_URL`.</ParamField>
<ParamField path="speakerVoice" type="string">Voice type. Default `en_female_anna_mars_bigtts`. Env: `VOLCENGINE_TTS_VOICE`. Legacy alias: `voice`.</ParamField>
<ParamField path="speedRatio" type="number">Provider-native speed ratio, `0.2..3`.</ParamField>
<ParamField path="emotion" type="string">Provider-native emotion tag.</ParamField>
<ParamField path="appId / token / cluster" type="string" deprecated>Legacy Volcengine Speech Console fields. Env: `VOLCENGINE_TTS_APPID`, `VOLCENGINE_TTS_TOKEN`, `VOLCENGINE_TTS_CLUSTER` (default `volcano_tts`).</ParamField>
</Accordion>
<Accordion title="xAI">
<ParamField path="apiKey" type="string">Env: `XAI_API_KEY`.</ParamField>
<ParamField path="baseUrl" type="string">Default `https://api.x.ai/v1`. Env: `XAI_BASE_URL`.</ParamField>
<ParamField path="speakerVoiceId" type="string">Default `eve`. Live voices: `ara`, `eve`, `leo`, `rex`, `sal`, `una`. Legacy alias: `voiceId`.</ParamField>
<ParamField path="language" type="string">BCP-47 language code or `auto`. Default `en`.</ParamField>
<ParamField path="responseFormat" type='"mp3" | "wav" | "pcm" | "mulaw" | "alaw"'>Default `mp3`.</ParamField>
<ParamField path="speed" type="number">Provider-native speed override, `0.7..1.5`.</ParamField>
</Accordion>
<Accordion title="Xiaomi MiMo">
<ParamField path="apiKey" type="string">Env: `XIAOMI_API_KEY`.</ParamField>
<ParamField path="baseUrl" type="string">Default `https://api.xiaomimimo.com/v1`. Env: `XIAOMI_BASE_URL`.</ParamField>
<ParamField path="model" type="string">Default `mimo-v2.5-tts`. Env: `XIAOMI_TTS_MODEL`. Also supports `mimo-v2-tts` and `mimo-v2.5-tts-voicedesign`.</ParamField>
<ParamField path="speakerVoice" type="string">Default `mimo_default` for preset-voice models. Env: `XIAOMI_TTS_VOICE`. Legacy alias: `voice`. Not sent for `mimo-v2.5-tts-voicedesign`.</ParamField>
<ParamField path="format" type='"mp3" | "wav"'>Default `mp3`. Env: `XIAOMI_TTS_FORMAT`.</ParamField>
<ParamField path="style" type="string">Optional natural-language style instruction sent as the user message; not spoken. For `mimo-v2.5-tts-voicedesign`, this is the voice-design prompt; OpenClaw supplies a default when omitted.</ParamField>
</Accordion>
</AccordionGroup>
## Agent tool
The `tts` tool converts text to speech and returns an audio attachment for
reply delivery. On Feishu, Matrix, Telegram, and WhatsApp, the audio is
delivered as a voice message rather than a file attachment. Feishu and
WhatsApp can transcode non-Opus TTS output on this path when `ffmpeg` is
available.
WhatsApp sends audio through Baileys as a PTT voice note (`audio` with
`ptt: true`) and sends visible text **separately** from PTT audio because
clients do not consistently render captions on voice notes.
The tool accepts optional `channel` and `timeoutMs` fields; `timeoutMs` is a
per-call provider request timeout in milliseconds. Per-call values override
`messages.tts.timeoutMs`; configured TTS timeouts override any plugin-authored
provider default.
## Gateway RPC
| Method | Purpose |
| ----------------- | -------------------------------------------- |
| `tts.status` | Read current TTS state and last attempt. |
| `tts.enable` | Set local auto preference to `always`. |
| `tts.disable` | Set local auto preference to `off`. |
| `tts.convert` | One-off text → audio. |
| `tts.setProvider` | Set local provider preference. |
| `tts.personas` | List configured personas and the active one. |
| `tts.setPersona` | Set local persona preference. |
| `tts.providers` | List configured providers and status. |
## Service links
- [OpenAI text-to-speech guide](https://platform.openai.com/docs/guides/text-to-speech)
- [OpenAI Audio API reference](https://platform.openai.com/docs/api-reference/audio)
- [Azure Speech REST text-to-speech](https://learn.microsoft.com/azure/ai-services/speech-service/rest-text-to-speech)
- [Azure Speech provider](/providers/azure-speech)
- [ElevenLabs Text to Speech](https://elevenlabs.io/docs/api-reference/text-to-speech)
- [ElevenLabs Authentication](https://elevenlabs.io/docs/api-reference/authentication)
- [Gradium](/providers/gradium)
- [Inworld TTS API](https://docs.inworld.ai/tts/tts)
- [MiniMax T2A v2 API](https://platform.minimaxi.com/document/T2A%20V2)
- [Volcengine TTS HTTP API](/providers/volcengine#text-to-speech)
- [Xiaomi MiMo speech synthesis](/providers/xiaomi#text-to-speech)
- [node-edge-tts](https://github.com/SchneeHertz/node-edge-tts)
- [Microsoft Speech output formats](https://learn.microsoft.com/azure/ai-services/speech-service/rest-text-to-speech#audio-outputs)
- [xAI text to speech](https://docs.x.ai/developers/rest-api-reference/inference/voice#text-to-speech-rest)
## Related
- [Media overview](/tools/media-overview)
- [Music generation](/tools/music-generation)
- [Video generation](/tools/video-generation)
- [Slash commands](/tools/slash-commands)
- [Voice call plugin](/plugins/voice-call)

View File

@@ -0,0 +1,551 @@
---
summary: "Generate videos via video_generate from text, image, or video references across 16 provider backends"
read_when:
- Generating videos via the agent
- Configuring video-generation providers and models
- Understanding the video_generate tool parameters
title: "Video generation"
sidebarTitle: "Video generation"
---
OpenClaw agents generate videos from text prompts, reference images, or
existing videos through `video_generate`. Sixteen provider backends are
supported; the agent picks the right one automatically based on config and
available API keys.
<Note>
`video_generate` only appears when at least one video-generation provider is
available. If it is missing from your agent tools, set a provider API key or
configure `agents.defaults.videoGenerationModel`.
</Note>
`video_generate` has three runtime modes, resolved from the reference inputs
in the call:
- `generate` - no reference media (text-to-video).
- `imageToVideo` - one or more reference images.
- `videoToVideo` - one or more reference videos.
Providers can support any subset of those modes. The tool validates the
active mode before submission and reports supported modes in `action=list`.
## Quick start
<Steps>
<Step title="Configure auth">
Set an API key for any supported provider:
```bash
export GEMINI_API_KEY="your-key"
```
</Step>
<Step title="Pick a default model (optional)">
```bash
openclaw config set agents.defaults.videoGenerationModel.primary "google/veo-3.1-fast-generate-preview"
```
</Step>
<Step title="Ask the agent">
> Generate a 5-second cinematic video of a friendly lobster surfing at sunset.
The agent calls `video_generate` automatically. No tool allowlisting
is needed.
</Step>
</Steps>
## How async generation works
Video generation is asynchronous:
1. OpenClaw submits the request to the provider and immediately returns a task id.
2. The provider processes the job in the background (typically 30 seconds to several minutes depending on the provider and resolution; slow queue-backed providers can run up to the configured timeout).
3. When the video is ready, OpenClaw wakes the same session with an internal completion event.
4. The agent reports it through the session's normal visible-reply mode:
automatic final reply, or `message(action="send")` when the session requires
the message tool. If the requester session is inactive, or its wake fails and
generated media is still missing from the completion reply, OpenClaw sends
an idempotent direct fallback with the media.
While a job is in flight, duplicate `video_generate` calls in the same
session return the current task status instead of starting another
generation. Use `action: "status"` to check without triggering a new
generation, or `openclaw tasks list` / `openclaw tasks show <lookup>` from the
CLI (see [Background tasks](/automation/tasks)).
Outside of session-backed agent runs (for example, direct tool invocations),
the tool falls back to inline generation and returns the final media path
in the same turn.
Generated video files save under OpenClaw-managed media storage when the
provider returns bytes. The default cap is 16MB (the shared video media
limit); `agents.defaults.mediaMaxMb` raises it for larger renders. When a
provider also returns a hosted output URL, OpenClaw delivers that URL instead
of failing the task if local persistence rejects an oversized file.
### Task lifecycle
| State | Meaning |
| ----------- | ------------------------------------------------------------------------------------------------------ |
| `queued` | Task created, waiting for the provider to accept it. |
| `running` | Provider is processing (typically 30 seconds to several minutes depending on provider and resolution). |
| `succeeded` | Video ready; the agent wakes and posts it to the conversation. |
| `failed` | Provider error or timeout; the agent wakes with error details. |
Check status from the CLI:
```bash
openclaw tasks list
openclaw tasks show <lookup>
openclaw tasks cancel <lookup>
```
## Supported providers
| Provider | Default model | Text | Image ref | Video ref | Auth |
| --------------------- | ------------------------------- | :--: | ---------------------------------------------------- | ----------------------------------------------- | ---------------------------------------- |
| Alibaba | `wan2.6-t2v` | ✓ | Yes (remote URL) | Yes (remote URL) | `MODELSTUDIO_API_KEY` |
| BytePlus (1.0) | `seedance-1-0-pro-250528` | ✓ | Up to 2 images (I2V models only; first + last frame) | - | `BYTEPLUS_API_KEY` |
| BytePlus Seedance 1.5 | `seedance-1-5-pro-251215` | ✓ | Up to 2 images (first + last frame via role) | - | `BYTEPLUS_API_KEY` |
| BytePlus Seedance 2.0 | `dreamina-seedance-2-0-260128` | ✓ | Up to 9 reference images | Up to 3 videos | `BYTEPLUS_API_KEY` |
| ComfyUI | `workflow` | ✓ | 1 image | - | `COMFY_API_KEY` or `COMFY_CLOUD_API_KEY` |
| DeepInfra | `Pixverse/Pixverse-T2V` | ✓ | - | - | `DEEPINFRA_API_KEY` |
| fal | `fal-ai/minimax/video-01-live` | ✓ | 1 image; up to 9 with Seedance reference-to-video | Up to 3 videos with Seedance reference-to-video | `FAL_KEY` |
| Google | `veo-3.1-fast-generate-preview` | ✓ | 1 image | 1 video | `GEMINI_API_KEY` |
| MiniMax | `MiniMax-Hailuo-2.3` | ✓ | 1 image | - | `MINIMAX_API_KEY` or MiniMax OAuth |
| OpenAI | `sora-2` | ✓ | 1 image | 1 video | `OPENAI_API_KEY` |
| OpenRouter | `google/veo-3.1-fast` | ✓ | Up to 4 images (first/last frame or references) | - | `OPENROUTER_API_KEY` |
| Qwen | `wan2.6-t2v` | ✓ | Yes (remote URL) | Yes (remote URL) | `QWEN_API_KEY` |
| Runway | `gen4.5` | ✓ | 1 image | 1 video | `RUNWAYML_API_SECRET` |
| Together | `Wan-AI/Wan2.2-T2V-A14B` | ✓ | `Wan-AI/Wan2.2-I2V-A14B` only | - | `TOGETHER_API_KEY` |
| Vydra | `veo3` | ✓ | 1 image (`kling`) | - | `VYDRA_API_KEY` |
| xAI | `grok-imagine-video` | ✓ | 1 first-frame image or up to 7 `reference_image`s | 1 video | `XAI_API_KEY` |
Some providers accept additional or alternate API key env vars. See
individual [provider pages](#related) for details.
Run `video_generate action=list` to inspect available providers, models, and
runtime modes at runtime.
### Capability matrix
The explicit mode contract used by `video_generate`, contract tests, and
the shared live sweep:
| Provider | `generate` | `imageToVideo` | `videoToVideo` | Shared live lanes today |
| ---------- | :--------: | :------------: | :------------: | --------------------------------------------------------------------------------------------------------------------------------------- |
| Alibaba | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; `videoToVideo` skipped because this provider needs remote `http(s)` video URLs |
| BytePlus | ✓ | ✓ | - | `generate`, `imageToVideo` |
| ComfyUI | ✓ | ✓ | - | Not in the shared sweep; workflow-specific coverage lives with Comfy tests |
| DeepInfra | ✓ | - | - | `generate`; native DeepInfra video schemas are text-to-video in the plugin contract |
| fal | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; `videoToVideo` only when using Seedance reference-to-video |
| Google | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; shared `videoToVideo` skipped because the current buffer-backed Gemini/Veo sweep does not accept that input |
| MiniMax | ✓ | ✓ | - | `generate`, `imageToVideo` |
| OpenAI | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; shared `videoToVideo` skipped because this org/input path currently needs provider-side video edit access |
| OpenRouter | ✓ | ✓ | - | `generate`, `imageToVideo` |
| Qwen | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; `videoToVideo` skipped because this provider needs remote `http(s)` video URLs |
| Runway | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; `videoToVideo` runs only when the selected model is `runway/gen4_aleph` |
| Together | ✓ | ✓ | - | `generate`, `imageToVideo` |
| Vydra | ✓ | ✓ | - | `generate`; shared `imageToVideo` skipped because bundled `veo3` is text-only and bundled `kling` requires a remote image URL |
| xAI | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; `videoToVideo` skipped because this provider currently needs a remote MP4 URL |
## Tool parameters
### Required
<ParamField path="prompt" type="string" required>
Text description of the video to generate. Required for `action: "generate"`.
</ParamField>
### Content inputs
<ParamField path="image" type="string">Single reference image (path or URL).</ParamField>
<ParamField path="images" type="string[]">Multiple reference images (up to 9).</ParamField>
<ParamField path="imageRoles" type="string[]">
Optional per-position role hints parallel to the combined image list.
Canonical values: `first_frame`, `last_frame`, `reference_image`.
</ParamField>
<ParamField path="video" type="string">Single reference video (path or URL).</ParamField>
<ParamField path="videos" type="string[]">Multiple reference videos (up to 4).</ParamField>
<ParamField path="videoRoles" type="string[]">
Optional per-position role hints parallel to the combined video list.
Canonical value: `reference_video`.
</ParamField>
<ParamField path="audioRef" type="string">
Single reference audio (path or URL). Used for background music or voice
reference when the provider supports audio inputs.
</ParamField>
<ParamField path="audioRefs" type="string[]">Multiple reference audios (up to 3).</ParamField>
<ParamField path="audioRoles" type="string[]">
Optional per-position role hints parallel to the combined audio list.
Canonical value: `reference_audio`.
</ParamField>
<Note>
Role hints are forwarded to the provider as-is. Canonical values come from
the `VideoGenerationAssetRole` union but providers may accept additional
role strings. `*Roles` arrays must not have more entries than the
corresponding reference list; off-by-one mistakes fail with a clear error.
Use an empty string to leave a slot unset. For xAI, set every image role to
`reference_image` to use its `reference_images` generation mode; omit the
role or use `first_frame` for single-image image-to-video.
</Note>
### Style controls
<ParamField path="aspectRatio" type="string">
Aspect-ratio hint such as `1:1`, `16:9`, `9:16`, `adaptive`, or a provider-specific value. OpenClaw normalizes or ignores unsupported values per provider.
</ParamField>
<ParamField path="resolution" type="string">Resolution hint such as `360P`, `480P`, `540P`, `720P`, `768P`, `1080P`, `4K`, or a provider-specific value. OpenClaw normalizes or ignores unsupported values per provider.</ParamField>
<ParamField path="durationSeconds" type="number">
Target duration in seconds (rounded to nearest provider-supported value).
</ParamField>
<ParamField path="size" type="string">Size hint when the provider supports it.</ParamField>
<ParamField path="audio" type="boolean">
Enable generated audio in the output when supported. Distinct from `audioRef*` (inputs).
</ParamField>
<ParamField path="watermark" type="boolean">Toggle provider watermarking when supported.</ParamField>
`adaptive` is a provider-specific sentinel: it is forwarded as-is to
providers that declare `adaptive` in their capabilities (e.g. BytePlus
Seedance uses it to auto-detect the ratio from the input image
dimensions). Providers that do not declare it surface the value via
`details.ignoredOverrides` in the tool result so the drop is visible.
### Advanced
<ParamField path="action" type='"generate" | "status" | "list"' default="generate">
`"status"` returns the current session task; `"list"` inspects providers.
</ParamField>
<ParamField path="model" type="string">Provider/model override (e.g. `runway/gen4.5`).</ParamField>
<ParamField path="filename" type="string">Output filename hint.</ParamField>
<ParamField path="timeoutMs" type="number">Optional provider operation timeout in milliseconds. When omitted, OpenClaw uses `agents.defaults.videoGenerationModel.timeoutMs` if configured, otherwise the plugin-authored provider default when one exists.</ParamField>
<ParamField path="providerOptions" type="object">
Provider-specific options as a JSON object (e.g. `{"seed": 42, "draft": true}`).
Providers that declare a typed schema validate the keys and types; unknown
keys or mismatches skip the candidate during fallback. Providers without a
declared schema receive the options as-is. Run `video_generate action=list`
to see what each provider accepts.
</ParamField>
<Note>
Not all providers support all parameters. OpenClaw normalizes duration to
the closest provider-supported value, and remaps translated geometry hints
such as size-to-aspect-ratio when a fallback provider exposes a different
control surface. Truly unsupported overrides are ignored on a best-effort
basis and reported as warnings in the tool result. Hard capability limits
(such as too many reference inputs) fail before submission. Tool results
report applied settings; `details.normalization` captures any
requested-to-applied translation.
</Note>
Reference inputs select the runtime mode:
- No reference media -> `generate`
- Any image reference -> `imageToVideo`
- Any video reference -> `videoToVideo`
- Reference audio inputs **do not** change the resolved mode; they apply on
top of whatever mode the image/video references select, and only work
with providers that declare `maxInputAudios`.
Mixed image and video references are not a stable shared capability surface.
Prefer one reference type per request.
#### Fallback and typed options
Some capability checks apply at the fallback layer rather than the tool
boundary, so a request that exceeds the primary provider's limits can still
run on a capable fallback:
- Active candidate declaring no `maxInputAudios` (or `0`) is skipped when
the request contains audio references; next candidate is tried. The same
guard applies to image and video reference counts against
`maxInputImages`/`maxInputVideos`.
- Active candidate's `maxDurationSeconds` below the requested `durationSeconds`
with no declared `supportedDurationSeconds` list -> skipped.
- Request contains `providerOptions` and the active candidate explicitly
declares a typed `providerOptions` schema -> skipped if supplied keys are
not in the schema or value types do not match. Providers without a
declared schema receive options as-is (backward-compatible
pass-through). A provider can opt out of all provider options by
declaring an empty schema (`capabilities.providerOptions: {}`), which
causes the same skip as a type mismatch.
The first skip reason in a request logs at `warn` so operators see when
their primary provider was passed over; subsequent skips log at `debug` to
keep long fallback chains quiet. If every candidate is skipped, the
aggregated error includes the skip reason for each.
## Actions
| Action | What it does |
| ---------- | -------------------------------------------------------------------------------------------------------- |
| `generate` | Default. Create a video from the given prompt and optional reference inputs. |
| `status` | Check the state of the in-flight video task for the current session without starting another generation. |
| `list` | Show available providers, models, and their capabilities. |
## Model selection
OpenClaw resolves the model in this order:
1. **`model` tool parameter** - if the agent specifies one in the call.
2. **`videoGenerationModel.primary`** from config.
3. **`videoGenerationModel.fallbacks`** in order.
4. **Auto-detection** - providers that have valid auth, starting with the
current default provider, then remaining providers in alphabetical
order.
If a provider fails, the next candidate is tried automatically. If all
candidates fail, the error includes details from each attempt.
Set `agents.defaults.mediaGenerationAutoProviderFallback: false` to use
only the explicit `model`, `primary`, and `fallbacks` entries.
```json5
{
agents: {
defaults: {
videoGenerationModel: {
primary: "google/veo-3.1-fast-generate-preview",
fallbacks: ["runway/gen4.5", "qwen/wan2.6-t2v"],
timeoutMs: 180000, // optional per-tool provider request timeout override
},
},
},
}
```
## Provider notes
<AccordionGroup>
<Accordion title="Alibaba">
Uses DashScope / Model Studio async endpoint. Reference images and
videos must be remote `http(s)` URLs.
</Accordion>
<Accordion title="BytePlus (1.0)">
Provider id: `byteplus`.
Models: `seedance-1-0-pro-250528` (default),
`seedance-1-0-pro-t2v-250528`, `seedance-1-0-pro-fast-251015`,
`seedance-1-0-lite-t2v-250428`, `seedance-1-0-lite-i2v-250428`.
T2V models (`*-t2v-*`) do not accept image inputs; I2V models and
general `*-pro-*` models support a single reference image (first
frame). Pass the image positionally or set `role: "first_frame"`.
T2V model IDs are automatically switched to the corresponding I2V
variant when an image is provided.
Supported `providerOptions` keys: `seed` (number), `draft` (boolean -
forces 480p), `camera_fixed` (boolean).
</Accordion>
<Accordion title="BytePlus Seedance 1.5">
Requires the [`@openclaw/byteplus-modelark`](https://www.npmjs.com/package/@openclaw/byteplus-modelark)
plugin (external, not bundled). Provider id: `byteplus-seedance15`. Model:
`seedance-1-5-pro-251215`.
Uses the unified `content[]` API. Supports at most 2 input images
(`first_frame` + `last_frame`). All inputs must be remote `https://`
URLs. Set `role: "first_frame"` / `"last_frame"` on each image, or
pass images positionally.
`aspectRatio: "adaptive"` auto-detects ratio from the input image.
`audio: true` maps to `generate_audio`. `providerOptions.seed`
(number) is forwarded.
</Accordion>
<Accordion title="BytePlus Seedance 2.0">
Requires the [`@openclaw/byteplus-modelark`](https://www.npmjs.com/package/@openclaw/byteplus-modelark)
plugin (external, not bundled). Provider id: `byteplus-seedance2`. Models:
`dreamina-seedance-2-0-260128`,
`dreamina-seedance-2-0-fast-260128`.
Uses the unified `content[]` API. Supports up to 9 reference images,
3 reference videos, and 3 reference audios. All inputs must be remote
`https://` URLs. Set `role` on each asset - supported values:
`"first_frame"`, `"last_frame"`, `"reference_image"`,
`"reference_video"`, `"reference_audio"`.
`aspectRatio: "adaptive"` auto-detects ratio from the input image.
`audio: true` maps to `generate_audio`. `providerOptions.seed`
(number) is forwarded.
</Accordion>
<Accordion title="ComfyUI">
Workflow-driven local or cloud execution. Supports text-to-video and
image-to-video through the configured graph.
</Accordion>
<Accordion title="fal">
Uses a queue-backed flow for long-running jobs. OpenClaw waits up to 20
minutes by default before treating an in-progress fal queue job as timed
out. Most fal video models
accept a single image reference. Seedance 2.0 reference-to-video
models accept up to 9 images, 3 videos, and 3 audio references, with
at most 12 total reference files.
</Accordion>
<Accordion title="Google (Gemini / Veo)">
Supports one image or one video reference. Generated-audio requests are
ignored with a warning on the Gemini API path because that API rejects
the `generateAudio` parameter for current Veo video generation.
</Accordion>
<Accordion title="MiniMax">
Single image reference only. MiniMax accepts `768P` and `1080P`
resolutions; requests such as `720P` are normalized to the closest
supported value before submission.
</Accordion>
<Accordion title="OpenAI">
Only `size` override is forwarded. Other style overrides
(`aspectRatio`, `resolution`, `audio`, `watermark`) are ignored with
a warning.
</Accordion>
<Accordion title="OpenRouter">
Uses OpenRouter's asynchronous `/videos` API. OpenClaw submits the
job, polls `polling_url`, and downloads either `unsigned_urls` or the
documented job content endpoint. The bundled `google/veo-3.1-fast` default
advertises 4/6/8 second durations, `720P`/`1080P` resolutions, and
`16:9`/`9:16` aspect ratios.
</Accordion>
<Accordion title="Qwen">
Same DashScope backend as Alibaba. Reference inputs must be remote
`http(s)` URLs; local files are rejected upfront.
</Accordion>
<Accordion title="Runway">
Supports local files via data URIs. Video-to-video requires
`runway/gen4_aleph`. Text-only runs expose `16:9` and `9:16` aspect
ratios.
</Accordion>
<Accordion title="Together">
Single image reference only.
</Accordion>
<Accordion title="Vydra">
Uses `https://www.vydra.ai/api/v1` directly to avoid auth-dropping
redirects. `veo3` is bundled as text-to-video only; `kling` requires
a remote image URL.
</Accordion>
<Accordion title="xAI">
Supports text-to-video, single first-frame image-to-video, up to 7
`reference_image` inputs through xAI `reference_images`, and remote
video edit/extend flows.
</Accordion>
</AccordionGroup>
## Provider capability modes
The shared video-generation contract supports mode-specific capabilities
instead of only flat aggregate limits. New provider implementations
should prefer explicit mode blocks:
```typescript
capabilities: {
generate: {
maxVideos: 1,
maxDurationSeconds: 10,
supportsResolution: true,
},
imageToVideo: {
enabled: true,
maxVideos: 1,
maxInputImages: 1,
maxInputImagesByModel: { "provider/reference-to-video": 9 },
maxDurationSeconds: 5,
},
videoToVideo: {
enabled: true,
maxVideos: 1,
maxInputVideos: 1,
maxDurationSeconds: 5,
},
}
```
Flat aggregate fields such as `maxInputImages` and `maxInputVideos` are
**not** enough to advertise transform-mode support. Providers should
declare `generate`, `imageToVideo`, and `videoToVideo` explicitly so live
tests, contract tests, and the shared `video_generate` tool can validate
mode support deterministically.
When one model in a provider has wider reference-input support than the
rest, use `maxInputImagesByModel`, `maxInputVideosByModel`, or
`maxInputAudiosByModel` instead of raising the mode-wide limit.
## Live tests
Opt-in live coverage for the shared bundled providers:
```bash
OPENCLAW_LIVE_TEST=1 pnpm test:live -- extensions/video-generation-providers.live.test.ts
```
Repo wrapper:
```bash
pnpm test:live:media video
```
This live file uses already-exported provider env vars ahead of stored auth
profiles by default, and runs a release-safe smoke by default:
- `generate` for every non-FAL provider in the sweep.
- One-second lobster prompt.
- Per-provider operation cap from
`OPENCLAW_LIVE_VIDEO_GENERATION_TIMEOUT_MS` (`180000` by default).
FAL is opt-in because provider-side queue latency can dominate release
time:
```bash
pnpm test:live:media video --video-providers fal
```
Set `OPENCLAW_LIVE_VIDEO_GENERATION_FULL_MODES=1` to also run declared
transform modes the shared sweep can exercise safely with local media:
- `imageToVideo` when `capabilities.imageToVideo.enabled`.
- `videoToVideo` when `capabilities.videoToVideo.enabled` and the
provider/model accepts buffer-backed local video input in the shared
sweep.
Today the shared `videoToVideo` live lane covers `runway` only when you
select `runway/gen4_aleph`.
## Configuration
Set the default video-generation model in your OpenClaw config:
```json5
{
agents: {
defaults: {
videoGenerationModel: {
primary: "qwen/wan2.6-t2v",
fallbacks: ["qwen/wan2.6-r2v-flash"],
},
},
},
}
```
Or via the CLI:
```bash
openclaw config set agents.defaults.videoGenerationModel.primary "qwen/wan2.6-t2v"
```
## Related
- [Alibaba Model Studio](/providers/alibaba)
- [Background tasks](/automation/tasks) - task tracking for async video generation
- [BytePlus](/concepts/model-providers#byteplus-international)
- [ComfyUI](/providers/comfy)
- [Configuration reference](/gateway/config-agents#agent-defaults)
- [fal](/providers/fal)
- [Google (Gemini)](/providers/google)
- [MiniMax](/providers/minimax)
- [Models](/concepts/models)
- [OpenAI](/providers/openai)
- [Qwen](/providers/qwen)
- [Runway](/providers/runway)
- [Together AI](/providers/together)
- [Tools overview](/tools)
- [Vydra](/providers/vydra)
- [xAI](/providers/xai)

209
docs/tools/web-fetch.md Normal file
View File

@@ -0,0 +1,209 @@
---
summary: "web_fetch tool -- HTTP fetch with readable content extraction"
read_when:
- You want to fetch a URL and extract readable content
- You need to configure web_fetch or its Firecrawl fallback
- You want to understand web_fetch limits and caching
title: "Web fetch"
sidebarTitle: "Web Fetch"
---
`web_fetch` 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](/tools/browser) instead.
## Quick start
Enabled by default, no configuration needed:
```javascript
await web_fetch({ url: "https://example.com/article" });
```
## Tool parameters
<ParamField path="url" type="string" required>
URL to fetch. `http(s)` only.
</ParamField>
<ParamField path="extractMode" type="'markdown' | 'text'" default="markdown">
Output format after main-content extraction.
</ParamField>
<ParamField path="maxChars" type="number">
Truncate output to this many characters. Clamped to `tools.web.fetch.maxCharsCap`.
</ParamField>
## How it works
<Steps>
<Step title="Fetch">
Sends an HTTP GET with a Chrome-like User-Agent and `Accept-Language`
header. Blocks private/internal hostnames and re-checks redirects.
</Step>
<Step title="Extract">
Runs Readability (main-content extraction) on the HTML response.
</Step>
<Step title="Fallback (optional)">
If Readability fails and a fetch provider is available, retries through
that provider (for example Firecrawl's bot-circumvention mode).
</Step>
<Step title="Cache">
Results are cached for 15 minutes (configurable) to reduce repeated
fetches of the same URL.
</Step>
</Steps>
## Progress updates
`web_fetch` emits a public progress line only when the fetch is still pending
after five seconds:
```text
Fetching page content...
```
Fast cache hits and quick network responses finish before the timer fires, so
they never show a progress line. Canceling the call clears the timer. The
progress line is channel UI state only and never contains fetched page content.
## Config
```json5
{
tools: {
web: {
fetch: {
enabled: true, // default: true
provider: "firecrawl", // optional; omit for auto-detect
maxChars: 20000, // default output chars; capped by maxCharsCap
maxCharsCap: 20000, // hard cap for maxChars param
maxResponseBytes: 750000, // max download size before truncation (32000-10000000)
timeoutSeconds: 30,
cacheTtlMinutes: 15,
maxRedirects: 3,
useTrustedEnvProxy: false, // let a trusted HTTP(S) env proxy resolve DNS
readability: true, // use Readability extraction
userAgent: "Mozilla/5.0 ...", // override User-Agent
ssrfPolicy: {
allowRfc2544BenchmarkRange: true, // opt-in for trusted fake-IP proxies using 198.18.0.0/15
allowIpv6UniqueLocalRange: true, // opt-in for trusted fake-IP proxies using fc00::/7
},
},
},
},
}
```
## Firecrawl fallback
If Readability extraction fails, `web_fetch` can fall back to
[Firecrawl](/tools/firecrawl) for bot-circumvention and better extraction:
```json5
{
tools: {
web: {
fetch: {
provider: "firecrawl", // optional; omit for auto-detect from available credentials
},
},
},
plugins: {
entries: {
firecrawl: {
enabled: true,
config: {
webFetch: {
// apiKey: "fc-...", // optional; omit for keyless starter access
baseUrl: "https://api.firecrawl.dev",
onlyMainContent: true,
maxAgeMs: 172800000, // cache duration (2 days)
timeoutSeconds: 60,
},
},
},
},
},
}
```
`plugins.entries.firecrawl.config.webFetch.apiKey` is optional and supports SecretRef objects.
Legacy `tools.web.fetch.firecrawl.*` config auto-migrates to
`plugins.entries.firecrawl.config.webFetch` via `openclaw doctor --fix`.
<Note>
If you configure a Firecrawl API-key SecretRef and it is unresolved with no
`FIRECRAWL_API_KEY` env fallback, gateway startup fails fast.
</Note>
<Note>
Firecrawl `baseUrl` overrides are locked down: hosted traffic uses
`https://api.firecrawl.dev`; self-hosted overrides must target private or
internal endpoints, and `http://` is accepted only for those private targets.
</Note>
Current runtime behavior:
- `tools.web.fetch.provider` selects the fetch fallback provider explicitly.
- If `provider` is omitted, OpenClaw auto-detects the first ready web-fetch
provider from configured credentials. Non-sandboxed `web_fetch` can use
installed plugins that declare `contracts.webFetchProviders` and register a
matching provider at runtime. The official Firecrawl plugin provides this
fallback today.
- Sandboxed `web_fetch` calls allow bundled providers plus installed providers
whose official npm or ClawHub provenance is verified. Today that permits the
official Firecrawl plugin; third-party external fetch plugins stay excluded.
- If Readability is disabled, `web_fetch` skips straight to the selected
provider fallback. If no provider is available, it fails closed.
## Trusted env proxy
If your deployment requires `web_fetch` to go through a trusted outbound
HTTP(S) proxy, set `tools.web.fetch.useTrustedEnvProxy: true`.
In this mode, OpenClaw still applies hostname-based SSRF checks before sending
the request, but it lets the proxy resolve DNS instead of doing local DNS
pinning. Enable this only when the proxy is operator-controlled and enforces
outbound policy after DNS resolution.
<Note>
If no HTTP(S) proxy env var is configured, or the target host is excluded by
`NO_PROXY`, `web_fetch` falls back to the normal strict path with local DNS
pinning.
</Note>
## Limits and safety
- `maxChars` is clamped to `tools.web.fetch.maxCharsCap` (default `20000`)
- Response body is capped at `maxResponseBytes` (default `750000`, clamped to
32000-10000000) before parsing; oversized responses are truncated with a warning
- Private/internal hostnames are blocked
- `tools.web.fetch.ssrfPolicy.allowRfc2544BenchmarkRange` and
`tools.web.fetch.ssrfPolicy.allowIpv6UniqueLocalRange` are narrow opt-ins
for trusted fake-IP proxy stacks; leave them unset unless your proxy owns
those synthetic ranges and enforces its own destination policy
- Redirects are checked and limited by `maxRedirects` (default `3`)
- `useTrustedEnvProxy` is an explicit opt-in and should only be enabled for
operator-controlled proxies that still enforce outbound policy after DNS
resolution
- `web_fetch` is best-effort -- some sites need the [Web Browser](/tools/browser)
## Tool profiles
If you use tool profiles or allowlists, add `web_fetch` or `group:web`:
```json5
{
tools: {
allow: ["web_fetch"],
// or: allow: ["group:web"] (includes web_fetch, web_search, and x_search)
},
}
```
## Related
- [Web Search](/tools/web) -- search the web with multiple providers
- [Web Browser](/tools/browser) -- full browser automation for JS-heavy sites
- [Firecrawl](/tools/firecrawl) -- Firecrawl search and scrape tools

537
docs/tools/web.md Normal file
View File

@@ -0,0 +1,537 @@
---
summary: "web_search, x_search, and web_fetch -- search the web, search X posts, or fetch page content"
title: "Web search"
sidebarTitle: "Web Search"
read_when:
- You want to enable or configure web_search
- You want to enable or configure x_search
- You need to choose a search provider
- You want to understand auto-detection and provider selection
---
`web_search` searches the web with your configured provider and returns
normalized results, cached by query for 15 minutes (configurable). OpenClaw
also bundles `x_search` for X (formerly Twitter) posts and `web_fetch` for
lightweight URL fetching. `web_fetch` always runs locally; `web_search` routes
through xAI Responses when Grok is the provider, and `x_search` always uses
xAI Responses.
<Info>
`web_search` is a lightweight HTTP tool, not browser automation. For
JS-heavy sites or logins, use the [Web Browser](/tools/browser). For
fetching a specific URL, use [Web Fetch](/tools/web-fetch).
</Info>
## Quick start
<Steps>
<Step title="Choose a provider">
Pick a provider and complete any required setup. Some providers are
key-free, others need an API key. See the provider pages below for
details.
</Step>
<Step title="Configure">
```bash
openclaw configure --section web
```
This stores the provider and any needed credential. For API-backed
providers you can instead set the provider's env var (for example
`BRAVE_API_KEY`) and skip this step.
</Step>
<Step title="Use it">
```javascript
await web_search({ query: "OpenClaw plugin SDK" });
```
For X posts:
```javascript
await x_search({ query: "dinner recipes" });
```
</Step>
</Steps>
## Choosing a provider
<CardGroup cols={2}>
<Card title="Brave Search" icon="shield" href="/tools/brave-search">
Structured results with snippets. Supports `llm-context` mode, country/language filters. Free tier available.
</Card>
<Card title="Codex Hosted Search" icon="search" href="/plugins/codex-harness">
AI-synthesized grounded answers through your Codex app-server account.
</Card>
<Card title="DuckDuckGo" icon="bird" href="/tools/duckduckgo-search">
Key-free provider. No API key needed. Unofficial HTML-based integration.
</Card>
<Card title="Exa" icon="brain" href="/tools/exa-search">
Neural + keyword search with content extraction (highlights, text, summaries).
</Card>
<Card title="Firecrawl" icon="flame" href="/tools/firecrawl">
Structured results. Best paired with `firecrawl_search` and `firecrawl_scrape` for deep extraction.
</Card>
<Card title="Gemini" icon="sparkles" href="/tools/gemini-search">
AI-synthesized answers with citations via Google Search grounding.
</Card>
<Card title="Grok" icon="zap" href="/tools/grok-search">
AI-synthesized answers with citations via xAI web grounding.
</Card>
<Card title="Kimi" icon="moon" href="/tools/kimi-search">
AI-synthesized answers with citations via Moonshot web search; ungrounded chat fallbacks fail explicitly.
</Card>
<Card title="MiniMax Search" icon="globe" href="/tools/minimax-search">
Structured results via the MiniMax Token Plan search API.
</Card>
<Card title="Ollama Web Search" icon="globe" href="/tools/ollama-search">
Search via a signed-in local Ollama host or the hosted Ollama API.
</Card>
<Card title="Parallel" icon="layer-group" href="/tools/parallel-search">
Paid Parallel Search API (`PARALLEL_API_KEY`); higher rate limits and objective tuning.
</Card>
<Card title="Parallel Search (Free)" icon="layer-group" href="/tools/parallel-search">
Key-free opt-in. Parallel's free Search MCP, with LLM-optimized dense excerpts and no API key.
</Card>
<Card title="Perplexity" icon="search" href="/tools/perplexity-search">
Structured results with content extraction controls and domain filtering.
</Card>
<Card title="SearXNG" icon="server" href="/tools/searxng-search">
Self-hosted meta-search. No API key needed. Aggregates Google, Bing, DuckDuckGo, and more.
</Card>
<Card title="Tavily" icon="globe" href="/tools/tavily">
Structured results with search depth, topic filtering, and `tavily_extract` for URL extraction.
</Card>
</CardGroup>
### Provider comparison
| Provider | Result style | Filters | API key |
| ------------------------------------------------ | -------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------- |
| [Brave](/tools/brave-search) | Structured snippets | Country, language, time, `llm-context` mode | `BRAVE_API_KEY` |
| [Codex Hosted Search](/plugins/codex-harness) | AI-synthesized + source URLs | Domains, context size, user location | None; uses Codex/OpenAI sign-in |
| [DuckDuckGo](/tools/duckduckgo-search) | Structured snippets | -- | None (key-free) |
| [Exa](/tools/exa-search) | Structured + extracted | Neural/keyword mode, date, content extraction | `EXA_API_KEY` |
| [Firecrawl](/tools/firecrawl) | Structured snippets | Via `firecrawl_search` tool | `FIRECRAWL_API_KEY` |
| [Gemini](/tools/gemini-search) | AI-synthesized + citations | -- | `GEMINI_API_KEY` |
| [Grok](/tools/grok-search) | AI-synthesized + citations | -- | xAI OAuth, `XAI_API_KEY`, or `plugins.entries.xai.config.webSearch.apiKey` |
| [Kimi](/tools/kimi-search) | AI-synthesized + citations; fails on ungrounded chat fallbacks | -- | `KIMI_API_KEY` / `MOONSHOT_API_KEY` |
| [MiniMax Search](/tools/minimax-search) | Structured snippets | Region (`global` / `cn`) | `MINIMAX_CODE_PLAN_KEY` / `MINIMAX_CODING_API_KEY` / `MINIMAX_OAUTH_TOKEN` |
| [Ollama Web Search](/tools/ollama-search) | Structured snippets | -- | None for signed-in local hosts; `OLLAMA_API_KEY` for direct `https://ollama.com` search |
| [Parallel](/tools/parallel-search) | Dense excerpts ranked for LLM context | -- | `PARALLEL_API_KEY` (paid) |
| [Parallel Search (Free)](/tools/parallel-search) | Dense excerpts ranked for LLM context | -- | None (free Search MCP) |
| [Perplexity](/tools/perplexity-search) | Structured snippets | Country, language, time, domains, content limits | `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` |
| [SearXNG](/tools/searxng-search) | Structured snippets | Categories, language | None (self-hosted) |
| [Tavily](/tools/tavily) | Structured snippets | Via `tavily_search` tool | `TAVILY_API_KEY` |
## Auto-detection
Provider lists in docs and setup flows are alphabetical. Auto-detection uses a
separate, fixed precedence order and only picks a provider that needs a
credential (`requiresCredential !== false`) when it finds one configured. If
no `provider` is set, OpenClaw checks providers in this order and uses the
first one that is ready:
API-backed providers first:
1. **Brave** -- `BRAVE_API_KEY` or `plugins.entries.brave.config.webSearch.apiKey` (order 10)
2. **MiniMax Search** -- `MINIMAX_CODE_PLAN_KEY` / `MINIMAX_CODING_API_KEY` / `MINIMAX_OAUTH_TOKEN` / `MINIMAX_API_KEY` or `plugins.entries.minimax.config.webSearch.apiKey` (order 15)
3. **Gemini** -- `plugins.entries.google.config.webSearch.apiKey`, `GEMINI_API_KEY`, or `models.providers.google.apiKey` (order 20)
4. **Grok** -- xAI OAuth, `XAI_API_KEY`, or `plugins.entries.xai.config.webSearch.apiKey` (order 30)
5. **Kimi** -- `KIMI_API_KEY` / `MOONSHOT_API_KEY` or `plugins.entries.moonshot.config.webSearch.apiKey` (order 40)
6. **Perplexity** -- `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` or `plugins.entries.perplexity.config.webSearch.apiKey` (order 50)
7. **Firecrawl** -- `FIRECRAWL_API_KEY` or `plugins.entries.firecrawl.config.webSearch.apiKey` (order 60)
8. **Exa** -- `EXA_API_KEY` or `plugins.entries.exa.config.webSearch.apiKey`; optional `plugins.entries.exa.config.webSearch.baseUrl` overrides the Exa endpoint (order 65)
9. **Tavily** -- `TAVILY_API_KEY` or `plugins.entries.tavily.config.webSearch.apiKey` (order 70)
10. **Parallel** -- paid Parallel Search API via `PARALLEL_API_KEY` or `plugins.entries.parallel.config.webSearch.apiKey`; optional `plugins.entries.parallel.config.webSearch.baseUrl` overrides the endpoint (order 75)
Configured endpoint providers after that:
11. **SearXNG** -- `SEARXNG_BASE_URL` or `plugins.entries.searxng.config.webSearch.baseUrl` (order 200)
Key-free providers such as **Parallel Search (Free)**, **DuckDuckGo**,
**Ollama Web Search**, and **Codex Hosted Search** never win auto-detection,
even though they have an internal order value. They are used only when you
select them explicitly with `tools.web.search.provider` or through
`openclaw configure --section web`. OpenClaw does not send managed
`web_search` queries to a key-free provider just because no API-backed
provider is configured.
OpenAI Responses models are an exception: while `tools.web.search.provider`
is unset, they use OpenAI's native web search instead of the managed
providers above (see below). Set `tools.web.search.provider` to
`parallel-free` (or another provider) to route them through the managed path
instead.
<Note>
All provider key fields support SecretRef objects. Plugin-scoped SecretRefs
under `plugins.entries.<plugin>.config.webSearch.apiKey` are resolved for the
installed API-backed web search providers, including Brave, Exa, Firecrawl,
Gemini, Grok, Kimi, MiniMax, Parallel, Perplexity, and Tavily,
whether the provider is picked explicitly via `tools.web.search.provider` or
selected through auto-detect. In auto-detect mode, OpenClaw resolves only the
selected provider key -- non-selected SecretRefs stay inactive, so you can
keep multiple providers configured without paying resolution cost for the
ones you are not using.
</Note>
## Native OpenAI web search
Direct OpenAI Responses models (`api: "openai-responses"`, provider `openai`,
no base URL or an official OpenAI API base URL) use OpenAI's hosted
`web_search` tool automatically when OpenClaw web search is enabled and no
managed provider is pinned. This is provider-owned behavior in the bundled
OpenAI plugin and does not apply to OpenAI-compatible proxy base URLs or Azure
routes. Set `tools.web.search.provider` to another provider such as `brave` to
keep the managed `web_search` tool for OpenAI models, or set
`tools.web.search.enabled: false` to disable both managed search and native
OpenAI search.
## Native Codex web search
The Codex app-server runtime uses Codex's hosted `web_search` tool automatically
when web search is enabled and no managed provider is selected. Native hosted
search and OpenClaw's managed `web_search` dynamic tool are mutually exclusive,
so managed search cannot bypass native domain restrictions. OpenClaw uses the
managed tool when hosted search is unavailable, explicitly disabled, or
replaced by a selected managed provider. OpenClaw keeps Codex's standalone
`web.run` extension disabled (`features.standalone_web_search: false`)
because production app-server traffic rejects its user-defined `web`
namespace.
- Configure native search under `tools.web.search.openaiCodex`
- Set `tools.web.search.provider: "codex"` to provision Codex Hosted Search as
the managed `web_search` provider for any parent model. Each call runs a
bounded ephemeral Codex app-server turn and fails if Codex does not emit a
hosted `webSearch` item.
- `mode: "cached"` is the default preference, but Codex resolves it to live
external access for unrestricted app-server turns; set `"live"` to request
live access explicitly
- Set `tools.web.search.provider` to a managed provider such as `brave` to use
OpenClaw's managed `web_search` instead
- Set `tools.web.search.openaiCodex.enabled: false` to opt out of Codex-hosted
search; other managed providers remain available
- Restricting the Codex native tool surface also keeps managed `web_search`
available
- When `allowedDomains` is set, automatic managed fallback fails closed if
hosted search is unavailable so the native allowlist cannot be bypassed
- Tool-disabled LLM-only runs disable both native and managed search
- `tools.web.search.enabled: false` disables both managed and native search
Persistent effective Codex search-policy changes start a fresh bound thread so
an already loaded app-server thread cannot keep stale hosted-search access.
Transient per-turn restrictions use a temporary restricted thread and preserve
the existing binding for later resume.
Direct OpenAI ChatGPT Responses traffic can also use OpenAI's hosted
`web_search` tool. That separate path remains opt-in through
`tools.web.search.openaiCodex.enabled: true` and only applies to eligible
`openai/*` models using `api: "openai-chatgpt-responses"`.
```json5
{
tools: {
web: {
search: {
enabled: true,
// Optional: use Codex Hosted Search from non-Codex parent models too.
provider: "codex",
openaiCodex: {
enabled: true,
mode: "cached",
allowedDomains: ["example.com"],
contextSize: "high",
userLocation: {
country: "US",
city: "New York",
timezone: "America/New_York",
},
},
},
},
},
}
```
For runtimes and providers that do not support native Codex search, Codex can
use the managed `web_search` fallback through OpenClaw's dynamic tool namespace.
Use an explicit managed provider when you need OpenClaw's provider-specific
network controls instead of Codex-hosted search.
Selecting `provider: "codex"` enables the bundled `codex` plugin and uses the
same `tools.web.search.openaiCodex` restrictions shown above. Authenticate the
Codex app-server first with `openclaw models auth login --provider openai`.
The parent agent can use any model or runtime; only the bounded search worker
runs through Codex.
## Network safety
Managed HTTP `web_search` provider calls use OpenClaw's guarded fetch path,
scoped to the current provider's own hostname. For that hostname only,
OpenClaw allows Surge, Clash, and sing-box fake-IP DNS answers in
`198.18.0.0/15` and `fc00::/7`. Other private, loopback, link-local, and
metadata destinations remain blocked. Codex Hosted Search is the exception:
its bounded worker delegates network access to Codex app-server's hosted
`web_search` tool.
This automatic allowance does not apply to arbitrary `web_fetch` URLs. For
`web_fetch`, enable `tools.web.fetch.ssrfPolicy.allowRfc2544BenchmarkRange` and
`tools.web.fetch.ssrfPolicy.allowIpv6UniqueLocalRange` explicitly only when your
trusted proxy owns those synthetic ranges.
## Config
```json5
{
tools: {
web: {
search: {
enabled: true, // default: true
provider: "brave", // or omit for auto-detection
maxResults: 5,
timeoutSeconds: 30,
cacheTtlMinutes: 15,
},
},
},
}
```
Provider-specific config (API keys, base URLs, modes) lives under
`plugins.entries.<plugin>.config.webSearch.*`. Gemini can also reuse
`models.providers.google.apiKey` and `models.providers.google.baseUrl` as lower-priority
fallbacks after its dedicated web-search config and `GEMINI_API_KEY`. See the
provider pages for examples.
Grok can also reuse an xAI OAuth auth profile from `openclaw models auth login
--provider xai --method oauth`; API-key config remains the fallback.
`tools.web.search.provider` is validated against the web-search provider ids
declared by bundled and installed plugin manifests. A typo such as `"brvae"`
fails config validation instead of silently falling back to auto-detection. If a
configured provider only has stale plugin evidence, such as a leftover
`plugins.entries.<plugin>` block after uninstalling a third-party plugin,
OpenClaw keeps startup resilient and reports a warning so you can reinstall the
plugin or run `openclaw doctor --fix` to clean up the stale config.
`web_fetch` fallback provider selection is separate:
- choose it with `tools.web.fetch.provider`
- or omit that field and let OpenClaw auto-detect the first ready web-fetch
provider from configured credentials
- non-sandboxed `web_fetch` can use installed plugin providers that declare
`contracts.webFetchProviders`; sandboxed fetches allow bundled providers and
verified official plugin installs, but exclude third-party external plugins
- the official Firecrawl plugin is the only bundled `webFetchProviders`
contributor today, configured under
`plugins.entries.firecrawl.config.webFetch.*`
When you choose **Kimi** during `openclaw onboard` or
`openclaw configure --section web`, OpenClaw can also ask for:
- the Moonshot API region (`https://api.moonshot.ai/v1` or `https://api.moonshot.cn/v1`)
- the default Kimi web-search model (defaults to `kimi-k2.6`)
For `x_search`, configure `plugins.entries.xai.config.xSearch.*`. It uses the
same xAI auth profile as chat, or the `XAI_API_KEY` / plugin web-search
credential used by Grok web search.
Legacy `tools.web.x_search.*` config is auto-migrated by `openclaw doctor --fix`.
When you choose Grok during `openclaw onboard` or `openclaw configure --section web`,
OpenClaw also offers optional `x_search` setup with the same credential right
after Grok setup completes. This is a separate follow-up step inside the Grok
path, not a separate top-level web-search provider choice. If you pick another
provider, OpenClaw does not show the `x_search` prompt.
### Storing API keys
<Tabs>
<Tab title="Config file">
Run `openclaw configure --section web` or set the key directly:
```json5
{
plugins: {
entries: {
brave: {
config: {
webSearch: {
apiKey: "YOUR_KEY", // pragma: allowlist secret
},
},
},
},
},
}
```
</Tab>
<Tab title="Environment variable">
Set the provider env var in the Gateway process environment:
```bash
export BRAVE_API_KEY="YOUR_KEY"
```
For a gateway install, put it in `~/.openclaw/.env`.
See [Env vars](/help/faq#env-vars-and-env-loading).
</Tab>
</Tabs>
## 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") |
| `search_lang` | Search-language code (Brave only) |
| `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 token budget, native Perplexity Search API only |
| `max_tokens_per_page` | Per-page extraction token limit, native Perplexity Search API only |
<Warning>
Not all parameters work with all providers. Brave `llm-context` mode
rejects `ui_lang`; `date_before` also needs `date_after` because Brave custom
freshness ranges require both start and end dates.
Gemini, Grok, and Kimi return one synthesized answer with citations. They
accept `count` for shared-tool compatibility, but it does not change the
grounded answer shape. Gemini treats `day` freshness as a recency hint; wider
freshness values and explicit dates set Google Search grounding time ranges.
Perplexity behaves the same way when you use the Sonar/OpenRouter
compatibility path (`plugins.entries.perplexity.config.webSearch.baseUrl` /
`model` or `OPENROUTER_API_KEY`); that path also drops `max_tokens` and
`max_tokens_per_page` support.
SearXNG accepts `http://` only for trusted private-network or loopback hosts;
public SearXNG endpoints must use `https://`.
Firecrawl and Tavily only support `query` and `count` through `web_search`
-- use their dedicated tools for advanced options.
</Warning>
## x_search
`x_search` queries X (formerly Twitter) posts using xAI and returns
AI-synthesized answers with citations. It accepts natural-language queries and
optional structured filters. OpenClaw constructs the built-in xAI `x_search`
tool per request rather than keeping it permanently registered, so it is only
active for the turn that actually calls it.
<Note>
xAI documents `x_search` as supporting keyword search, semantic search, user
search, and thread fetch. For per-post engagement stats such as reposts,
replies, bookmarks, or views, prefer a targeted lookup for the exact post URL
or status ID. Broad keyword searches may find the right post but return less
complete per-post metadata. A good pattern is: locate the post first, then
run a second `x_search` query focused on that exact post.
</Note>
### x_search config
```json5
{
plugins: {
entries: {
xai: {
config: {
xSearch: {
enabled: true,
model: "grok-4-1-fast-non-reasoning",
baseUrl: "https://api.x.ai/v1", // optional, overrides webSearch.baseUrl
inlineCitations: false,
maxTurns: 2,
timeoutSeconds: 30,
cacheTtlMinutes: 15,
},
webSearch: {
apiKey: "xai-...", // optional if an xAI auth profile or XAI_API_KEY is set
baseUrl: "https://api.x.ai/v1", // optional shared xAI Responses base URL
},
},
},
},
},
}
```
`x_search` posts to `<baseUrl>/responses` when
`plugins.entries.xai.config.xSearch.baseUrl` is set. If that field is omitted,
it falls back to `plugins.entries.xai.config.webSearch.baseUrl`, then the
legacy `tools.web.search.grok.baseUrl`, and finally the public xAI endpoint
(`https://api.x.ai/v1`).
### x_search parameters
| Parameter | Description |
| ---------------------------- | ------------------------------------------------------ |
| `query` | Search query (required) |
| `allowed_x_handles` | Restrict results to specific X handles |
| `excluded_x_handles` | Exclude specific X handles |
| `from_date` | Only include posts on or after this date (YYYY-MM-DD) |
| `to_date` | Only include posts on or before this date (YYYY-MM-DD) |
| `enable_image_understanding` | Let xAI inspect images attached to matching posts |
| `enable_video_understanding` | Let xAI inspect videos attached to matching posts |
### x_search example
```javascript
await x_search({
query: "dinner recipes",
allowed_x_handles: ["nytfood"],
from_date: "2026-03-01",
});
```
```javascript
// Per-post stats: use the exact status URL or status ID when possible
await x_search({
query: "https://x.com/huntharo/status/1905678901234567890",
});
```
## Examples
```javascript
// Basic search
await web_search({ query: "OpenClaw plugin SDK" });
// German-specific search
await web_search({ query: "TV online schauen", country: "DE", language: "de" });
// Recent results (past week)
await web_search({ query: "AI developments", freshness: "week" });
// Date range
await web_search({
query: "climate research",
date_after: "2024-01-01",
date_before: "2024-06-30",
});
// Domain filtering (Perplexity only)
await web_search({
query: "product reviews",
domain_filter: ["-reddit.com", "-pinterest.com"],
});
```
## Tool profiles
If you use tool profiles or allowlists, add `web_search`, `x_search`, or `group:web`:
```json5
{
tools: {
allow: ["web_search", "x_search"],
// or: allow: ["group:web"] (includes web_search, x_search, and web_fetch)
},
}
```
## Related
- [Web Fetch](/tools/web-fetch) -- fetch a URL and extract readable content
- [Web Browser](/tools/browser) -- full browser automation for JS-heavy sites
- [Grok Search](/tools/grok-search) -- Grok as the `web_search` provider
- [Ollama Web Search](/tools/ollama-search) -- key-free web search through your Ollama host