diff --git a/docs/automation/cron-jobs.md b/docs/automation/cron-jobs.md
index 6857de41dcba..355861454d28 100644
--- a/docs/automation/cron-jobs.md
+++ b/docs/automation/cron-jobs.md
@@ -690,7 +690,7 @@ Why this shape is safer:
- `allowedAgentIds` prevents this hook endpoint from selecting another agent. If the Gateway serves other hook workflows, include only their intended agent ids too.
- `scope: "session"` gives each Gmail message its own sandbox; `workspaceAccess: "none"` keeps the host agent workspace out of that sandbox.
- `allow: ["session_status"]` is an absolute per-agent clamp, so global `tools.alsoAllow` additions cannot leak into the reader. The minimal profile and explicit deny list make the intended boundary auditable.
-- `deliver: false` keeps completion inside the hook flow. To announce a summary externally after validating the reader, set `deliver: true` and add an explicit `channel` and `to`. Keep agent-to-agent handoff disabled unless you deliberately expose the exact coordination tool and pair it with a narrow [`tools.agentToAgent`](/gateway/config-tools#toolsagenttoagent) policy.
+- `deliver: false` keeps completion inside the hook flow. To announce a summary externally after validating the reader, set `deliver: true` and add an explicit `channel` and `to`. Keep agent-to-agent handoff disabled unless you deliberately expose the exact coordination tool and pair it with a narrow [`tools.agentToAgent`](/gateway/config-tools#tools-agenttoagent) policy.
Tool policies can only become more restrictive as global, provider, agent, and sandbox rules are combined. The per-agent allowlist cannot restore `session_status` if an earlier policy removed it. Ensure inherited policies retain `session_status`; an empty effective tool set aborts before the model sees the email.
@@ -719,7 +719,7 @@ This writes `hooks.gmail` transport settings, enables the Gmail preset, preserve
The built-in Gmail preset's per-message session separates conversation context; it does not restrict the target agent's tools or workspace. Without a custom mapping that sets `agentId`, Gmail hooks run as the default agent.
-For untrusted inboxes, route the hook to a dedicated reader agent, give that agent read-only or no workspace access, and deny filesystem-write, shell, browser, and other unnecessary tools. If it needs to notify the main agent, expose only the required coordination tool and constrain its targets with `tools.agentToAgent`. See [Prompt injection](/gateway/security#prompt-injection), [Multi-agent sandbox and tools](/tools/multi-agent-sandbox-tools), and [`tools.agentToAgent`](/gateway/config-tools#toolsagenttoagent).
+For untrusted inboxes, route the hook to a dedicated reader agent, give that agent read-only or no workspace access, and deny filesystem-write, shell, browser, and other unnecessary tools. If it needs to notify the main agent, expose only the required coordination tool and constrain its targets with `tools.agentToAgent`. See [Prompt injection](/gateway/security#prompt-injection), [Multi-agent sandbox and tools](/tools/multi-agent-sandbox-tools), and [`tools.agentToAgent`](/gateway/config-tools#tools-agenttoagent).
### Verify the reader boundary
diff --git a/docs/channels/groups.md b/docs/channels/groups.md
index a6b223994f9e..b9eea4daae5f 100644
--- a/docs/channels/groups.md
+++ b/docs/channels/groups.md
@@ -214,7 +214,7 @@ Related:
- Configuration keys and defaults: [Gateway configuration](/gateway/config-agents#agentsdefaultssandbox)
- Debugging why a tool is blocked: [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)
-- Bind mounts details: [Sandboxing](/gateway/sandboxing#custom-bind-mounts)
+- Bind mounts details: [Sandboxing](/gateway/sandboxing#multiple-folders-for-one-agent)
## Display labels
diff --git a/docs/channels/msteams.md b/docs/channels/msteams.md
index d2e1bb6e4a18..16bf65de6d66 100644
--- a/docs/channels/msteams.md
+++ b/docs/channels/msteams.md
@@ -736,7 +736,7 @@ Key settings (see [/gateway/configuration](/gateway/configuration) for shared ch
- `channels.msteams.streaming.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
- `channels.msteams.mediaAllowHosts`: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains: Graph, SharePoint/OneDrive, Teams CDN, Bot Framework, Azure Media Services).
- `channels.msteams.mediaAuthAllowHosts`: allowlist for attaching Authorization headers on media retries (defaults to Graph + Bot Framework hosts).
-- `channels.msteams.graphMediaFallback`: opt into Graph message lookups when channel/group HTML omits file markers (default `false`; see [Channel/group file recovery](#channelgroup-file-recovery-graphmediafallback)).
+- `channels.msteams.graphMediaFallback`: opt into Graph message lookups when channel/group HTML omits file markers (default `false`; see [Channel/group file recovery](/channels/msteams#channel%2Fgroup-file-recovery-graphmediafallback)).
- `channels.msteams.mediaMaxMb`: per-channel media size limit override in MB. Falls back to `agents.defaults.mediaMaxMb` when unset.
- `channels.msteams.requireMention`: require @mention in channels/groups (default `true`).
- `channels.msteams.replyStyle`: `thread | top-level` (see [Reply style](#reply-style-threads-vs-posts)).
diff --git a/docs/channels/qqbot.md b/docs/channels/qqbot.md
index a495a5731826..11ec46b1f2c4 100644
--- a/docs/channels/qqbot.md
+++ b/docs/channels/qqbot.md
@@ -210,16 +210,16 @@ group, then mention it or configure the group to run without a mention.
`groups["*"]` sets defaults for every group; a concrete `groups.GROUP_OPENID`
entry overrides those defaults for one group. Group settings:
-| Field | Default | Description |
-| --------------------- | ---------------- | -------------------------------------------------------------------------------------------------- |
-| `requireMention` | `true` | Require an `@`-mention before the bot replies. |
-| `commandLevel` | `all` | Which built-in slash commands can run in the group (see below). |
-| `ignoreOtherMentions` | `false` | Drop messages that mention someone else but not the bot. |
-| `historyLimit` | `50` | Recent non-mention messages kept as context for the next mentioned turn. `0` disables history. |
-| `tools` | — | Allow/deny tools for the whole group. |
-| `toolsBySender` | — | Per-sender tool overrides; see [Groups](/channels/groups#groupchannel-tool-restrictions-optional). |
-| `name` | openid prefix | Friendly label used in logs and group context. |
-| `prompt` | built-in default | Per-group behavior prompt appended to the agent context. |
+| Field | Default | Description |
+| --------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- |
+| `requireMention` | `true` | Require an `@`-mention before the bot replies. |
+| `commandLevel` | `all` | Which built-in slash commands can run in the group (see below). |
+| `ignoreOtherMentions` | `false` | Drop messages that mention someone else but not the bot. |
+| `historyLimit` | `50` | Recent non-mention messages kept as context for the next mentioned turn. `0` disables history. |
+| `tools` | — | Allow/deny tools for the whole group. |
+| `toolsBySender` | — | Per-sender tool overrides; see [Groups](/channels/groups#group%2Fchannel-tool-restrictions-optional). |
+| `name` | openid prefix | Friendly label used in logs and group context. |
+| `prompt` | built-in default | Per-group behavior prompt appended to the agent context. |
`commandLevel` accepts:
diff --git a/docs/cli/agents.md b/docs/cli/agents.md
index 37b9867d667a..f54e4597f130 100644
--- a/docs/cli/agents.md
+++ b/docs/cli/agents.md
@@ -76,7 +76,7 @@ Options: `--force`, `--json`.
Use routing bindings to pin inbound channel traffic to a specific agent.
-If you also want different visible skills per agent, configure `agents.defaults.skills` and `agents.entries.*.skills` in `openclaw.json`. See [Skills config](/tools/skills-config) and [Configuration reference](/gateway/config-agents#agentsdefaultsskills).
+If you also want different visible skills per agent, configure `agents.defaults.skills` and `agents.entries.*.skills` in `openclaw.json`. See [Skills config](/tools/skills-config) and [Configuration reference](/gateway/config-agents#agents-defaults-skills).
List bindings:
diff --git a/docs/cli/connect.md b/docs/cli/connect.md
index 97d83e137dd5..3154fdc338a7 100644
--- a/docs/cli/connect.md
+++ b/docs/cli/connect.md
@@ -85,7 +85,7 @@ A join code and a paired device have separate lifecycles:
- Burning or expiring a join code prevents another enrollment with that code.
- It does not disconnect or remove a node that already redeemed it.
- To revoke an enrolled machine, remove its paired device with
- [`openclaw devices remove `](/cli/devices#openclaw-devices-remove-deviceid).
+ [`openclaw devices remove `](/cli/devices#openclaw-devices-remove-%3Cdeviceid%3E).
## Troubleshooting
diff --git a/docs/concepts/model-failover.md b/docs/concepts/model-failover.md
index 8726f9078842..984ee8bda484 100644
--- a/docs/concepts/model-failover.md
+++ b/docs/concepts/model-failover.md
@@ -295,7 +295,7 @@ OpenClaw builds the candidate list from the currently requested `provider/model`
- explicit aborts that are not timeout/failover-shaped
- context overflow errors that should stay inside compaction/retry logic (for example `request_too_large`, `input token count exceeds the maximum number of input tokens`, `input exceeds the maximum number of tokens`, `input too long for the model`, or `ollama error: context length exceeded`)
- a final unknown error when there are no candidates left
- - Claude Fable 5 safety refusals; direct API-key requests handle those at the provider level via Anthropic's server-side fallback to `claude-opus-4-8` instead (see [Anthropic](/providers/anthropic#safety-refusal-fallback-claude-fable-5))
+ - Claude Fable 5 safety refusals; direct API-key requests handle those at the provider level via Anthropic's server-side fallback to `claude-opus-4-8` instead (see [Anthropic](/providers/anthropic#safety-refusal-fallback-claude-opus-5-and-fable-5))
diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md
index db155a215a16..4aeafb6a4a51 100644
--- a/docs/concepts/model-providers.md
+++ b/docs/concepts/model-providers.md
@@ -164,7 +164,7 @@ Claude CLI reuse (`claude -p`) is a sanctioned OpenClaw integration path. Anthro
- `agents.defaults.models["openai/"].params.transport` and `params.serviceTier` are authored embedded-provider request settings. They keep implicit runtime selection on OpenClaw; native Codex owns its app-server transport and service tier.
- Valid model-scoped `params.fastMode` / `params.fast_mode` values and valid cutoff keys are portable typed agent-runtime controls. They do not count as authored provider request params and do not select a runtime. Pin `agentRuntime.id: "openclaw"` or `agentRuntime.id: "codex"` when a recipe depends on one runtime.
- Hidden OpenClaw attribution headers (`originator`, `version`, `User-Agent`) are only attached on native Codex traffic to `chatgpt.com/backend-api`, not generic OpenAI-compatible proxies
-- The shared `/fast` toggle, configured defaults, and valid model-scoped Fast params resolve through one runtime-control policy. See [Thinking levels](/tools/thinking#fast-mode-fast) for precedence.
+- The shared `/fast` toggle, configured defaults, and valid model-scoped Fast params resolve through one runtime-control policy. See [Thinking levels](/tools/thinking#fast-mode-%2Ffast) for precedence.
- OpenAI API Fast mode is premium-priced and model-specific. GPT-5.6 Sol currently costs 2× Standard token pricing, and long-context multipliers stack. ChatGPT/Codex-credit Fast mode is separate: GPT-5.6 and GPT-5.5 currently consume 2.5× Standard credits, while API-key Codex runs use API token pricing. See [Fast mode](https://openai.com/api-priority-processing/), [API pricing](https://developers.openai.com/api/docs/pricing), and [Codex speed](https://learn.chatgpt.com/docs/agent-configuration/speed).
- The native Codex catalog can expose exact `openai/gpt-5.6-sol`, `openai/gpt-5.6-terra`, and `openai/gpt-5.6-luna` refs according to account access. It does not apply the direct API's bare `gpt-5.6` alias client-side.
- `openai/gpt-5.5` uses the Codex catalog native `contextWindow = 400000` and default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai.models[].contextTokens`
diff --git a/docs/concepts/progress-drafts.md b/docs/concepts/progress-drafts.md
index f8e1d83e2336..683e1cd24dd1 100644
--- a/docs/concepts/progress-drafts.md
+++ b/docs/concepts/progress-drafts.md
@@ -278,7 +278,7 @@ enabling `streaming.progress.commentary` hands preambles to the interleaved
commentary lane instead.
On Discord, when a utility model resolves for the agent — an explicit
-[`utilityModel`](/gateway/config-agents#utilitymodel), or the primary
+[`utilityModel`](/gateway/config-agents#agents-defaults-model), or the primary
provider's declared small-model default (OpenAI → `gpt-5.6-luna`,
Anthropic → `claude-haiku-4-5`) — it supplies a short plain-language filler
when the model emits no preamble or has been quiet for about 20 seconds
diff --git a/docs/concepts/qa-e2e-automation.md b/docs/concepts/qa-e2e-automation.md
index 22ef49be2455..1e46475a55d9 100644
--- a/docs/concepts/qa-e2e-automation.md
+++ b/docs/concepts/qa-e2e-automation.md
@@ -1346,11 +1346,11 @@ candidate refs are replaced with neutral labels such as `candidate-01`; the
report maps rankings back to real refs after parsing.
Candidate runs default to `high` thinking, with `medium` for GPT-5.6 Luna and
-`xhigh` for older OpenAI eval refs that support it. Override a specific
-candidate inline with `--model provider/model,thinking=`; inline
-options also support `fast`, `no-fast`, and `fast=`. `--thinking
-` still sets a global fallback, and the older `--model-thinking
-` form is kept for compatibility. OpenAI candidate
+`xhigh` for older OpenAI eval refs that support it. Override a specific candidate
+inline with `--model provider/model,thinking=`; inline options also support
+`fast`, `no-fast`, and `fast=`. `--thinking ` still sets a global
+fallback, and the older `--model-thinking ` form is kept for
+compatibility. OpenAI candidate
refs default to fast mode so priority processing is used where the provider
supports it. Pass `--fast` only when you want to force fast mode on for
every candidate model. Candidate and judge durations are recorded in the
diff --git a/docs/gateway/config-agents.md b/docs/gateway/config-agents.md
index 548db189abb3..e374d0406e9e 100644
--- a/docs/gateway/config-agents.md
+++ b/docs/gateway/config-agents.md
@@ -378,7 +378,7 @@ date context. Falls back to the host timezone.
- `model`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
- String form sets only the primary model.
- Object form sets primary plus ordered failover models.
-- `utilityModel`: optional `provider/model` ref or alias for short internal tasks. It currently powers generated Control UI session titles, Telegram DM topic titles, Discord auto-thread titles, and [progress-draft narration](/concepts/progress-drafts#narrated-status). When unset, OpenClaw derives the primary provider's declared small-model default when one exists (OpenAI → `gpt-5.6-luna`, Anthropic → `claude-haiku-4-5`); title tasks otherwise use the agent's primary model, and narration stays off. If a distinct utility model cannot prepare or complete a generated title, OpenClaw retries that title once with the primary model. For dashboard titles, automatic utility derivation and the regular fallback use the effective session provider and auth profile; an explicit utility model keeps its configured provider/auth. Set `utilityModel: ""` to skip the alternate utility route; dashboard title generation still proceeds directly to the regular session model. `agents.entries.*.utilityModel` overrides the default, and an operation-specific model override wins over both. Utility tasks make separate model calls and send task-specific content to the selected model provider. Dashboard title generation sends at most the first 1,000 characters of the first non-command message; narration sends the inbound request plus compact redacted tool summaries. Choose a provider that matches your cost and data-handling requirements.
+- `utilityModel`: optional `provider/model` ref or alias for short internal tasks. It currently powers generated Control UI session titles, Telegram DM topic titles, Discord auto-thread titles, and [progress-draft narration](/concepts/progress-drafts#status-headline). When unset, OpenClaw derives the primary provider's declared small-model default when one exists (OpenAI → `gpt-5.6-luna`, Anthropic → `claude-haiku-4-5`); title tasks otherwise use the agent's primary model, and narration stays off. If a distinct utility model cannot prepare or complete a generated title, OpenClaw retries that title once with the primary model. For dashboard titles, automatic utility derivation and the regular fallback use the effective session provider and auth profile; an explicit utility model keeps its configured provider/auth. Set `utilityModel: ""` to skip the alternate utility route; dashboard title generation still proceeds directly to the regular session model. `agents.entries.*.utilityModel` overrides the default, and an operation-specific model override wins over both. Utility tasks make separate model calls and send task-specific content to the selected model provider. Dashboard title generation sends at most the first 1,000 characters of the first non-command message; narration sends the inbound request plus compact redacted tool summaries. Choose a provider that matches your cost and data-handling requirements.
- `imageModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
- Used by the `image` tool path as its vision-model config when the active model cannot accept images. Native-vision models receive loaded image bytes directly instead.
- Also used as fallback routing when the selected/default model cannot accept image input.
diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md
index 2db7154be596..94bd978a85d9 100644
--- a/docs/gateway/configuration-reference.md
+++ b/docs/gateway/configuration-reference.md
@@ -1110,7 +1110,7 @@ Validation and safety notes:
- The built-in Gmail preset uses `sessionKey: "hook:gmail:{{messages[0].id}}"`.
- This per-message key isolates conversation context, not tools or workspace access. Without a custom mapping that sets `agentId`, the preset uses the default agent.
-- For untrusted inboxes, route Gmail to a dedicated reader agent and restrict that agent with [per-agent sandbox and tool policy](/tools/multi-agent-sandbox-tools). If the reader must notify the main agent, constrain the handoff with [`tools.agentToAgent`](/gateway/config-tools#toolsagenttoagent). See [Prompt injection](/gateway/security#prompt-injection) for the recommended threat model and model tier.
+- For untrusted inboxes, route Gmail to a dedicated reader agent and restrict that agent with [per-agent sandbox and tool policy](/tools/multi-agent-sandbox-tools). If the reader must notify the main agent, constrain the handoff with [`tools.agentToAgent`](/gateway/config-tools#tools-agenttoagent). See [Prompt injection](/gateway/security#prompt-injection) for the recommended threat model and model tier.
- The setup wizard configures Gmail transport but does not create the reader agent or required session-key policy. Apply the complete [restricted Gmail reader configuration](/automation/cron-jobs#configure-a-restricted-gmail-reader-recommended) before running setup for untrusted mail.
- 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.
diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md
index 3c27f109cf8d..01a7c0284a56 100644
--- a/docs/gateway/security/index.md
+++ b/docs/gateway/security/index.md
@@ -245,7 +245,7 @@ What helps in practice:
- Limit high-risk tools (`exec`, `browser`, `web_fetch`, `web_search`) to trusted agents or explicit allowlists.
- If you allowlist interpreters (`python`, `node`, `ruby`, `perl`, `php`, `lua`, `osascript`), enable `tools.exec.strictInlineEval` so inline eval forms (`-c`, `-e`, and similar) still need explicit approval. In allowlist mode, any heredoc segment (`<<`) always requires reviewer or explicit approval, regardless of quoting - an allowlisted command cannot use a heredoc body to bypass allowlist review.
- Reduce blast radius by using a read-only or tool-disabled **reader agent** to summarize untrusted content, then pass the summary to your main agent.
-- For Gmail hooks, the built-in per-message session isolates conversation context but does not remove the target agent's tool or workspace permissions. Route untrusted mail to a dedicated reader agent, apply [per-agent sandbox and tool restrictions](/tools/multi-agent-sandbox-tools), and constrain any handoff to the main agent with [`tools.agentToAgent`](/gateway/config-tools#toolsagenttoagent). See [Gmail integration](/gateway/configuration-reference#gmail-integration).
+- For Gmail hooks, the built-in per-message session isolates conversation context but does not remove the target agent's tool or workspace permissions. Route untrusted mail to a dedicated reader agent, apply [per-agent sandbox and tool restrictions](/tools/multi-agent-sandbox-tools), and constrain any handoff to the main agent with [`tools.agentToAgent`](/gateway/config-tools#tools-agenttoagent). See [Gmail integration](/gateway/configuration-reference#gmail-integration).
- Keep `web_search` / `web_fetch` / `browser` off for tool-enabled agents unless needed.
- For OpenResponses URL inputs (`input_file` / `input_image`), set a tight `gateway.http.endpoints.responses.files.urlAllowlist` / `images.urlAllowlist` and keep `maxUrlParts` low (empty allowlists count as unset). Use `files.allowUrl: false` / `images.allowUrl: false` to disable URL fetching entirely.
- Keep secrets out of prompts; pass them via env/config on the gateway host instead.
@@ -741,7 +741,7 @@ The Control UI needs a secure context (HTTPS or localhost) to generate device id
## Deployment and host trust
- Full-disk encryption on the gateway host; prefer a dedicated OS user account for the Gateway if the host is shared.
-- Published package dependency lock: source checkouts use `pnpm-lock.yaml`; the published `openclaw` npm package and OpenClaw-owned npm plugin packages include `npm-shrinkwrap.json` so installs use the reviewed transitive dependency graph from the release instead of resolving a fresh graph at install time. This is a supply-chain hardening and release reproducibility boundary, not a sandbox - see [npm shrinkwrap](/gateway/security/shrinkwrap).
+- Published package dependency lock: source checkouts use `pnpm-lock.yaml`; the published `openclaw` npm package and OpenClaw-owned npm plugin packages include `npm-shrinkwrap.json` so installs use the reviewed transitive dependency graph from the release instead of resolving a fresh graph at install time. This is a supply-chain hardening and release reproducibility boundary, not a sandbox - see [npm shrinkwrap](/gateway/security/dependency-locking).
- Secure file operations: OpenClaw uses `@openclaw/fs-safe` for root-bounded file access, atomic writes, archive extraction, temp workspaces, and secret-file helpers. Optional native acceleration defaults **off**; set `OPENCLAW_FS_SAFE_NATIVE_MODE=auto` to use an installed platform binding or `require` to fail closed when native support is unavailable. Details: [Secure file operations](/gateway/security/secure-file-operations).
- Shared Slack workspace risk: if everyone in Slack can message the bot, the core risk is delegated tool authority - any allowed sender can induce tool calls (`exec`, browser, network/file tools) within the agent's policy, prompt/content injection from one sender can affect shared state/devices/outputs, and if the shared agent has sensitive credentials/files, any allowed sender can potentially drive exfiltration via tool usage. Use separate agents/gateways with minimal tools for team workflows; keep personal-data agents private.
- Company-shared agent (acceptable pattern): fine when everyone using the agent is in the same trust boundary (for example one company team) and the agent is strictly business-scoped. Run it on a dedicated machine/VM/container, use a dedicated OS user + dedicated browser/profile/accounts, and do not sign that runtime into personal Apple/Google accounts or personal password-manager/browser profiles. Mixing personal and company identities on the same runtime collapses the separation and increases personal-data exposure risk.
diff --git a/docs/gateway/stable-https-url.md b/docs/gateway/stable-https-url.md
index 13b04f976161..ddca942708b7 100644
--- a/docs/gateway/stable-https-url.md
+++ b/docs/gateway/stable-https-url.md
@@ -11,7 +11,7 @@ Tailscale Serve gives your Gateway one HTTPS URL without exposing the Gateway po
The result is `https://..ts.net`, reachable from permitted devices on your tailnet and not from the public internet. The matching WebSocket URL is `wss://..ts.net`.
-If you need a public URL, use [Tailscale Funnel](/gateway/tailscale#public-internet-funnel-shared-password) instead. Funnel is public, and OpenClaw requires password auth for it.
+If you need a public URL, use [Tailscale Funnel](/gateway/tailscale#public-internet-funnel-%2B-shared-password) instead. Funnel is public, and OpenClaw requires password auth for it.
## Before you begin
diff --git a/docs/help/faq.md b/docs/help/faq.md
index 68d0251ed14c..bcf13195f98e 100644
--- a/docs/help/faq.md
+++ b/docs/help/faq.md
@@ -405,7 +405,7 @@ First-run Q&A - install, onboard, auth routes, subscriptions, initial failures -
OpenClaw validates bind sources against both the normalized path and the canonical path resolved through the deepest existing ancestor, so symlink-parent escapes fail closed even when the final path segment does not exist yet.
- See [Sandboxing](/gateway/sandboxing#custom-bind-mounts) and [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated#bind-mounts-security-quick-check).
+ See [Sandboxing](/gateway/sandboxing#multiple-folders-for-one-agent) and [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated#bind-mounts-security-quick-check).
diff --git a/docs/help/testing.md b/docs/help/testing.md
index 890bd9978c02..2773d94e6c01 100644
--- a/docs/help/testing.md
+++ b/docs/help/testing.md
@@ -420,7 +420,7 @@ gh workflow run package-acceptance.yml --ref main \
homeserver. Source-checkout only - packaged installs do not ship
`qa-lab`.
- Full CLI, profile/scenario catalog, env vars, and artifact layout:
- [Matrix smoke lanes](/concepts/qa-e2e-automation#matrix-smoke-lanes).
+ [Matrix smoke lanes](/concepts/qa-e2e-automation#matrix-live-lane).
- `pnpm openclaw qa telegram`
- Runs the Telegram live QA lane against a real private group using the
driver and SUT bot tokens from env.
@@ -495,7 +495,7 @@ table through the Mantis GitHub App when `pr_number` is set.
Live transport lanes share one standard contract so new transports do not
drift; the per-lane coverage matrix lives in
-[QA overview - Live transport coverage](/concepts/qa-e2e-automation#live-transport-coverage).
+[QA overview - Live transport coverage](/concepts/qa-e2e-automation#buzz%2C-discord%2C-slack%2C-telegram%2C-and-whatsapp-qa-reference).
`qa-channel` is the broad synthetic suite and is not part of that matrix.
### Shared Telegram credentials via Convex (v1)
diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md
index 6e611f7240a4..593a2faca7cf 100644
--- a/docs/help/troubleshooting.md
+++ b/docs/help/troubleshooting.md
@@ -147,7 +147,7 @@ openclaw plugins inspect --runtime --json
openclaw plugins enable
```
-Reference: [Operator install policy](/tools/skills-config#operator-install-policy-securityinstallpolicy)
+Reference: [Operator install policy](/tools/skills-config#operator-install-policy-security-installpolicy)
## Plugin present but blocked by suspicious ownership
diff --git a/docs/install/docker.md b/docs/install/docker.md
index 56019b087937..eb452c2464f9 100644
--- a/docs/install/docker.md
+++ b/docs/install/docker.md
@@ -200,7 +200,7 @@ Optional variables accepted by `scripts/docker/setup.sh` (and, for the gateway c
| `OPENCLAW_SANDBOX` | Opt in to sandbox bootstrap (`1`, `true`, `yes`, `on`) |
| `OPENCLAW_SKIP_ONBOARDING` | Skip the interactive onboarding step (`1`, `true`, `yes`, `on`) |
| `OPENCLAW_DOCKER_SOCKET` | Override the Docker socket path |
-| `OPENCLAW_DISABLE_BONJOUR` | Force Bonjour/mDNS advertising on (`0`) or off (`1`); see [Bonjour / mDNS](#bonjour--mdns) |
+| `OPENCLAW_DISABLE_BONJOUR` | Force Bonjour/mDNS advertising on (`0`) or off (`1`); see [Bonjour / mDNS](/install/docker#bonjour-%2F-mdns) |
| `OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS` | Disable bundled plugin source bind-mount overlays |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Shared OTLP/HTTP collector endpoint for OpenTelemetry export |
| `OTEL_EXPORTER_OTLP_*_ENDPOINT` | Signal-specific OTLP endpoints for traces, metrics, or logs |
diff --git a/docs/platforms/mac/bundled-gateway.md b/docs/platforms/mac/bundled-gateway.md
index 83cc64e2cc00..829890220878 100644
--- a/docs/platforms/mac/bundled-gateway.md
+++ b/docs/platforms/mac/bundled-gateway.md
@@ -87,7 +87,7 @@ Logging:
- If the host loops with repeated `EADDRINUSE` or fast restarts, check for
duplicate `ai.openclaw.gateway` / `ai.openclaw.node` LaunchAgents and the
launchd-marker workaround in
- [Gateway troubleshooting](/gateway/troubleshooting#macos-launchd-supervisor-loop-with-duplicate-gatewaynode-launchagents).
+ [Gateway troubleshooting](/gateway/troubleshooting#macos-launchd-supervisor-loop-with-duplicate-gateway%2Fnode-launchagents).
## Version compatibility
diff --git a/docs/plugins/codex-computer-use.md b/docs/plugins/codex-computer-use.md
index 956605115fa7..d63d8fc38d51 100644
--- a/docs/plugins/codex-computer-use.md
+++ b/docs/plugins/codex-computer-use.md
@@ -327,7 +327,7 @@ tools when available, and the specific message for the failing setup step.
This Codex-owned Computer Use path runs on macOS, where the MCP server may need
local OS permissions before it can inspect or control apps. (For cross-platform
desktop control on Windows and Linux node hosts, see the
-[cua-computer fulfiller](/nodes/computer-use#windows-and-linux-experimental-via-cua-driver).)
+[cua-computer fulfiller](/nodes/computer-use#windows-and-linux-experimental%2C-via-cua-driver-sdk).)
If OpenClaw says Computer Use is installed but the MCP server is unavailable,
verify the Codex-side Computer Use setup first:
diff --git a/docs/plugins/copilot.md b/docs/plugins/copilot.md
index 6f0dfbe87a7c..130ff230cd5c 100755
--- a/docs/plugins/copilot.md
+++ b/docs/plugins/copilot.md
@@ -14,7 +14,7 @@ agent loop: native tool execution, native compaction (`infiniteSessions`), and
CLI-managed thread state under `copilotHome`. OpenClaw still owns chat
channels, session files, model selection, dynamic tools (bridged), approvals,
media delivery, the visible transcript mirror, `/btw` side questions (see
-[Side questions (`/btw`)](#side-questions-btw)), and `openclaw doctor`.
+[Side questions (`/btw`)](/plugins/copilot#side-questions-%2Fbtw)), and `openclaw doctor`.
For the broader model/provider/runtime split, start with
[Agent runtimes](/concepts/agent-runtimes).
diff --git a/docs/plugins/hooks.md b/docs/plugins/hooks.md
index c980cd0e07a6..dfb0521f45d2 100644
--- a/docs/plugins/hooks.md
+++ b/docs/plugins/hooks.md
@@ -1047,7 +1047,7 @@ before the next major release:
- **Plaintext channel envelopes** in `inbound_claim` and `message_received`
handlers. Read `BodyForAgent` and the structured user-context blocks
instead of parsing flat envelope text. See
- [Plaintext channel envelopes → BodyForAgent](/plugins/sdk-migration#active-deprecations).
+ [Plaintext channel envelopes → BodyForAgent](/plugins/sdk-migration#removal-timeline).
- **`subagent_spawning`** remains for compatibility with older plugins, but
new plugins should not return thread routing from it. Core prepares
`thread: true` subagent bindings through channel session-binding adapters
@@ -1063,7 +1063,7 @@ before the next major release:
For the full list - memory capability registration, provider thinking
profile, external auth providers, provider discovery types, task runtime
accessors, and the `command-auth` → `command-status` rename - see
-[Plugin SDK migration → Active deprecations](/plugins/sdk-migration#active-deprecations).
+[Plugin SDK migration → Active deprecations](/plugins/sdk-migration#removal-timeline).
## Related
diff --git a/docs/plugins/memory-lancedb.md b/docs/plugins/memory-lancedb.md
index e167343f9089..647a0f1b96f8 100644
--- a/docs/plugins/memory-lancedb.md
+++ b/docs/plugins/memory-lancedb.md
@@ -191,17 +191,17 @@ local server returns context-length errors.
## Recall and capture limits
-| Setting | Default | Range | Applies to |
-| ----------------- | ------- | ---------------------------- | ---------------------------------------------------------- |
-| `recallMaxChars` | `1000` | 100-10000 | Text sent to the embedding API for recall. |
-| `captureMaxChars` | `500` | 100-10000 | Message length eligible for auto-capture. |
-| `customTriggers` | `[]` | 0-50 items, each <=100 chars | Literal phrases that make auto-capture consider a message. |
+| Setting | Default | Range | Applies to |
+| ----------------- | ------- | --------------------------- | ---------------------------------------------------------- |
+| `recallMaxChars` | `1000` | 100-10000 | Text sent to the embedding API for recall. |
+| `captureMaxChars` | `500` | 100-10000 | Message length eligible for auto-capture. |
+| `customTriggers` | `[]` | 0-50 items, each ≤100 chars | Literal phrases that make auto-capture consider a message. |
`recallMaxChars` bounds the `before_prompt_build` auto-recall query, the
-`memory_recall` tool, the `memory_forget` query path, and `openclaw ltm
-search`. Auto-recall embeds the latest user message from the turn and falls
-back to the full prompt only when no user message is present, keeping channel
-metadata and large prompt blocks out of the embedding request.
+`memory_recall` tool, the `memory_forget` query path, and `openclaw ltm search`.
+Auto-recall embeds the latest user message from the turn and falls back to the
+full prompt only when no user message is present, keeping channel metadata and
+large prompt blocks out of the embedding request.
`captureMaxChars` gates whether a user message from the turn's `agent_end`
event is short enough to be considered for auto-capture; it does not affect
diff --git a/docs/plugins/sdk-entrypoints.md b/docs/plugins/sdk-entrypoints.md
index f1acc53edc92..3aec3d32cd7c 100644
--- a/docs/plugins/sdk-entrypoints.md
+++ b/docs/plugins/sdk-entrypoints.md
@@ -157,7 +157,7 @@ export default definePluginEntry({
`resolveCreateSession({ agentId })` must return a config-derived model/runtime
target before OpenClaw advertises creation or calls `startTerminalSession`.
Use
- [`api.runtime.agent.resolveSessionCatalogCreateTarget(...)`](/plugins/sdk-runtime#apiruntimeagent)
+ [`api.runtime.agent.resolveSessionCatalogCreateTarget(...)`](/plugins/sdk-runtime#api-runtime-agent)
to apply the host's runtime and model-allowlist policy instead of duplicating
it.
diff --git a/docs/providers/openai.md b/docs/providers/openai.md
index e69470f7340f..5dcc2154ad34 100644
--- a/docs/providers/openai.md
+++ b/docs/providers/openai.md
@@ -1398,7 +1398,7 @@ not declared Codex-compatible.
global default, per-model `params.fastMode`, then off. `/fast default`
clears only the session layer. `/status` reports the resolved OpenClaw
policy and runtime, not the upstream service tier actually honored or
- returned. See [Thinking levels](/tools/thinking#fast-mode-fast) and
+ returned. See [Thinking levels](/tools/thinking#fast-mode-%2Ffast) and
[Codex harness](/plugins/codex-harness#shared-fast-mode-and-codex-fast-mode).
diff --git a/docs/reference/memory-config.md b/docs/reference/memory-config.md
index 80a55bb83e6c..eb905d59f139 100644
--- a/docs/reference/memory-config.md
+++ b/docs/reference/memory-config.md
@@ -469,7 +469,7 @@ when you intentionally want both representations.
Ordinary model-invoked session transcript search obeys
-[`tools.sessions.visibility`](/gateway/config-tools#toolssessions). The default
+[`tools.sessions.visibility`](/gateway/config-tools#tools-sessions). The default
`tree` visibility exposes the current session, sessions it spawned, and
same-agent group sessions watched through ambient group awareness. Other
unrelated sessions require `agent` visibility (or `all` only when cross-agent
diff --git a/docs/reference/session-management-compaction.md b/docs/reference/session-management-compaction.md
index 3e5d1ad2a50f..f9fe3da59a65 100644
--- a/docs/reference/session-management-compaction.md
+++ b/docs/reference/session-management-compaction.md
@@ -274,7 +274,7 @@ OpenClaw supports "silent" turns for background tasks where the user should not
Before auto-compaction happens, OpenClaw can run a silent agentic turn that writes durable state to disk (for example `memory/YYYY-MM-DD.md` in the agent workspace) so compaction cannot erase critical context. It monitors session context usage, and once it crosses a soft threshold below the compaction threshold, it sends a silent "write memory now" directive using the exact silent token `NO_REPLY` / `no_reply` so the user sees nothing.
-Config (`agents.defaults.compaction.memoryFlush`), full reference at [/gateway/config-agents](/gateway/config-agents#agentsdefaultscompaction):
+Config (`agents.defaults.compaction.memoryFlush`), full reference at [/gateway/config-agents](/gateway/config-agents#agents-defaults-compaction):
| Key | Default | Notes |
| --------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
diff --git a/docs/releases/2026.6.11.md b/docs/releases/2026.6.11.md
index a8781d74c4a2..43c0255a8348 100644
--- a/docs/releases/2026.6.11.md
+++ b/docs/releases/2026.6.11.md
@@ -84,7 +84,7 @@ Across [Telegram](/channels/telegram), [WhatsApp](/channels/whatsapp), [Matrix](
[Model selection](/providers/models) and setup recover more clearly when catalogs, credentials, streams, timeouts, compaction, or fallbacks go wrong. Affected [OpenAI](/providers/openai), [OpenRouter](/providers/openrouter), and [OpenCode Go](/providers/opencode-go) configurations now report or recover from failures more consistently instead of leaving users with a stale choice or a stalled request.
-Follow-up fixes for [fast mode](/tools/thinking#fast-mode-fast) improve its behavior in affected provider and fallback paths; automatic fast mode itself is not new in this release.
+Follow-up fixes for [fast mode](/tools/thinking#fast-mode-%2Ffast) improve its behavior in affected provider and fallback paths; automatic fast mode itself is not new in this release.
diff --git a/docs/start/onboarding-redesign.md b/docs/start/onboarding-redesign.md
index 673a5cb1915e..bedd6fadc809 100644
--- a/docs/start/onboarding-redesign.md
+++ b/docs/start/onboarding-redesign.md
@@ -37,8 +37,8 @@ Design principles (decided, do not relitigate casually):
- **The hatch is a ceremony**: same thread, avatar swap, the agent names itself
and picks its own face. The custodian teaches the hierarchy once: "ask me
about the system, or just ask your agent — it relays."
-- **Trust is tiered by source**: official catalog entries may be pre-selected;
- third-party ClawHub skills are never pre-selected regardless of model
+- **Trust is tiered by source**: official catalog entries may be preselected;
+ third-party ClawHub skills are never preselected regardless of model
ranking, and their labels say they install the publisher's code.
- **Configured installs are sacred**: re-running onboarding is a verification
pass. It never re-applies setup and never restarts the Gateway service.
@@ -113,7 +113,7 @@ Remote-gateway onboarding keeps its legacy conversational handoff
explicit cap is passed).
- **Supply-chain guard**: ClawHub listing text is publisher-controlled and
reaches the matcher prompt, so a listing can promote itself to
- "recommended". Only official catalog entries may be pre-selected; ClawHub
+ "recommended". Only official catalog entries may be preselected; ClawHub
skills always require an explicit tick and are labeled "third-party ClawHub
skill; installs its publisher's code".
- Node command `device.apps` (TS node-host, Android envelope parity), sharing
@@ -324,7 +324,7 @@ restart` from the real environment and verify the plist. Product follow-up:
- Magical scan with kill switch, not consent-first (phase 1; persistent output
discloses model and ClawHub use before scanning, and the results note repeats it).
- Full vertical including the node `device.apps` command (phase 1).
-- Third-party ClawHub skills are never pre-selected and are labeled as
+- Third-party ClawHub skills are never preselected and are labeled as
installing the publisher's code; official entries may be pre-checked
(phase 1, shipped security posture).
- Two access cards, not three; consent front-loaded into the choice (phase 2).
diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md
index 4a32fef55435..a4a703c618e5 100644
--- a/docs/tools/exec-approvals.md
+++ b/docs/tools/exec-approvals.md
@@ -58,7 +58,7 @@ Exec approvals are enforced locally on the execution host:
| `openclaw exec-policy set` / `preset` | Synchronize the local requested policy with the local host approvals document in one step. |
-Per-session `/exec` overrides are not included. Run `/exec` in the relevant session to inspect its current defaults. See [session overrides](/tools/exec#session-overrides-exec).
+Per-session `/exec` overrides are not included. Run `/exec` in the relevant session to inspect its current defaults. See [session overrides](/tools/exec#session-overrides-%2Fexec).
Full CLI reference (flags, JSON output, allowlist add/remove): [Approvals CLI](/cli/approvals).
@@ -305,18 +305,15 @@ 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
-`.
+`openclaw approvals set --gateway` or
+`openclaw approvals set --node `.
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 --security
- --ask --ask-fallback
-` with any subset of those flags.
+To set individual fields instead of a full preset, use `openclaw exec-policy set --host --security --ask --ask-fallback ` with any subset of those flags.
### Node host
diff --git a/docs/tools/loop-detection.md b/docs/tools/loop-detection.md
index 15e7eaf00ba3..0967c1355940 100644
--- a/docs/tools/loop-detection.md
+++ b/docs/tools/loop-detection.md
@@ -152,7 +152,7 @@ spend and lockups while preserving normal tool access.
Spawning isolated agents to bound runaway behavior.
-
+
Full `tools.loopDetection` schema and merging semantics.
diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md
index bb70b83fbc6e..0164b10020bf 100644
--- a/docs/tools/plugin.md
+++ b/docs/tools/plugin.md
@@ -154,7 +154,7 @@ deprecated `--dangerously-force-unsafe-install` flag is accepted for
compatibility but is a no-op: it does not bypass install policy or
`before_install` hook blocks.
-See [Skills config](/tools/skills-config#operator-install-policy-securityinstallpolicy)
+See [Skills config](/tools/skills-config#operator-install-policy-security-installpolicy)
for the shared `security.installPolicy` exec schema used by both skills and
plugins.
diff --git a/docs/tools/screen.md b/docs/tools/screen.md
index 362ce6cff68a..06126052ad6f 100644
--- a/docs/tools/screen.md
+++ b/docs/tools/screen.md
@@ -46,5 +46,5 @@ panels.
## Related
- [Control UI](/web/control-ui)
-- [Gateway protocol](/gateway/protocol#method-families)
+- [Gateway protocol](/gateway/protocol#rpc-method-families)
- [Browser tool](/tools/browser)
diff --git a/scripts/codespell-ignore.txt b/scripts/codespell-ignore.txt
index bbd84a4bff23..a760fcaaa888 100644
--- a/scripts/codespell-ignore.txt
+++ b/scripts/codespell-ignore.txt
@@ -15,3 +15,4 @@ wit
allowIn
planText
nwe
+smoe
diff --git a/scripts/docs-link-audit.mts b/scripts/docs-link-audit.mts
index a6233df5d346..fb5a839a425e 100644
--- a/scripts/docs-link-audit.mts
+++ b/scripts/docs-link-audit.mts
@@ -608,6 +608,16 @@ export function prepareAnchorAuditDocsDir(sourceDir = DOCS_DIR) {
fs.rmSync(path.join(tempDir, entry.name), { recursive: true, force: true });
}
+ for (const filePath of walk(tempDir).filter((entry) => /\.mdx?$/iu.test(entry))) {
+ const raw = fs.readFileSync(filePath, "utf8");
+ const normalized = raw.replace(//gu, (comment) =>
+ comment.replace(/[^\r\n]/gu, ""),
+ );
+ if (normalized !== raw) {
+ fs.writeFileSync(filePath, normalized, "utf8");
+ }
+ }
+
const docsJsonPath = path.join(tempDir, "docs.json");
const docsConfig = JSON.parse(fs.readFileSync(docsJsonPath, "utf8"));
const sanitized = sanitizeDocsConfigForEnglishOnly(docsConfig);
diff --git a/src/scripts/docs-link-audit.test.ts b/src/scripts/docs-link-audit.test.ts
index 80886d89d0d1..9d7f25292705 100644
--- a/src/scripts/docs-link-audit.test.ts
+++ b/src/scripts/docs-link-audit.test.ts
@@ -254,13 +254,31 @@ describe("docs-link-audit", () => {
)}\n`,
"utf8",
);
- fs.writeFileSync(path.join(docsRoot, "help", "testing.md"), "# testing\n", "utf8");
+ fs.writeFileSync(
+ path.join(docsRoot, "help", "testing.md"),
+ [
+ "# testing",
+ "",
+ "visible text",
+ "",
+ "",
+ ].join("\n"),
+ "utf8",
+ );
fs.writeFileSync(path.join(docsRoot, "zh-CN", "help", "testing.md"), "# 测试\n", "utf8");
const anchorDocsDir = prepareAnchorAuditDocsDir(docsRoot);
try {
expect(fs.existsSync(path.join(anchorDocsDir, "help", "testing.md"))).toBe(true);
expect(fs.existsSync(path.join(anchorDocsDir, "zh-CN"))).toBe(false);
+ const preparedMarkdown = fs.readFileSync(
+ path.join(anchorDocsDir, "help", "testing.md"),
+ "utf8",
+ );
+ expect(preparedMarkdown).not.toContain("