docs: OpenRouter routing, security controls, tool guards

ober

53d2ece033da3847103d4da2385ed87a45c8e59a

diff --git a/docs/cli.md b/docs/cli.md
index 2989d3f..ec40e4f 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -146,6 +146,7 @@ Available in the REPL and TUI.
 | `/plan` | Enter PLAN mode (read-only; write tools hidden). |
 | `/build` | Enter BUILD mode (read + write). |
 | `/mode` | Show the current mode. |
+| `/security` | Show the current security posture: mode, disabled tools, MCP status, and sandbox state. |
 | `/doit [on\|off\|status\|max N\|max unlimited]` | Local-only auto-continue mode. When enabled on an `ollama*` or `mlx*` provider, jcode automatically sends a continuation prompt after each stopped assistant turn until the model marks the task done or blocked. |
 | `/clear` | Start a new session. |
 | `/sessions` | List saved sessions. |
@@ -174,6 +175,8 @@ Available in the REPL and TUI.
 | Command | Effect |
 |---|---|
 | `/tools` | List available tools. |
+| `/tool off <name>` | Disable a tool for the current session. Hidden from the model and rejected at execute time. |
+| `/tool on <name>` | Re-enable a previously disabled tool. |
 | `/agents` | List named sub-agent roles for the `task` tool (built-ins + `jcode.json` variants). |
 | `/mcp` | Toggle MCP tools on/off. |
 | `/plugins` | List loaded plugins. |
diff --git a/docs/providers.md b/docs/providers.md
index 522a352..57a90d7 100644
--- a/docs/providers.md
+++ b/docs/providers.md
@@ -161,6 +161,95 @@ every OpenAI-compatible endpoint:
   reuse reduces latency. For explicit compatibility testing, set
   `providers.ollama.wire` to `"openai"`.
 
+### OpenRouter routing (auto-router, fallbacks, price caps)
+
+When the active provider is `openrouter`, `jcode` passes through provider/model
+`options` and applies a hard `max_price` guardrail so routing knobs that live
+only in the OpenRouter API become usable from config.
+
+Config lives under `providers.openrouter` (provider-level) and
+`providers.openrouter.models.<MODEL>` (per-model overrides). `options` is a
+free-form object that is deep-merged into the request body, so any field the
+OpenRouter `chat/completions` endpoint accepts can be set there:
+
+```json
+{
+  "providers": {
+    "openrouter": {
+      "options": {
+        "route": "auto",
+        "provider": {
+          "data_collection": "deny",
+          "sort": "price"
+        }
+      }
+    }
+  }
+}
+```
+
+Common knobs:
+
+| Field | Meaning |
+|---|---|
+| `route: "auto"` | Use OpenRouter's auto-router instead of a concrete model. |
+| `provider.data_collection: "deny"` | Refuse providers that train on request data. |
+| `provider.sort: "price"` | Prefer the cheapest adequate provider. |
+| `provider.fallbacks: [...]` | Ordered list of providers to try on failure. |
+
+For non-OpenRouter providers, passthrough is **opt-in** via
+`providers.<NAME>.options_passthrough: true` to avoid leaking unsupported fields
+to arbitrary endpoints.
+
+`max_price` is a fail-closed guardrail under `providers.openrouter.max_price`.
+It is merged into the request's `provider` object automatically:
+
+```json
+{
+  "providers": {
+    "openrouter": {
+      "max_price": {
+        "prompt": 0.00001,
+        "completion": 0.00005,
+        "image": 0.00010,
+        "request": 0.005
+      }
+    }
+  }
+}
+```
+
+OpenRouter rejects the request if the cheapest candidate exceeds the cap, so
+this is a server-side guarantee rather than client-side accounting.
+
+### Routed-model surfacing
+
+When the provider returns a `model` field, `jcode` stores it as the
+conversation's routed model (`(current-routed-model)`) and surfaces it in TUI
+status and trace logs. This matters when the request targets a router such as
+`openrouter/auto-beta` and the actual serving model is chosen server-side.
+
+Model capability metadata is also looked up from config under
+`providers.<NAME>.models.<MODEL>.tool_call` and `.context_window`, falling back
+to built-in heuristics only when no card is configured. This lets new models be
+added without code changes:
+
+```json
+{
+  "providers": {
+    "openrouter": {
+      "models": {
+        "moonshotai/kimi-k3": {
+          "tool_call": true,
+          "context_window": 256000,
+          "output_limit": 32000
+        }
+      }
+    }
+  }
+}
+```
+
 ### Grok CLI session
 
 `grok` is a separate provider from `xai` even though both surface Grok models.
diff --git a/docs/threat-model.md b/docs/threat-model.md
index cc65312..7503dbf 100644
--- a/docs/threat-model.md
+++ b/docs/threat-model.md
@@ -38,6 +38,16 @@ production use.
   tools avoid logging full shell commands, prompts, or commit messages.
 - Tool execution is routed through registry checks, mode checks, and permission
   decisions. Write tools enforce path checks before filesystem mutation.
+- Runtime tool ACL (`/tool off/on`) and PLAN/BUILD mode let the user shrink the
+  tool surface during a session. Disabled tools are hidden from the model and
+  rejected at execute time.
+- A duplicate-call loop breaker tracks identical tool calls within a turn:
+  it warns after 3 repetitions and blocks the same call after 5, preventing
+  runaway loops from burning context and tokens.
+- Tool results are scanned for common secret shapes (API keys such as `sk-`,
+  `AIza`, `ghp_`, Slack tokens, etc.) and replaced with a redaction notice
+  before being returned to the model or logged, reducing the risk of leaking
+  credentials across the provider boundary.
 - The key store encrypts stored provider keys and writes with owner-only file
   permissions.
 - Remote server and relay tokens are generated locally, stored outside the
diff --git a/docs/tools.md b/docs/tools.md
index a41c182..0d7c7b8 100644
--- a/docs/tools.md
+++ b/docs/tools.md
@@ -206,12 +206,21 @@ keys. Every other provider's credentials plus `~/.ssh`, `~/.aws`, `~/.gnupg`,
 when the chosen CLI is `claude`, `~/.grok` is denied; when the chosen CLI is
 `grok`, `~/.claude` is denied; no CLI can read another's bearer token.
 
-### 4. Generated-artifact protection
+### 4. Generated-artifact and Scheme-source protection
 
 The write tools refuse to clobber build output: paths ending in `.sls` or `.so`,
 and anything under `lib/`, are rejected — those are compiled from `src/**/*.ss`
 and editing them by hand is always a mistake.
 
+Additionally, native write tools (`write`, `edit`, `edit_block`, `multi-edit`,
+`patch`, `apply_patch`) reject edits to `.ss` and `.sls` Scheme source files.
+Jerboa source must be modified through the **jerboa-mcp balanced tools**
+(`jerboa_balanced_replace`, `jerboa_balanced_insert`, `jerboa_write_file`), which
+check delimiter balance before writing. This prevents the paren-imbalance bugs
+that are easy to introduce with unrestricted text edits. The `verified` tool can
+still edit Scheme source inside its own `write_scope` because it runs through
+the same balanced-tool layer.
+
 ### Checkpoints
 
 After any successful write-tool call, `registry.ss` snapshots the filesystem