--- summary: "Gateway config reference for core OpenClaw keys, defaults, and links to dedicated subsystem references" title: "Configuration reference" read_when: - You need exact field-level config semantics or defaults - You are validating channel, model, gateway, or tool config blocks --- Core config reference for `~/.openclaw/openclaw.json`. For a task-oriented overview, see [Configuration](/gateway/configuration). This page covers the main OpenClaw config surfaces and links out when a subsystem has its own deeper reference. It does **not** try to inline every channel/plugin-owned command catalog or every deep memory/QMD knob on one page. Code truth: - `openclaw config schema` prints the live JSON Schema used for validation and Control UI, with bundled/plugin/channel metadata merged in when available - `config.schema.lookup` returns one path-scoped schema node for drill-down tooling - `pnpm config:docs:check` / `pnpm config:docs:gen` validate the config-doc baseline hash against the current schema surface Dedicated deep references: - [Memory configuration reference](/reference/memory-config) for `agents.defaults.memorySearch.*`, `memory.qmd.*`, `memory.citations`, and dreaming config under `plugins.entries.memory-core.config.dreaming` - [Slash Commands](/tools/slash-commands) for the current built-in + bundled command catalog - owning channel/plugin pages for channel-specific command surfaces Config format is **JSON5** (comments + trailing commas allowed). All fields are optional — OpenClaw uses safe defaults when omitted. --- ## Channels Per-channel config keys moved to a dedicated page — see [Configuration — channels](/gateway/config-channels) for `channels.*`, including Slack, Discord, Telegram, WhatsApp, Matrix, iMessage, and other bundled channels (auth, access control, multi-account, mention gating). ## Agent defaults, multi-agent, sessions, and messages Moved to a dedicated page — see [Configuration — agents](/gateway/config-agents) for: - `agents.defaults.*` (workspace, model, thinking, heartbeat, memory, media, skills, sandbox) - `multiAgent.*` (multi-agent routing and bindings) - `session.*` (session lifecycle, compaction, pruning) - `messages.*` (message delivery, TTS, markdown rendering) - `talk.*` (Talk mode) ## Tools ### Tool profiles `tools.profile` sets a base allowlist before `tools.allow`/`tools.deny`: Local onboarding defaults new local configs to `tools.profile: "coding"` when unset (existing explicit profiles are preserved). | Profile | Includes | | ----------- | ------------------------------------------------------------------------------------------------------------------------------- | | `minimal` | `session_status` only | | `coding` | `group:fs`, `group:runtime`, `group:web`, `group:sessions`, `group:memory`, `cron`, `image`, `image_generate`, `video_generate` | | `messaging` | `group:messaging`, `sessions_list`, `sessions_history`, `sessions_send`, `session_status` | | `full` | No restriction (same as unset) | ### Tool groups | Group | Tools | | ------------------ | ----------------------------------------------------------------------------------------------------------------------- | | `group:runtime` | `exec`, `process`, `code_execution` (`bash` is accepted as an alias for `exec`) | | `group:fs` | `read`, `write`, `edit`, `apply_patch` | | `group:sessions` | `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `sessions_yield`, `subagents`, `session_status` | | `group:memory` | `memory_search`, `memory_get` | | `group:web` | `web_search`, `x_search`, `web_fetch` | | `group:ui` | `browser`, `canvas` | | `group:automation` | `cron`, `gateway` | | `group:messaging` | `message` | | `group:nodes` | `nodes` | | `group:agents` | `agents_list` | | `group:media` | `image`, `image_generate`, `video_generate`, `tts` | | `group:openclaw` | All built-in tools (excludes provider plugins) | ### `tools.allow` / `tools.deny` Global tool allow/deny policy (deny wins). Case-insensitive, supports `*` wildcards. Applied even when Docker sandbox is off. ```json5 { tools: { deny: ["browser", "canvas"] }, } ``` ### `tools.byProvider` Further restrict tools for specific providers or models. Order: base profile → provider profile → allow/deny. ```json5 { tools: { profile: "coding", byProvider: { "google-antigravity": { profile: "minimal" }, "openai/gpt-5.4": { allow: ["group:fs", "sessions_list"] }, }, }, } ``` ### `tools.elevated` Controls elevated exec access outside the sandbox: ```json5 { tools: { elevated: { enabled: true, allowFrom: { whatsapp: ["+15555550123"], discord: ["1234567890123", "987654321098765432"], }, }, }, } ``` - Per-agent override (`agents.list[].tools.elevated`) can only further restrict. - `/elevated on|off|ask|full` stores state per session; inline directives apply to single message. - Elevated `exec` bypasses sandboxing and uses the configured escape path (`gateway` by default, or `node` when the exec target is `node`). ### `tools.exec` ```json5 { tools: { exec: { backgroundMs: 10000, timeoutSec: 1800, cleanupMs: 1800000, notifyOnExit: true, notifyOnExitEmptySuccess: false, applyPatch: { enabled: false, allowModels: ["gpt-5.5"], }, }, }, } ``` ### `tools.loopDetection` Tool-loop safety checks are **disabled by default**. Set `enabled: true` to activate detection. Settings can be defined globally in `tools.loopDetection` and overridden per-agent at `agents.list[].tools.loopDetection`. ```json5 { tools: { loopDetection: { enabled: true, historySize: 30, warningThreshold: 10, criticalThreshold: 20, globalCircuitBreakerThreshold: 30, detectors: { genericRepeat: true, knownPollNoProgress: true, pingPong: true, }, }, }, } ``` - `historySize`: max tool-call history retained for loop analysis. - `warningThreshold`: repeating no-progress pattern threshold for warnings. - `criticalThreshold`: higher repeating threshold for blocking critical loops. - `globalCircuitBreakerThreshold`: hard stop threshold for any no-progress run. - `detectors.genericRepeat`: warn on repeated same-tool/same-args calls. - `detectors.knownPollNoProgress`: warn/block on known poll tools (`process.poll`, `command_status`, etc.). - `detectors.pingPong`: warn/block on alternating no-progress pair patterns. - If `warningThreshold >= criticalThreshold` or `criticalThreshold >= globalCircuitBreakerThreshold`, validation fails. ### `tools.web` ```json5 { tools: { web: { search: { enabled: true, apiKey: "brave_api_key", // or BRAVE_API_KEY env maxResults: 5, timeoutSeconds: 30, cacheTtlMinutes: 15, }, fetch: { enabled: true, provider: "firecrawl", // optional; omit for auto-detect maxChars: 50000, maxCharsCap: 50000, maxResponseBytes: 2000000, timeoutSeconds: 30, cacheTtlMinutes: 15, maxRedirects: 3, readability: true, userAgent: "custom-ua", }, }, }, } ``` ### `tools.media` Configures inbound media understanding (image/audio/video): ```json5 { tools: { media: { concurrency: 2, asyncCompletion: { directSend: false, // opt-in: send finished async music/video directly to the channel }, audio: { enabled: true, maxBytes: 20971520, scope: { default: "deny", rules: [{ action: "allow", match: { chatType: "direct" } }], }, models: [ { provider: "openai", model: "gpt-4o-mini-transcribe" }, { type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] }, ], }, video: { enabled: true, maxBytes: 52428800, models: [{ provider: "google", model: "gemini-3-flash-preview" }], }, }, }, } ``` **Provider entry** (`type: "provider"` or omitted): - `provider`: API provider id (`openai`, `anthropic`, `google`/`gemini`, `groq`, etc.) - `model`: model id override - `profile` / `preferredProfile`: `auth-profiles.json` profile selection **CLI entry** (`type: "cli"`): - `command`: executable to run - `args`: templated args (supports `{{MediaPath}}`, `{{Prompt}}`, `{{MaxChars}}`, etc.) **Common fields:** - `capabilities`: optional list (`image`, `audio`, `video`). Defaults: `openai`/`anthropic`/`minimax` → image, `google` → image+audio+video, `groq` → audio. - `prompt`, `maxChars`, `maxBytes`, `timeoutSeconds`, `language`: per-entry overrides. - Failures fall back to the next entry. Provider auth follows standard order: `auth-profiles.json` → env vars → `models.providers.*.apiKey`. **Async completion fields:** - `asyncCompletion.directSend`: when `true`, completed async `music_generate` and `video_generate` tasks try direct channel delivery first. Default: `false` (legacy requester-session wake/model-delivery path). ### `tools.agentToAgent` ```json5 { tools: { agentToAgent: { enabled: false, allow: ["home", "work"], }, }, } ``` ### `tools.sessions` Controls which sessions can be targeted by the session tools (`sessions_list`, `sessions_history`, `sessions_send`). Default: `tree` (current session + sessions spawned by it, such as subagents). ```json5 { tools: { sessions: { // "self" | "tree" | "agent" | "all" visibility: "tree", }, }, } ``` Notes: - `self`: only the current session key. - `tree`: current session + sessions spawned by the current session (subagents). - `agent`: any session belonging to the current agent id (can include other users if you run per-sender sessions under the same agent id). - `all`: any session. Cross-agent targeting still requires `tools.agentToAgent`. - Sandbox clamp: when the current session is sandboxed and `agents.defaults.sandbox.sessionToolsVisibility="spawned"`, visibility is forced to `tree` even if `tools.sessions.visibility="all"`. ### `tools.sessions_spawn` Controls inline attachment support for `sessions_spawn`. ```json5 { tools: { sessions_spawn: { attachments: { enabled: false, // opt-in: set true to allow inline file attachments maxTotalBytes: 5242880, // 5 MB total across all files maxFiles: 50, maxFileBytes: 1048576, // 1 MB per file retainOnSessionKeep: false, // keep attachments when cleanup="keep" }, }, }, } ``` Notes: - Attachments are only supported for `runtime: "subagent"`. ACP runtime rejects them. - Files are materialized into the child workspace at `.openclaw/attachments//` with a `.manifest.json`. - Attachment content is automatically redacted from transcript persistence. - Base64 inputs are validated with strict alphabet/padding checks and a pre-decode size guard. - File permissions are `0700` for directories and `0600` for files. - Cleanup follows the `cleanup` policy: `delete` always removes attachments; `keep` retains them only when `retainOnSessionKeep: true`. ### `tools.experimental` Experimental built-in tool flags. Default off unless a strict-agentic GPT-5 auto-enable rule applies. ```json5 { tools: { experimental: { planTool: true, // enable experimental update_plan }, }, } ``` Notes: - `planTool`: enables the structured `update_plan` tool for non-trivial multi-step work tracking. - Default: `false` unless `agents.defaults.embeddedPi.executionContract` (or a per-agent override) is set to `"strict-agentic"` for an OpenAI or OpenAI Codex GPT-5-family run. Set `true` to force the tool on outside that scope, or `false` to keep it off even for strict-agentic GPT-5 runs. - When enabled, the system prompt also adds usage guidance so the model only uses it for substantial work and keeps at most one step `in_progress`. ### `agents.defaults.subagents` ```json5 { agents: { defaults: { subagents: { allowAgents: ["research"], model: "minimax/MiniMax-M2.7", maxConcurrent: 8, runTimeoutSeconds: 900, archiveAfterMinutes: 60, }, }, }, } ``` - `model`: default model for spawned sub-agents. If omitted, sub-agents inherit the caller's model. - `allowAgents`: default allowlist of target agent ids for `sessions_spawn` when the requester agent does not set its own `subagents.allowAgents` (`["*"]` = any; default: same agent only). - `runTimeoutSeconds`: default timeout (seconds) for `sessions_spawn` when the tool call omits `runTimeoutSeconds`. `0` means no timeout. - Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny`. --- ## Custom providers and base URLs OpenClaw uses the built-in model catalog. Add custom providers via `models.providers` in config or `~/.openclaw/agents//agent/models.json`. ```json5 { models: { mode: "merge", // merge (default) | replace providers: { "custom-proxy": { baseUrl: "http://localhost:4000/v1", apiKey: "LITELLM_KEY", api: "openai-completions", // openai-completions | openai-responses | anthropic-messages | google-generative-ai models: [ { id: "llama-3.1-8b", name: "Llama 3.1 8B", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, contextTokens: 96000, maxTokens: 32000, }, ], }, }, }, } ``` - Use `authHeader: true` + `headers` for custom auth needs. - Override agent config root with `OPENCLAW_AGENT_DIR` (or `PI_CODING_AGENT_DIR`, a legacy environment variable alias). - Merge precedence for matching provider IDs: - Non-empty agent `models.json` `baseUrl` values win. - Non-empty agent `apiKey` values win only when that provider is not SecretRef-managed in current config/auth-profile context. - SecretRef-managed provider `apiKey` values are refreshed from source markers (`ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs) instead of persisting resolved secrets. - SecretRef-managed provider header values are refreshed from source markers (`secretref-env:ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs). - Empty or missing agent `apiKey`/`baseUrl` fall back to `models.providers` in config. - Matching model `contextWindow`/`maxTokens` use the higher value between explicit config and implicit catalog values. - Matching model `contextTokens` preserves an explicit runtime cap when present; use it to limit effective context without changing native model metadata. - Use `models.mode: "replace"` when you want config to fully rewrite `models.json`. - Marker persistence is source-authoritative: markers are written from the active source config snapshot (pre-resolution), not from resolved runtime secret values. ### Provider field details - `models.mode`: provider catalog behavior (`merge` or `replace`). - `models.providers`: custom provider map keyed by provider id. - Safe edits: use `openclaw config set models.providers. '' --strict-json --merge` or `openclaw config set models.providers..models '' --strict-json --merge` for additive updates. `config set` refuses destructive replacements unless you pass `--replace`. - `models.providers.*.api`: request adapter (`openai-completions`, `openai-responses`, `anthropic-messages`, `google-generative-ai`, etc). - `models.providers.*.apiKey`: provider credential (prefer SecretRef/env substitution). - `models.providers.*.auth`: auth strategy (`api-key`, `token`, `oauth`, `aws-sdk`). - `models.providers.*.injectNumCtxForOpenAICompat`: for Ollama + `openai-completions`, inject `options.num_ctx` into requests (default: `true`). - `models.providers.*.authHeader`: force credential transport in the `Authorization` header when required. - `models.providers.*.baseUrl`: upstream API base URL. - `models.providers.*.headers`: extra static headers for proxy/tenant routing. - `models.providers.*.request`: transport overrides for model-provider HTTP requests. - `request.headers`: extra headers (merged with provider defaults). Values accept SecretRef. - `request.auth`: auth strategy override. Modes: `"provider-default"` (use provider's built-in auth), `"authorization-bearer"` (with `token`), `"header"` (with `headerName`, `value`, optional `prefix`). - `request.proxy`: HTTP proxy override. Modes: `"env-proxy"` (use `HTTP_PROXY`/`HTTPS_PROXY` env vars), `"explicit-proxy"` (with `url`). Both modes accept an optional `tls` sub-object. - `request.tls`: TLS override for direct connections. Fields: `ca`, `cert`, `key`, `passphrase` (all accept SecretRef), `serverName`, `insecureSkipVerify`. - `request.allowPrivateNetwork`: when `true`, allow HTTPS to `baseUrl` when DNS resolves to private, CGNAT, or similar ranges, via the provider HTTP fetch guard (operator opt-in for trusted self-hosted OpenAI-compatible endpoints). WebSocket uses the same `request` for headers/TLS but not that fetch SSRF gate. Default `false`. - `models.providers.*.models`: explicit provider model catalog entries. - `models.providers.*.models.*.contextWindow`: native model context window metadata. - `models.providers.*.models.*.contextTokens`: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native `contextWindow`. - `models.providers.*.models.*.compat.supportsDeveloperRole`: optional compatibility hint. For `api: "openai-completions"` with a non-empty non-native `baseUrl` (host not `api.openai.com`), OpenClaw forces this to `false` at runtime. Empty/omitted `baseUrl` keeps default OpenAI behavior. - `models.providers.*.models.*.compat.requiresStringContent`: optional compatibility hint for string-only OpenAI-compatible chat endpoints. When `true`, OpenClaw flattens pure text `messages[].content` arrays into plain strings before sending the request. - `plugins.entries.amazon-bedrock.config.discovery`: Bedrock auto-discovery settings root. - `plugins.entries.amazon-bedrock.config.discovery.enabled`: turn implicit discovery on/off. - `plugins.entries.amazon-bedrock.config.discovery.region`: AWS region for discovery. - `plugins.entries.amazon-bedrock.config.discovery.providerFilter`: optional provider-id filter for targeted discovery. - `plugins.entries.amazon-bedrock.config.discovery.refreshInterval`: polling interval for discovery refresh. - `plugins.entries.amazon-bedrock.config.discovery.defaultContextWindow`: fallback context window for discovered models. - `plugins.entries.amazon-bedrock.config.discovery.defaultMaxTokens`: fallback max output tokens for discovered models. ### Provider examples ```json5 { env: { CEREBRAS_API_KEY: "sk-..." }, agents: { defaults: { model: { primary: "cerebras/zai-glm-4.7", fallbacks: ["cerebras/zai-glm-4.6"], }, models: { "cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" }, "cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" }, }, }, }, models: { mode: "merge", providers: { cerebras: { baseUrl: "https://api.cerebras.ai/v1", apiKey: "${CEREBRAS_API_KEY}", api: "openai-completions", models: [ { id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" }, { id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" }, ], }, }, }, } ``` Use `cerebras/zai-glm-4.7` for Cerebras; `zai/glm-4.7` for Z.AI direct. ```json5 { agents: { defaults: { model: { primary: "opencode/claude-opus-4-6" }, models: { "opencode/claude-opus-4-6": { alias: "Opus" } }, }, }, } ``` Set `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`). Use `opencode/...` refs for the Zen catalog or `opencode-go/...` refs for the Go catalog. Shortcut: `openclaw onboard --auth-choice opencode-zen` or `openclaw onboard --auth-choice opencode-go`. ```json5 { agents: { defaults: { model: { primary: "zai/glm-4.7" }, models: { "zai/glm-4.7": {} }, }, }, } ``` Set `ZAI_API_KEY`. `z.ai/*` and `z-ai/*` are accepted aliases. Shortcut: `openclaw onboard --auth-choice zai-api-key`. - General endpoint: `https://api.z.ai/api/paas/v4` - Coding endpoint (default): `https://api.z.ai/api/coding/paas/v4` - For the general endpoint, define a custom provider with the base URL override. ```json5 { env: { MOONSHOT_API_KEY: "sk-..." }, agents: { defaults: { model: { primary: "moonshot/kimi-k2.6" }, models: { "moonshot/kimi-k2.6": { alias: "Kimi K2.6" } }, }, }, models: { mode: "merge", providers: { moonshot: { baseUrl: "https://api.moonshot.ai/v1", apiKey: "${MOONSHOT_API_KEY}", api: "openai-completions", models: [ { id: "kimi-k2.6", name: "Kimi K2.6", reasoning: false, input: ["text", "image"], cost: { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 }, contextWindow: 262144, maxTokens: 262144, }, ], }, }, }, } ``` For the China endpoint: `baseUrl: "https://api.moonshot.cn/v1"` or `openclaw onboard --auth-choice moonshot-api-key-cn`. Native Moonshot endpoints advertise streaming usage compatibility on the shared `openai-completions` transport, and OpenClaw keys that off endpoint capabilities rather than the built-in provider id alone. ```json5 { env: { KIMI_API_KEY: "sk-..." }, agents: { defaults: { model: { primary: "kimi/kimi-code" }, models: { "kimi/kimi-code": { alias: "Kimi Code" } }, }, }, } ``` Anthropic-compatible, built-in provider. Shortcut: `openclaw onboard --auth-choice kimi-code-api-key`. ```json5 { env: { SYNTHETIC_API_KEY: "sk-..." }, agents: { defaults: { model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" }, models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax M2.5" } }, }, }, models: { mode: "merge", providers: { synthetic: { baseUrl: "https://api.synthetic.new/anthropic", apiKey: "${SYNTHETIC_API_KEY}", api: "anthropic-messages", models: [ { id: "hf:MiniMaxAI/MiniMax-M2.5", name: "MiniMax M2.5", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 192000, maxTokens: 65536, }, ], }, }, }, } ``` Base URL should omit `/v1` (Anthropic client appends it). Shortcut: `openclaw onboard --auth-choice synthetic-api-key`. ```json5 { agents: { defaults: { model: { primary: "minimax/MiniMax-M2.7" }, models: { "minimax/MiniMax-M2.7": { alias: "Minimax" }, }, }, }, models: { mode: "merge", providers: { minimax: { baseUrl: "https://api.minimax.io/anthropic", apiKey: "${MINIMAX_API_KEY}", api: "anthropic-messages", models: [ { id: "MiniMax-M2.7", name: "MiniMax M2.7", reasoning: true, input: ["text", "image"], cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 }, contextWindow: 204800, maxTokens: 131072, }, ], }, }, }, } ``` Set `MINIMAX_API_KEY`. Shortcuts: `openclaw onboard --auth-choice minimax-global-api` or `openclaw onboard --auth-choice minimax-cn-api`. The model catalog defaults to M2.7 only. On the Anthropic-compatible streaming path, OpenClaw disables MiniMax thinking by default unless you explicitly set `thinking` yourself. `/fast on` or `params.fastMode: true` rewrites `MiniMax-M2.7` to `MiniMax-M2.7-highspeed`. See [Local Models](/gateway/local-models). TL;DR: run a large local model via LM Studio Responses API on serious hardware; keep hosted models merged for fallback. --- ## Skills ```json5 { skills: { allowBundled: ["gemini", "peekaboo"], load: { extraDirs: ["~/Projects/agent-scripts/skills"], }, install: { preferBrew: true, nodeManager: "npm", // npm | pnpm | yarn | bun }, entries: { "image-lab": { apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" }, }, peekaboo: { enabled: true }, sag: { enabled: false }, }, }, } ``` - `allowBundled`: optional allowlist for bundled skills only (managed/workspace skills unaffected). - `load.extraDirs`: extra shared skill roots (lowest precedence). - `install.preferBrew`: when true, prefer Homebrew installers when `brew` is available before falling back to other installer kinds. - `install.nodeManager`: node installer preference for `metadata.openclaw.install` specs (`npm` | `pnpm` | `yarn` | `bun`). - `entries..enabled: false` disables a skill even if bundled/installed. - `entries..apiKey`: convenience for skills declaring a primary env var (plaintext string or SecretRef object). --- ## Plugins ```json5 { plugins: { enabled: true, allow: ["voice-call"], deny: [], load: { paths: ["~/Projects/oss/voice-call-plugin"], }, entries: { "voice-call": { enabled: true, hooks: { allowPromptInjection: false, }, config: { provider: "twilio" }, }, }, }, } ``` - Loaded from `~/.openclaw/extensions`, `/.openclaw/extensions`, plus `plugins.load.paths`. - Discovery accepts native OpenClaw plugins plus compatible Codex bundles and Claude bundles, including manifestless Claude default-layout bundles. - **Config changes require a gateway restart.** - `allow`: optional allowlist (only listed plugins load). `deny` wins. - `plugins.entries..apiKey`: plugin-level API key convenience field (when supported by the plugin). - `plugins.entries..env`: plugin-scoped env var map. - `plugins.entries..hooks.allowPromptInjection`: when `false`, core blocks `before_prompt_build` and ignores prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride`. Applies to native plugin hooks and supported bundle-provided hook directories. - `plugins.entries..subagent.allowModelOverride`: explicitly trust this plugin to request per-run `provider` and `model` overrides for background subagent runs. - `plugins.entries..subagent.allowedModels`: optional allowlist of canonical `provider/model` targets for trusted subagent overrides. Use `"*"` only when you intentionally want to allow any model. - `plugins.entries..config`: plugin-defined config object (validated by native OpenClaw plugin schema when available). - `plugins.entries.firecrawl.config.webFetch`: Firecrawl web-fetch provider settings. - `apiKey`: Firecrawl API key (accepts SecretRef). Falls back to `plugins.entries.firecrawl.config.webSearch.apiKey`, legacy `tools.web.fetch.firecrawl.apiKey`, or `FIRECRAWL_API_KEY` env var. - `baseUrl`: Firecrawl API base URL (default: `https://api.firecrawl.dev`). - `onlyMainContent`: extract only the main content from pages (default: `true`). - `maxAgeMs`: maximum cache age in milliseconds (default: `172800000` / 2 days). - `timeoutSeconds`: scrape request timeout in seconds (default: `60`). - `plugins.entries.xai.config.xSearch`: xAI X Search (Grok web search) settings. - `enabled`: enable the X Search provider. - `model`: Grok model to use for search (e.g. `"grok-4-1-fast"`). - `plugins.entries.memory-core.config.dreaming`: memory dreaming settings. See [Dreaming](/concepts/dreaming) for phases and thresholds. - `enabled`: master dreaming switch (default `false`). - `frequency`: cron cadence for each full dreaming sweep (`"0 3 * * *"` by default). - phase policy and thresholds are implementation details (not user-facing config keys). - Full memory config lives in [Memory configuration reference](/reference/memory-config): - `agents.defaults.memorySearch.*` - `memory.backend` - `memory.citations` - `memory.qmd.*` - `plugins.entries.memory-core.config.dreaming` - Enabled Claude bundle plugins can also contribute embedded Pi defaults from `settings.json`; OpenClaw applies those as sanitized agent settings, not as raw OpenClaw config patches. - `plugins.slots.memory`: pick the active memory plugin id, or `"none"` to disable memory plugins. - `plugins.slots.contextEngine`: pick the active context engine plugin id; defaults to `"legacy"` unless you install and select another engine. - `plugins.installs`: CLI-managed install metadata used by `openclaw plugins update`. - Includes `source`, `spec`, `sourcePath`, `installPath`, `version`, `resolvedName`, `resolvedVersion`, `resolvedSpec`, `integrity`, `shasum`, `resolvedAt`, `installedAt`. - Treat `plugins.installs.*` as managed state; prefer CLI commands over manual edits. See [Plugins](/tools/plugin). --- ## Browser ```json5 { browser: { enabled: true, evaluateEnabled: true, defaultProfile: "user", ssrfPolicy: { // dangerouslyAllowPrivateNetwork: true, // opt in only for trusted private-network access // allowPrivateNetwork: true, // legacy alias // hostnameAllowlist: ["*.example.com", "example.com"], // allowedHostnames: ["localhost"], }, profiles: { openclaw: { cdpPort: 18800, color: "#FF4500" }, work: { cdpPort: 18801, color: "#0066CC" }, user: { driver: "existing-session", attachOnly: true, color: "#00AA00" }, brave: { driver: "existing-session", attachOnly: true, userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser", color: "#FB542B", }, remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" }, }, color: "#FF4500", // headless: false, // noSandbox: false, // extraArgs: [], // executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", // attachOnly: false, }, } ``` - `evaluateEnabled: false` disables `act:evaluate` and `wait --fn`. - `ssrfPolicy.dangerouslyAllowPrivateNetwork` is disabled when unset, so browser navigation stays strict by default. - Set `ssrfPolicy.dangerouslyAllowPrivateNetwork: true` only when you intentionally trust private-network browser navigation. - In strict mode, remote CDP profile endpoints (`profiles.*.cdpUrl`) are subject to the same private-network blocking during reachability/discovery checks. - `ssrfPolicy.allowPrivateNetwork` remains supported as a legacy alias. - In strict mode, use `ssrfPolicy.hostnameAllowlist` and `ssrfPolicy.allowedHostnames` for explicit exceptions. - Remote profiles are attach-only (start/stop/reset disabled). - `profiles.*.cdpUrl` accepts `http://`, `https://`, `ws://`, and `wss://`. Use HTTP(S) when you want OpenClaw to discover `/json/version`; use WS(S) when your provider gives you a direct DevTools WebSocket URL. - `existing-session` profiles use Chrome MCP instead of CDP and can attach on the selected host or through a connected browser node. - `existing-session` profiles can set `userDataDir` to target a specific Chromium-based browser profile such as Brave or Edge. - `existing-session` profiles keep the current Chrome MCP route limits: snapshot/ref-driven actions instead of CSS-selector targeting, one-file upload hooks, no dialog timeout overrides, no `wait --load networkidle`, and no `responsebody`, PDF export, download interception, or batch actions. - Local managed `openclaw` profiles auto-assign `cdpPort` and `cdpUrl`; only set `cdpUrl` explicitly for remote CDP. - Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary. - Control service: loopback only (port derived from `gateway.port`, default `18791`). - `extraArgs` appends extra launch flags to local Chromium startup (for example `--disable-gpu`, window sizing, or debug flags). --- ## UI ```json5 { ui: { seamColor: "#FF4500", assistant: { name: "OpenClaw", avatar: "CB", // emoji, short text, image URL, or data URI }, }, } ``` - `seamColor`: accent color for native app UI chrome (Talk Mode bubble tint, etc.). - `assistant`: Control UI identity override. Falls back to active agent identity. --- ## Gateway ```json5 { gateway: { mode: "local", // local | remote port: 18789, bind: "loopback", auth: { mode: "token", // none | token | password | trusted-proxy token: "your-token", // password: "your-password", // or OPENCLAW_GATEWAY_PASSWORD // trustedProxy: { userHeader: "x-forwarded-user" }, // for mode=trusted-proxy; see /gateway/trusted-proxy-auth allowTailscale: true, rateLimit: { maxAttempts: 10, windowMs: 60000, lockoutMs: 300000, exemptLoopback: true, }, }, tailscale: { mode: "off", // off | serve | funnel resetOnExit: false, }, controlUi: { enabled: true, basePath: "/openclaw", // root: "dist/control-ui", // embedSandbox: "scripts", // strict | scripts | trusted // allowExternalEmbedUrls: false, // dangerous: allow absolute external http(s) embed URLs // allowedOrigins: ["https://control.example.com"], // required for non-loopback Control UI // dangerouslyAllowHostHeaderOriginFallback: false, // dangerous Host-header origin fallback mode // allowInsecureAuth: false, // dangerouslyDisableDeviceAuth: false, }, remote: { url: "ws://gateway.tailnet:18789", transport: "ssh", // ssh | direct token: "your-token", // password: "your-password", }, trustedProxies: ["10.0.0.1"], // Optional. Default false. allowRealIpFallback: false, tools: { // Additional /tools/invoke HTTP denies deny: ["browser"], // Remove tools from the default HTTP deny list allow: ["gateway"], }, push: { apns: { relay: { baseUrl: "https://relay.example.com", timeoutMs: 10000, }, }, }, }, } ``` - `mode`: `local` (run gateway) or `remote` (connect to remote gateway). Gateway refuses to start unless `local`. - `port`: single multiplexed port for WS + HTTP. Precedence: `--port` > `OPENCLAW_GATEWAY_PORT` > `gateway.port` > `18789`. - `bind`: `auto`, `loopback` (default), `lan` (`0.0.0.0`), `tailnet` (Tailscale IP only), or `custom`. - **Legacy bind aliases**: use bind mode values in `gateway.bind` (`auto`, `loopback`, `lan`, `tailnet`, `custom`), not host aliases (`0.0.0.0`, `127.0.0.1`, `localhost`, `::`, `::1`). - **Docker note**: the default `loopback` bind listens on `127.0.0.1` inside the container. With Docker bridge networking (`-p 18789:18789`), traffic arrives on `eth0`, so the gateway is unreachable. Use `--network host`, or set `bind: "lan"` (or `bind: "custom"` with `customBindHost: "0.0.0.0"`) to listen on all interfaces. - **Auth**: required by default. Non-loopback binds require gateway auth. In practice that means a shared token/password or an identity-aware reverse proxy with `gateway.auth.mode: "trusted-proxy"`. Onboarding wizard generates a token by default. - If both `gateway.auth.token` and `gateway.auth.password` are configured (including SecretRefs), set `gateway.auth.mode` explicitly to `token` or `password`. Startup and service install/repair flows fail when both are configured and mode is unset. - `gateway.auth.mode: "none"`: explicit no-auth mode. Use only for trusted local loopback setups; this is intentionally not offered by onboarding prompts. - `gateway.auth.mode: "trusted-proxy"`: delegate auth to an identity-aware reverse proxy and trust identity headers from `gateway.trustedProxies` (see [Trusted Proxy Auth](/gateway/trusted-proxy-auth)). This mode expects a **non-loopback** proxy source; same-host loopback reverse proxies do not satisfy trusted-proxy auth. - `gateway.auth.allowTailscale`: when `true`, Tailscale Serve identity headers can satisfy Control UI/WebSocket auth (verified via `tailscale whois`). HTTP API endpoints do **not** use that Tailscale header auth; they follow the gateway's normal HTTP auth mode instead. This tokenless flow assumes the gateway host is trusted. Defaults to `true` when `tailscale.mode = "serve"`. - `gateway.auth.rateLimit`: optional failed-auth limiter. Applies per client IP and per auth scope (shared-secret and device-token are tracked independently). Blocked attempts return `429` + `Retry-After`. - On the async Tailscale Serve Control UI path, failed attempts for the same `{scope, clientIp}` are serialized before the failure write. Concurrent bad attempts from the same client can therefore trip the limiter on the second request instead of both racing through as plain mismatches. - `gateway.auth.rateLimit.exemptLoopback` defaults to `true`; set `false` when you intentionally want localhost traffic rate-limited too (for test setups or strict proxy deployments). - Browser-origin WS auth attempts are always throttled with loopback exemption disabled (defense-in-depth against browser-based localhost brute force). - On loopback, those browser-origin lockouts are isolated per normalized `Origin` value, so repeated failures from one localhost origin do not automatically lock out a different origin. - `tailscale.mode`: `serve` (tailnet only, loopback bind) or `funnel` (public, requires auth). - `controlUi.allowedOrigins`: explicit browser-origin allowlist for Gateway WebSocket connects. Required when browser clients are expected from non-loopback origins. - `controlUi.dangerouslyAllowHostHeaderOriginFallback`: dangerous mode that enables Host-header origin fallback for deployments that intentionally rely on Host-header origin policy. - `remote.transport`: `ssh` (default) or `direct` (ws/wss). For `direct`, `remote.url` must be `ws://` or `wss://`. - `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`: client-side break-glass override that allows plaintext `ws://` to trusted private-network IPs; default remains loopback-only for plaintext. - `gateway.remote.token` / `.password` are remote-client credential fields. They do not configure gateway auth by themselves. - `gateway.push.apns.relay.baseUrl`: base HTTPS URL for the external APNs relay used by official/TestFlight iOS builds after they publish relay-backed registrations to the gateway. This URL must match the relay URL compiled into the iOS build. - `gateway.push.apns.relay.timeoutMs`: gateway-to-relay send timeout in milliseconds. Defaults to `10000`. - Relay-backed registrations are delegated to a specific gateway identity. The paired iOS app fetches `gateway.identity.get`, includes that identity in the relay registration, and forwards a registration-scoped send grant to the gateway. Another gateway cannot reuse that stored registration. - `OPENCLAW_APNS_RELAY_BASE_URL` / `OPENCLAW_APNS_RELAY_TIMEOUT_MS`: temporary env overrides for the relay config above. - `OPENCLAW_APNS_RELAY_ALLOW_HTTP=true`: development-only escape hatch for loopback HTTP relay URLs. Production relay URLs should stay on HTTPS. - `gateway.channelHealthCheckMinutes`: channel health-monitor interval in minutes. Set `0` to disable health-monitor restarts globally. Default: `5`. - `gateway.channelStaleEventThresholdMinutes`: stale-socket threshold in minutes. Keep this greater than or equal to `gateway.channelHealthCheckMinutes`. Default: `30`. - `gateway.channelMaxRestartsPerHour`: maximum health-monitor restarts per channel/account in a rolling hour. Default: `10`. - `channels..healthMonitor.enabled`: per-channel opt-out for health-monitor restarts while keeping the global monitor enabled. - `channels..accounts..healthMonitor.enabled`: per-account override for multi-account channels. When set, it takes precedence over the channel-level override. - Local gateway call paths can use `gateway.remote.*` as fallback only when `gateway.auth.*` is unset. - If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking). - `trustedProxies`: reverse proxy IPs that terminate TLS or inject forwarded-client headers. Only list proxies you control. Loopback entries are still valid for same-host proxy/local-detection setups (for example Tailscale Serve or a local reverse proxy), but they do **not** make loopback requests eligible for `gateway.auth.mode: "trusted-proxy"`. - `allowRealIpFallback`: when `true`, the gateway accepts `X-Real-IP` if `X-Forwarded-For` is missing. Default `false` for fail-closed behavior. - `gateway.tools.deny`: extra tool names blocked for HTTP `POST /tools/invoke` (extends default deny list). - `gateway.tools.allow`: remove tool names from the default HTTP deny list. ### OpenAI-compatible endpoints - Chat Completions: disabled by default. Enable with `gateway.http.endpoints.chatCompletions.enabled: true`. - Responses API: `gateway.http.endpoints.responses.enabled`. - Responses URL-input hardening: - `gateway.http.endpoints.responses.maxUrlParts` - `gateway.http.endpoints.responses.files.urlAllowlist` - `gateway.http.endpoints.responses.images.urlAllowlist` Empty allowlists are treated as unset; use `gateway.http.endpoints.responses.files.allowUrl=false` and/or `gateway.http.endpoints.responses.images.allowUrl=false` to disable URL fetching. - Optional response hardening header: - `gateway.http.securityHeaders.strictTransportSecurity` (set only for HTTPS origins you control; see [Trusted Proxy Auth](/gateway/trusted-proxy-auth#tls-termination-and-hsts)) ### Multi-instance isolation Run multiple gateways on one host with unique ports and state dirs: ```bash OPENCLAW_CONFIG_PATH=~/.openclaw/a.json \ OPENCLAW_STATE_DIR=~/.openclaw-a \ openclaw gateway --port 19001 ``` Convenience flags: `--dev` (uses `~/.openclaw-dev` + port `19001`), `--profile ` (uses `~/.openclaw-`). See [Multiple Gateways](/gateway/multiple-gateways). ### `gateway.tls` ```json5 { gateway: { tls: { enabled: false, autoGenerate: false, certPath: "/etc/openclaw/tls/server.crt", keyPath: "/etc/openclaw/tls/server.key", caPath: "/etc/openclaw/tls/ca-bundle.crt", }, }, } ``` - `enabled`: enables TLS termination at the gateway listener (HTTPS/WSS) (default: `false`). - `autoGenerate`: auto-generates a local self-signed cert/key pair when explicit files are not configured; for local/dev use only. - `certPath`: filesystem path to the TLS certificate file. - `keyPath`: filesystem path to the TLS private key file; keep permission-restricted. - `caPath`: optional CA bundle path for client verification or custom trust chains. ### `gateway.reload` ```json5 { gateway: { reload: { mode: "hybrid", // off | restart | hot | hybrid debounceMs: 500, deferralTimeoutMs: 300000, }, }, } ``` - `mode`: controls how config edits are applied at runtime. - `"off"`: ignore live edits; changes require an explicit restart. - `"restart"`: always restart the gateway process on config change. - `"hot"`: apply changes in-process without restarting. - `"hybrid"` (default): try hot reload first; fall back to restart if required. - `debounceMs`: debounce window in ms before config changes are applied (non-negative integer). - `deferralTimeoutMs`: maximum time in ms to wait for in-flight operations before forcing a restart (default: `300000` = 5 minutes). --- ## Hooks ```json5 { hooks: { enabled: true, token: "shared-secret", path: "/hooks", maxBodyBytes: 262144, defaultSessionKey: "hook:ingress", allowRequestSessionKey: true, allowedSessionKeyPrefixes: ["hook:", "hook:gmail:"], allowedAgentIds: ["hooks", "main"], presets: ["gmail"], transformsDir: "~/.openclaw/hooks/transforms", mappings: [ { match: { path: "gmail" }, action: "agent", agentId: "hooks", wakeMode: "now", name: "Gmail", sessionKey: "hook:gmail:{{messages[0].id}}", messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}", deliver: true, channel: "last", model: "openai/gpt-5.4-mini", }, ], }, } ``` Auth: `Authorization: Bearer ` or `x-openclaw-token: `. Query-string hook tokens are rejected. Validation and safety notes: - `hooks.enabled=true` requires a non-empty `hooks.token`. - `hooks.token` must be **distinct** from `gateway.auth.token`; reusing the Gateway token is rejected. - `hooks.path` cannot be `/`; use a dedicated subpath such as `/hooks`. - If `hooks.allowRequestSessionKey=true`, constrain `hooks.allowedSessionKeyPrefixes` (for example `["hook:"]`). - If a mapping or preset uses a templated `sessionKey`, set `hooks.allowedSessionKeyPrefixes` and `hooks.allowRequestSessionKey=true`. Static mapping keys do not require that opt-in. **Endpoints:** - `POST /hooks/wake` → `{ text, mode?: "now"|"next-heartbeat" }` - `POST /hooks/agent` → `{ message, name?, agentId?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }` - `sessionKey` from request payload is accepted only when `hooks.allowRequestSessionKey=true` (default: `false`). - `POST /hooks/` → resolved via `hooks.mappings` - Template-rendered mapping `sessionKey` values are treated as externally supplied and also require `hooks.allowRequestSessionKey=true`. - `match.path` matches sub-path after `/hooks` (e.g. `/hooks/gmail` → `gmail`). - `match.source` matches a payload field for generic paths. - Templates like `{{messages[0].subject}}` read from the payload. - `transform` can point to a JS/TS module returning a hook action. - `transform.module` must be a relative path and stays within `hooks.transformsDir` (absolute paths and traversal are rejected). - `agentId` routes to a specific agent; unknown IDs fall back to default. - `allowedAgentIds`: restricts explicit routing (`*` or omitted = allow all, `[]` = deny all). - `defaultSessionKey`: optional fixed session key for hook agent runs without explicit `sessionKey`. - `allowRequestSessionKey`: allow `/hooks/agent` callers and template-driven mapping session keys to set `sessionKey` (default: `false`). - `allowedSessionKeyPrefixes`: optional prefix allowlist for explicit `sessionKey` values (request + mapping), e.g. `["hook:"]`. It becomes required when any mapping or preset uses a templated `sessionKey`. - `deliver: true` sends final reply to a channel; `channel` defaults to `last`. - `model` overrides LLM for this hook run (must be allowed if model catalog is set). ### Gmail integration - The built-in Gmail preset uses `sessionKey: "hook:gmail:{{messages[0].id}}"`. - If you keep that per-message routing, set `hooks.allowRequestSessionKey: true` and constrain `hooks.allowedSessionKeyPrefixes` to match the Gmail namespace, for example `["hook:", "hook:gmail:"]`. - If you need `hooks.allowRequestSessionKey: false`, override the preset with a static `sessionKey` instead of the templated default. ```json5 { hooks: { gmail: { account: "openclaw@gmail.com", topic: "projects//topics/gog-gmail-watch", subscription: "gog-gmail-watch-push", pushToken: "shared-push-token", hookUrl: "http://127.0.0.1:18789/hooks/gmail", includeBody: true, maxBytes: 20000, renewEveryMinutes: 720, serve: { bind: "127.0.0.1", port: 8788, path: "/" }, tailscale: { mode: "funnel", path: "/gmail-pubsub" }, model: "openrouter/meta-llama/llama-3.3-70b-instruct:free", thinking: "off", }, }, } ``` - Gateway auto-starts `gog gmail watch serve` on boot when configured. Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to disable. - Don't run a separate `gog gmail watch serve` alongside the Gateway. --- ## Canvas host ```json5 { canvasHost: { root: "~/.openclaw/workspace/canvas", liveReload: true, // enabled: false, // or OPENCLAW_SKIP_CANVAS_HOST=1 }, } ``` - Serves agent-editable HTML/CSS/JS and A2UI over HTTP under the Gateway port: - `http://:/__openclaw__/canvas/` - `http://:/__openclaw__/a2ui/` - Local-only: keep `gateway.bind: "loopback"` (default). - Non-loopback binds: canvas routes require Gateway auth (token/password/trusted-proxy), same as other Gateway HTTP surfaces. - Node WebViews typically don't send auth headers; after a node is paired and connected, the Gateway advertises node-scoped capability URLs for canvas/A2UI access. - Capability URLs are bound to the active node WS session and expire quickly. IP-based fallback is not used. - Injects live-reload client into served HTML. - Auto-creates starter `index.html` when empty. - Also serves A2UI at `/__openclaw__/a2ui/`. - Changes require a gateway restart. - Disable live reload for large directories or `EMFILE` errors. --- ## Discovery ### mDNS (Bonjour) ```json5 { discovery: { mdns: { mode: "minimal", // minimal | full | off }, }, } ``` - `minimal` (default): omit `cliPath` + `sshPort` from TXT records. - `full`: include `cliPath` + `sshPort`. - Hostname defaults to `openclaw`. Override with `OPENCLAW_MDNS_HOSTNAME`. ### Wide-area (DNS-SD) ```json5 { discovery: { wideArea: { enabled: true }, }, } ``` Writes a unicast DNS-SD zone under `~/.openclaw/dns/`. For cross-network discovery, pair with a DNS server (CoreDNS recommended) + Tailscale split DNS. Setup: `openclaw dns setup --apply`. --- ## Environment ### `env` (inline env vars) ```json5 { env: { OPENROUTER_API_KEY: "sk-or-...", vars: { GROQ_API_KEY: "gsk-...", }, shellEnv: { enabled: true, timeoutMs: 15000, }, }, } ``` - Inline env vars are only applied if the process env is missing the key. - `.env` files: CWD `.env` + `~/.openclaw/.env` (neither overrides existing vars). - `shellEnv`: imports missing expected keys from your login shell profile. - See [Environment](/help/environment) for full precedence. ### Env var substitution Reference env vars in any config string with `${VAR_NAME}`: ```json5 { gateway: { auth: { token: "${OPENCLAW_GATEWAY_TOKEN}" }, }, } ``` - Only uppercase names matched: `[A-Z_][A-Z0-9_]*`. - Missing/empty vars throw an error at config load. - Escape with `$${VAR}` for a literal `${VAR}`. - Works with `$include`. --- ## Secrets Secret refs are additive: plaintext values still work. ### `SecretRef` Use one object shape: ```json5 { source: "env" | "file" | "exec", provider: "default", id: "..." } ``` Validation: - `provider` pattern: `^[a-z][a-z0-9_-]{0,63}$` - `source: "env"` id pattern: `^[A-Z][A-Z0-9_]{0,127}$` - `source: "file"` id: absolute JSON pointer (for example `"/providers/openai/apiKey"`) - `source: "exec"` id pattern: `^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$` - `source: "exec"` ids must not contain `.` or `..` slash-delimited path segments (for example `a/../b` is rejected) ### Supported credential surface - Canonical matrix: [SecretRef Credential Surface](/reference/secretref-credential-surface) - `secrets apply` targets supported `openclaw.json` credential paths. - `auth-profiles.json` refs are included in runtime resolution and audit coverage. ### Secret providers config ```json5 { secrets: { providers: { default: { source: "env" }, // optional explicit env provider filemain: { source: "file", path: "~/.openclaw/secrets.json", mode: "json", timeoutMs: 5000, }, vault: { source: "exec", command: "/usr/local/bin/openclaw-vault-resolver", passEnv: ["PATH", "VAULT_ADDR"], }, }, defaults: { env: "default", file: "filemain", exec: "vault", }, }, } ``` Notes: - `file` provider supports `mode: "json"` and `mode: "singleValue"` (`id` must be `"value"` in singleValue mode). - File and exec provider paths fail closed when Windows ACL verification is unavailable. Set `allowInsecurePath: true` only for trusted paths that cannot be verified. - `exec` provider requires an absolute `command` path and uses protocol payloads on stdin/stdout. - By default, symlink command paths are rejected. Set `allowSymlinkCommand: true` to allow symlink paths while validating the resolved target path. - If `trustedDirs` is configured, the trusted-dir check applies to the resolved target path. - `exec` child environment is minimal by default; pass required variables explicitly with `passEnv`. - Secret refs are resolved at activation time into an in-memory snapshot, then request paths read the snapshot only. - Active-surface filtering applies during activation: unresolved refs on enabled surfaces fail startup/reload, while inactive surfaces are skipped with diagnostics. --- ## Auth storage ```json5 { auth: { profiles: { "anthropic:default": { provider: "anthropic", mode: "api_key" }, "anthropic:work": { provider: "anthropic", mode: "api_key" }, "openai-codex:personal": { provider: "openai-codex", mode: "oauth" }, }, order: { anthropic: ["anthropic:default", "anthropic:work"], "openai-codex": ["openai-codex:personal"], }, }, } ``` - Per-agent profiles are stored at `/auth-profiles.json`. - `auth-profiles.json` supports value-level refs (`keyRef` for `api_key`, `tokenRef` for `token`) for static credential modes. - OAuth-mode profiles (`auth.profiles..mode = "oauth"`) do not support SecretRef-backed auth-profile credentials. - Static runtime credentials come from in-memory resolved snapshots; legacy static `auth.json` entries are scrubbed when discovered. - Legacy OAuth imports from `~/.openclaw/credentials/oauth.json`. - See [OAuth](/concepts/oauth). - Secrets runtime behavior and `audit/configure/apply` tooling: [Secrets Management](/gateway/secrets). ### `auth.cooldowns` ```json5 { auth: { cooldowns: { billingBackoffHours: 5, billingBackoffHoursByProvider: { anthropic: 3, openai: 8 }, billingMaxHours: 24, authPermanentBackoffMinutes: 10, authPermanentMaxMinutes: 60, failureWindowHours: 24, overloadedProfileRotations: 1, overloadedBackoffMs: 0, rateLimitedProfileRotations: 1, }, }, } ``` - `billingBackoffHours`: base backoff in hours when a profile fails due to true billing/insufficient-credit errors (default: `5`). Explicit billing text can still land here even on `401`/`403` responses, but provider-specific text matchers stay scoped to the provider that owns them (for example OpenRouter `Key limit exceeded`). Retryable HTTP `402` usage-window or organization/workspace spend-limit messages stay in the `rate_limit` path instead. - `billingBackoffHoursByProvider`: optional per-provider overrides for billing backoff hours. - `billingMaxHours`: cap in hours for billing backoff exponential growth (default: `24`). - `authPermanentBackoffMinutes`: base backoff in minutes for high-confidence `auth_permanent` failures (default: `10`). - `authPermanentMaxMinutes`: cap in minutes for `auth_permanent` backoff growth (default: `60`). - `failureWindowHours`: rolling window in hours used for backoff counters (default: `24`). - `overloadedProfileRotations`: maximum same-provider auth-profile rotations for overloaded errors before switching to model fallback (default: `1`). Provider-busy shapes such as `ModelNotReadyException` land here. - `overloadedBackoffMs`: fixed delay before retrying an overloaded provider/profile rotation (default: `0`). - `rateLimitedProfileRotations`: maximum same-provider auth-profile rotations for rate-limit errors before switching to model fallback (default: `1`). That rate-limit bucket includes provider-shaped text such as `Too many concurrent requests`, `ThrottlingException`, `concurrency limit reached`, `workers_ai ... quota limit exceeded`, and `resource exhausted`. --- ## Logging ```json5 { logging: { level: "info", file: "/tmp/openclaw/openclaw.log", consoleLevel: "info", consoleStyle: "pretty", // pretty | compact | json redactSensitive: "tools", // off | tools redactPatterns: ["\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1"], }, } ``` - Default log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log`. - Set `logging.file` for a stable path. - `consoleLevel` bumps to `debug` when `--verbose`. - `maxFileBytes`: maximum log file size in bytes before writes are suppressed (positive integer; default: `524288000` = 500 MB). Use external log rotation for production deployments. --- ## Diagnostics ```json5 { diagnostics: { enabled: true, flags: ["telegram.*"], stuckSessionWarnMs: 30000, otel: { enabled: false, endpoint: "https://otel-collector.example.com:4318", protocol: "http/protobuf", // http/protobuf | grpc headers: { "x-tenant-id": "my-org" }, serviceName: "openclaw-gateway", traces: true, metrics: true, logs: false, sampleRate: 1.0, flushIntervalMs: 5000, }, cacheTrace: { enabled: false, filePath: "~/.openclaw/logs/cache-trace.jsonl", includeMessages: true, includePrompt: true, includeSystem: true, }, }, } ``` - `enabled`: master toggle for instrumentation output (default: `true`). - `flags`: array of flag strings enabling targeted log output (supports wildcards like `"telegram.*"` or `"*"`). - `stuckSessionWarnMs`: age threshold in ms for emitting stuck-session warnings while a session remains in processing state. - `otel.enabled`: enables the OpenTelemetry export pipeline (default: `false`). - `otel.endpoint`: collector URL for OTel export. - `otel.protocol`: `"http/protobuf"` (default) or `"grpc"`. - `otel.headers`: extra HTTP/gRPC metadata headers sent with OTel export requests. - `otel.serviceName`: service name for resource attributes. - `otel.traces` / `otel.metrics` / `otel.logs`: enable trace, metrics, or log export. - `otel.sampleRate`: trace sampling rate `0`–`1`. - `otel.flushIntervalMs`: periodic telemetry flush interval in ms. - `cacheTrace.enabled`: log cache trace snapshots for embedded runs (default: `false`). - `cacheTrace.filePath`: output path for cache trace JSONL (default: `$OPENCLAW_STATE_DIR/logs/cache-trace.jsonl`). - `cacheTrace.includeMessages` / `includePrompt` / `includeSystem`: control what is included in cache trace output (all default: `true`). --- ## Update ```json5 { update: { channel: "stable", // stable | beta | dev checkOnStart: true, auto: { enabled: false, stableDelayHours: 6, stableJitterHours: 12, betaCheckIntervalHours: 1, }, }, } ``` - `channel`: release channel for npm/git installs — `"stable"`, `"beta"`, or `"dev"`. - `checkOnStart`: check for npm updates when the gateway starts (default: `true`). - `auto.enabled`: enable background auto-update for package installs (default: `false`). - `auto.stableDelayHours`: minimum delay in hours before stable-channel auto-apply (default: `6`; max: `168`). - `auto.stableJitterHours`: extra stable-channel rollout spread window in hours (default: `12`; max: `168`). - `auto.betaCheckIntervalHours`: how often beta-channel checks run in hours (default: `1`; max: `24`). --- ## ACP ```json5 { acp: { enabled: false, dispatch: { enabled: true }, backend: "acpx", defaultAgent: "main", allowedAgents: ["main", "ops"], maxConcurrentSessions: 10, stream: { coalesceIdleMs: 50, maxChunkChars: 1000, repeatSuppression: true, deliveryMode: "live", // live | final_only hiddenBoundarySeparator: "paragraph", // none | space | newline | paragraph maxOutputChars: 50000, maxSessionUpdateChars: 500, }, runtime: { ttlMinutes: 30, }, }, } ``` - `enabled`: global ACP feature gate (default: `false`). - `dispatch.enabled`: independent gate for ACP session turn dispatch (default: `true`). Set `false` to keep ACP commands available while blocking execution. - `backend`: default ACP runtime backend id (must match a registered ACP runtime plugin). - `defaultAgent`: fallback ACP target agent id when spawns do not specify an explicit target. - `allowedAgents`: allowlist of agent ids permitted for ACP runtime sessions; empty means no additional restriction. - `maxConcurrentSessions`: maximum concurrently active ACP sessions. - `stream.coalesceIdleMs`: idle flush window in ms for streamed text. - `stream.maxChunkChars`: maximum chunk size before splitting streamed block projection. - `stream.repeatSuppression`: suppress repeated status/tool lines per turn (default: `true`). - `stream.deliveryMode`: `"live"` streams incrementally; `"final_only"` buffers until turn terminal events. - `stream.hiddenBoundarySeparator`: separator before visible text after hidden tool events (default: `"paragraph"`). - `stream.maxOutputChars`: maximum assistant output characters projected per ACP turn. - `stream.maxSessionUpdateChars`: maximum characters for projected ACP status/update lines. - `stream.tagVisibility`: record of tag names to boolean visibility overrides for streamed events. - `runtime.ttlMinutes`: idle TTL in minutes for ACP session workers before eligible cleanup. - `runtime.installCommand`: optional install command to run when bootstrapping an ACP runtime environment. --- ## CLI ```json5 { cli: { banner: { taglineMode: "off", // random | default | off }, }, } ``` - `cli.banner.taglineMode` controls banner tagline style: - `"random"` (default): rotating funny/seasonal taglines. - `"default"`: fixed neutral tagline (`All your chats, one OpenClaw.`). - `"off"`: no tagline text (banner title/version still shown). - To hide the entire banner (not just taglines), set env `OPENCLAW_HIDE_BANNER=1`. --- ## Wizard Metadata written by CLI guided setup flows (`onboard`, `configure`, `doctor`): ```json5 { wizard: { lastRunAt: "2026-01-01T00:00:00.000Z", lastRunVersion: "2026.1.4", lastRunCommit: "abc1234", lastRunCommand: "configure", lastRunMode: "local", }, } ``` --- ## Identity See `agents.list` identity fields under [Agent defaults](/gateway/config-agents#agent-defaults). --- ## Bridge (legacy, removed) Current builds no longer include the TCP bridge. Nodes connect over the Gateway WebSocket. `bridge.*` keys are no longer part of the config schema (validation fails until removed; `openclaw doctor --fix` can strip unknown keys). ```json { "bridge": { "enabled": true, "port": 18790, "bind": "tailnet", "tls": { "enabled": true, "autoGenerate": true } } } ``` --- ## Cron ```json5 { cron: { enabled: true, maxConcurrentRuns: 2, webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs webhookToken: "replace-with-dedicated-token", // optional bearer token for outbound webhook auth sessionRetention: "24h", // duration string or false runLog: { maxBytes: "2mb", // default 2_000_000 bytes keepLines: 2000, // default 2000 }, }, } ``` - `sessionRetention`: how long to keep completed isolated cron run sessions before pruning from `sessions.json`. Also controls cleanup of archived deleted cron transcripts. Default: `24h`; set `false` to disable. - `runLog.maxBytes`: max size per run log file (`cron/runs/.jsonl`) before pruning. Default: `2_000_000` bytes. - `runLog.keepLines`: newest lines retained when run-log pruning is triggered. Default: `2000`. - `webhookToken`: bearer token used for cron webhook POST delivery (`delivery.mode = "webhook"`), if omitted no auth header is sent. - `webhook`: deprecated legacy fallback webhook URL (http/https) used only for stored jobs that still have `notify: true`. ### `cron.retry` ```json5 { cron: { retry: { maxAttempts: 3, backoffMs: [30000, 60000, 300000], retryOn: ["rate_limit", "overloaded", "network", "timeout", "server_error"], }, }, } ``` - `maxAttempts`: maximum retries for one-shot jobs on transient errors (default: `3`; range: `0`–`10`). - `backoffMs`: array of backoff delays in ms for each retry attempt (default: `[30000, 60000, 300000]`; 1–10 entries). - `retryOn`: error types that trigger retries — `"rate_limit"`, `"overloaded"`, `"network"`, `"timeout"`, `"server_error"`. Omit to retry all transient types. Applies only to one-shot cron jobs. Recurring jobs use separate failure handling. ### `cron.failureAlert` ```json5 { cron: { failureAlert: { enabled: false, after: 3, cooldownMs: 3600000, mode: "announce", accountId: "main", }, }, } ``` - `enabled`: enable failure alerts for cron jobs (default: `false`). - `after`: consecutive failures before an alert fires (positive integer, min: `1`). - `cooldownMs`: minimum milliseconds between repeated alerts for the same job (non-negative integer). - `mode`: delivery mode — `"announce"` sends via a channel message; `"webhook"` posts to the configured webhook. - `accountId`: optional account or channel id to scope alert delivery. ### `cron.failureDestination` ```json5 { cron: { failureDestination: { mode: "announce", channel: "last", to: "channel:C1234567890", accountId: "main", }, }, } ``` - Default destination for cron failure notifications across all jobs. - `mode`: `"announce"` or `"webhook"`; defaults to `"announce"` when enough target data exists. - `channel`: channel override for announce delivery. `"last"` reuses the last known delivery channel. - `to`: explicit announce target or webhook URL. Required for webhook mode. - `accountId`: optional account override for delivery. - Per-job `delivery.failureDestination` overrides this global default. - When neither global nor per-job failure destination is set, jobs that already deliver via `announce` fall back to that primary announce target on failure. - `delivery.failureDestination` is only supported for `sessionTarget="isolated"` jobs unless the job's primary `delivery.mode` is `"webhook"`. See [Cron Jobs](/automation/cron-jobs). Isolated cron executions are tracked as [background tasks](/automation/tasks). --- ## Media model template variables Template placeholders expanded in `tools.media.models[].args`: | Variable | Description | | ------------------ | ------------------------------------------------- | | `{{Body}}` | Full inbound message body | | `{{RawBody}}` | Raw body (no history/sender wrappers) | | `{{BodyStripped}}` | Body with group mentions stripped | | `{{From}}` | Sender identifier | | `{{To}}` | Destination identifier | | `{{MessageSid}}` | Channel message id | | `{{SessionId}}` | Current session UUID | | `{{IsNewSession}}` | `"true"` when new session created | | `{{MediaUrl}}` | Inbound media pseudo-URL | | `{{MediaPath}}` | Local media path | | `{{MediaType}}` | Media type (image/audio/document/…) | | `{{Transcript}}` | Audio transcript | | `{{Prompt}}` | Resolved media prompt for CLI entries | | `{{MaxChars}}` | Resolved max output chars for CLI entries | | `{{ChatType}}` | `"direct"` or `"group"` | | `{{GroupSubject}}` | Group subject (best effort) | | `{{GroupMembers}}` | Group members preview (best effort) | | `{{SenderName}}` | Sender display name (best effort) | | `{{SenderE164}}` | Sender phone number (best effort) | | `{{Provider}}` | Provider hint (whatsapp, telegram, discord, etc.) | --- ## Config includes (`$include`) Split config into multiple files: ```json5 // ~/.openclaw/openclaw.json { gateway: { port: 18789 }, agents: { $include: "./agents.json5" }, broadcast: { $include: ["./clients/mueller.json5", "./clients/schmidt.json5"], }, } ``` **Merge behavior:** - Single file: replaces the containing object. - Array of files: deep-merged in order (later overrides earlier). - Sibling keys: merged after includes (override included values). - Nested includes: up to 10 levels deep. - Paths: resolved relative to the including file, but must stay inside the top-level config directory (`dirname` of `openclaw.json`). Absolute/`../` forms are allowed only when they still resolve inside that boundary. - OpenClaw-owned writes that change only one top-level section backed by a single-file include write through to that included file. For example, `plugins install` updates `plugins: { $include: "./plugins.json5" }` in `plugins.json5` and leaves `openclaw.json` intact. - Root includes, include arrays, and includes with sibling overrides are read-only for OpenClaw-owned writes; those writes fail closed instead of flattening the config. - Errors: clear messages for missing files, parse errors, and circular includes. --- _Related: [Configuration](/gateway/configuration) · [Configuration Examples](/gateway/configuration-examples) · [Doctor](/gateway/doctor)_ ## Related - [Configuration](/gateway/configuration) - [Configuration examples](/gateway/configuration-examples)