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
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:
346
docs/tools/acp-agents-setup.md
Normal file
346
docs/tools/acp-agents-setup.md
Normal 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
868
docs/tools/acp-agents.md
Normal 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
154
docs/tools/agent-send.md
Normal 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
64
docs/tools/apply-patch.md
Normal 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
138
docs/tools/brave-search.md
Normal 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 (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 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
|
||||
442
docs/tools/browser-control.md
Normal file
442
docs/tools/browser-control.md
Normal 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)
|
||||
168
docs/tools/browser-linux-troubleshooting.md
Normal file
168
docs/tools/browser-linux-troubleshooting.md
Normal 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)
|
||||
86
docs/tools/browser-login.md
Normal file
86
docs/tools/browser-login.md
Normal 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)
|
||||
183
docs/tools/browser-wsl2-windows-remote-cdp-troubleshooting.md
Normal file
183
docs/tools/browser-wsl2-windows-remote-cdp-troubleshooting.md
Normal 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
867
docs/tools/browser.md
Normal 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
95
docs/tools/btw.md
Normal 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>
|
||||
12
docs/tools/capability-cookbook.md
Normal file
12
docs/tools/capability-cookbook.md
Normal 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
5
docs/tools/clawhub.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
summary: "Redirect to /clawhub"
|
||||
title: "ClawHub (redirect)"
|
||||
redirect: /clawhub
|
||||
---
|
||||
161
docs/tools/code-execution.md
Normal file
161
docs/tools/code-execution.md
Normal 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>
|
||||
268
docs/tools/creating-skills.md
Normal file
268
docs/tools/creating-skills.md
Normal 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
413
docs/tools/diffs.md
Normal 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)
|
||||
100
docs/tools/duckduckgo-search.md
Normal file
100
docs/tools/duckduckgo-search.md
Normal 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
127
docs/tools/elevated.md
Normal 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
157
docs/tools/exa-search.md
Normal 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
|
||||
445
docs/tools/exec-approvals-advanced.md
Normal file
445
docs/tools/exec-approvals-advanced.md
Normal 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
|
||||
512
docs/tools/exec-approvals.md
Normal file
512
docs/tools/exec-approvals.md
Normal 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
261
docs/tools/exec.md
Normal 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
165
docs/tools/firecrawl.md
Normal 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
114
docs/tools/gemini-search.md
Normal 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
192
docs/tools/goal.md
Normal 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
123
docs/tools/grok-search.md
Normal 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
|
||||
554
docs/tools/image-generation.md
Normal file
554
docs/tools/image-generation.md
Normal 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
188
docs/tools/index.md
Normal 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
101
docs/tools/kimi-search.md
Normal 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
143
docs/tools/llm-task.md
Normal 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
380
docs/tools/lobster.md
Normal 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
|
||||
193
docs/tools/loop-detection.md
Normal file
193
docs/tools/loop-detection.md
Normal 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>
|
||||
176
docs/tools/media-overview.md
Normal file
176
docs/tools/media-overview.md
Normal 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)
|
||||
103
docs/tools/minimax-search.md
Normal file
103
docs/tools/minimax-search.md
Normal 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
|
||||
409
docs/tools/multi-agent-sandbox-tools.md
Normal file
409
docs/tools/multi-agent-sandbox-tools.md
Normal 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)
|
||||
390
docs/tools/music-generation.md
Normal file
390
docs/tools/music-generation.md
Normal 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
143
docs/tools/ollama-search.md
Normal 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
|
||||
166
docs/tools/parallel-search.md
Normal file
166
docs/tools/parallel-search.md
Normal 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
189
docs/tools/pdf.md
Normal 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
|
||||
111
docs/tools/permission-modes.md
Normal file
111
docs/tools/permission-modes.md
Normal 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)
|
||||
220
docs/tools/perplexity-search.md
Normal file
220
docs/tools/perplexity-search.md
Normal 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
387
docs/tools/plugin.md
Normal 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
104
docs/tools/reactions.md
Normal 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
|
||||
146
docs/tools/searxng-search.md
Normal file
146
docs/tools/searxng-search.md
Normal 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
|
||||
278
docs/tools/skill-workshop.md
Normal file
278
docs/tools/skill-workshop.md
Normal 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
484
docs/tools/skills-config.md
Normal 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
609
docs/tools/skills.md
Normal 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>
|
||||
524
docs/tools/slash-commands.md
Normal file
524
docs/tools/slash-commands.md
Normal 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
77
docs/tools/steer.md
Normal 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
658
docs/tools/subagents.md
Normal 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
169
docs/tools/tavily.md
Normal 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
143
docs/tools/thinking.md
Normal 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
84
docs/tools/tokenjuice.md
Normal 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
309
docs/tools/tool-search.md
Normal 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
214
docs/tools/trajectory.md
Normal 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
998
docs/tools/tts.md
Normal 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, 0–10)
|
||||
- `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 **100–4096** (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 100–4096) |
|
||||
| `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)
|
||||
551
docs/tools/video-generation.md
Normal file
551
docs/tools/video-generation.md
Normal 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
209
docs/tools/web-fetch.md
Normal 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
537
docs/tools/web.md
Normal 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
|
||||
Reference in New Issue
Block a user