## File: README.md

# Context Mode

**The other half of the context problem.**

[](https://www.npmjs.com/package/context-mode) [](https://www.npmjs.com/package/context-mode) [](https://github.com/mksglu/context-mode) [](https://github.com/mksglu/context-mode/stargazers) [](https://github.com/mksglu/context-mode/network/members) [](https://github.com/mksglu/context-mode/commits) [](LICENSE)
[](https://discord.gg/DCN9jUgN5v)
[](https://news.ycombinator.com/item?id=47193064)



## The Problem

Every MCP tool call dumps raw data into your context window. A Playwright snapshot costs 56 KB. Twenty GitHub issues cost 59 KB. One access log — 45 KB. After 30 minutes, 40% of your context is gone. And when the agent compacts the conversation to free space, it forgets which files it was editing, what tasks are in progress, and what you last asked for. On top of that, the agent wastes output tokens on filler, pleasantries, and verbose explanations — burning context from both sides.

### How Context Mode Solves It

Context Mode is an MCP server that solves all four sides of this problem:

1. **Context Saving** — Sandbox tools keep raw data out of the context window. 315 KB becomes 5.4 KB. 98% reduction.
2. **Session Continuity** — Every file edit, git operation, task, error, and user decision is tracked in SQLite. When the conversation compacts, context-mode doesn't dump this data back into context — it indexes events into FTS5 and retrieves only what's relevant via BM25 search. The model picks up exactly where you left off. If you don't `--continue`, previous session data is deleted immediately — a fresh session means a clean slate.
3. **Think in Code** — The LLM should program the analysis, not compute it. Instead of reading 50 files into context to count functions, the agent writes a script that does the counting and `console.log()`s only the result. One script replaces ten tool calls and saves 100x context. This is a mandatory paradigm across all 17 supported clients, plus the OpenClaw gateway integration: stop treating the LLM as a data processor, treat it as a code generator.

   ```js
   // Before: 47 × Read() = 700 KB.  After: 1 × ctx_execute() = 3.6 KB.
   ctx_execute("javascript", `
     const files = fs.readdirSync('src').filter(f => f.endsWith('.ts'));
     files.forEach(f => console.log(f + ': ' + fs.readFileSync('src/'+f,'utf8').split('\\n').length + ' lines'));
   `);
   ```
4. **No prose-style enforcement** — context-mode keeps raw data out of context but never dictates how the model writes its final answer. Brevity, completeness, formatting — your model's call (or yours via your own `CLAUDE.md` / `AGENTS.md`). Aggressive brevity prompts have been shown to degrade coding/reasoning benchmarks ([Moonshot AI on `kimi-k2.5`](https://github.com/anomalyco/opencode/issues/20258)) — the routing block stays focused on *where data goes*, not on *how the model talks*.

<a href="https://www.youtube.com/watch?v=QUHrntlfPo4">
  
    
  
</a>


## Install

Platforms are grouped by install complexity. Hook-capable platforms get automatic routing enforcement. Non-hook platforms need a one-time routing file copy.


**Claude Code** — plugin marketplace, fully automatic

**Prerequisites:** Claude Code v1.0.33+ (`claude --version`). If `/plugin` is not recognized, update first: `brew upgrade claude-code` or `npm update -g @anthropic-ai/claude-code`.

**Install:**

```bash
/plugin marketplace add mksglu/context-mode
/plugin install context-mode@context-mode
```

Restart Claude Code (or run `/reload-plugins`).

**Verify:**

```
/context-mode:ctx-doctor
```

All checks should show `[x]`. The doctor validates runtimes, hooks, FTS5, and plugin registration.

**Routing:** Automatic. The SessionStart hook injects routing instructions at runtime — no file is written to your project. The plugin registers all hooks (PreToolUse, PostToolUse, UserPromptSubmit, PreCompact, SessionStart, Stop) and 11 MCP tools — six sandbox tools (`ctx_batch_execute`, `ctx_execute`, `ctx_execute_file`, `ctx_index`, `ctx_search`, `ctx_fetch_and_index`) plus five meta-tools (`ctx_stats`, `ctx_doctor`, `ctx_upgrade`, `ctx_purge`, `ctx_insight`).

| Slash Command | What it does |
|---|---|
| `/context-mode:ctx-stats` | Context savings — per-tool breakdown, tokens consumed, savings ratio. |
| `/context-mode:ctx-doctor` | Diagnostics — runtimes, hooks, FTS5, plugin registration, versions. |
| `/context-mode:ctx-index` | Index a local file or directory into the persistent FTS5 knowledge base. |
| `/context-mode:ctx-search` | Search previously indexed content. |
| `/context-mode:ctx-upgrade` | Pull latest, rebuild, migrate cache, fix hooks. |
| `/context-mode:ctx-purge` | Permanently delete all indexed content from the knowledge base. |
| `/context-mode:ctx-insight` | Opens the hosted Insight dashboard ([context-mode.com/insight](https://context-mode.com/insight)) in your browser — org analytics for AI-assisted engineering teams. |

> **Note:** Slash commands are a Claude Code plugin feature. On other platforms, type `ctx stats`, `ctx doctor`, `ctx index`, `ctx search`, `ctx upgrade`, or `ctx insight` in the chat — the model calls the MCP tool automatically. See [Utility Commands](#utility-commands).

**Status line (optional):** Claude Code's plugin manifest cannot declare a status line, so this is a one-time manual edit to `~/.claude/settings.json`:

```json
{
  "statusLine": {
    "type": "command",
    "command": "context-mode statusline"
  }
}
```

After saving, restart Claude Code. The bar shows `$ saved this session · $ saved across sessions · % efficient` so you can see savings accumulate in real time. The wiring is path-free — `context-mode statusline` resolves through the bundled CLI regardless of where the plugin cache lives.


Alternative — MCP-only install (no hooks or slash commands)

```bash
claude mcp add context-mode -- npx -y context-mode
```

This gives you all 11 MCP tools without automatic routing. The model can still use them — it just won't be nudged to prefer them over raw Bash/Read/WebFetch. Good for trying it out before committing to the full plugin.






**Gemini CLI** — one config file, hooks included

**Prerequisites:** Node.js >= 22.5 (or Bun), Gemini CLI installed.

**Install:**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Add the following to `~/.gemini/settings.json`. This single file registers the MCP server and all four hooks:

   ```json
   {
     "mcpServers": {
       "context-mode": {
         "command": "context-mode"
       }
     },
     "hooks": {
       "BeforeTool": [
         {
           "matcher": "run_shell_command|read_file|read_many_files|grep_search|search_file_content|web_fetch|activate_skill|mcp__plugin_context-mode|mcp__context-mode|mcp__(?!.*context-mode)",
           "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli beforetool" }]
         }
       ],
       "AfterTool": [
         {
           "matcher": "",
           "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli aftertool" }]
         }
       ],
       "PreCompress": [
         {
           "matcher": "",
           "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli precompress" }]
         }
       ],
       "SessionStart": [
         {
           "matcher": "",
           "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli sessionstart" }]
         }
       ]
     }
   }
   ```

3. Restart Gemini CLI.

**Verify:**

```
/mcp list
```

You should see `context-mode: ... - Connected`.

**Routing:** Automatic via SessionStart hook. Optionally copy routing instructions for full model awareness:

```bash
cp node_modules/context-mode/configs/gemini-cli/GEMINI.md ./GEMINI.md
```

> **Why the BeforeTool matcher?** It targets only tools that produce large output (`run_shell_command`, `read_file`, `read_many_files`, `grep_search`, `search_file_content`, `web_fetch`, `activate_skill`) plus context-mode's own tools (`mcp__plugin_context-mode`). This avoids unnecessary hook overhead on lightweight tools while intercepting every tool that could flood your context window.

Full config reference: [`configs/gemini-cli/settings.json`](configs/gemini-cli/settings.json)




**VS Code Copilot** — hooks with SessionStart

**Prerequisites:** Node.js >= 22.5 (or Bun), VS Code with Copilot Chat v0.32+.

**Install:**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Create `.vscode/mcp.json` in your project root:

   ```json
   {
     "servers": {
       "context-mode": {
         "command": "context-mode"
       }
     }
   }
   ```

3. Create `.github/hooks/context-mode.json`:

   ```json
   {
     "hooks": {
       "PreToolUse": [
         { "type": "command", "command": "context-mode hook vscode-copilot pretooluse" }
       ],
       "PostToolUse": [
         { "type": "command", "command": "context-mode hook vscode-copilot posttooluse" }
       ],
       "SessionStart": [
         { "type": "command", "command": "context-mode hook vscode-copilot sessionstart" }
       ]
     }
   }
   ```

4. Restart VS Code.

**Verify:** Open Copilot Chat and type `ctx stats`. Context-mode tools should appear and respond.

**Routing:** Automatic via SessionStart hook. Optionally copy routing instructions for full model awareness:

```bash
cp node_modules/context-mode/configs/vscode-copilot/copilot-instructions.md .github/copilot-instructions.md
```

Full hook config including PreCompact: [`configs/vscode-copilot/hooks.json`](configs/vscode-copilot/hooks.json)




**JetBrains Copilot** — hooks with SessionStart

**Prerequisites:** Node.js >= 22.5 (or Bun), JetBrains IDE with GitHub Copilot plugin v1.5.57+.

**Install:**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Add MCP server via Settings UI: **Settings > Tools > AI Assistant > Model Context Protocol (MCP) > Add Server**:
   - **Name:** `context-mode`
   - **Command:** `context-mode`

3. Create `.github/hooks/context-mode.json`:

   ```json
   {
     "hooks": {
       "PreToolUse": [
         { "type": "command", "command": "context-mode hook jetbrains-copilot pretooluse" }
       ],
       "PostToolUse": [
         { "type": "command", "command": "context-mode hook jetbrains-copilot posttooluse" }
       ],
       "SessionStart": [
         { "type": "command", "command": "context-mode hook jetbrains-copilot sessionstart" }
       ]
     }
   }
   ```

4. Restart the JetBrains IDE.

**Verify:** Open Copilot Chat and type `ctx stats`. Context-mode tools should appear and respond.

**Routing:** Automatic via SessionStart hook. Optionally copy routing instructions for full model awareness:

```bash
cp node_modules/context-mode/configs/jetbrains-copilot/copilot-instructions.md .github/copilot-instructions.md
```

Full hook config including PreCompact: [`configs/jetbrains-copilot/hooks.json`](configs/jetbrains-copilot/hooks.json)

Full setup guide: [`docs/jetbrains-copilot.md`](docs/jetbrains-copilot.md)




**GitHub Copilot CLI** — MCP + hooks

**Prerequisites:** Node.js >= 22.5 (or Bun), GitHub Copilot CLI (`copilot`) installed. Set `COPILOT_HOME` first if you use an isolated Copilot home.

**Install — Option A (plugin, one command — recommended):**

```bash
npm install context-mode                                     # the plugin's MCP server runs the global binary
copilot plugin install mksglu/context-mode:configs/copilot-cli  # registers MCP + hooks + routing skill
```

The bundle's `.mcp.json` pins `CONTEXT_MODE_PLATFORM=copilot-cli`, so context-mode self-identifies as Copilot — `ctx_upgrade` and platform detection resolve `copilot-cli` even when Claude Code is co-installed (whose `~/.claude/` would otherwise win). No `context-mode upgrade` / agent call needed. To try it from a local clone before it lands on the default branch, point Copilot at the bundle directory: `copilot --plugin-dir /path/to/context-mode/configs/copilot-cli`.

**Install — Option B (manual, no plugin):**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Register the MCP server with Copilot CLI's built-in command (writes `~/.copilot/mcp-config.json` for you):

   ```bash
   copilot mcp add context-mode -- context-mode
   ```

3. Configure hooks in `~/.copilot/hooks/context-mode.json` (or `$COPILOT_HOME/hooks/context-mode.json`). The config uses flat `{ "type": "command", "command": "..." }` entries; context-mode also writes a top-level `"version": 1`, but that field is **optional** — the Copilot CLI accepts hook configs that omit it (it is pinned only for self-documentation). Copilot CLI fires six events context-mode uses:

   ```json
   {
     "version": 1,
     "hooks": {
      "preToolUse":          [{ "type": "command", "command": "context-mode hook copilot-cli pretooluse" }],
      "postToolUse":         [{ "type": "command", "command": "context-mode hook copilot-cli posttooluse" }],
      "preCompact":          [{ "type": "command", "command": "context-mode hook copilot-cli precompact" }],
      "sessionStart":        [{ "type": "command", "command": "context-mode hook copilot-cli sessionstart" }],
      "userPromptSubmitted": [{ "type": "command", "command": "context-mode hook copilot-cli userpromptsubmit" }],
      "agentStop":           [{ "type": "command", "command": "context-mode hook copilot-cli stop" }]
     }
   }
   ```

   Or let context-mode write **this hooks file** for you: `context-mode upgrade` (run from a Copilot CLI context, or with `CONTEXT_MODE_PLATFORM=copilot-cli`). `upgrade` writes the **hooks file only** — register the MCP server with `copilot mcp add` in step 2.

4. Restart Copilot CLI.

> **Plugins:** Option A above uses Copilot CLI's plugin system, which registers MCP servers (`.mcp.json`), hooks (`hooks.json`), and skills (`skills/`) together — not just skills/agents. The shipped bundle is `configs/copilot-cli/`; `copilot plugin install owner/repo:path` installs it in one command (no clone). Option B is the equivalent without a plugin.

> **Version note:** the hook commands run the **global** `context-mode` (`context-mode hook copilot-cli …`), so they need a context-mode version with Copilot CLI support. On an older global the hooks are inert (no routing/capture) until you upgrade — but they do **not** block your tools (context-mode fails open). Upgrade with `npm install context-mode@latest`.

**Verify:** In a Copilot CLI session, type `ctx stats`. Context-mode tools should appear and respond. Run `context-mode doctor` to confirm hook + MCP registration.

**Routing:** Automatic via hooks (PreToolUse interception + SessionStart routing block). Auto-detected via MCP `clientInfo.name` (`GitHub Copilot CLI`) or, in a bare shell, a context-mode-written marker (`~/.copilot/mcp-config.json` or `~/.copilot/hooks/context-mode.json`) — not a bare `~/.copilot/` dir, so a co-installed-but-unconfigured Copilot CLI is not mis-detected as context-mode-on-copilot.

See [`docs/platform-support.md`](docs/platform-support.md#github-copilot-cli) for the full reference. Tracking: [#775](https://github.com/mksglu/context-mode/issues/775).




**Cursor** — hooks with stop support

**Prerequisites:** Node.js >= 22.5 (or Bun), Cursor with agent mode.

> **🚧 Work in progress** — the Marketplace plugin is **awaiting Cursor team review**. Until it's listed, install via the local-folder path described in Option A. Tracking in [#485](https://github.com/mksglu/context-mode/issues/485) / [#489](https://github.com/mksglu/context-mode/pull/489).

### Option A — Marketplace plugin (recommended once published)

After Cursor lists context-mode in the [Marketplace](https://cursor.com/marketplace), install with one click. The plugin auto-registers MCP, hooks (`preToolUse`, `postToolUse`, `sessionStart`, `stop`, `afterAgentResponse`), rules, and skills. No manual config required.

**Until then, use the local-folder path:**

**Windows (PowerShell)** — Cursor does not follow Windows symlinks/junctions, so use `robocopy`:

```powershell
git clone https://github.com/mksglu/context-mode.git
cd context-mode
robocopy . "$env:USERPROFILE\.cursor\plugins\local\context-mode" /MIR `
  /XD node_modules .git build web tests scripts .vscode `
  /XF *.log .gitignore *.bundle.mjs.map
```

**macOS / Linux:**

```bash
git clone https://github.com/mksglu/context-mode.git
ln -s "$PWD/context-mode" ~/.cursor/plugins/local/context-mode
```

Restart Cursor. The plugin appears in **Settings → Plugins** as "Context Mode (Local)". To pull updates, re-run the same `robocopy` / `ln -s` line.

> **Note:** if `.cursor/hooks.json` already contains context-mode entries from a prior `Option B` install, `context-mode doctor` will warn about duplicate hook firings. Remove one configuration to keep events single-fire.

### Option B — Manual install (existing path)

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Create `.cursor/mcp.json` in your project root (or `~/.cursor/mcp.json` for global):

   ```json
   {
     "mcpServers": {
       "context-mode": {
         "command": "context-mode"
       }
     }
   }
   ```

3. Create `.cursor/hooks.json` (or `~/.cursor/hooks.json` for global):

   ```json
   {
     "version": 1,
     "hooks": {
       "preToolUse": [
         {
           "command": "context-mode hook cursor pretooluse",
           "matcher": "Shell|Read|Grep|WebFetch|Task|MCP:ctx_execute|MCP:ctx_execute_file|MCP:ctx_batch_execute"
         }
       ],
       "postToolUse": [
         {
           "command": "context-mode hook cursor posttooluse"
         }
       ],
       "stop": [
         {
           "command": "context-mode hook cursor stop"
         }
       ]
     }
   }
   ```

   The `preToolUse` matcher is optional — without it, the hook fires on all tools. The `stop` hook fires when the agent turn ends and can send a followup message to continue the loop. `afterAgentResponse` is also available (fire-and-forget, receives full response text).

4. Copy the routing rules file. Cursor lacks a SessionStart hook, so the model needs a rules file for routing awareness:

   ```bash
   mkdir -p .cursor/rules
   cp node_modules/context-mode/configs/cursor/context-mode.mdc .cursor/rules/context-mode.mdc
   ```

5. Restart Cursor or open a new agent session.

**Verify:** Open Cursor Settings > MCP and confirm "context-mode" shows as connected. In agent chat, type `ctx stats`.

**Routing:** Hooks enforce routing programmatically via `preToolUse`/`postToolUse`/`stop`. The `.cursor/rules/context-mode.mdc` file provides routing instructions at session start since Cursor's `sessionStart` hook is currently rejected by their validator ([forum report](https://forum.cursor.com/t/unknown-hook-type-sessionstart/149566)). Project `.cursor/hooks.json` overrides `~/.cursor/hooks.json`.

**Known limitation:** Cursor accepts `additional_context` in hook responses but does not surface it to the model ([forum #155689](https://forum.cursor.com/t/native-posttooluse-hooks-accept-and-log-additional-context-successfully-but-the-injected-context-is-not-surfaced-to-the-model/155689)). Routing relies on the `.mdc` rules file instead of hook context injection.

Full configs: [`configs/cursor/hooks.json`](configs/cursor/hooks.json) | [`configs/cursor/mcp.json`](configs/cursor/mcp.json) | [`configs/cursor/context-mode.mdc`](configs/cursor/context-mode.mdc)




**OpenCode** — TypeScript plugin with hooks

**Prerequisites:** Node.js >= 22.5 (or Bun), OpenCode installed.

**Install:**

1. Add to `opencode.json` in your project root (or `~/.config/opencode/opencode.json` for global):

   ```json
   {
     "$schema": "https://opencode.ai/config.json",
     "plugin": ["context-mode"]
   }
   ```

   The `plugin` entry registers all 11 `ctx_*` tools natively and enables hooks — OpenCode calls context-mode's TypeScript plugin in-process, so there is no redundant stdio MCP child per session.

2. *(Optional)* Copy the routing rules file. The model needs an `AGENTS.md` file for routing awareness:

   ```bash
   cp node_modules/context-mode/configs/opencode/AGENTS.md AGENTS.md
   ```

   This tells the model which tools to use and which commands are blocked. Without it, hooks still enforce routing — but the model won't know *why* a command was denied.

3. Restart OpenCode.

**Verify:** In the OpenCode session, type `ctx stats`. Context-mode tools should appear and respond.

**Upgrade note:** If an existing config has BOTH `plugin: ["context-mode"]` AND `mcp.context-mode`, OpenCode will register zero `ctx_*` tools — the plugin path correctly suppresses MCP duplicates, but the legacy MCP entry confuses the loader. Run `context-mode upgrade` to remove the legacy `mcp.context-mode` entry; your other MCP servers are preserved. v1.0.140+ emits a stderr diagnostic with the same guidance when this happens.

**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`. The optional [`AGENTS.md`](configs/opencode/AGENTS.md) file provides routing instructions for model awareness. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts. The `experimental.chat.system.transform` hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).

> **Note:** OpenCode lacks a real SessionStart hook ([#14808](https://github.com/sst/opencode/issues/14808), [#5409](https://github.com/sst/opencode/issues/5409)). The plugin uses `experimental.chat.system.transform` as a surrogate — it injects both the routing block and resume snapshots into the system prompt. User-prompt capture uses `chat.message` instead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.

Full configs: [`configs/opencode/opencode.json`](configs/opencode/opencode.json) | [`configs/opencode/AGENTS.md`](configs/opencode/AGENTS.md)




**KiloCode** — TypeScript plugin with hooks

**Prerequisites:** Node.js >= 22.5 (or Bun), KiloCode installed.

**Install:**

1. Add to `kilo.json` in your project root (or `~/.config/kilo/kilo.json` for global):

   ```json
   {
     "$schema": "https://app.kilo.ai/config.json",
     "plugin": ["context-mode"]
   }
   ```

   The `plugin` entry registers all 11 `ctx_*` tools natively and enables hooks — KiloCode calls context-mode's TypeScript plugin in-process, so there is no redundant stdio MCP child per session.

2. *(Optional)* Copy the routing rules file. KiloCode shares the OpenCode plugin architecture, so the model needs an `AGENTS.md` file for routing awareness:

   ```bash
   cp node_modules/context-mode/configs/opencode/AGENTS.md AGENTS.md
   ```

3. Restart KiloCode.

**Verify:** In the KiloCode session, type `ctx stats`. Context-mode tools should appear and respond.

**Upgrade note:** If an existing config has BOTH `plugin: ["context-mode"]` AND `mcp.context-mode`, KiloCode will register zero `ctx_*` tools — the plugin path correctly suppresses MCP duplicates, but the legacy MCP entry confuses the loader. Run `context-mode upgrade` to remove the legacy `mcp.context-mode` entry; your other MCP servers are preserved. v1.0.140+ emits a stderr diagnostic with the same guidance when this happens.

**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`. The optional [`AGENTS.md`](configs/opencode/AGENTS.md) file provides routing instructions for model awareness. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts. The `experimental.chat.system.transform` hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).

> **Note:** KiloCode shares the same plugin architecture as OpenCode, using the OpenCodeAdapter with platform-specific configuration paths (`kilo.json` instead of `opencode.json`, `~/.config/kilo/` instead of `~/.config/opencode/`). Like OpenCode, it lacks a real SessionStart hook — the plugin uses `experimental.chat.system.transform` as a surrogate. User-prompt capture uses `chat.message` instead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.




**OpenClaw / Pi Agent** — native gateway plugin

**Prerequisites:** OpenClaw gateway running ([>2026.1.29](https://github.com/openclaw/openclaw/pull/9761)), Node.js 22+.

context-mode runs as a native [OpenClaw](https://github.com/openclaw) gateway plugin, targeting **Pi Agent** sessions (Read/Write/Edit/Bash tools). Unlike other platforms, there's no separate MCP server — the plugin registers directly into the gateway runtime via OpenClaw's [plugin API](https://docs.openclaw.ai/tools/plugin).

**Install:**

1. Clone and install:

   ```bash
   git clone https://github.com/mksglu/context-mode.git
   cd context-mode
   npm run install:openclaw
   ```

   The installer uses `$OPENCLAW_STATE_DIR` from your environment (default: `/openclaw`). To specify a custom path:

   ```bash
   npm run install:openclaw -- /path/to/openclaw-state
   ```

   Common locations: **Docker** — `/openclaw` (the default). **Local** — `~/.openclaw` or wherever you set `OPENCLAW_STATE_DIR`.

   The installer handles everything: `npm install`, `npm run build`, `better-sqlite3` native rebuild, extension registration in `runtime.json`, and gateway restart via SIGUSR1.

2. Open a Pi Agent session.

**Verify:** The plugin registers 8 hooks via [`api.on()`](https://docs.openclaw.ai/tools/plugin) (lifecycle) and [`api.registerHook()`](https://docs.openclaw.ai/tools/plugin) (commands). Type `ctx stats` to confirm tools are loaded.

**Routing:** Automatic. All tool interception, session tracking, and compaction recovery hooks activate automatically — no manual hook configuration or routing file needed.

> **Minimum version:** OpenClaw >2026.1.29 — this includes the `api.on()` lifecycle fix from [PR #9761](https://github.com/openclaw/openclaw/pull/9761). On older versions, lifecycle hooks silently fail. The adapter falls back to DB snapshot reconstruction (less precise but preserves critical state).

Full documentation: [`docs/adapters/openclaw.md`](docs/adapters/openclaw.md)




**Codex CLI** — MCP + hooks

**Prerequisites:** Node.js >= 22.5 (or Bun), Codex CLI installed.

**Install:**

1. Add the context-mode marketplace and install the plugin from Codex's plugin UI:

   ```bash
   codex plugin marketplace add mksglu/context-mode
   ```

2. Enable plugin-provided hooks while the Codex feature is still gated:

   ```toml
   [features]
   plugin_hooks = true
   hooks = true
   ```

   > **Feature flag note:** Current Codex builds expose hooks under `[features].hooks`
   > (or `codex --enable hooks`). Prefer `[features].hooks`; `[features].codex_hooks`
   > remains accepted as a legacy alias in current Codex builds. Bundled plugin hooks
   > additionally require `plugin_hooks` until Codex enables plugin hooks by default.

   **Custom storage location:** if Codex cannot write the adapter default storage directory, set
   `CONTEXT_MODE_DIR` to an absolute writable root in the environment that launches Codex. Sessions
   and stats use `<root>/sessions`; indexed content uses `<root>/content`.

   ```bash
   CONTEXT_MODE_DIR="$HOME/.codex-context-mode" codex
   ```

3. Restart Codex CLI and verify MCP with `ctx stats`.

   `ctx stats` proves the plugin MCP server is installed and reachable; it does
   not prove hooks are trusted or running.

4. Review and trust the context-mode plugin hooks if Codex prompts for hook
   approval. Plugin hooks are only active after both feature flags are enabled
   and Codex has accepted the hook commands.

The Codex plugin manifest provides MCP via `.codex-plugin/mcp.json`, skills via
`skills/`, and bundled hooks via `.codex-plugin/hooks.json`. No manual
`[mcp_servers.context-mode]` block or `$CODEX_HOME/hooks.json` is needed when
`plugin_hooks` is enabled and the plugin hooks are trusted.

> **Node/PATH note:** context-mode still needs `node` visible to the Codex process.
> The plugin removes manual Codex config, but it does not vendor Node or inherit
> login-shell PATH fixes automatically.

**Manual fallback for Codex builds without `plugin_hooks`:**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Add to `~/.codex/config.toml`:

   ```toml
   [features]
   hooks = true

   [mcp_servers.context-mode]
   command = "context-mode"

   [mcp_servers.context-mode.env]
   CONTEXT_MODE_PLATFORM = "codex"
   ```

3. Create `$CODEX_HOME/hooks.json` (or `~/.codex/hooks.json` when `CODEX_HOME` is unset):

   ```json
   {
     "hooks": {
      "PreToolUse": [{ "matcher": "local_shell|shell|shell_command|exec_command|Bash|Shell|apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__", "hooks": [{ "type": "command", "command": "context-mode hook codex pretooluse" }] }],
       "PostToolUse": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex posttooluse" }] }],
       "SessionStart": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex sessionstart" }] }],
       "PreCompact": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex precompact" }] }],
       "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex userpromptsubmit" }] }],
       "Stop": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex stop" }] }]
     }
   }
   ```

   `PreToolUse` enforces deny/block routing today and is prepared for input rewrites once Codex supports them. `PostToolUse` captures session events. `PreCompact` builds the resume snapshot before compaction. `SessionStart` restores state after compaction. `UserPromptSubmit` captures user decisions and corrections. `Stop` records turn-end state.

   > **Note:** Codex PreToolUse routing currently supports deny rules only (blocks dangerous commands). It still needs upstream `updatedInput` support before context-mode can rewrite tool input; track [openai/codex#18491](https://github.com/openai/codex/issues/18491). Context injection (`additionalContext`) is not supported in Codex PreToolUse — it works via PostToolUse and SessionStart instead. This is handled automatically.
   >
   > `PreCompact` support is runtime-gated: it is present in Codex CLI 0.130.0, while the public Codex hooks docs may lag the shipped hook-event list. Older Codex builds that do not emit `PreCompact` will not create pre-compaction snapshots.

4. Copy routing instructions (recommended even with hooks for full routing awareness):

   ```bash
   CM_ROOT="$(npm root -g)/context-mode"
   cp "$CM_ROOT/configs/codex/AGENTS.md" ./AGENTS.md
   ```

   For global use: `CM_ROOT="$(npm root -g)/context-mode"; cp "$CM_ROOT/configs/codex/AGENTS.md" ~/.codex/AGENTS.md`. Global applies to all projects. If both exist, Codex CLI merges them.

5. Restart Codex CLI.

**Verify:** Start a session and type `ctx stats` to verify MCP. To verify hook routing, confirm Codex lists/trusts the context-mode plugin hooks, then run a command that matches the routing rules.

**Routing:** MCP tools work after plugin install. Plugin hook routing is active only when `hooks` and `plugin_hooks` are enabled and Codex trusts the plugin hook commands. Manual hook routing is active when `$CODEX_HOME/hooks.json` or `~/.codex/hooks.json` is configured. The `AGENTS.md` file provides routing instructions for model awareness.




**Kimi Code** — MCP + hooks (TOML config, same JSON wire protocol as Codex)

**Prerequisites:** Node.js >= 22.5 (or Bun), Kimi Code CLI installed.

1. Install context-mode:

   ```bash
   npm install context-mode
   ```

2. Add context-mode as an MCP server. Add to `~/.kimi-code/mcp.json`:

   ```json
   {
     "mcpServers": {
       "context-mode": {
         "command": "context-mode",
         "args": []
       }
     }
   }
   ```

3. Add hooks to `~/.kimi-code/config.toml`:

   ```toml
   [[hooks]]
   event = "PreToolUse"
   matcher = "Bash|Shell|Read|Edit|Write|WebFetch|Agent|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__"
   command = "context-mode hook kimi pretooluse"
   timeout = 30

   [[hooks]]
   event = "PostToolUse"
   command = "context-mode hook kimi posttooluse"
   timeout = 30

   [[hooks]]
   event = "SessionStart"
   command = "context-mode hook kimi sessionstart"
   timeout = 30

   [[hooks]]
   event = "PreCompact"
   command = "context-mode hook kimi precompact"
   timeout = 30

   [[hooks]]
   event = "UserPromptSubmit"
   command = "context-mode hook kimi userpromptsubmit"
   timeout = 30

   [[hooks]]
   event = "Stop"
   command = "context-mode hook kimi stop"
   timeout = 30
   ```

4. Restart Kimi Code CLI and verify MCP with `ctx stats`.

   > **Note:** Kimi Code uses the same JSON stdin/stdout wire protocol as Codex, but accepts `additionalContext`, `updatedInput`, and `permissionDecision: "ask"` in PreToolUse responses (Codex rejects these). The kimi hook normalizes `ContentPart[]` prompt arrays to strings for downstream extractors.

5. (Optional) Copy the routing instructions file for your project:

   ```bash
   cp "$(npm root -g)/context-mode/configs/codex/AGENTS.md" ./AGENTS.md
   ```

   Or for global use:

   ```bash
   CM_ROOT="$(npm root -g)/context-mode"; cp "$CM_ROOT/configs/codex/AGENTS.md" ~/.kimi-code/AGENTS.md
   ```

Full documentation: [`docs/adapters/kimi-code.md`](docs/adapters/kimi-code.md)




**Qwen Code** — MCP + hooks (identical wire protocol to Claude Code)

**Prerequisites:** Node.js >= 22.5 (or Bun), Qwen Code installed (`npm install @qwen-code/qwen-code`).

1. Install context-mode:

   ```bash
   npm install context-mode
   ```

2. Add context-mode as an MCP server. Add to `~/.qwen/settings.json`:

   ```json
   {
     "mcpServers": {
       "context-mode": {
         "command": "context-mode",
         "args": []
       }
     }
   }
   ```

3. Add hooks for routing enforcement and session tracking. Add to `~/.qwen/settings.json`:

   ```json
   {
     "hooks": {
       "PreToolUse": [{ "matcher": "run_shell_command|read_file|read_many_files|grep_search|web_fetch|agent|mcp__plugin_context-mode_context-mode__ctx_execute|mcp__plugin_context-mode_context-mode__ctx_execute_file|mcp__plugin_context-mode_context-mode__ctx_batch_execute|mcp__(?!.*context-mode)", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code pretooluse" }] }],
       "PostToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code posttooluse" }] }],
       "SessionStart": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code sessionstart" }] }],
       "PreCompact": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code precompact" }] }],
       "UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code userpromptsubmit" }] }]
     }
   }
   ```

4. Copy routing instructions (recommended for full routing awareness):

   ```bash
   cp node_modules/context-mode/configs/qwen-code/QWEN.md ./QWEN.md
   ```

   For global use: `cp node_modules/context-mode/configs/qwen-code/QWEN.md ~/.qwen/QWEN.md`

5. Restart Qwen Code.

**Verify:** Start a session and type `ctx stats`. Context-mode tools should appear and respond.

**Note:** Qwen Code uses the same hook wire protocol as Claude Code (JSON stdin/stdout, same event names). Auto-detected via MCP clientInfo (`qwen-cli-mcp-client-*`) or `QWEN_PROJECT_DIR` env var.




**Antigravity IDE** — MCP-only, no hooks

> This is the Antigravity **desktop IDE**. For the `agy` **command-line tool**, see **Antigravity CLI (`agy`)** below — it installs as a full plugin with hooks.

**Prerequisites:** Node.js >= 22.5 (or Bun), the Antigravity IDE installed.

**Install:**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Add to `~/.gemini/antigravity/mcp_config.json`:

   ```json
   {
     "mcpServers": {
       "context-mode": {
         "command": "context-mode"
       }
     }
   }
   ```

3. Copy routing instructions (Antigravity has no hook support):

   ```bash
   cp node_modules/context-mode/configs/antigravity/GEMINI.md ./GEMINI.md
   ```

4. Restart Antigravity.

**Verify:** In an Antigravity session, type `ctx stats`. Context-mode tools should appear and respond.

**Routing:** Manual. The `GEMINI.md` file is the only enforcement method (~60% compliance). There is no programmatic interception. Auto-detected via MCP protocol handshake (`clientInfo.name`) — no manual platform configuration needed.

Full configs: [`configs/antigravity/mcp_config.json`](configs/antigravity/mcp_config.json) | [`configs/antigravity/GEMINI.md`](configs/antigravity/GEMINI.md)




**Antigravity CLI (`agy`)** — plugin (MCP + skill + hooks)

> The `agy` **command-line tool**, not the Antigravity desktop IDE above.

**Prerequisites:** Node.js >= 22.5 (or Bun), Antigravity CLI (`agy`) **≥ 1.0.7** (`agy update` to upgrade). Verified on agy 1.0.10.

**Install:**

```bash
npm install context-mode                                                                  # the plugin's MCP server + hooks run the global binary
agy plugin install https://github.com/mksglu/context-mode/tree/main/configs/antigravity-cli  # registers MCP + rule + skill + hooks
```

Restart `agy`.

**MCP-only (no plugin, no hooks):** if you only want the `ctx_*` tools, skip the plugin and add context-mode to agy's global MCP profile `~/.gemini/config/mcp_config.json` (distinct from the Antigravity IDE's `~/.gemini/antigravity/` path), then restart `agy`:

```json
{ "mcpServers": { "context-mode": { "command": "context-mode" } } }
```

**Verify:** type `ctx stats` in an agy session, or run any prompt from [Try It](#try-it) and check the savings. `context-mode doctor` confirms MCP + hook registration. Remove with `agy plugin uninstall context-mode`.

**Routing:** the routing rule and skill provide the instruction layer; bounded `PreToolUse` blocks high-flood tools and `PostToolUse` captures sessions. The bundle pins `CONTEXT_MODE_PLATFORM=antigravity-cli` so agy is detected even when Claude Code is co-installed ([#774](https://github.com/mksglu/context-mode/issues/774)).




**Kiro** — hooks with steering file

**Prerequisites:** Node.js >= 22.5 (or Bun), Kiro with MCP enabled (Settings > search "MCP").

**Install:**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Add to `.kiro/settings/mcp.json` in your project (or `~/.kiro/settings/mcp.json` for global):

   ```json
   {
     "mcpServers": {
       "context-mode": {
         "command": "context-mode"
       }
     }
   }
   ```

3. Create `.kiro/hooks/context-mode.json`:

   ```json
   {
     "name": "context-mode",
     "description": "Context-mode hooks for context window protection",
     "hooks": {
       "preToolUse": [
         { "matcher": "execute_bash|fs_read|@context-mode/ctx_execute|@context-mode/ctx_execute_file|@context-mode/ctx_batch_execute|@(?!context-mode/)", "command": "context-mode hook kiro pretooluse" }
       ],
       "postToolUse": [
         { "matcher": "*", "command": "context-mode hook kiro posttooluse" }
       ]
     }
   }
   ```

4. Copy routing instructions. Kiro's `agentSpawn` (SessionStart) is not yet implemented, so the model needs a routing file at session start:

   ```bash
   cp node_modules/context-mode/configs/kiro/KIRO.md ./KIRO.md
   ```

5. Restart Kiro.

**Verify:** Open the Kiro panel > MCP Servers tab and confirm "context-mode" shows a green status indicator. In chat, type `ctx stats`.

**Routing:** Hooks enforce routing programmatically via `preToolUse`/`postToolUse`. The `KIRO.md` file provides routing instructions since `agentSpawn` (SessionStart equivalent) is not yet wired. Tool names appear as `@context-mode/ctx_batch_execute`, `@context-mode/ctx_search`, etc. Auto-detected via MCP protocol handshake.

Full configs: [`configs/kiro/mcp.json`](configs/kiro/mcp.json) | [`configs/kiro/agent.json`](configs/kiro/agent.json) | [`configs/kiro/KIRO.md`](configs/kiro/KIRO.md)




**Zed** — MCP-only, no hooks

**Prerequisites:** Node.js >= 22.5 (or Bun), Zed installed.

**Install:**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Add to `~/.config/zed/settings.json` (Windows: `%APPDATA%\Zed\settings.json`):

   ```json
   {
     "context_servers": {
       "context-mode": {
         "command": "context-mode",
         "args": [],
         "env": {}
       }
     }
   }
   ```

   Note: Zed uses `"context_servers"` instead of `"mcpServers"`. `args` and `env` are optional for context-mode, but are shown here to match Zed's custom MCP server shape.

3. Copy routing instructions (Zed has no hook support):

   ```bash
   cp node_modules/context-mode/configs/zed/AGENTS.md ./AGENTS.md
   ```

4. Restart Zed (or save `settings.json` — Zed auto-restarts context servers on config change).

**Verify:** Open the Agent Panel (`Cmd+Shift+A`), go to settings, and check the indicator dot next to "context-mode" — green means active. Type `ctx stats` in the agent chat.

**Routing:** Manual. The `AGENTS.md` file is the only enforcement method (~60% compliance). There is no programmatic interception. Tool names appear as `mcp:context-mode:ctx_batch_execute`, `mcp:context-mode:ctx_search`, etc. Auto-detected via MCP protocol handshake.




**Pi Coding Agent** — extension with full hook support

**Prerequisites:** Node.js >= 22.5 (or Bun), Pi Coding Agent installed.

**Install:**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Install the package into Pi:

   ```bash
   pi install npm:context-mode
   ```

   Alternative — add it manually to `~/.pi/agent/settings.json` (or `.pi/settings.json` for project-level):

   ```json
   {
     "packages": ["npm:context-mode"]
   }
   ```

3. Add to `~/.pi/agent/mcp.json` (or `.pi/mcp.json` for project-level):

   ```json
   {
     "mcpServers": {
       "context-mode": {
         "command": "context-mode"
       }
     }
   }
   ```

4. Restart Pi.

**Verify:** In a Pi session, type `ctx stats`. Context-mode tools should appear and respond.

**Routing:** Automatic. The extension registers all key lifecycle events (`tool_call`, `tool_result`, `session_start`, `session_before_compact`), providing full session continuity and routing enforcement.




**OMP (Oh My Pi)** — plugin with full hook support

**Prerequisites:** Node.js >= 22.5 (or Bun), Oh My Pi installed.

**Install — plugin path (recommended):**

1. Run the OMP plugin install:

   ```bash
   omp plugin install context-mode
   ```

2. Restart OMP.

3. Verify:

   ```bash
   omp plugin list
   omp plugin doctor
   ```

   Both should show `context-mode` as `enabled`.

   > The plugin self-registers its MCP server in `~/.omp/agent/mcp.json` on first load (spawned as `node /server.bundle.mjs`, since the plugin-install package directory is not on `PATH`), so the 11 `ctx_*` tools become reachable after the restart in step 2 — no manual `mcp.json` edit needed ([#677](https://github.com/mksglu/context-mode/issues/677)). An existing `context-mode` entry is never overwritten; remove it if you want the plugin to re-register the bundled path.

**Install — manual plugin path (if `omp plugin install` is unavailable):**

OMP loads anything listed under `~/.omp/plugins/package.json` `dependencies` whose own `package.json` carries an `omp` (or `pi`) field. New plugins default to enabled — the lock file at `~/.omp/plugins/omp-plugins.lock.json` is only consulted when a plugin needs to be explicitly **disabled** (loader skips `runtimeState && !runtimeState.enabled` per [`extensibility/plugins/loader.ts:89-94`](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/extensibility/plugins/loader.ts)). So the manual install is two commands:

```bash
cd ~/.omp/plugins
bun add context-mode    # or: npm install context-mode
```

Then restart OMP. No lock file edit, no version pin — version is read from the freshly-installed package each time the loader runs (see [`loader.ts:87`](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/extensibility/plugins/loader.ts) `manifest.version = pluginPkg.version`).

**Install — MCP-only path (no plugin):**

1. Install context-mode globally:

   ```bash
   npm install context-mode
   ```

2. Add to `~/.omp/agent/mcp.json` (user scope) or `/.omp/mcp.json` (project scope):

   ```json
   {
     "mcpServers": {
       "context-mode": {
         "command": "context-mode"
       }
     }
   }
   ```

3. Copy routing instructions:

   ```bash
   cp node_modules/context-mode/configs/omp/SYSTEM.md ~/.omp/agent/SYSTEM.md
   ```

   Project-scoped alternative: `cp ... .omp/SYSTEM.md`. OMP also auto-discovers any `AGENTS.md` in the project tree.

4. Restart OMP.

**Verify (any path):** In an OMP session, type `ctx stats`. Context-mode tools should appear and respond.

**Routing:** Plugin path — programmatic enforcement via four `pi.on(...)` handlers (`tool_call` returns `{ block: true, reason }` for `curl`/`wget`/inline-fetch per upstream [`hooks/types.ts:566`](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/extensibility/hooks/types.ts), `tool_result` captures session events, `session_start` initializes the per-session DB row, `session_before_compact` persists a resume snapshot). ~98% compliance, parity with Claude Code hooks. MCP-only path — rule-based via `SYSTEM.md`, ~60% compliance. Auto-detected via `PI_CODING_AGENT_DIR` env var or presence of `~/.omp/`. Storage roots at `~/.omp/context-mode/` so OMP and Pi installs never share session DBs, content indices, or stats files.

Full configs: [`configs/omp/mcp.json`](configs/omp/mcp.json) | [`configs/omp/SYSTEM.md`](configs/omp/SYSTEM.md) | plugin source: [`src/adapters/omp/plugin.ts`](src/adapters/omp/plugin.ts)




**Build Prerequisites** <sup>(CentOS, RHEL, Alpine)</sup>

Context Mode uses [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) on Node.js, which ships prebuilt native binaries for most platforms. On glibc >= 2.31 systems (Ubuntu 20.04+, Debian 11+, Fedora 34+, macOS, Windows), `npm install` works without any build tools.

**Linux + Node.js >= 22.5:** Context Mode automatically uses the built-in `node:sqlite` module instead of `better-sqlite3`. This eliminates the native addon entirely, avoiding [sporadic SIGSEGV crashes](https://github.com/nodejs/node/issues/62515) caused by V8's `madvise(MADV_DONTNEED)` corrupting the addon's `.got.plt` section on Linux. No configuration needed — detection is automatic. **Linux + Node < 22.5 is unsupported** ([#564](https://github.com/mksglu/context-mode/issues/564)) — `npm install` will fail with remediation instructions.

**Bun users:** No native compilation needed. Context Mode automatically detects Bun and uses the built-in `bun:sqlite` module via a compatibility adapter. `better-sqlite3` and all its build dependencies are skipped entirely.

On older glibc systems (CentOS 7/8, RHEL 8, Debian 10), prebuilt binaries don't load and better-sqlite3 **automatically falls back to compiling from source** via `prebuild-install || node-gyp rebuild --release`. This requires a C++20 compiler (GCC 10+), Make, and Python with setuptools.

**Windows / missing binding self-heal:** if `better_sqlite3.node` ends up missing after install (e.g. `prebuild-install` not on cmd.exe PATH, no MSVC toolchain), the postinstall script and the runtime hook automatically re-fetch the prebuild and repair the binding — no manual `npm rebuild` needed (#408).

**CentOS 8 / RHEL 8** (glibc 2.28):

```bash
dnf install -y gcc-toolset-10-gcc gcc-toolset-10-gcc-c++ make python3 python3-setuptools
scl enable gcc-toolset-10 'npm install context-mode'
```

**CentOS 7 / RHEL 7** (glibc 2.17):

```bash
yum install -y centos-release-scl
yum install -y devtoolset-10-gcc devtoolset-10-gcc-c++ make python3
pip3 install setuptools
scl enable devtoolset-10 'npm install context-mode'
```

**Alpine Linux:**

Alpine prebuilt binaries (musl) are available in better-sqlite3 v12.8.0+. With the `^12.6.2` dependency range, `npm install` resolves to the latest 12.x and works without build tools on Alpine. If you pin an older version:

```bash
apk add build-base python3 py3-setuptools
npm install context-mode
```



## Tools

| Tool | What it does | Context saved |
|---|---|---|
| `ctx_batch_execute` | Run multiple commands + search multiple queries in ONE call. Opt-in `concurrency: 1-8` for I/O-bound batches. | 986 KB → 62 KB |
| `ctx_execute` | Run code in 12 languages. Only stdout enters context. | 56 KB → 299 B |
| `ctx_execute_file` | Process files in sandbox. Raw content never leaves. | 45 KB → 155 B |
| `ctx_index` | Chunk markdown into FTS5 with BM25 ranking. | 60 KB → 40 B |
| `ctx_search` | Query indexed content with multiple queries in one call. | On-demand retrieval |
| `ctx_fetch_and_index` | Fetch URL, chunk and index. Cache reuses content within TTL (default 24h, override per-call with `ttl: <ms>`). `ttl: 0` or `force: true` to bypass. Pass `requests: [{url, source}, ...]` + `concurrency: 1-8` for parallel multi-URL. | 60 KB → 40 B |
| `ctx_stats` | Show context savings, call counts, and session statistics. | — |
| `ctx_doctor` | Diagnose installation: runtimes, hooks, FTS5, versions. | — |
| `ctx_upgrade` | Upgrade to latest version from GitHub, rebuild, reconfigure hooks. | — |
| `ctx_purge` | Permanently deletes all indexed content from the knowledge base. | — |

## How the Sandbox Works

Each `ctx_execute` call spawns an isolated subprocess with its own process boundary. Scripts can't access each other's memory or state. The subprocess runs your code, captures stdout, and only that stdout enters the conversation context. The raw data — log files, API responses, snapshots — never leaves the sandbox.

Twelve language runtimes are available: JavaScript, TypeScript, Python, Shell, Ruby, Go, Rust, PHP, Perl, R, Elixir, and C#. Bun is auto-detected for 3-5x faster JS/TS execution.

Authenticated CLIs work through credential passthrough — `gh`, `aws`, `gcloud`, `kubectl`, `docker` inherit environment variables and config paths without exposing them to the conversation.

When output exceeds 5 KB and an `intent` is provided, Context Mode switches to intent-driven filtering: it indexes the full output into the knowledge base, searches for sections matching your intent, and returns only the relevant matches with a vocabulary of searchable terms for follow-up queries.

## How the Knowledge Base Works

The `ctx_index` tool chunks markdown content by headings while keeping code blocks intact, then stores them in a **SQLite FTS5** (Full-Text Search 5) virtual table. The SQLite backend is selected automatically at runtime: `bun:sqlite` on Bun, `node:sqlite` on Node.js >= 22.5, and `better-sqlite3` everywhere else. Search uses **BM25 ranking** — a probabilistic relevance algorithm that scores documents based on term frequency, inverse document frequency, and document length normalization. **Porter stemming** is applied at index time so "running", "runs", and "ran" match the same stem. Titles and headings are weighted **5x** in BM25 scoring for precise navigational queries.

When you call `ctx_search`, it returns relevant content snippets focused around matching query terms — not full documents, not approximations, the actual indexed content with smart extraction around what you're looking for. `ctx_fetch_and_index` extends this to URLs: fetch, convert HTML to markdown, chunk, index. The raw page never enters context. Use the `contentType` parameter to filter results by type (e.g. `code` or `prose`).

### Ranking: Reciprocal Rank Fusion

Search runs two parallel strategies and merges them with **Reciprocal Rank Fusion (RRF)**:

- **Porter stemming** — FTS5 MATCH with porter tokenizer. "caching" matches "cached", "caches", "cach".
- **Trigram substring** — FTS5 trigram tokenizer matches partial strings. "useEff" finds "useEffect", "authenticat" finds "authentication".

RRF merges both ranked lists into a single result set, so a document that ranks well in both strategies surfaces higher than one that ranks well in only one. This replaces the old cascading fallback approach where trigram results were only used if porter returned nothing.

### Proximity Reranking

Multi-term queries get an additional reranking pass. Results where query terms appear close together are boosted — `"session continuity"` ranks passages with adjacent terms higher than pages where "session" and "continuity" appear paragraphs apart.

### Fuzzy Correction

Levenshtein distance corrects typos before re-searching. "kuberntes" becomes "kubernetes", "autentication" becomes "authentication".

### Smart Snippets

Search results use intelligent extraction instead of truncation. Instead of returning the first N characters (which might miss the important part), Context Mode finds where your query terms appear in the content and returns windows around those matches.

### TTL Cache

Indexed content persists in a per-project SQLite database at `~/.context-mode/content/`. When `ctx_fetch_and_index` is called for a URL that was already indexed within its TTL window, the fetch is skipped entirely and the model searches the existing index directly.

- **Default TTL:** 24 hours. Override per-call with `ttl: <milliseconds>` (PR #666). Longer for stable specs, shorter for changelogs you want re-checked often.
- **Cache hit (within TTL):** Returns a cache hint (~0.3KB) instead of re-fetching (48KB+). Model proceeds to `ctx_search`.
- **Cache miss (TTL expired):** Re-fetches silently. No user action needed.
- **`ttl: 0`** or **`force: true`:** Bypasses cache and re-fetches regardless of freshness.
- **14-day cleanup:** Content databases and sources older than 14 days are removed on startup.

This means `--continue` sessions preserve indexed docs across restarts. No re-fetching, no wasted context tokens.

`ctx_stats` reports cache performance separately: hits, data avoided, network requests saved, and total context savings including cache.

### Progressive Throttling

- **Calls 1-3:** Normal results (2 per query)
- **Calls 4-8:** Reduced results (1 per query) + warning
- **Calls 9+:** Blocked — redirects to `ctx_batch_execute`

## Session Continuity

When the context window fills up, the agent compacts the conversation — dropping older messages to make room. Without session tracking, the model forgets which files it was editing, what tasks are in progress, what errors were resolved, and what you last asked for.

Context Mode captures every meaningful event during your session and persists them in a per-project SQLite database. When the conversation compacts (or you resume with `--continue`, `--resume`, or `/resume`), your working state is rebuilt automatically — the model continues from your last prompt without asking you to repeat anything.

> Resuming a non-latest session via `/resume ` works the same way: the SessionStart hook detects the empty live-event table for the freshly issued session id and falls back to the most recent unconsumed snapshot for the project (`session_resume` table). The picker selects the conversation; context-mode rehydrates the prior working state.

Session continuity requires 5 hooks working together:

| Hook | Role | Claude Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | GitHub Copilot CLI | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Antigravity | Antigravity CLI (`agy`) | Kiro | Zed | Pi | OMP |
|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **PreToolUse** | Enforces sandbox routing before tool execution | Yes | -- | -- | -- | Yes | Yes | -- | -- | -- | Yes | -- | Bounded | Yes | -- | ✓ (via tool_call event) | ✓ (via tool_call event) |
| **PostToolUse** | Captures events after each tool call | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes (capture-only) | Yes | -- | ✓ (via tool_result event) | ✓ (via tool_result event) |
| **UserPromptSubmit** | Captures user decisions and corrections | Yes | -- | -- | -- | Yes | -- | Plugin (via chat.message) | Plugin (via chat.message) | -- | Yes | -- | -- | -- | -- | -- | -- |
| **Stop** | Captures assistant turn-end state | Yes | -- | -- | -- | Yes | Yes | -- | -- | -- | Yes | -- | Best-effort | -- | -- | -- | -- |
| **PreCompact** | Builds snapshot before compaction | Yes | Yes | Yes | Yes | Yes | -- | Plugin | Plugin | Plugin | Yes | -- | -- | -- | -- | ✓ (via session_before_compact) | ✓ (via session_before_compact) |
| **SessionStart** | Restores state after compaction or resume | Yes | Yes | Yes | Yes | Yes | -- | ✓ (via experimental.chat.system.transform) | ✓ (via experimental.chat.system.transform) | Plugin | Yes | -- | -- | -- | -- | ✓ (via session_start event) | ✓ (via session_start event) |
| | **Session completeness** | **Full** | **High** | **High** | **High** | **High** | **Partial** | **Full** | **Full** | **High** | **Partial** | **--** | **Partial** | **Partial** | **--** | **High** | **High** |

> **Note:** Full session continuity (capture + snapshot + restore) works on **Claude Code**, **Gemini CLI**, **VS Code Copilot**, **JetBrains Copilot**, **OpenCode**, and **KiloCode**. **GitHub Copilot CLI** uses its own camelCase hook config keys (`preToolUse`, `postToolUse`, `preCompact`, `sessionStart`, `userPromptSubmitted`, `agentStop`) and top-level hook responses; it captures prompt, tool, compaction, session-start, and stop events when the plugin hooks are installed. **OpenCode** and **KiloCode** use `experimental.chat.system.transform` as a SessionStart surrogate to inject the routing block and restore prior sessions, plus `chat.message` for user-prompt capture; full SessionStart hook support is not yet available ([#14808](https://github.com/sst/opencode/issues/14808), [#5409](https://github.com/sst/opencode/issues/5409)), but prior-session continuity and user-decision capture work fully. **Cursor** captures tool events via `preToolUse`/`postToolUse`, but `sessionStart` is currently rejected by Cursor's validator ([forum report](https://forum.cursor.com/t/unknown-hook-type-sessionstart/149566)), so session restore after compaction is not available yet. **OpenClaw** uses native gateway plugin hooks (`api.on()`) for full session continuity. **Pi Coding Agent** provides high session continuity via extension hooks (`tool_call`, `tool_result`, `session_start`, `session_before_compact`). **Codex CLI** provides partial hook-based session tracking through PreToolUse, PostToolUse, PreCompact, SessionStart, UserPromptSubmit, and Stop; MCP tools work. **Antigravity IDE** and **Zed** have no hook support in the current release, so session tracking is not available there. **Antigravity CLI (`agy`)** is separate from the IDE and supports bounded `PreToolUse`, capture-only `PostToolUse`, and best-effort `Stop` through its plugin hooks. **Kiro** captures tool events via native `preToolUse`/`postToolUse` hooks, but its SessionStart equivalent (`agentSpawn`) is not yet wired, so session restore after compaction is unavailable. **OMP** (Oh My Pi) ships full plugin-based hook support — `omp plugin install context-mode` registers `tool_call`, `tool_result`, `session_start`, and `session_before_compact` handlers and storage roots cleanly under `~/.omp/context-mode/` so OMP and Pi installs never share state.


**What gets captured**

Every tool call passes through hooks that extract structured events:

| Category | Events | Priority | Captured By |
|---|---|---|---|
| **Files** | read, edit, write, glob, grep | Critical (P1) | PostToolUse |
| **Tasks** | create, update, complete | Critical (P1) | PostToolUse |
| **Plans** | enter, exit, approved, rejected, file write | Critical (P1) | PostToolUse |
| **Rules** | CLAUDE.md / GEMINI.md / AGENTS.md paths + content | Critical (P1) | SessionStart |
| **User Prompts** | Every user message (for last-prompt restore) | Critical (P1) | UserPromptSubmit |
| **Decisions** | User corrections, preferences ("use X instead", "don't do Y") | High (P2) | UserPromptSubmit |
| **Git** | checkout, commit, merge, rebase, stash, push, pull, diff, status | High (P2) | PostToolUse |
| **Errors** | Tool failures, non-zero exit codes | High (P2) | PostToolUse |
| **Error Resolution** | Error → fix pairs detected across sequential tool calls | High (P2) | PostToolUse |
| **Constraints** | Discovered limitations ("not supported", "permission denied") | High (P2) | PostToolUse |
| **Blockers** | "blocked on", "waiting for", "depends on" — tracked until resolved | High (P2) | UserPromptSubmit |
| **Rejected Approaches** | Tool calls denied by user (PreToolUse → PostToolUse marker) | High (P2) | PreToolUse |
| **Environment** | cwd changes, venv, nvm, conda, worktree, package installs | High (P2) | PostToolUse |
| **Agent Findings** | Completed subagent results (first 500 chars) | High (P2) | PostToolUse |
| **Iteration Loops** | Same tool called 3+ times with similar input (retry detection) | High (P2) | PostToolUse |
| **Latency** | Tool calls exceeding 5s (tool name + duration in ms) | Normal (P3) | PreToolUse |
| **MCP Tools** | All `mcp__*` tool calls with usage counts | Normal (P3) | PostToolUse |
| **Subagents** | Agent tool launches and completions | Normal (P3) | PostToolUse |
| **Skills** | Slash command invocations | Normal (P3) | PostToolUse |
| **External Refs** | URLs, GitHub issue references (#123), deduped | Normal (P3) | PostToolUse |
| **Role** | Persona / behavioral directives ("act as senior engineer") | Normal (P3) | UserPromptSubmit |
| **Intent** | Session mode classification (investigate, implement, review) | Low (P4) | UserPromptSubmit |
| **Data** | Large user-pasted data references (>1 KB) | Low (P4) | UserPromptSubmit |




**How sessions survive compaction**

```
PreCompact fires
  → Read all session events from SQLite
  → Build priority-tiered XML snapshot (≤2 KB)
  → Store snapshot in session_resume table

SessionStart fires (source: "compact")
  → Retrieve stored snapshot
  → Write structured events file → auto-indexed into FTS5
  → Build Session Guide with 15 categories
  → Inject <session_knowledge> directive into context
  → Model continues from last user prompt with full working state
```

The snapshot is built in priority tiers — if the 2 KB budget is tight, lower-priority events (intent, MCP tool counts) are dropped first while critical state (active files, tasks, rules, decisions) is always preserved.

After compaction, the model receives a **Session Guide** — a structured narrative with actionable sections:

- **Last Request** — user's last prompt, so the model continues without asking "what were we doing?"
- **Tasks** — checkbox format with completion status (`[x]` completed, `[ ]` pending)
- **Plans** — plan mode entries, exits, approvals, and rejections
- **Key Decisions** — user corrections and preferences ("use X instead", "don't do Y")
- **Files Modified** — all files touched during the session
- **Unresolved Errors** — errors that haven't been fixed, plus error→fix resolution pairs
- **Constraints** — discovered limitations and boundaries
- **Blockers** — open and resolved blockers ("blocked on X", "waiting for Y")
- **Git** — operations performed (checkout, commit, push, status)
- **Project Rules** — CLAUDE.md / GEMINI.md / AGENTS.md paths
- **MCP Tools Used** — tool names with call counts
- **Subagent Tasks** — delegated work summaries + agent findings
- **Skills Used** — slash commands invoked
- **Rejected Approaches** — tool calls the user denied
- **External References** — URLs and GitHub issue references
- **Environment** — working directory, env variables, worktrees
- **Data References** — large data pasted during the session
- **Session Intent** — mode classification (implement, investigate, review, discuss)
- **User Role** — behavioral directives set during the session

Detailed event data is also indexed into FTS5 for on-demand retrieval via `ctx_search()`.




**Per-platform details**

**Claude Code** — Full session support. All 5 hook types fire, capturing tool events, user decisions, building compaction snapshots, and restoring state after compaction, `--continue`, `--resume`, or `/resume`.

**Gemini CLI** — High coverage. PostToolUse (AfterTool), PreCompact (PreCompress), and SessionStart all fire. Missing UserPromptSubmit, so user decisions and corrections aren't captured — but file edits, git ops, errors, and tasks are fully tracked.

**VS Code Copilot** — High coverage. Same as Gemini CLI — PostToolUse, PreCompact, and SessionStart all fire. User decisions aren't captured but all tool-level events are.

**JetBrains Copilot** — High coverage. Same capabilities as VS Code Copilot — PostToolUse, PreCompact, and SessionStart all fire. Uses the same hook wire protocol and response format. User decisions aren't captured but all tool-level events are.

**GitHub Copilot CLI** — High coverage. Native plugin hooks use camelCase config keys (`preToolUse`, `postToolUse`, `preCompact`, `sessionStart`, `userPromptSubmitted`, `agentStop`) and top-level hook response fields. The plugin captures user prompts, tool events, compaction snapshots, session start restore, and stop events.

**Cursor** — Partial coverage. Native `preToolUse` and `postToolUse` hooks capture tool events. `sessionStart` is documented by Cursor but currently rejected by their validator, so session restore is not available. Routing instructions are delivered via MCP server startup instead.

**OpenCode** — Full session support. The TypeScript plugin captures PostToolUse events via `tool.execute.after`, user prompts and decisions via `chat.message`, builds compaction snapshots via `experimental.session.compacting`, and restores prior sessions via `experimental.chat.system.transform` (SessionStart surrogate). Routing block is injected on first `chat.system.transform` per session. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire.

**KiloCode** — Full session support. Shares the same plugin architecture as OpenCode via the OpenCodeAdapter. The TypeScript plugin captures PostToolUse events via `tool.execute.after`, user prompts and decisions via `chat.message`, builds compaction snapshots via `experimental.session.compacting`, and restores prior sessions via `experimental.chat.system.transform` (SessionStart surrogate).

**OpenClaw / Pi Agent** — High coverage. All tool lifecycle hooks (`after_tool_call`, `before_compaction`, `session_start`) fire via the native gateway plugin. User decisions aren't captured but file edits, git ops, errors, and tasks are fully tracked. Falls back to DB snapshot reconstruction if compaction hooks fail on older gateway versions. See [`docs/adapters/openclaw.md`](docs/adapters/openclaw.md).

**Codex CLI** — MCP active, hooks require `[features].hooks = true`. Hook scripts (PreToolUse, PostToolUse, PreCompact, SessionStart, UserPromptSubmit, Stop) are implemented and tested; `PreCompact` remains runtime-gated on Codex builds that emit the event. PreToolUse deny routing works; input rewriting still depends on upstream `updatedInput` support ([openai/codex#18491](https://github.com/openai/codex/issues/18491)).

**Antigravity** — No session support. No hooks, no event capture. Requires manually copying `GEMINI.md` to your project root. Auto-detected via MCP protocol handshake (`clientInfo.name`).

**Antigravity CLI (`agy`)** — Partial coverage. The standalone CLI is separate from the IDE and supports bounded native `PreToolUse` enforcement for mapped high-flood tools, capture-only `PostToolUse`, and best-effort `Stop` through the shipped plugin hooks. It does not currently provide PreCompact/SessionStart/UserPromptSubmit coverage.

**Zed** — No session support. No hooks, no event capture. Requires manually copying `AGENTS.md` to your project root. Auto-detected via MCP protocol handshake (`clientInfo.name`).

**Kiro** — Partial coverage. Native `preToolUse` and `postToolUse` hooks capture tool events and enforce sandbox routing. `agentSpawn` (the Kiro equivalent of SessionStart) is not yet implemented, so session restore after compaction is not available. Requires manually copying `KIRO.md` to your project root. Auto-detected via MCP protocol handshake (`clientInfo.name`).

**Pi Coding Agent** — High coverage. The extension registers all key lifecycle events: `tool_call` (PreToolUse), `tool_result` (PostToolUse), `session_start` (SessionStart), and `session_before_compact` (PreCompact). File edits, git ops, errors, and tasks are fully tracked. Session restore after compaction works via the extension's event hooks.
Tool call output can be collapsed/expanded with the default Pi's default keybinding (Ctrl+O)

**OMP (Oh My Pi)** — High coverage. The plugin (installed via `omp plugin install context-mode`) registers all key lifecycle events: `tool_call` (PreToolUse), `tool_result` (PostToolUse), `session_start` (SessionStart), and `session_before_compact` (PreCompact). Storage roots cleanly under `~/.omp/context-mode/` so OMP and Pi installs never share state (issue [#473](https://github.com/mksglu/context-mode/issues/473)). Auto-detected via `PI_CODING_AGENT_DIR` env var or presence of `~/.omp/`.



## Platform Compatibility

| Feature | Claude Code | Qwen Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | GitHub Copilot CLI | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Kimi Code | Antigravity | Antigravity CLI (`agy`) | Kiro | Zed | Pi | OMP |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| MCP Server / Native Tools | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Native plugin | Native plugin | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| PreToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | Yes | -- | Bounded | Yes | -- | Yes (extension) | Plugin |
| PostToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | Yes | -- | Yes (capture-only) | Yes | -- | Yes (extension) | Plugin |
| SessionStart Hook | Yes | Yes | Yes | Yes | Yes | Yes | -- | ✓ (via experimental.chat.system.transform) | ✓ (via experimental.chat.system.transform) | Plugin | Yes | Yes | -- | -- | -- | -- | Yes (extension) | Plugin |
| PreCompact Hook | Yes | Yes | Yes | Yes | Yes | Yes | -- | Plugin | Plugin | Plugin | Yes | Yes | -- | -- | -- | -- | Yes (extension) | Plugin |
| Can Modify Args | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | -- | Yes | -- | -- | -- | -- | Yes (extension) | -- |
| Can Block Tools | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | Yes | -- | Bounded | Yes | -- | Yes (extension) | Plugin |
| Utility Commands (ctx) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes (/ctx-stats, /ctx-doctor) | Yes |
| Slash Commands | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
| Plugin Marketplace | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |

> **OpenCode** uses a TypeScript plugin paradigm — hooks run as in-process functions via `tool.execute.before`, `tool.execute.after`, `experimental.session.compacting`, `experimental.chat.system.transform`, and `chat.message`, providing full routing enforcement, session continuity, and user-prompt capture. The `experimental.chat.system.transform` hook acts as a SessionStart surrogate to inject the routing block and restore prior sessions. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).
>
> **KiloCode** shares the same TypeScript plugin architecture as OpenCode via the OpenCodeAdapter, with platform-specific configuration paths (`kilo.json` instead of `opencode.json`, `~/.config/kilo/` instead of `~/.config/opencode/`). Hook capabilities match OpenCode, including SessionStart surrogate via `experimental.chat.system.transform` and user-prompt capture via `chat.message`.
>
> **OpenClaw** runs context-mode as a native gateway plugin targeting Pi Agent sessions. Hooks register via `api.on()` (tool/lifecycle) and `api.registerHook()` (commands). All tool interception and compaction hooks are supported. See [`docs/adapters/openclaw.md`](docs/adapters/openclaw.md).
>
> **Codex CLI** hooks require `[features].hooks = true`. MCP tools work, and hook scripts activate through `$CODEX_HOME/hooks.json` or `~/.codex/hooks.json`. PreToolUse supports `permissionDecision: "deny"` only; input modification still needs upstream `updatedInput` support ([openai/codex#18491](https://github.com/openai/codex/issues/18491)). `additionalContext` is not supported in PreToolUse (context injection works via PostToolUse and SessionStart instead; the codex formatter handles this automatically). PreCompact stores resume snapshots before compaction on Codex builds that emit the event, SessionStart restores them, and UserPromptSubmit/Stop capture prompt and turn-end continuity events. See the Codex install section for setup. **Antigravity** and **Zed** do not support hooks. They rely solely on manually-copied routing instruction files (`AGENTS.md` / `GEMINI.md`) for enforcement (~60% compliance). See each platform's install section for copy instructions. Antigravity and Zed are auto-detected via MCP protocol handshake — no manual platform configuration needed.
>
> **Antigravity CLI (`agy`)** supports bounded `PreToolUse` blocking for mapped Bash/Read/Grep/WebFetch surfaces, plus `PostToolUse` capture and best-effort `Stop` capture through its plugin `hooks.json`. The routing rule and routing skill remain the broader instruction layer; `PreInvocation`/`PostInvocation` are not wired until their payload/response semantics are verified.
>
> **Kiro** supports native `preToolUse` and `postToolUse` hooks for routing enforcement and tool event capture. `agentSpawn` (SessionStart equivalent) and `stop` are not yet wired. Requires manually copying `KIRO.md` to your project root. Kiro is auto-detected via MCP protocol handshake (`clientInfo.name`).
>
> **Pi Coding Agent** runs context-mode as an extension with full hook support. The extension registers `tool_call`, `tool_result`, `session_start`, and `session_before_compact` events, providing high session continuity coverage. The MCP server provides all 11 MCP tools.
>
> **OMP (Oh My Pi)** runs context-mode as a plugin via `omp plugin install context-mode`. The plugin registers `tool_call`, `tool_result`, `session_start`, and `session_before_compact` events for hard-block routing and full session continuity. Storage isolated under `~/.omp/context-mode/` so OMP and Pi never share state. Auto-detected via `PI_CODING_AGENT_DIR` (default agent dir `~/.omp/agent`) or `~/.omp/` directory. See [issue #473](https://github.com/mksglu/context-mode/issues/473) for the storage-isolation history.

### Routing Enforcement

Hooks intercept tool calls programmatically — they can block dangerous commands and redirect them to the sandbox before execution. Instruction files guide the model via prompt instructions but cannot block anything. **Always enable hooks where supported.**

> **Note:** Routing instruction files were previously auto-written to project directories on first session start. This was disabled to prevent git tree pollution ([#158](https://github.com/mksglu/context-mode/issues/158), [#164](https://github.com/mksglu/context-mode/issues/164)). Hook-capable platforms (Claude Code, Gemini CLI, VS Code Copilot, JetBrains Copilot, GitHub Copilot CLI, Cursor, OpenCode, OpenClaw, Codex CLI, Antigravity CLI for bounded tool hooks, Kiro for tool hooks, OMP via plugin) inject or enforce routing without writing files. Platforms without hook support — Zed and Antigravity IDE — require a one-time manual copy of the routing file; see each platform's install section.

| Platform | Hooks | Instruction File | With Hooks | Without Hooks |
|---|:---:|---|:---:|:---:|
| Claude Code | Yes (auto) | [`CLAUDE.md`](configs/claude-code/CLAUDE.md) | **~98% saved** | ~60% saved |
| Gemini CLI | Yes | [`GEMINI.md`](configs/gemini-cli/GEMINI.md) | **~98% saved** | ~60% saved |
| VS Code Copilot | Yes | [`copilot-instructions.md`](configs/vscode-copilot/copilot-instructions.md) | **~98% saved** | ~60% saved |
| JetBrains Copilot | Yes | [`copilot-instructions.md`](configs/vscode-copilot/copilot-instructions.md) | **~98% saved** | ~60% saved |
| GitHub Copilot CLI | Yes | [`copilot-instructions.md`](configs/vscode-copilot/copilot-instructions.md) | **~98% saved** | ~60% saved |
| Cursor | Yes | [`context-mode.mdc`](configs/cursor/context-mode.mdc) | **~98% saved** | ~60% saved |
| OpenCode | Plugin | [`AGENTS.md`](configs/opencode/AGENTS.md) | **~98% saved** | ~60% saved |
| OpenClaw | Plugin | [`AGENTS.md`](configs/openclaw/AGENTS.md) | **~98% saved** | ~60% saved |
| Codex CLI | Yes | [`AGENTS.md`](configs/codex/AGENTS.md) | **~98% saved** | ~60% saved |
| Antigravity | -- | [`GEMINI.md`](configs/antigravity/GEMINI.md) | -- | ~60% saved |
| Antigravity CLI (`agy`) | Bounded | routing rule + skill ([`rules`](configs/antigravity-cli/rules/context-mode.md), [`skill`](configs/antigravity-cli/skills/context-mode/SKILL.md)) | bounded Bash/Read/Grep/WebFetch enforcement | ~60% saved |
| Kiro | Yes | [`KIRO.md`](configs/kiro/KIRO.md) | **~98% saved** | ~60% saved |
| Zed | -- | [`AGENTS.md`](configs/zed/AGENTS.md) | -- | ~60% saved |
| Pi | ✓ | [`AGENTS.md`](configs/pi/AGENTS.md) | **~98% saved** | ~60% saved |
| OMP | Plugin | [`SYSTEM.md`](configs/omp/SYSTEM.md) | **~98% saved** | ~60% saved |

Without hooks, one unrouted `curl` or Playwright snapshot can dump 56 KB into context — wiping out an entire session's worth of savings.

See [`docs/platform-support.md`](docs/platform-support.md) for the full capability comparison.

## Utility Commands

**Inside any AI session** — just type the command. The LLM calls the MCP tool automatically:

```
ctx stats       → context savings, call counts, session report
ctx doctor      → diagnose runtimes, hooks, FTS5, versions
ctx index       → index a local file or directory for later search
ctx search      → search previously indexed content
ctx upgrade     → update from GitHub, rebuild, reconfigure hooks
ctx purge       → permanently delete all indexed content from the knowledge base
ctx insight     → opens the hosted Insight dashboard in your browser
```

**From your terminal** — run directly without an AI session:

```bash
context-mode doctor
context-mode index . --source project:my-app
context-mode search "authentication middleware" --source project:my-app
context-mode upgrade
context-mode insight          # opens the hosted Insight dashboard in browser
bash scripts/ctx-debug.sh    # full diagnostic report for bug reports
```

The debug script collects OS info, runtime versions, better-sqlite3 status, adapter detection, config files (redacted), hook validation, FTS5/SQLite test, executor test, process check, session databases, and environment variables into a single pasteable markdown report.

Works on **all platforms**. On Claude Code, slash commands (`/ctx-stats`, `/ctx-doctor`, `/ctx-index`, `/ctx-search`, `/ctx-upgrade`, `/ctx-purge`, `/ctx-insight`) are also available.

## Benchmarks

| Scenario | Raw | Context | Saved |
|---|---|---|---|
| Playwright snapshot | 56.2 KB | 299 B | 99% |
| GitHub Issues (20) | 58.9 KB | 1.1 KB | 98% |
| Access log (500 requests) | 45.1 KB | 155 B | 100% |
| Context7 React docs | 5.9 KB | 261 B | 96% |
| Analytics CSV (500 rows) | 85.5 KB | 222 B | 100% |
| Git log (153 commits) | 11.6 KB | 107 B | 99% |
| Test output (30 suites) | 6.0 KB | 337 B | 95% |
| Repo research (subagent) | 986 KB | 62 KB | 94% |

Over a full session: 315 KB of raw output becomes 5.4 KB. Session time extends from ~30 minutes to ~3 hours.

[Full benchmark data with 21 scenarios →](BENCHMARK.md)

## Try It

These prompts work out of the box. Run `/context-mode:ctx-stats` after each to see the savings.

**Deep repo research** — 5 calls, 62 KB context (raw: 986 KB, 94% saved)
```
Research https://github.com/modelcontextprotocol/servers — architecture, tech stack,
top contributors, open issues, and recent activity. Then run /context-mode:ctx-stats.
```

**Git history analysis** — 1 call, 5.6 KB context
```
Clone https://github.com/facebook/react and analyze the last 500 commits:
top contributors, commit frequency by month, and most changed files.
Then run /context-mode:ctx-stats.
```

**Web scraping** — 1 call, 3.2 KB context
```
Fetch the Hacker News front page, extract all posts with titles, scores,
and domains. Group by domain. Then run /context-mode:ctx-stats.
```

**Large JSON API** — 7.5 MB raw → 0.9 KB context (99% saved)
```
Create a local server that returns a 7.5 MB JSON with 20,000 records and a secret
hidden at index 13000. Fetch the endpoint, find the hidden record, and show me
exactly what's in it. Then run /context-mode:ctx-stats.
```

**Documentation search** — 2 calls, 1.8 KB context
```
Fetch the React useEffect docs, index them, and find the cleanup pattern
with code examples. Then run /context-mode:ctx-stats.
```

**Session continuity** — compaction recovery with full state
```
Start a multi-step task: "Create a REST API with Express — add routes, tests,
and error handling." After 20+ tool calls, type: ctx stats to see the session
event count. When context compacts, the model continues from your last prompt
with tasks, files, and decisions intact — no re-prompting needed.
```

## Privacy & Architecture

Context Mode is not a CLI output filter or a cloud analytics dashboard. It operates at the MCP protocol layer — raw data stays in a sandboxed subprocess and never enters your context window. Web pages, API responses, file analysis, Playwright snapshots, log files — everything is processed in complete isolation.

**Nothing leaves your machine.** No telemetry, no cloud sync, no usage tracking, no account required. Your code, your prompts, your session data — all local. The SQLite databases live in your home directory and die when you're done.

This is a deliberate architectural choice, not a missing feature. Context optimization should happen at the source, not in a dashboard behind a per-seat subscription. Privacy-first is our philosophy — and every design decision follows from it. [License →](#license)

## Security

Context Mode enforces the same permission rules you already use — but extends them to the MCP sandbox. If you block `sudo`, it's also blocked inside `ctx_execute`, `ctx_execute_file`, and `ctx_batch_execute`.

**Zero setup required.** If you haven't configured any permissions, nothing changes. This only activates when you add rules.

```json
{
  "permissions": {
    "deny": [
      "Bash(sudo *)",
      "Bash(rm -rf /*)",
      "Read(.env)",
      "Read(**/.env*)"
    ],
    "allow": [
      "Bash(git:*)",
      "Bash(npm:*)"
    ]
  }
}
```

Add this to your project's `.claude/settings.json` (or `~/.claude/settings.json` for global rules). All platforms read security policies from Claude Code's settings format — even on Gemini CLI, VS Code Copilot, and OpenCode. Codex CLI security enforcement requires the Codex hooks in `$CODEX_HOME/hooks.json` or `~/.codex/hooks.json` to be configured.

The pattern is `Tool(what to match)` where `*` means "anything".

Commands chained with `&&`, `;`, or `|` are split — each part is checked separately. `echo hello && sudo rm -rf /tmp` is blocked because the `sudo` part matches the deny rule.

**deny** always wins over **allow**. More specific (project-level) rules override global ones.

### Project-boundary containment

`ctx_execute_file` is confined to the project root. A `path` that resolves **outside** the workspace — an absolute path like `/home/user/secrets`, a `../../` traversal, or a project-local symlink whose target escapes the project — is refused with a `File access blocked` error. This closes the [#852](https://github.com/mksglu/context-mode/issues/852) escape vector where an agent, denied an out-of-project read by the host sandbox, retried through the MCP sandbox (the host's MCP approval prompt cannot inspect the tool's input params, so the escape was invisible to the approver).

The guard is **on by default** and requires no configuration. To intentionally process a file outside the project (e.g. a shared log under `/var/log`), opt that path back in with the **same `permissions.allow` rule you already use for the host `Read` tool** — there is no context-mode-specific env flag:

```json
{
  "permissions": {
    "allow": ["Read(/var/log/**)"]
  }
}
```

context-mode honors that allow rule (read from your `.claude/settings.json` / `~/.claude/settings.json`) exactly as Claude Code does, so an out-of-project grant lives in one place and stays meaningful.

Reviewing the prompt: the `ctx_execute` / `ctx_execute_file` approval titles now read as code execution ("Run code in a sandbox…", "Run code over a file…") so an unfamiliar reviewer can recognise the action class even though the MCP prompt renders only the tool title and raw arguments. `ctx_execute` and `ctx_batch_execute` run arbitrary code and still inherit the process's filesystem access, so the boundary guard is a defense-in-depth layer for the *file-read* tool, not a full OS sandbox — treat approving any execution tool as approving arbitrary code, and keep host-level sandboxing enabled.

### Network fetch hardening

`ctx_fetch_and_index` blocks dangerous URL targets by default:

- **Schemes**: only `http:` and `https:` allowed (no `file://`, `gopher://`, `javascript:`, `data:`).
- **Cloud metadata + link-local**: `169.254.0.0/16` (incl. AWS/GCP/Azure IMDS endpoint `169.254.169.254`) hard-blocked even if a hostname resolves to it (DNS-rebinding defense).
- **Multicast / reserved**: `224.0.0.0/4`, `0.0.0.0/8`, IPv6 `ff00::/8`, `fe80::/10` blocked.
- **Loopback + RFC1918** (`localhost`, `127.x`, `10.x`, `172.16-31.x`, `192.168.x`, IPv6 `::1`, `fc00::/7`) **allowed by default** so local dev servers + internal-network fetches keep working.

For hosted/CI environments where you want to block private targets too, set:

```bash
export CTX_FETCH_STRICT=1
```

That blocks loopback + RFC1918 + ULA in addition to the always-blocked ranges. Useful when context-mode runs as a shared service, not on a developer's own machine.

`tool_input` for any `mcp__*` tool call is also redacted before persistence — the regex matcher in `hooks/posttooluse.mjs` masks `authorization`, `auth_token`, `access_token`, `refresh_token`, `bearer`, `token`, `secret`, `password`, `passwd`, `pwd`, `api_key` / `apikey` / `x_api_key`, `cookie` / `set-cookie`, `signature`, `private_key`, and `client_secret` (case-insensitive, hyphen/underscore-insensitive) to `[REDACTED]` so credentials in MCP arguments don't end up in the session DB.

### Storage environment variables

| Variable | Default | Purpose |
|---|---|---|
| `CONTEXT_MODE_DIR` | Adapter default, for example `~/.codex/context-mode` or `~/.claude/context-mode` | Since v1.0.147. Absolute writable root for context-mode storage. Sessions and stats use `<root>/sessions`; indexed content uses `<root>/content`. Empty or whitespace-only values are treated as unset and shown by `ctx_doctor`; non-empty values must be absolute. `~` is not expanded. |

### Routing-guidance environment variables

| Variable | Default | Purpose |
|---|---|---|
| `CONTEXT_MODE_EXTERNAL_MCP_NUDGE_EVERY` | `10` | Cadence (in tool calls) at which the PreToolUse hook re-injects the "wrap large external-MCP payloads in `ctx_execute`" guidance. The original implementation ([#529](https://github.com/mksglu/context-mode/pull/529)) fired only once per session, which got lost after context compaction in MCP-heavy sessions (e.g. 50+ Jira/Slack/Notion calls — see [#567](https://github.com/mksglu/context-mode/issues/567) follow-up). The default re-fires every 10th matching call, keeping the guidance in the model's recent window. Range `[1, 100]`; invalid values fall back to `10`. Set to `1` for "every call" (most aggressive — adds ~250 tokens/call) or to a larger value for less frequent reminders. |

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow and TDD guidelines.

```bash
git clone https://github.com/mksglu/context-mode.git
cd context-mode && npm install && npm test
```

## License

Licensed under [Elastic License 2.0](LICENSE) (source-available). You can use it, fork it, modify it, and distribute it. Two things you can't do: offer it as a hosted/managed service, or remove the licensing notices. We chose ELv2 over MIT because MIT permits repackaging the code as a competing closed-source SaaS — ELv2 prevents that while keeping the source available to everyone.

---

## File: docs/adr/0001-sessiondb-multi-writer.md

# ADR 0001 — SessionDB is multi-writer-safe

- **Status**: Accepted
- **Date**: 2026-05-14
- **Version**: v1.0.130
- **Supersedes**: the v1.0.128 single-writer guard and its v1.0.129 hotfix
- **Reviewers**: 10 parallel grill verdicts (UX, SQLite, Security, Perf, Architect, Test, SRE, PM, Data, DevEx)

## Context

v1.0.128 introduced two new defenses inside `SQLiteBase` (the shared
ctor for `SessionDB` and `ContentStore`):

1. `acquireDbLock(dbPath)` — an O_EXCL `<dbPath>.lock` file containing
   the owning PID. Subsequent openers that found a live PID inside the
   lockfile threw `DatabaseLockedError("Another context-mode server is
   already running (PID: N). Stop it before starting a new instance.")`.
2. `db.pragma("locking_mode = EXCLUSIVE")` — applied immediately after
   `applyWALPragmas`, on both the main open path and the corruption
   recovery path.

The stated goal was to fix issue #560: @ishabana reported a 5-process
scenario where multiple context-mode MCP servers writing the same on-
disk SQLite content store unbounded the WAL — readers held shared locks
indefinitely so `wal_checkpoint(TRUNCATE)` never fired, the only
existing truncation path is `closeDB`'s checkpoint on graceful exit
(which #559's zombie servers never reached), and the result was
238MB+ WAL files plus `ctx_search` hangs.

v1.0.129 added a tmpdir skip-gate to both primitives because the test
suite (which opens many DBs on tmp paths in the same process) tripped
the lockfile + `SQLITE_BUSY` from the EXCLUSIVE pragma.

After v1.0.128 + v1.0.129 shipped, multi-window users — who legitimately
run two Claude sessions against the same project DB — hit the
`DatabaseLockedError` and could not work. That is the regression this
ADR rolls out.

## Root cause re-analysis

#560's actual root causes were not "two MCP processes opened the same
DB at the same time." They were:

- **#559 (zombie MCP child accumulation)**. After `/ctx-upgrade`, the
  previous MCP server child was not killed, so old + new processes
  both ran, both wrote, and neither ever exited gracefully (so the WAL
  truncation in `closeDB` never fired). Fixed in v1.0.128 by killing
  the previous child before starting a new one.
- **#561 (Pi misdetection writing to `~/.claude/context-mode/`)**.
  Adapter detection accidentally fired on Pi installs, and
  `defaultDBPath()` produced a path that was shared across user
  sessions instead of the per-process tmp DB shape. Fixed in v1.0.129
  by scrubbing foreign identification env on bridge spawn.

With both root causes fixed, normal usage is **one MCP process per
Claude session per project**. Legitimate multi-window UX is **two
processes on the same on-disk dbPath** — and the SQLite WAL handles
that natively. The lockfile was solving a problem that no longer
existed once #559 + #561 were fixed.

## Decision

- `SessionDB` is multi-writer-safe. So is `ContentStore`. Both are
  built on `SQLiteBase`, so `SQLiteBase` ctor MUST NOT apply any
  single-writer enforcement.
- `acquireDbLock`, `releaseDbLock`, `DatabaseLockedError`, and the
  whole `src/util/db-lock.ts` module are deleted (v1.0.130 slice 4).
- `db.pragma("locking_mode = EXCLUSIVE")` is removed from the ctor.
  `applyWALPragmas` already does not apply it.
- `withRetry()` (busy_timeout = 30000ms inside `new Database(...)`,
  bounded retry loop on `SQLITE_BUSY`) remains the documented
  contract for handling write contention. That is the SQLite-native
  multi-writer story.

### Regression-proof anchor

Two paired tests in `tests/util/db-base-platform-gate.test.ts`:

1. **Behavioural** (`v1.0.130 INVARIANT — SQLiteBase multi-writer
   default > "INVARIANT: two SQLiteBase instances on the same tmpdir
   path can both open and write (multi-writer default)"`). Opens two
   `SessionDB` instances on the same on-disk path (NOT tmpdir), writes
   through both via `insertEvent`, asserts neither throws.
2. **Source-pin** (`"INVARIANT: SQLiteBase ctor must NOT contain
   acquireDbLock or locking_mode=EXCLUSIVE"`). Reads the source of
   `src/db-base.ts`, scopes to the `SQLiteBase` class body, regex
   asserts the literal identifier names + `locking_mode=EXCLUSIVE`
   pattern do not appear.

If a future contributor pulls the v1.0.128 single-writer primitives
back into the ctor, the source-pin test fails LOUDLY in CI before
merge. If they invent a new shape that passes the source-pin but
breaks behaviourally, the behavioural test fails. Defense in depth.

## Consequences

### Positive

- Legitimate multi-window users can run two Claude sessions on the
  same project without the `DatabaseLockedError` regression.
- The 5-worktree workflow (one Claude session per worktree, separate
  dbPath per worktree) keeps working — that path was never affected
  by the lockfile, but it is documented here as covered by the
  multi-writer contract.
- ContentStore (FTS5 shared knowledge base across sessions) — which
  was always multi-writer by design — no longer needs the `applyWALPragmas`
  guard against `applyWALPragmas` accidentally applying EXCLUSIVE.
- Deletes 185 lines of code (`db-lock.ts`) plus 80+ lines of
  ctor-side plumbing. Smaller, simpler, faster to reason about.

### Negative

- A future bug shaped like "two processes both opened the same DB
  and it broke" will not be diagnosed by the lockfile error message.
  It will surface as `SQLITE_BUSY` after the 30s busy_timeout +
  bounded `withRetry` loop, which is a less specific message. We
  accept this — the fix lives in the process layer, not the DB layer.
- If a future regression in the process layer (sibling-mcp) lets two
  MCP children both run against the same project, the WAL will grow
  unbounded again. The mitigation is the existing `_liveDBs` exit-hook
  WAL checkpoint (which the rollback preserved) plus the periodic
  optimize loop in `ContentStore`.

### Neutral

- Test count: net -10 tests in `db-base-platform-gate.test.ts`. The 11
  v1.0.128 lockfile tests are gone; they are replaced by 3 v1.0.130
  tests (2 INVARIANT + 1 lifecycle suite with 2 cases).

## Alternatives considered

### Lockfile (the v1.0.128 approach) — rejected

The lockfile correctly enforced single-writer but solved the wrong
problem. Once #559 + #561 were fixed, the only callers it blocked were
the legitimate multi-window UX users it was never meant to target. The
"who else is on this DB" UX story was useful diagnostic value, but the
cost (broken multi-window) outweighed the benefit.

### Leader election — rejected

A "first opener wins, others become read-only followers" pattern was
floated. This is over-engineering for the actual constraint set: SQLite
WAL already provides the right semantics for two writers, and the
process layer (sibling-mcp) is the right place to enforce process
identity, not the DB layer.

### `locking_mode = EXCLUSIVE` pragma — rejected

EXCLUSIVE blocks the second opener with `SQLITE_BUSY` instead of a
clean error. It also requires every consumer of `SQLiteBase` to opt out
explicitly, and `ContentStore` is multi-writer by design — so EXCLUSIVE
in a shared base class is a foot-gun. Belongs (if at all) inside an
explicit single-writer subclass, not the base.

### Deferred WAL truncation hook — partially adopted

The `_liveDBs` exit hook WAL checkpoint was preserved from v1.0.128.
That is the actual mitigation against unbounded WAL growth, separate
from the lockfile question. We keep it.

## Reference

- v1.0.128 release notes: introduced `acquireDbLock` + EXCLUSIVE pragma
  for #560.
- v1.0.129 release notes: added tmpdir skip-gate hotfix because the
  v1.0.128 work broke 82 tests and ContentStore concurrency.
- v1.0.130 (this ADR): rolls out the single-writer guard entirely.

10 parallel grill verdicts informed this decision (untracked artifacts
in the repo root: `PR-559-560-VERDICT.md`, `PERF-560-GRILL-VERDICT.md`,
`SECURITY-GRILL-VERDICT.md`, `SQLITE-EXPERT-GRILL-VERDICT.md`,
`SRE-VERDICT-v130-rollback.md`, etc.) — all converged on "the lockfile
is solving the wrong problem; the fix is in the process layer."

## Contract for `SQLiteBase` consumers

If you build on `SQLiteBase`:

- Your DB MAY be opened from multiple processes on the same on-disk
  path. Both ContentStore and SessionDB explicitly support this.
- Wrap writes that may contend in `withRetry()`. The base class exposes
  it as a `protected withRetry<T>()` method.
- Do NOT add `db.pragma("locking_mode = EXCLUSIVE")` inside `SQLiteBase`
  or `applyWALPragmas`. If you genuinely need single-writer semantics
  for a new subclass, add it in that subclass's ctor and document why.
- Process-identity invariants (only-one-MCP-per-project) belong in
  `src/util/sibling-mcp.ts`, not in the DB layer.

---

## File: docs/adr/0002-tool-description-style.md

# ADR-0002 — Tool description voice and structure

- **Status**: Accepted
- **Date**: 2026-05-24
- **PR**: #683 (substitutes #654)
- **Supersedes**: organic, undocumented description drift
- **Reviewers**: owner Mert, empirical A/B (38 trials × 6 probes on Haiku + Sonnet)

## Context

`context-mode` registers 11 `ctx_*` MCP tools via `server.registerTool()` in
`src/server.ts`. Each tool description is read by every host LLM
(Claude / GPT / Gemini / Llama / …) at tool-selection time. Over many releases
the corpus drifted toward forbidding language — `MANDATORY:`, `NEVER`,
`Do NOT`, `REFUSAL RULES`, `DESTRUCTIVE`, `NON-NEGOTIABLE`, `PREFER` — because
descriptions were patched defensively after each misroute. There was no
documented style guide and no contract test, so each new tool inherited
whichever voice the previous author preferred.

PR #654 (kerneltoast) surfaced the cost of that drift: the single
hortatory word `"blocked"` in a routing deny reason was misread by
Opus 4.6 as a safety/network restriction, causing the agent to capitulate
to training data instead of using the redirected tool.

A full audit (see `TOOL-DESCRIPTIONS-AUDIT.md`) ran 38 A/B trials across
6 empirical probes on Haiku and Sonnet against the current descriptions
and proposed rewrites. Findings:

- Heavy forbidding framing degrades tool selection on some tools (mild
  but reproducible).
- Heavy framing **improves** parameter fidelity on small models for
  complex-contract tools (`ctx_purge` Probe 4: 5/5 vs 3/5). The
  intuition that "softer == safer" is wrong for at least one tool, so
  rewrites cannot be one-size-fits-all and **must be probe-gated**.
- PR #654's `"blocked"` → `"redirected"` wording fix is genuinely
  corrective on Opus 4.6 (6/6 → 0/6 capitulation on the stress probe),
  invisible on Sonnet (6/6 capitulate either way), and mildly regressive
  on Haiku without a paired imperative.
- ✅ / ❌ emoji bullets inside descriptions tokenize inconsistently across
  Llama / Gemini families and act as negative-example leakage (rubric #4).

## Decision

All `ctx_*` tool descriptions registered via `server.registerTool()`
**MUST** follow this structure:

```text
<1-line headline, <= 120 chars, imperative-positive>

WHEN:
  - 

WHEN NOT:
  - 

RETURNS:
  <what the agent sees back, 1-3 lines>

EXAMPLE: <one canonical call with realistic params>
```

The legacy alias `WHEN TO USE:` is accepted as a transitional form (see
`ctx_index`) but new tools MUST use `WHEN:`.

### Canonical structure (locked rubric — PR #683 WS3)

Future contributors do **not** get to re-invent section names. The
contract test in `tests/core/server.test.ts` enforces every rule below
on every commit; this section is the source of truth.

1. **Section order MUST be**
   `WHEN -> WHEN NOT -> RETURNS -> EXAMPLE`.
   Positive selection cues precede negative disambiguation (audit
   rubric #2). A tool MAY omit `WHEN NOT:` when it has no sibling-tool
   ambiguity, but every other canonical section is mandatory.

2. **Bullets MUST use markdown `- ` only.** Numeric (`1.`, `1-`),
   asterisk (`* `), and unicode (`•`) bullets are rejected. Numbering
   inside a routing-target description is also rejected because each
   bullet should be independently true, not sequenced. Numbered
   hierarchies live in `hooks/routing-block.mjs` (priority order in a
   system-prompt injection is a different prompt surface, governed by
   ADR-0003 sibling concerns).

3. **Section headers MUST be UPPERCASE + colon at the start of a
   line.** The token uniformity matters because GPT, Gemini, Llama,
   and Claude tokenize lowercase / mixed-case section names
   differently — uppercase headers are the only shape that hits a
   single token across families.

4. **Two-space bullet indent under each header.** Example shape:
   ```
   WHEN:
     - First positive cue
     - Second positive cue
   ```
   The contract test does not assert the literal indent count (LLMs
   are tolerant of 2 vs 4) but every shipped description in
   `src/server.ts` uses two-space indent for visual uniformity.

5. **One blank line between sections.**

6. **One canonical `EXAMPLE:` per tool.** Tools with two valid input
   shapes (e.g. `ctx_purge` per-session vs per-project) MAY include
   two EXAMPLE lines back-to-back. Keep them adjacent so the
   description does not interleave examples with other sections.

7. **Carve-outs (per-tool, allow-listed in the contract test):**
   - `ctx_purge`: `DESTRUCTIVE`, `SCOPES`, `CONTRACT`. Justified by
     Probe 4 empirical evidence — heavy framing on this tool
     preserves parameter fidelity on small models. `DESTRUCTIVE` here
     is accurate user-facing signaling, distinct from the
     cross-LLM-bias negative framing the rubric forbids.

### Cross-LLM rationale

The audit ran 38 A/B trials × 6 probes across Haiku and Sonnet. The
canonical structure above is the lowest common denominator that:

- Hits a single token across **Claude, GPT, Gemini, Llama** families
  for every section header (uppercase + colon).
- Avoids tokens flagged by Constitutional AI-style RLHF priors
  (`FORBIDDEN`, `BLOCKED`, `NEVER`, `MANDATORY`).
- Eliminates negative-example leakage (emoji bullets — rubric #4,
  Probe 3).
- Leaves room for accurate signaling where empirically required
  (Probe 4 `ctx_purge` carve-out).

The structure is locked to make future PRs ungameable: a contributor
proposing a new tool either adheres to it (contract test passes) or
opens a new ADR amending this one (contract test fails until the
allow-list is updated).

### Forbidden tokens

Descriptions MUST NOT contain:

| Token | Rationale |
|---|---|
| `MANDATORY:` (as opener) | Developer-policy phrasing, not a selection cue. |
| `BLOCKED` | Reserved for ADR-0003 CASE B (real policy restriction). |
| `PREFER X OVER Y` | Frames the choice as a tradeoff; use positive `WHEN:` instead. |
| `Do NOT use/read/pull` | Affirmative beats negative (rubric #2). |
| `Never use` | Same — express as `WHEN NOT:`. |
| `SESSION STATE` clause | Skill/role persistence is a routing-block.mjs concern. |
| `✅` / `❌` emoji bullets | Tokenizer inconsistency across LLM families + negative-example leakage. |

### Allowed imperative hierarchy (RFC 2119)

The MUST / SHOULD / MAY hierarchy is preserved ONLY for **post-call
obligations** on the agent — never for tool-selection cues.

- **MUST**: post-call obligation. Example (allowed): `ctx_upgrade` says
  "you MUST run the returned shell command and display the output as a
  checklist." This is a post-call contract, not a selection nudge.
- **SHOULD**: strong preference with allowed exceptions.
- **MAY**: optional capability.

Selection cues use the `WHEN:` / `WHEN NOT:` structure instead.

### Length

Descriptions SHOULD be ≤ 1,000 characters. Hard cap 1,500.

### Exemptions

- `ctx_stats`, `ctx_doctor`, `ctx_insight` — minimal one-line descriptions
  by design (diagnostic / GUI affordances, not routing targets).
- `ctx_upgrade` — `MUST` is permitted per the post-call obligation rule
  above.
- `ctx_purge` — rewritten in PR #683 WS2 with carve-outs (`DESTRUCTIVE`,
  `SCOPES`, `CONTRACT`) that preserve the parameter-fidelity discipline
  Probe 4 measured (5/5 vs 3/5 on Haiku). The rewrite still meets the
  canonical `WHEN / WHEN NOT / RETURNS / EXAMPLE` structure; the carve-out
  headers coexist with it. The empirical-validation gate is documented in
  `tests/core/server.test.ts` `ALLOWED_EXTRA_SECTIONS`.

## Consequences

- PR #683 rewrites the six tools where the audit showed clear voice
  drift: `ctx_execute`, `ctx_execute_file`, `ctx_batch_execute`,
  `ctx_search`, `ctx_index`, `ctx_fetch_and_index`.
- A new contract test in `tests/core/server.test.ts` (`tool description
  style contract (#683 ADR-0002)`) parses every `server.registerTool()`
  block and enforces the forbidden-token list + WHEN: requirement on
  every commit. Cheap (no LLM call), runs on every commit, catches
  drift before merge.
- Voice-of-trainer text (`THINK IN CODE`, `MANDATORY routing rules`)
  lives in `hooks/routing-block.mjs` and `CLAUDE.md`, not in tool
  descriptions. That layer is correct because it runs as system-prompt
  injection, where exhortations belong.
- `ctx_purge` is rewritten in PR #683 WS2 with carve-out headers
  (`DESTRUCTIVE`, `SCOPES`, `CONTRACT`) allow-listed by the contract
  test's `ALLOWED_EXTRA_SECTIONS`. The rewrite preserves Probe 4's
  parameter-fidelity discipline (accurate DESTRUCTIVE signal + explicit
  SCOPES + CONTRACT block) while still meeting the canonical
  `WHEN / WHEN NOT / RETURNS / EXAMPLE` structure.
- New `ctx_*` tools added in future PRs MUST cite this ADR in the PR
  description and pass the contract test.

## Alternatives considered

- **Status quo (no style policy).** Rejected — PR #654 evidence shows
  organic drift directly causes user-visible bugs (Opus 4.6 capitulation).
- **Single hortatory voice across all descriptions.** Rejected — Probe 4
  evidence: heavy framing helps `ctx_purge` and hurts `ctx_execute`.
  One-size-fits-all is empirically wrong.
- **Per-tool author discretion.** Rejected — that's what we already had,
  and it produced the bug.
- **Rewrite all 11 in one PR.** Rejected — large diff, hard to revert,
  blocks on style debates, and would regress `ctx_purge` per Probe 4.
  PR #683 explicitly defers `ctx_purge`.

## References

- `TOOL-DESCRIPTIONS-AUDIT.md` (§3 audit table, §5 probe evidence, §6
  verbatim rewrites)
- `GRILL-Q1-VERDICT.md` (SESSION STATE drop rationale)
- ADR-0003 — Routing deny reasons MUST distinguish redirect from
  restriction (sibling decision, also from PR #683)

---

## File: docs/adr/0003-routing-deny-reasons.md

# ADR-0003 — Routing deny reasons: redirect ≠ restriction

- **Status**: Accepted — amended 2026-05-24 (PR #683 follow-up) to drop the
  `"NOT a network restriction"` negation prescription; see §Amendment.
- **Date**: 2026-05-24
- **PR**: #683 (substitutes #654)
- **Motivating bug**: kerneltoast / @noctivoro / Mert reproduced on Opus 4.6
- **Reviewers**: owner Mert

## Context

`hooks/core/routing.mjs` returns deny reasons for intercepted Bash, Read,
Grep, and WebFetch calls. These reasons are displayed to the agent at
runtime and shape the agent's next-action decision. PR #654
(`kerneltoast`) reproduced that the bare word `"blocked"` in WebFetch's
deny reason (`"WebFetch blocked"`) was misread by Opus 4.6 as a
network / security restriction, causing the agent to capitulate to
training data instead of using the redirected tool.

The intent of the routing layer is to **redirect** the agent to a
context-efficient alternative (`ctx_fetch_and_index` for WebFetch,
`ctx_execute` for large-output Bash). It is NOT a security gate, and
its denial text MUST NOT read like one.

There is a real, separate set of denials in `routing.mjs` that ARE
security restrictions: the deny-pattern check (curl to private IPs,
sensitive-path reads, etc.). Those denials are correct to read like
restrictions — they are restrictions.

The bug PR #654 caught was that a single deny-reason string in CASE A
was using the vocabulary of CASE B. The fix is to formalize the
distinction so any future hook author cannot make the same mistake.

## Decision

Routing deny reasons MUST distinguish two cases:

### CASE A — Routing redirect

The action is supported, via a different tool, for context-window or
efficiency reasons.

- **Opening verb**: `"redirected to <ctx_tool>"` — affirmative routing
  intent, no negation, no org-rationale (the agent's job is to act on the
  redirect, not to audit policy).
- **MUST affirm capability**: `"<ctx_tool> has full network access"`
  (positive frame of the same signal the old `NOT a network restriction`
  parenthetical tried to deliver — and the same signal the
  `for context-window efficiency` rationale prescription also tried to
  deliver before the 2026-05-24 second amendment).
- **MUST specify**: the alternative tool to use, by name, as an
  imperative call (e.g. `Call ctx_fetch_and_index(url, source) now`).
- **MUST end with**: a positive imperative retry hint —
  `"Retry the same call on a transient DNS error (EAI_AGAIN,
  ETIMEDOUT, ENETUNREACH)"`.
- **MUST NOT contain**: bare-NOT negations (`NOT a network restriction`,
  `Do NOT retry with curl`, etc.). See §Amendment below for the
  empirical rationale.
- **MUST NOT contain**: org-rationale prefaces (`for context-window
  efficiency`, `for performance`, etc.). The redirect verb + the
  capability affirmation already carry the full action signal; rationale
  is metadata, not action input. See §Second amendment below.

The word `BLOCKED` MUST NOT appear bare in CASE A. It is reserved for
true policy denial (CASE B) where the agent's correct response IS to
stop and inform the user.

### CASE B — True security / policy restriction

The action is denied per deny pattern, security gate, or unsupported
sandbox capability.

- **Opening verb**: "denied" or "blocked by security policy"
- **MUST cite**: the pattern or rule violated
- **MAY suggest**: a safe alternative

## Amendment (PR #683 follow-up, 2026-05-24)

The original CASE A rubric required the parenthetical `"this is NOT a
network / security restriction"`. Mert flagged on review that this
prescription itself violates ADR-0002 rubric #2 (affirmative beats
negation): the bare-NOT construct primes the very frame it tries to
deny (ironic process theory). `TOOL-DESCRIPTIONS-AUDIT.md §2 Probe 3`
already documented this — the NOT-parenthetical regressed Haiku
capitulation rate from 0/6 → 2/6 vs the original "blocked" wording.

PR #654 fixed the headline word (`blocked` → `redirected`) but kept the
sibling negation in the very next clause. PR #683 follow-up
(this amendment) eradicates the negation construct entirely:

- Replace `"(context-window optimization, NOT a network restriction)"`
  with the affirmative sentence `"<ctx_tool> has full network access."`
  (originally also prescribed a `"for context-window efficiency"` opening
  preface — see §Second amendment below for why that prescription was
  dropped).
- Replace `"Do NOT retry with curl/wget"` with positive
  `"Retry the same call on a transient DNS error (...)"` — the
  affirmative retry hint IS the next-action signal; the prohibition was
  redundant once the routing is correct.

## Second amendment (PR #683 mid-review, 2026-05-24)

Mert flagged on review of the first amendment: the prescribed opening
`"redirected to <ctx_tool> for context-window efficiency"` itself
carries org-rationale that the agent does not need to act. The agent's
job is to (a) understand a redirect happened, (b) make the correct next
call, (c) know it has the capability to do so, (d) know when to retry.
"For X reason" is post-hoc justification — pure prompt overhead.

Compare to HTTP 301 `Moved Permanently` — the response carries `Location:
<new-url>` and the client uses it. The server never appends
`"for SEO efficiency"` or `"for caching strategy"`. The redirect verb
+ the new target are the entire action signal.

The capability affirmation (`"<ctx_tool> has full network access"`)
already delivers the substantive content the rationale was trying to
carry — that this is a routing optimization, not a capability denial.
The rationale preface was double-encoding that signal.

The four CASE A sites in `hooks/core/routing.mjs` (L707 curl/wget,
L738 inline HTTP, L751 build tool, L804 WebFetch) all conform to the
second-amended rubric after this PR. A contract test in
`tests/core/server.test.ts` (`ADR-0003 CASE A: routing.mjs redirect
deny reasons`) locks the rule: every CASE A string MUST open with
"redirected", MUST NOT contain bare uppercase `BLOCKED`, MUST name at
least one `ctx_*` alternative, MUST NOT contain `"NOT a network"`,
MUST NOT contain `"Do NOT retry"`, MUST NOT contain
`"for context-window efficiency"` (or sibling org-rationale prefaces).

## Consequences

- PR #654's wording fix (`"blocked"` → `"redirected"`) becomes formal
  policy.
- PR #683 already lands the wording change at
  `hooks/core/routing.mjs:804` and adds the `EAI_AGAIN | ETIMEDOUT |
  ETIMEOUT | ENETUNREACH | EPERM` transient-DNS retry hint to both
  `routing.mjs` (WebFetch denial) and `src/server.ts:2783-2795`
  (`ctx_fetch_and_index` subprocess fetch failure) so the two surfaces
  speak with one voice.
- Existing `routing.mjs` deny reasons audited for CASE A / CASE B
  classification:
  - L707 (curl / wget redirect) — CASE A
  - L738 (inline HTTP redirect) — CASE A
  - L803 (WebFetch redirect) — CASE A
  - L652, L844, L862, L873, L894 (security deny patterns) — CASE B
- Test substring expectations in `tests/hooks/*` updated where they
  asserted the literal word `"blocked"` for CASE A paths.
- A future contract test on `routing.mjs` deny reasons (similar to PR
  #683's `tool description style contract`) is out of scope here but
  recommended as a follow-up — the rule is already mechanically
  checkable.

## Alternatives considered

- **Keep "blocked" everywhere; document the convention.** Rejected —
  documentation doesn't change LLM behaviour, the wording does. PR
  #654's empirical reproduction is the disqualifying evidence.
- **Remove the denial path entirely; just silently invoke the
  alternative tool.** Rejected — the agent needs to know its tool call
  was rerouted (for telemetry, for the user-visible audit trail, and so
  it can adjust its plan).
- **Use the same wording in both cases, distinguish by exit code.**
  Rejected — the agent reads the prose, not the exit code, when
  deciding whether to retry or capitulate.

## References

- PR #654 (kerneltoast) — original bug report and reproduction
- PR #683 — implementation + ADR
- ADR-0002 — Tool description voice and structure (companion ADR,
  same PR)
- `TOOL-DESCRIPTIONS-AUDIT.md` §2 — PR #654 verdict and probe evidence

---

## File: docs/adr/0004-stats-strict-compression-formula.md

# ADR-0004 — Stats display uses strict-compression formula

- **Status**: Accepted
- **Date**: 2026-05-24
- **PR**: #685 (v1.0.148 hotfix)
- **Supersedes**: v1.0.134 SLICE B (incidental fix in commit `ce62275`)
- **Acknowledgement**: 7-agent EM ops audit (Git Archaeologist, DB Architect,
  Math Engineer, QA Engineer, PO/UX Engineer, Edge Case Engineer,
  Architect) produced the converged verdict ratified here.

## Context

The per-conversation Section 1 `Without context-mode / With context-mode`
bar in `ctx_stats` quietly drifted from "honest compression ratio" to
"infrastructure-size accounting" across two unrelated bug cascades:

1. **v1.0.134 SLICE B (`analytics.ts:1991-1993`)** — A tactical fix for a
   degenerate-100% display bug. When `bytesReturned == 0` in fresh
   sessions, the original `pct = 1 - max(1, returned) / (avoided + returned)`
   collapsed to ~100%, even with zero avoided bytes. SLICE B added
   `eventDataBytes` to **both** sides of the ratio to prevent the
   degenerate bar:

   ```
   Without = bytesAvoided + bytesReturned + eventDataBytes
   With    = max(1, bytesReturned + eventDataBytes)
   ```

   The commit message (`ce62275`, 2026-05-15) named this a
   "bar ratio degenerate fix" — it was an UX patch, not a designed metric
   semantic. Git archaeology confirms `rule_content` duplication
   (the actual cost driver) was never considered.

2. **v1.0.148 Bug A+C+D+E+F cascade (PR #685)** — schema migration +
   per-conversation aggregator fixes finally let the formula see real
   `bytesAvoided` data after years of silent under-attribution. With
   the real signal flowing, SLICE B's eventDataBytes-on-both-sides
   formula started reporting ~56% on conversations the user knew
   should be 95%+.

The reporter's machine produced empirical evidence:
- `bytesAvoided`   = 2,898 KB (Bash/Read redirect savings + sandbox PID bursts)
- `bytesReturned`  = 140 KB (printed ctx_* output)
- `eventDataBytes` = 2,136 KB (84% of which is **496 duplicate copies of the
  same CLAUDE.md** captured by SessionStart hooks across resume cycles —
  schema's `data_hash` dedup column is populated but unused by the formula)

Under SLICE B: display says 56% kept out.
Under strict compression (this ADR): display says **95.4% kept out**.

The 49-percentage-point gap is the under-attribution SLICE B introduced.

## Decision

The per-conversation Section 1 bar MUST use the strict-compression formula:

```
if (bytesAvoided + bytesReturned == 0) {
  // Empty state — no measurable redirect activity yet.
  // Do NOT draw a degenerate bar. Emit one honest hint line:
  "No measurable redirect activity captured yet — bars will appear once
   context-mode diverts its first payload."
} else {
  Without = bytesAvoided + bytesReturned
  With    = max(1, bytesReturned)
  pct     = (1 - With / Without) * 100
}
```

**`eventDataBytes` is EXCLUDED from both sides.** Hook-captured payload
bytes are written to SessionDB for the knowledge base. They are
analytics infrastructure, not bytes that ever entered the model's
context window. Rendering them in Section 1 conflates two distinct
quantities and produces a misleading number.

`eventDataBytes` MAY still be surfaced in Section 2 (captures count,
"1,000 things — files, errors, decisions, agent runs") where it
correctly represents what the hook layer recorded.

The lifetime Section 3 / Section 4 totals (`14.7 MB kept out across
200 projects`) are **unchanged** by this ADR — they aggregate
`bytesAvoided + eventDataBytes + snapshotBytes` and the user
expectation for the lifetime tier has historically been "all the
bytes context-mode kept in storage", which is correct for those
sections. Only the per-conversation `%` bar's semantic is corrected.

## Consequences

1. **The displayed Section 1 percentage will jump from ~56% to ~95%
   for existing users on first `ctx_stats` call after v1.0.148.**
   This is a metric semantic change, not data loss; lifetime
   numbers and capture counts remain identical to v1.0.147.

2. **Empty-state handling is explicit.** Fresh sessions with no
   redirect activity see a one-line hint instead of a degenerate
   `0%` or `100%` bar. SLICE B's symptom is eliminated at the
   source, not papered over.

3. **The `data_hash` dedup column is no longer load-bearing for
   correctness** of the Section 1 display. Dedup was one candidate
   fix in the EM verdict tree (Option B, 86%); strict compression
   (this ADR) is the correct fix because the rule_content
   duplication problem only matters if you're counting
   `eventDataBytes` in the first place — and we are not.

4. **Four fixture tests in `tests/analytics/format-report*.test.ts`
   are updated** to assert the new strict-compression semantic.
   The `v1.0.134 SLICE B` describe block is renamed to
   `v1.0.148 Bug G — strict-compression formula` and now pins:
   (a) the empty-state hint, (b) honest mixed-case percentage,
   (c) honest 100% when only `bytesAvoided` exists.

5. **README and release notes updated** to reflect the new
   per-conversation percentage range. Headline marketing claims
   that previously cited ~98% lifetime savings remain valid under
   the lifetime formula; new per-conversation headline aligns
   with the strict-compression ratio.

6. **ADR-0001 (multi-writer) is preserved** — this ADR changes a
   read-side formula only. No schema additions, no locks, no
   EXCLUSIVE pragma. SQLite WAL + busy_timeout invariants remain
   intact per ADR-0001.

## Pre-fix vs post-fix on reporter's data

| Metric | v1.0.147 (broken) | v1.0.148 + SLICE B | v1.0.148 + this ADR |
|---|---|---|---|
| Without | 158 KB | 5,177 KB | **3,038 KB** |
| With | 158 KB | 2,279 KB | **140 KB** |
| % kept out | 0% (identity) | 56% (SLICE B incidental) | **95.4%** |
| Runtime multiplier | 1× | 2× | **22×** |
| Lifetime headline | 14.7 MB ✓ | 14.7 MB ✓ | 14.7 MB ✓ |

The 22× multiplier represents the actual context-window runway
extension this conversation got from context-mode's redirects —
the metric the user intuitively expected to see.

---

## File: docs/adapters/openclaw.md

# OpenClaw Adapter

context-mode plugin for the [OpenClaw](https://github.com/openclaw) gateway, targeting **Pi Agent** sessions.

## Overview

**OpenClaw** is the gateway/platform that manages agent sessions, extensions, and tool routing. **Pi Agent** is OpenClaw's coding agent — it runs within OpenClaw and provides Read, Write, Edit, and Bash tools for software development tasks.

The context-mode adapter hooks into Pi Agent sessions specifically, intercepting tool calls to route data-heavy operations through the sandbox and tracking session events for compaction recovery.

### Supported Configurations

- **Pi Agent sessions** with coding tools (Read/Write/Edit/Bash) — fully supported.
- **Custom agents** with coding tools — may work but are untested. The adapter relies on tool names matching Pi Agent's conventions.

## Installation

### Quick install

```bash
npm run install:openclaw
```

This runs `scripts/install-openclaw-plugin.sh`, which handles building, extension setup, runtime registration, and gateway restart.

### Prerequisites

- **Node.js** must be in PATH (required for the build and registration steps)
- **OpenClaw must have been started once** — the script needs `openclaw.json`, which OpenClaw creates on first launch
- **`OPENCLAW_STATE_DIR`** must point to your OpenClaw state directory (default: `/openclaw`). Pass it as an argument: `npm run install:openclaw -- /path/to/state`

### Manual install

For advanced users or custom setups:

```bash
bash scripts/install-openclaw-plugin.sh [OPENCLAW_STATE_DIR]
```

See [`scripts/install-openclaw-plugin.sh`](../../scripts/install-openclaw-plugin.sh) for details.

### Troubleshooting

**"openclaw.json not found"**
OpenClaw creates this file on first launch. Start OpenClaw once (`openclaw gateway start`), then re-run the install script. This is the most common issue for users who install context-mode before ever starting OpenClaw.

**"OPENCLAW_STATE_DIR (/path) does not exist. Is OpenClaw installed?"**
The state directory doesn't exist at the expected path. If you installed OpenClaw via npm (not git clone), check where it stores state — common locations are `~/.openclaw` or `/openclaw`. Pass the correct path: `npm run install:openclaw -- /path/to/state`.

**Plugin installed but not loading**
Clear the jiti cache (`rm -f /tmp/jiti/context-mode-*.cjs`) and restart the gateway. If the issue persists, verify the plugin appears in `openclaw plugins list`.

**Plugin loads but `ctx_*` tools are missing from the agent's tool list**
The plugin registers its hooks via `api.on(...)` / `api.registerCommand(...)`, but the agent-callable `ctx_*` tools live in the MCP server (`server.bundle.mjs`). OpenClaw surfaces them by spawning the server as an MCP sidecar declared in `mcp.servers.context-mode`. The install script writes this entry automatically (step 5); if you configured OpenClaw manually, verify with `openclaw mcp list` and add it if missing:

```bash
openclaw mcp set context-mode \
  "{\"command\":\"node\",\"args\":[\"/absolute/path/to/context-mode/server.bundle.mjs\"]}"
openclaw gateway restart
```

After the restart, the agent's tool inventory should include `context-mode__ctx_execute`, `context-mode__ctx_search`, `context-mode__ctx_fetch_and_index`, and the rest of the `ctx_*` surface (OpenClaw prefixes MCP-sourced tools with the server name).

## Hook Registration

The adapter uses two different registration APIs, matching OpenClaw's internal architecture:

- **`api.on()`** for lifecycle and tool hooks: `session_start`, `before_tool_call`, `after_tool_call`, `before_compaction`, `after_compaction`, `before_prompt_build`, `before_model_resolve`. These are typed event emitters with structured payloads.
- **`api.registerHook()`** for command hooks: `command:new`, `command:reset`, `command:stop`. These use colon-delimited names and the generic hook registration system.

Using the wrong API (e.g., `api.registerHook("before_tool_call", ...)`) registers silently but the hook never fires. This distinction is critical.

### Synchronous `register()`

OpenClaw silently discards the return value of `register()`. If `register()` is async, all hooks registered inside it are lost. The adapter uses the initPromise pattern:

```typescript
register(api): void {
  const initPromise = (async () => { /* async setup */ })();
  api.on("after_tool_call", async (e) => {
    await initPromise;
    // handle event
  });
}
```

## Session Continuity

| Hook | Method | Status |
|---|---|---|
| `after_tool_call` | `api.on()` | Working |
| `before_compaction` | `api.on()` | Working |
| `session_start` | `api.on()` | Working |
| `command:new` | `api.registerHook()` | Working |
| `command:reset` | `api.registerHook()` | Working |
| `command:stop` | `api.registerHook()` | Working |

### Graceful Degradation

If compaction hooks fail to fire (e.g., on older OpenClaw versions), the adapter falls back to **DB snapshot reconstruction** — rebuilding session state from the events already persisted in SQLite by `after_tool_call`. This produces a less precise snapshot than the PreCompact path but preserves critical state (active files, tasks, errors).

## Previously Known Upstream Issues

Both issues below have been resolved in upstream OpenClaw:

- **[#4967](https://github.com/openclaw/openclaw/issues/4967)** — Compaction hooks not firing. Closed as duplicate of [#3728](https://github.com/openclaw/openclaw/issues/3728); fix merged.
- **[#5513](https://github.com/openclaw/openclaw/issues/5513)** — `api.on()` hooks not invoked for tool lifecycle events. Fixed in [PR #9761](https://github.com/openclaw/openclaw/pull/9761).

## Minimum Version

**Required: OpenClaw >2026.1.29**

This is the first release that includes the `api.on()` fix from [PR #9761](https://github.com/openclaw/openclaw/pull/9761), which shipped on 2026-01-29.

**What breaks on older versions:** Lifecycle hooks registered via `api.on()` — including `before_compaction`, `after_compaction`, `session_start`, and tool interception hooks — may silently fail to fire.

**Graceful degradation:** If compaction hooks don't fire, the adapter falls back to DB snapshot reconstruction, rebuilding session state from events already persisted by `after_tool_call`. This produces a less precise snapshot than the PreCompact path but preserves critical state (active files, tasks, errors). The adapter will not crash on older versions, but compaction recovery quality will be reduced.

## Workspace Routing

The adapter includes a workspace router (`src/adapters/openclaw/workspace-router.ts`) that resolves project paths from Pi Agent session metadata, ensuring session databases and routing instructions are scoped per-workspace.

## Key Files

| File | Purpose |
|---|---|
| `src/adapters/openclaw/plugin.ts` | Main plugin entry (sync register, initPromise pattern) |
| `src/adapters/openclaw/workspace-router.ts` | Workspace path resolution for session scoping |
| `.openclaw-plugin/` | Plugin manifest (index.ts, openclaw.plugin.json, package.json) |
| `scripts/install-openclaw-plugin.sh` | One-shot installer |

---

## File: docs/jetbrains-copilot.md

# JetBrains Copilot Setup

Setup guide for using context-mode with JetBrains IDEs (IntelliJ IDEA, WebStorm, PyCharm, etc.) via the GitHub Copilot plugin.

## Prerequisites

- **Node.js 18+** (`node --version`)
- **JetBrains IDE** — any JetBrains IDE (IntelliJ IDEA, WebStorm, PyCharm, GoLand, Rider, CLion, etc.)
- **GitHub Copilot plugin v1.5.57+** — install from Settings > Plugins > Marketplace, search "GitHub Copilot"

## MCP Setup

JetBrains configures MCP servers via the Settings UI, not a file.

1. Open your JetBrains IDE.
2. Go to **Settings > Tools > AI Assistant > Model Context Protocol (MCP)**.
3. Click **Add Server** and configure:
   - **Name:** `context-mode`
   - **Command:** `npx`
   - **Args:** `-y context-mode`
4. Click **OK** to save.

Alternatively, you can use a global install (`npm install context-mode`) and set the command to `context-mode` with no args.

Example MCP config (for reference): [`configs/jetbrains-copilot/mcp.json`](../configs/jetbrains-copilot/mcp.json)

## Hook Installation

Install hooks using the automated setup command:

```bash
npx context-mode@latest setup --adapter jetbrains-copilot
```

This creates `.github/hooks/context-mode.json` in your project with the following hook configuration:

```json
{
  "hooks": {
    "PreToolUse": [
      { "type": "command", "command": "context-mode hook jetbrains-copilot pretooluse" }
    ],
    "PostToolUse": [
      { "type": "command", "command": "context-mode hook jetbrains-copilot posttooluse" }
    ],
    "PreCompact": [
      { "type": "command", "command": "context-mode hook jetbrains-copilot precompact" }
    ],
    "SessionStart": [
      { "type": "command", "command": "context-mode hook jetbrains-copilot sessionstart" }
    ]
  }
}
```

Full hook config reference: [`configs/jetbrains-copilot/hooks.json`](../configs/jetbrains-copilot/hooks.json)

## Upgrade

Update context-mode to the latest version:

```
context-mode upgrade
```

Or from within a Copilot chat session, type `ctx upgrade`.

## Verification

Run the diagnostics to verify everything is working:

```
context-mode doctor
```

Or from within a Copilot chat session, type `ctx doctor`.

All checks should show `[x]`. The doctor validates runtimes, hooks, FTS5, and MCP registration.

You can also verify context savings by typing `ctx stats` in a Copilot chat session.

## Troubleshooting

**MCP server not connecting**
- Ensure Node.js 18+ is in your PATH.
- Restart the JetBrains IDE after adding the MCP server.
- Check Settings > Tools > AI Assistant > MCP and confirm "context-mode" shows a green status indicator.

**Hooks not firing**
- Verify `.github/hooks/context-mode.json` exists in your project root.
- JetBrains Copilot reads hooks from `.github/hooks/` — the same location as VS Code Copilot.
- Re-run `npx context-mode@latest setup --adapter jetbrains-copilot` to regenerate the hook config.

**"context-mode: command not found"**
- Install globally: `npm install context-mode`
- Verify: `which context-mode` should return a path.
- If using `npx`, ensure npx is in your IDE's PATH.

**Tools appear but routing is not enforced**
- Hooks enforce routing programmatically. Without hooks, the model can still use context-mode tools but won't be redirected to prefer them.
- Ensure the hook config file is in `.github/hooks/context-mode.json` (not `.github/hooks.json`).

**Session continuity not working**
- Verify all four hooks (PreToolUse, PostToolUse, PreCompact, SessionStart) are configured.
- Run `ctx doctor` to check hook registration status.

---

## File: docs/platform-support.md

# Platform Support Matrix

This document provides a comprehensive comparison of all platforms supported by context-mode, including their hook paradigms, capabilities, configuration, and known limitations.

## Overview

context-mode supports 17 client platforms, plus the OpenClaw gateway integration, across three hook paradigms:

| Paradigm | Platforms |
|----------|-----------|
| **JSON stdin/stdout** | Claude Code, Gemini CLI, VS Code Copilot, JetBrains Copilot, GitHub Copilot CLI, Cursor, Codex CLI, Qwen Code, Kimi Code, Antigravity CLI (`agy`), Kiro |
| **TS Plugin** | OpenCode, KiloCode, OpenClaw |
| **MCP-only** | Antigravity, Zed, Pi, OMP (Oh My Pi) |

The MCP server layer is 100% portable and needs no adapter. Only the hook layer requires platform-specific adapters.

## Prerequisites

All platforms (except Claude Code plugin install) require a global install:

```bash
npm install context-mode
```

This puts the `context-mode` binary in PATH, which is required for:
- **MCP server:** `"command": "context-mode"` (replaces ephemeral `npx -y context-mode`)
- **Hook dispatcher:** `context-mode hook  <event>` (replaces `node ./node_modules/...` paths)
- **Utility commands:** `context-mode doctor`, `context-mode upgrade`
- **Persistent upgrades:** `ctx-upgrade` updates the global binary in-place

---

## Main Comparison Table

| Feature | Claude Code | Qwen Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | GitHub Copilot CLI | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Kimi Code | Antigravity | Antigravity CLI (`agy`) | Kiro | Zed | Pi | OMP |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| **Paradigm** | json-stdio | json-stdio | json-stdio | json-stdio | json-stdio | json-stdio | json-stdio | ts-plugin | ts-plugin | ts-plugin | json-stdio | json-stdio | mcp-only | json-stdio | json-stdio | mcp-only | mcp-only | mcp-only |
| **PreToolUse equivalent** | `PreToolUse` | `PreToolUse` | `BeforeTool` | `PreToolUse` | `PreToolUse` | `preToolUse` | `preToolUse` | `tool.execute.before` | `tool.execute.before` | `tool_call:before` | `PreToolUse` | `PreToolUse` | -- | `PreToolUse` (bounded) | `preToolUse` | -- | -- | -- |
| **PostToolUse equivalent** | `PostToolUse` | `PostToolUse` | `AfterTool` | `PostToolUse` | `PostToolUse` | `postToolUse` | `postToolUse` | `tool.execute.after` | `tool.execute.after` | `tool_call:after` | `PostToolUse` | `PostToolUse` | -- | `PostToolUse` (capture-only) | `postToolUse` | -- | -- | -- |
| **PreCompact equivalent** | `PreCompact` | `PreCompact` | `PreCompress` | `PreCompact` | `PreCompact` | `preCompact` | -- | `experimental.session.compacting` | `experimental.session.compacting` | `registerContextEngine` | -- | `PreCompact` | -- | -- | -- | -- | -- | -- |
| **SessionStart** | `SessionStart` | `SessionStart` | `SessionStart` | `SessionStart` | `SessionStart` | `sessionStart` | -- (buggy in Cursor) | -- | -- | `command:new` | `SessionStart` | `SessionStart` | -- | -- | -- | -- | -- | -- |
| **Stop equivalent** | -- | -- | -- | `Stop` | `Stop` | `agentStop` | `stop` | -- | -- | -- | `Stop` | `Stop` | -- | `Stop` (best-effort, unverified on agy 1.0.6) | -- | -- | -- | -- |
| **Can modify args** | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | -- | -- | -- | -- | -- | -- |
| **Can modify output** | Yes | Yes | Yes | Yes | Yes | No | No | Yes (caveat) | Yes (caveat) | No | No | Yes | -- | -- | -- | -- | -- | -- |
| **Can inject session context** | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | -- | Yes | Yes | Yes | -- | -- | -- | -- | -- | -- |
| **Can block tools** | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes (throw) | Yes (throw) | Yes | Yes | Yes | -- | Bounded | Yes | -- | -- | -- |
| **Config location** | `~/.claude/settings.json` | `~/.qwen/settings.json` | `~/.gemini/settings.json` | `.github/hooks/*.json` | `.github/hooks/*.json` | `~/.copilot/hooks/context-mode.json` + `~/.copilot/mcp-config.json` | `.cursor/hooks.json` or `~/.cursor/hooks.json` | `opencode.json` | `kilo.json` | `openclaw.json` | `~/.codex/hooks.json` + `~/.codex/config.toml` | `~/.kimi-code/config.toml` | `~/.gemini/antigravity/mcp_config.json` | `~/.gemini/config/mcp_config.json` + `~/.gemini/config/hooks.json` | `~/.kiro/settings/mcp.json` | `~/.config/zed/settings.json` | `~/.pi/settings.json` | `~/.omp/agent/mcp_config.json` |
| **Session ID field** | `session_id` | `session_id` | `session_id` | `sessionId` (camelCase) | `sessionId` (camelCase) | `session_id` (snake_case; `sessionId` defensive fallback) | `conversation_id` | `sessionID` (camelCase) | `sessionID` (camelCase) | `pid-${ppid}` fallback | N/A | `session_id` | N/A | `conversationId` (unverified) | `pid-${ppid}` fallback | N/A | N/A | N/A |
| **Project dir env** | `CLAUDE_PROJECT_DIR` | `QWEN_PROJECT_DIR` | `GEMINI_PROJECT_DIR` | `CLAUDE_PROJECT_DIR` | `CLAUDE_PROJECT_DIR` | stdin `cwd` | stdin `workspace_roots` | `ctx.directory` (plugin init) | `ctx.directory` (plugin init) | `process.cwd()` | N/A | stdin `cwd` | N/A | stdin `workspace.current_dir` (refs-backed; `workspacePaths[0]` fallback) | stdin `cwd` | N/A | N/A | `OMP_PROCESSING_AGENT_DIR` |
| **MCP/tool naming** | `mcp__server__tool` | `mcp__server__tool` | `mcp__server__tool` | `f1e_` prefix | `f1e_` prefix | `mcp__server__tool` | `MCP:<tool>` in hook payloads | native `ctx_*` plugin tools | native `ctx_*` plugin tools | native `ctx_*` plugin tools | `mcp__server__tool` | `mcp__context-mode__tool` | `mcp__server__tool` | `context-mode/<tool>` | `mcp__server__tool` | `mcp__server__tool` | `mcp__server__tool` | `mcp__server__tool` |
| **Hook command format** | `context-mode hook claude-code <event>` | `context-mode hook qwen-code <event>` | `context-mode hook gemini-cli <event>` | `context-mode hook vscode-copilot <event>` | `context-mode hook jetbrains-copilot <event>` | `context-mode hook copilot-cli <event>` | `context-mode hook cursor <event>` | TS plugin (no command) | TS plugin (no command) | TS plugin (no command) | `context-mode hook codex <event>` | `context-mode hook kimi <event>` | N/A | `context-mode hook antigravity-cli <event>` | `context-mode hook kiro <event>` | N/A | N/A | N/A |
| **Hook registration** | settings.json hooks object | settings.json hooks object | settings.json hooks object | `.github/hooks/*.json` | `.github/hooks/*.json` | `~/.copilot/hooks/context-mode.json` (`version: 1`) | `hooks.json` native hook arrays | opencode.json plugin array | kilo.json plugin array | openclaw.json `plugins.entries` | `~/.codex/hooks.json` | `config.toml` hooks array | N/A | plugin root `hooks.json` (`PreToolUse`, `PostToolUse`, `Stop`; bundle mirrors `hooks/hooks.json` for agy validate/install) | Kiro CLI hooks (JSON stdin) | N/A | N/A | N/A |
| **MCP server command** | `context-mode` (or plugin auto) | `context-mode` | `context-mode` | `context-mode` | `context-mode` | `context-mode` | `context-mode` | N/A (native plugin tools) | N/A (native plugin tools) | N/A (native plugin tools) | `context-mode` | `context-mode` | `context-mode` | `context-mode` | `context-mode` | `context-mode` | `context-mode` | `context-mode` |
| **Plugin distribution** | Claude plugin registry | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | agy plugin (npm global) | npm global | npm global | npm global | npm global |
| **Session dir** | `~/.claude/context-mode/sessions/` | `~/.qwen/context-mode/sessions/` | `~/.gemini/context-mode/sessions/` | `.github/context-mode/sessions/` or `~/.vscode/context-mode/sessions/` | `.github/context-mode/sessions/` | `~/.copilot/context-mode/sessions/` | `~/.cursor/context-mode/sessions/` | `~/.config/opencode/context-mode/sessions/` | `~/.config/kilo/context-mode/sessions/` | `~/.openclaw/context-mode/sessions/` | `~/.codex/context-mode/sessions/` | `~/.kimi-code/context-mode/sessions/` | `~/.gemini/context-mode/sessions/` | `~/.gemini/context-mode/sessions/` | `~/.kiro/context-mode/sessions/` | `~/.config/zed/context-mode/sessions/` | `~/.pi/context-mode/sessions/` | `~/.omp/context-mode/sessions/` |

### Legend

- Yes = Fully supported
- -- = Not supported
- (caveat) = Supported with known issues

---

## Platform Details

### Claude Code

**Status:** Fully supported (primary platform)

**Hook Paradigm:** JSON stdin/stdout

Claude Code is the primary platform for context-mode. All hooks communicate via JSON on stdin/stdout. The adapter reads raw JSON input, normalizes it into platform-agnostic events, and formats responses back into Claude Code's expected output format.

**Hook Names:**
- `PreToolUse` -- fires before a tool is executed
- `PostToolUse` -- fires after a tool completes
- `PreCompact` -- fires before context compaction
- `SessionStart` -- fires when a session starts, resumes, or compacts
- `UserPromptSubmit` -- fires when user submits a prompt
- `Stop` -- fires when the assistant turn is about to end

**Blocking:** `permissionDecision: "deny"` in response JSON

**Arg Modification:** `updatedInput` field at top level of response

**Output Modification:** `updatedMCPToolOutput` for MCP tools, `additionalContext` for appending

**Session ID Extraction Priority:**
1. UUID from `transcript_path` field
2. `session_id` field
3. `CLAUDE_SESSION_ID` environment variable
4. Parent process ID fallback

**Hook Commands:**
```
context-mode hook claude-code pretooluse
context-mode hook claude-code posttooluse
context-mode hook claude-code precompact
context-mode hook claude-code sessionstart
context-mode hook claude-code userpromptsubmit
```

**Known Issues:** None significant.

---

### Gemini CLI

**Status:** Fully supported

**Hook Paradigm:** JSON stdin/stdout

Gemini CLI uses the same JSON stdin/stdout paradigm as Claude Code but with different hook names and response format.

**Hook Names:**
- `BeforeTool` -- equivalent to PreToolUse
- `AfterTool` -- equivalent to PostToolUse
- `PreCompress` -- equivalent to PreCompact (advisory only, async, cannot block)
- `SessionStart` -- fires when a session starts

**Blocking:** `decision: "deny"` in response (NOT `permissionDecision`)

**Arg Modification:** `hookSpecificOutput.tool_input` (merged with original, not `updatedInput`)

**Output Modification:** `decision: "deny"` + `reason` replaces output; `hookSpecificOutput.additionalContext` appends

**Environment Variables:**
- `GEMINI_PROJECT_DIR` -- primary project directory
- `CLAUDE_PROJECT_DIR` -- alias (also works)

**Hook Commands:**
```
context-mode hook gemini-cli beforetool
context-mode hook gemini-cli aftertool
context-mode hook gemini-cli precompress
context-mode hook gemini-cli sessionstart
```

**Known Issues / Caveats:**
- `PreCompress` is advisory only (async, cannot block)
- No `decision: "ask"` support
- Hooks don't fire for subagents yet

---

### OpenCode

**Status:** Fully supported

**Hook Paradigm:** TS Plugin

OpenCode uses a TypeScript plugin paradigm instead of JSON stdin/stdout. Hooks and the 11 `ctx_*` tools are registered via the `plugin` array in `opencode.json`; no separate `mcp` block or stdio MCP child is required.

**Hook Names:**
- `tool.execute.before` -- equivalent to PreToolUse
- `tool.execute.after` -- equivalent to PostToolUse
- `experimental.session.compacting` -- equivalent to PreCompact (experimental)
- `experimental.chat.system.transform` -- SessionStart-equivalent (cross-session resume injection)

**Blocking:** `throw Error` in `tool.execute.before` handler

**Arg Modification:** `output.args` mutation

**Output Modification:** `output.output` mutation (TUI bug for bash, see issue #13575)

**Session ID:** `input.sessionID` (camelCase, note the uppercase `ID`)

**Project Directory:** Available via `ctx.directory` in plugin init, not via environment variable

**Desktop markers:** OpenCode desktop shells also export `OPENCODE_CLIENT=desktop` and `OPENCODE_TERMINAL=1`; context-mode treats those as OpenCode identity signals when the CLI markers are absent.

**Configuration:**
- `opencode.json` or `.opencode/opencode.json`
- Plugin registered in the `plugin` array with npm package names
- `ctx_*` tools are native plugin tools, not `mcp__server__tool` calls
- KiloCode uses the same plugin path via `kilo.json`; `context-mode upgrade` removes stale `mcp.context-mode` entries for both hosts while preserving other MCP servers

**Cross-session resume:**
When OpenCode triggers `experimental.session.compacting` (auto on context overflow OR manual `/compact`), context-mode saves a snapshot to its per-project SQLite store. The NEXT new session in the same project — typically after `Ctrl+D` then re-running `opencode`, or starting a fresh chat — claims that snapshot via `experimental.chat.system.transform` and prepends it to `system[1]` (preserves OpenCode's `[header, body]` cache fold). The current session never claims its OWN snapshot back (self-injection guard, v1.0.106). To verify the injection landed, run with `OPENCODE_DEBUG=1` and grep for `<!-- context-mode v` in the system prompt — that's the visible marker.

**Known Issues / Caveats:**
- SessionStart is broken (issue #14808, no hook issue #5409) — we use `experimental.chat.system.transform` as a surrogate
- Output modification has TUI rendering bug for bash tool (issue #13575)
- `experimental.session.compacting` is marked experimental and may change
- No `canInjectSessionContext` capability
- Resume snapshots are scoped per-project (DB sharded by SHA-256 of `ctx.directory`); no cross-project bleed

---

### Codex CLI

**Status:** Supported (MCP active, hooks require `[features].hooks = true`)

**Hook Paradigm:** JSON stdin/stdout

Codex CLI's Rust backend (codex-rs) includes a hook system using the same JSON stdin/stdout wire protocol as Claude Code. Hooks are configured via `hooks.json`.

**Hook Names:**
- `PreToolUse` -- fires before a tool is executed
- `PostToolUse` -- fires after a tool completes
- `PreCompact` -- fires before context compaction on Codex builds that emit it
- `SessionStart` -- fires when a session starts, resumes, or clears
- `UserPromptSubmit` -- fires when user submits a prompt
- `Stop` -- fires when agent turn ends (can continue with followup)

**Blocking:** `permissionDecision: "deny"` in hookSpecificOutput, or exit code 2
**Arg Modification:** NOT supported (updatedInput returns error)
**Output Modification:** NOT supported (updatedMCPToolOutput returns error)
**Context Injection:** `additionalContext` in hookSpecificOutput (PostToolUse, SessionStart only). PreToolUse does NOT support `additionalContext` — the codex formatter handles this automatically (deny works, context/modify/ask responses are dropped).

**Configuration:**
- Hook config: `$CODEX_HOME/hooks.json` or `~/.codex/hooks.json` (JSON format, same structure as Claude Code)
- MCP config: `$CODEX_HOME/config.toml` or `~/.codex/config.toml` (TOML format, `[mcp_servers]` section)
- Feature flags: use `[features].hooks` (or `codex --enable hooks`) if you need
  to force hooks on. Prefer `[features].hooks`; `[features].codex_hooks` remains
  accepted as a legacy alias in current Codex builds.

**Hook Commands:**
```
context-mode hook codex pretooluse
context-mode hook codex posttooluse
context-mode hook codex precompact
context-mode hook codex sessionstart
context-mode hook codex userpromptsubmit
context-mode hook codex stop
```

**Known Issues / Caveats:**
- PreToolUse `additionalContext` is unsupported — context injection works via PostToolUse and SessionStart instead. The codex formatter handles this automatically (deny works, context is dropped). Source: `codex-rs/hooks/src/engine/output_parser.rs:267`.
- PreToolUse input rewriting still needs upstream `updatedInput` support. Track: [openai/codex#18491](https://github.com/openai/codex/issues/18491).
- PreCompact support is runtime-gated: context-mode configures it and treats a missing registration as a warning, because older Codex builds may not emit the event. The hook stores the resume snapshot out-of-band and SessionStart restores it.
- Codex emits structured tool names such as `Bash` and `apply_patch`; context-mode only normalizes legacy shell aliases.
- updatedInput and updatedMCPToolOutput are in the schema but NOT implemented
- Default hook timeout: 600 seconds
- Older context-mode releases used a `plugins/context-mode -> ..` symlink shim
  because Codex rejects the repository root (`"./"`) as an empty local plugin
  source path. On native Windows, Git can check that symlink out as a regular
  file containing only `..`, which makes `codex plugin add context-mode@context-mode`
  fail with `missing plugin.json`. Current releases avoid this by declaring the
  Codex marketplace plugin as a relative Git source (`url: "./"`), so Codex
  materializes the installed marketplace root and finds `.codex-plugin/plugin.json`
  without any symlink or junction.

  After installation succeeds, verify that Codex hooks are enabled in
  `%USERPROFILE%\.codex\config.toml`:

  ```toml
  [features]
  hooks = true
  ```

  Some Codex builds may also require `plugin_hooks = true`. Without hook support,
  the MCP tools can still work, but automatic session capture and persistent
  memory may not record events.

---

### Kimi Code

**Status:** Supported (JSON stdin/stdout hooks + MCP)

**Hook Paradigm:** JSON stdin/stdout

Kimi Code CLI uses the same JSON stdin/stdout wire protocol as Claude Code and Codex, configured via `~/.kimi-code/config.toml` with `[[hooks]]` array tables. The key difference from Codex is that Kimi accepts `additionalContext`, `updatedInput`, and `permissionDecision: "ask"` in PreToolUse responses — the codex formatter drops these, but the kimi formatter emits them fully.

**Hook Names:**
- `PreToolUse` — fires before a tool is executed
- `PostToolUse` — fires after a tool completes
- `PreCompact` — fires before context compaction
- `SessionStart` — fires when a session starts or resumes
- `UserPromptSubmit` — fires when user submits a prompt (payload is `ContentPart[]`)
- `Stop` — fires when the agent turn ends

**Blocking:** `permissionDecision: "deny"` in `hookSpecificOutput`, or exit code 2

**Arg Modification:** `updatedInput` inside `hookSpecificOutput` wrapper

**Output Modification:** `additionalContext` inside `hookSpecificOutput`

**Context Injection:** `additionalContext` in `hookSpecificOutput` (works in all hook types)

**Configuration:**
- Hooks: `~/.kimi-code/config.toml` (`[[hooks]]` array tables)
- MCP: `~/.kimi-code/mcp.json`
- Sessions: `~/.kimi-code/context-mode/sessions/`

**Hook Commands:**
```
context-mode hook kimi pretooluse
context-mode hook kimi posttooluse
context-mode hook kimi precompact
context-mode hook kimi sessionstart
context-mode hook kimi userpromptsubmit
context-mode hook kimi stop
```

**Known Issues / Caveats:**
- `UserPromptSubmit` sends `prompt` as a `ContentPart[]` array; the kimi hook normalizes this to a string for downstream extractors.
- SessionStart `additionalContext` injection is emitted but acceptance by the host is not documented in Kimi Code CLI docs (fails-open if unsupported).

---

### Qwen Code

**Status:** Supported (MCP + hooks — identical wire protocol to Claude Code)

**Hook Paradigm:** JSON stdin/stdout (same as Claude Code)

Qwen Code (by Alibaba/Qwen team) uses the exact same hook wire protocol as Claude Code, verified from source (`hookRunner.ts`, `claude-converter.ts`). Hooks are configured inside `~/.qwen/settings.json` under the `hooks` key.

**Hook Names:** `PreToolUse`, `PostToolUse`, `SessionStart`, `PreCompact`, `UserPromptSubmit` (Qwen supports 12 events total, context-mode uses these 5)

**Blocking:** `permissionDecision: "deny"` or exit code 2
**Arg Modification:** `updatedInput` in response
**Output Modification:** `updatedMCPToolOutput` in response
**Context Injection:** `additionalContext` in response

**Configuration:**
- Settings + hooks: `~/.qwen/settings.json`
- MCP: `mcpServers` in settings.json
- Sessions: `~/.qwen/context-mode/sessions/`

**Detection:** MCP clientInfo (`qwen-cli-mcp-client-*` pattern), `QWEN_PROJECT_DIR` env var, or `~/.qwen/` config dir.

**Hook Commands:**
```
context-mode hook qwen-code pretooluse
context-mode hook qwen-code posttooluse
context-mode hook qwen-code sessionstart
context-mode hook qwen-code precompact
context-mode hook qwen-code userpromptsubmit
```

---

### Antigravity

**Status:** MCP-only (no hooks)

**Hook Paradigm:** MCP-only

Google Antigravity is an AI-powered IDE by Google/DeepMind. It shares the `~/.gemini/` directory structure with Gemini CLI but uses a separate config path for MCP servers. Antigravity does not expose a public hook API — only MCP integration is available.

**Configuration:**
- `~/.gemini/antigravity/mcp_config.json` (JSON format)
- MCP servers configured in `mcpServers` object

**Detection:**
- Auto-detected via MCP protocol handshake (`clientInfo.name: "antigravity-client"`)
- Fallback: `CONTEXT_MODE_PLATFORM=antigravity` environment variable override

**Routing Instructions:**
- `GEMINI.md` auto-written at project root on first MCP server startup
- Antigravity reads `GEMINI.md` natively (same filename as Gemini CLI, different content — no hook references)

**Capabilities:**
- PreToolUse: --
- PostToolUse: --
- PreCompact: --
- SessionStart: --
- Can modify args: --
- Can modify output: --
- Can inject session context: --

**Known Issues / Caveats:**
- No hook support — only routing instruction files for enforcement (~60% compliance)
- Shares `~/.gemini/` directory with Gemini CLI — session DB uses project hash to prevent collision
- No verified Antigravity-specific environment variables exist

**Sources:**
- Config path: [Gemini CLI Issue #16058](https://github.com/google-gemini/gemini-cli/issues/16058)
- MCP support: [Antigravity MCP docs](https://antigravity.google/docs/mcp)
- clientInfo: [Apify MCP Client Capabilities Registry](https://github.com/apify/mcp-client-capabilities)

---

### Antigravity CLI (`agy`)

**Status:** Plugin — MCP + routing rule + routing skill + bounded hooks

**Hook Paradigm:** MCP for tools + JSON stdin/stdout hooks

The standalone Antigravity CLI (`agy`) is the command-line companion to Google Antigravity. Unlike the Antigravity IDE, `agy` has a **native plugin system** (`agy plugin install|import`) and a hook surface (`~/.gemini/config/hooks.json`). context-mode ships as a first-class agy plugin (`configs/antigravity-cli/`) bundling the MCP server, a routing rule, a routing skill, and bounded `PreToolUse`/`PostToolUse`/`Stop` hooks. It shares the `~/.gemini/` session root with the rest of the Gemini family; `agy` reads its **global** MCP profile from `~/.gemini/config/mcp_config.json` (not the IDE's `~/.gemini/antigravity/mcp_config.json`).

**Verified:** agy 1.0.10 (Linux). The GitHub-subpath install requires **agy ≥ 1.0.7** (subpath + branch resolution landed in 1.0.7 — run `agy update` to upgrade). No agy hook event was added, removed, or renamed through 1.0.10, and the shared `~/.gemini/config/hooks.json` location has been canonical since agy 1.0.8, so the bundle's `PreToolUse`/`PostToolUse`/`Stop` wiring is current.

**Install:**
- `npm install context-mode` (the plugin's MCP server + hooks run the `context-mode` binary), then `agy plugin install https://github.com/mksglu/context-mode/tree/main/configs/antigravity-cli`. agy clones the repo, resolves the `configs/antigravity-cli` subpath (with branch resolution), and registers the bundle's native `plugin.json` + `mcp_config.json`, routing rule, routing skill, and hooks into its plugin profile under `~/.gemini/config/plugins/context-mode/`. If `ctx_*` tools don't appear after an upgrade, clear agy's stale tool-schema cache (`~/.gemini/antigravity-cli/mcp/context-mode/`) and restart agy (agy caches MCP schemas and doesn't refresh them).
- Already on Claude Code: `agy plugin import claude` can import that existing Claude setup, but the native context-mode agy bundle above is the supported path for agy hooks.
- MCP only: add context-mode to `~/.gemini/config/mcp_config.json` under `mcpServers` (`{"command":"context-mode"}`).

**Detection:**
- MCP protocol handshake (`clientInfo.name: "agy"` / `"antigravity-cli"`)
- Config-dir markers for a bare shell: `~/.local/bin/agy`, `~/.gemini/antigravity-cli/`, or `~/.gemini/config/mcp_config.json` — probed **before** the generic `~/.claude` / `~/.gemini` fallbacks so a gemini-cli→agy migrant is not mis-detected as Claude Code ([#774](https://github.com/mksglu/context-mode/issues/774))
- Fallback: `CONTEXT_MODE_PLATFORM=antigravity-cli` override

**Hook payload:** the only refs-backed field is the working directory, read from `workspace.current_dir` — an object field, per the upstream hook example (refs/platforms/antigravity-cli/examples/title/title.sh:10, examples/title/README.md:11). context-mode reads `workspace.current_dir` FIRST for the project dir, falling back to `workspacePaths[0]`. The remaining payload shape — `{ conversationId, stepIdx, toolCall: { name, args }, error, workspacePaths: [..], transcriptPath }` — is empirically-derived/**unverified** (no upstream agy doc confirms it) and is treated as best-effort. The event name arrives via argv (set in `hooks.json`), and the hook CWD is `~/.gemini/config`. context-mode maps these onto its routing/capture pipeline (`workspace.current_dir`/`workspacePaths[0]`→project dir, `conversationId`→session id [unverified], `run_command`→`Bash`, `view_file`→`Read`, `grep_search`→`Grep`, `list_dir`→`LS`, `read_url_content`→`WebFetch`, `search_web`→`WebSearch`).

**Capabilities:**
- PreToolUse: bounded blocking for mapped Bash/Read/Grep/WebFetch surfaces (`run_command`, `view_file`, `grep_search`, `web_fetch`, `read_url_content`)
- PostToolUse: capture-only (records executed tool calls into the session DB)
- Stop: best-effort capture-only session-end marker (registered, but not observed in agy `-p` probes)
- PreCompact / SessionStart / PreInvocation / PostInvocation: -- (not wired)
- Can modify args / output / inject context: -- (not verified/used)

**Known Issues / Caveats:**
- **Bounded hook enforcement.** context-mode registers PreToolUse only for mapped high-flood tools with existing routing branches. `list_dir` and `search_web` are normalized for PostToolUse capture but are not PreToolUse-routed.
- `PreInvocation` and `PostInvocation` are visible in agy's hook list but intentionally not registered; agy 1.0.6 `-p` probes did not emit them, and their payload/response semantics are not verified against context-mode's pipeline.
- agy's `PostToolUse` payload carries no tool-output text, so byte-accounting for tool output is unavailable on this surface; the tool call + project + error state are still captured.
- Shares `~/.gemini/` with Gemini CLI and Antigravity — session DB uses the project hash to prevent collisions.
- **Gemini function-calling tool exposure.** agy exposes MCP tools as Gemini function declarations, and Gemini's API rejects JSON Schema `const` / `additionalProperties` — a rejected schema makes agy **silently drop** that tool from the model's function list (the agent then hand-rolls the tool via shell scripts instead of calling it). context-mode emits Gemini-safe tool schemas (`const`→`enum`, `additionalProperties` stripped) so the `ctx_*` tools are exposed. agy also **caches** each server's tool schemas under `~/.gemini/antigravity-cli/mcp/<server>/` and does **not** refresh them on reconnect, so a cache captured by an older context-mode keeps the tools hidden — delete `~/.gemini/antigravity-cli/mcp/context-mode/` and restart agy to force a re-fetch.

---

### Kiro

**Status:** Supported (MCP + native PreToolUse/PostToolUse hooks)

**Hook Paradigm:** JSON stdin/stdout

Kiro is an AWS agentic IDE and CLI. It supports MCP servers via `~/.kiro/settings/mcp.json` using the standard `mcpServers` JSON format. The Kiro CLI also fires `preToolUse`/`postToolUse` hooks over JSON stdin (exit code 2 blocking), which context-mode's `kiro` adapter implements for routing enforcement and tool-event capture. `agentSpawn` (the Kiro SessionStart equivalent) and `stop` are not yet wired, so session restore after compaction is not available.

**Detection:**
- Auto-detected via MCP protocol handshake (`clientInfo.name: "Kiro CLI"`)

**Configuration:**
- Global: `~/.kiro/settings/mcp.json` (JSON format, standard `mcpServers` object)
- Project: `.kiro/settings/mcp.json`

**Routing Instructions:**
- `KIRO.md` written at project root on first MCP server startup

**Hook System:**
- `preToolUse`/`postToolUse` hooks via JSON stdin (implemented)
- Blocking: exit code 2 (similar to Gemini CLI pattern)
- `agentSpawn` (SessionStart equivalent) and `stop` are not yet wired

**Hook Commands:**
```
context-mode hook kiro pretooluse
context-mode hook kiro posttooluse
```

**Built-in Tools:**
- `fs_read` / `read`, `fs_write` / `write`, `execute_bash` / `shell`, `use_aws` / `aws`

**Capabilities:**
- PreToolUse: Yes
- PostToolUse: Yes
- PreCompact: --
- SessionStart: -- (`agentSpawn` not yet wired)
- Can modify args: --
- Can modify output: --
- Can inject session context: -- (via `agentSpawn`, not yet wired)
- Can block tools: Yes (exit code 2)

**Known Issues / Caveats:**
- `agentSpawn` (SessionStart) and `stop` are not yet wired, so session restore after compaction is unavailable — copy `KIRO.md` to the project root for session-start routing
- Kiro IDE hooks use a UI-based "Run Command" shell action; stdin format unverified

**Sources:**
- clientInfo.name: [Kiro GitHub Issue #5205](https://github.com/kirodotdev/Kiro/issues/5205)
- MCP config: [Kiro MCP Configuration docs](https://kiro.dev/docs/mcp/configuration/)
- CLI hooks: [Kiro CLI Hooks docs](https://kiro.dev/docs/cli/hooks/)

---

### VS Code Copilot

**Status:** Fully supported (preview)

**Hook Paradigm:** JSON stdin/stdout

VS Code Copilot uses the same JSON stdin/stdout paradigm as Claude Code with PascalCase hook names. It also provides unique hooks for subagent lifecycle.

**Hook Names:**
- `PreToolUse` -- fires before a tool is executed
- `PostToolUse` -- fires after a tool completes
- `PreCompact` -- fires before context compaction
- `SessionStart` -- fires when a session starts
- `Stop` -- fires when agent stops (unique to VS Code)
- `SubagentStart` -- fires when a subagent starts (unique to VS Code)
- `SubagentStop` -- fires when a subagent stops (unique to VS Code)

**Blocking:** `permissionDecision: "deny"` (same as Claude Code)

**Arg Modification:** `updatedInput` inside `hookSpecificOutput` wrapper (NOT flat like Claude Code)
```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "updatedInput": { ... }
  }
}
```

**Output Modification:** `additionalContext` inside `hookSpecificOutput`, or `decision: "block"` + `reason`

**MCP Tool Naming:** Uses `f1e_` prefix (not `mcp__server__tool`)

**Session ID:** `sessionId` (camelCase, not `session_id`)

**Configuration:**
- Primary: `.github/hooks/*.json`
- Also reads: `.claude/settings.json`
- MCP config: `.vscode/mcp.json`

**Environment Detection:**
- `VSCODE_PID` environment variable
- `TERM_PROGRAM=vscode`

**Hook Commands:**
```
context-mode hook vscode-copilot pretooluse
context-mode hook vscode-copilot posttooluse
context-mode hook vscode-copilot precompact
context-mode hook vscode-copilot sessionstart
```

**Known Issues / Caveats:**
- Preview status -- API may change without notice
- Matchers are parsed but IGNORED (all hooks fire on all tools)
- Tool input property names use camelCase (`filePath` not `file_path`)
- Response must be wrapped in `hookSpecificOutput` with `hookEventName`

---

### JetBrains Copilot

**Status:** Fully supported (preview)

**Hook Paradigm:** JSON stdin/stdout

JetBrains Copilot (GitHub Copilot plugin for JetBrains IDEs) uses the same JSON stdin/stdout paradigm and hook wire protocol as VS Code Copilot. It shares hook names, response format, and MCP tool naming conventions.

**Hook Names:**
- `PreToolUse` -- fires before a tool is executed
- `PostToolUse` -- fires after a tool completes
- `PreCompact` -- fires before context compaction
- `SessionStart` -- fires when a session starts
- `Stop` -- fires when agent stops
- `SubagentStart` -- fires when a subagent starts
- `SubagentStop` -- fires when a subagent stops

**Blocking:** `permissionDecision: "deny"` (same as VS Code Copilot)

**Arg Modification:** `updatedInput` inside `hookSpecificOutput` wrapper (same as VS Code Copilot)
```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "updatedInput": { ... }
  }
}
```

**Output Modification:** `additionalContext` inside `hookSpecificOutput`, or `decision: "block"` + `reason`

**MCP Tool Naming:** Uses `f1e_` prefix (same as VS Code Copilot)

**Session ID:** `sessionId` (camelCase)

**Configuration:**
- Hook config: `.github/hooks/*.json`
- MCP config: Settings UI (Settings > Tools > AI Assistant > MCP)

**Hook Commands:**
```
context-mode hook jetbrains-copilot pretooluse
context-mode hook jetbrains-copilot posttooluse
context-mode hook jetbrains-copilot precompact
context-mode hook jetbrains-copilot sessionstart
```

**Known Issues / Caveats:**
- Preview status -- API may change without notice
- Shares the same hook wire protocol as VS Code Copilot
- MCP servers are configured via Settings UI, not a file
- Requires GitHub Copilot plugin v1.5.57+

---

### GitHub Copilot CLI

**Status:** Supported (native hooks + MCP)

**Hook Paradigm:** JSON stdin/stdout

The standalone GitHub Copilot CLI (`copilot`) is user-home rooted under `~/.copilot` (override with `COPILOT_HOME`). Its hook config uses camelCase event keys, and its command output contract is **top-level** (`permissionDecision`, `modifiedArgs`, `additionalContext`) rather than the VS Code `hookSpecificOutput` wrapper — context-mode's `copilot-cli` adapter formats responses accordingly.

**Hook config keys:** (Copilot CLI 1.0.59 fires six events context-mode uses; verified against the `@github/copilot` binary)
- `preToolUse` -- fires before a tool is executed
- `postToolUse` -- fires after a tool completes
- `preCompact` -- fires before context compaction
- `sessionStart` -- fires when a session starts
- `userPromptSubmitted` -- fires when the user submits a prompt (user-prompt capture)
- `agentStop` -- fires when the agent stops (session-end capture)

**Hook config shape:** flat `{ "type": "command", "command": "..." }` entries (NOT the Claude-Code nested `{ matcher, hooks: [...] }` shape). context-mode also writes a top-level `"version": 1`, but this field is **optional** — the Copilot CLI accepts hook config files that omit it (copilot-cli changelog.md:1109). We pin it for self-documentation, not because the runtime requires it.

**Blocking:** top-level `permissionDecision: "deny"` + `permissionDecisionReason`

**Arg Modification:** top-level `modifiedArgs`

**Output Modification:** not supported (the posttooluse hook is capture-only)

**Context Injection:** top-level `additionalContext` — **SessionStart** is the confirmed channel (verified reaching the model). PreToolUse/PostToolUse `additionalContext` is best-effort/unverified; context-mode's `posttooluse` hook is capture-only and emits no context.

**Configuration:**
- **Plugin (recommended):** context-mode ships a Copilot CLI plugin bundle at `configs/copilot-cli/` — a root `.mcp.json` (MCP), `hooks.json` (the six capture hooks), a routing skill (`skills/context-mode/`), and a `.github/plugin/plugin.json` manifest. `copilot plugin install mksglu/context-mode:configs/copilot-cli` registers all of it in one command (no clone, no `context-mode upgrade`/agent call). The bundle's `.mcp.json` pins `CONTEXT_MODE_PLATFORM=copilot-cli`, so the server self-identifies as Copilot and `ctx_upgrade`/detection resolve `copilot-cli` even when Claude Code is co-installed (whose `~/.claude/` would otherwise win). Verified on Windows via `copilot --plugin-dir `: `ctx_execute` resolves and the `PostToolUse` hook captures non-MCP tool I/O into the session DB. (This `.mcp.json` is the one committed instance in the repo — `.gitignore` un-ignores exactly this path, since a Copilot plugin has no other way to declare MCP.)
- MCP (manual, no plugin): register with Copilot CLI's own command — `copilot mcp add context-mode -- context-mode` — which writes `~/.copilot/mcp-config.json` (or `$COPILOT_HOME/mcp-config.json`). (Also `copilot mcp list` / `copilot mcp remove`.)
- Hook config (manual, no plugin): `$COPILOT_HOME/hooks/context-mode.json` or `~/.copilot/hooks/context-mode.json` (written by `context-mode upgrade`; standalone hooks fire — verified — independent of any plugin)
- Instruction files: `.github/copilot-instructions.md`, `AGENTS.md`

**Detection:**
- MCP protocol handshake (`clientInfo.name: "GitHub Copilot CLI"` / `"copilot-cli"`)
- Config-dir marker: a context-mode-written file under `~/.copilot/` (or `$COPILOT_HOME` — the marker check honors it) — `mcp-config.json` or `hooks/context-mode.json`, **not** a bare `~/.copilot/` directory, so a co-installed-but-unconfigured Copilot CLI is not mis-detected (probed before the generic `~/.claude` fallback)
- Fallback: `CONTEXT_MODE_PLATFORM=copilot-cli` override

**Hook Commands:**
```
context-mode hook copilot-cli pretooluse
context-mode hook copilot-cli posttooluse
context-mode hook copilot-cli precompact
context-mode hook copilot-cli sessionstart
context-mode hook copilot-cli userpromptsubmit
context-mode hook copilot-cli stop
```

**Known Issues / Caveats:**
- The top-level `"version": 1` is **optional**: the Copilot CLI accepts hook config files that omit the version field (copilot-cli changelog.md:1109). context-mode pins `"version": 1` for self-documentation; omitting it does not stop hooks from firing.
- Event names can be camelCase or PascalCase — the Copilot CLI accepts PascalCase event names alongside camelCase (copilot-cli changelog.md:1065). context-mode uses the native camelCase names; PascalCase is not silently ignored.
- `COPILOT_HOME` relocates the hook config, the MCP config, **and** the context-mode session-DB root (the adapter's `getSessionDir()` honors it, so the server reads sessions from the same place the hook runtime writes them).

**Sources:**
- Hooks schema: [GitHub Copilot CLI hooks configuration](https://docs.github.com/en/copilot/reference/hooks-configuration)
- Feature request: [#775](https://github.com/mksglu/context-mode/issues/775)

---

### Cursor

**Status:** Supported (native hooks, v1 scope)

**Hook Paradigm:** JSON stdin/stdout

Cursor uses native lower-camel hook names and flat hook entries in `.cursor/hooks.json` or `~/.cursor/hooks.json`. context-mode treats Cursor as a first-class adapter and does not rely on Claude-compat wrappers for official support.

**Hook Names:**
- `preToolUse` -- fires before a tool is executed
- `postToolUse` -- fires after a tool completes
- `stop` -- fires when agent turn ends (can send followup_message to continue loop)
- `afterAgentResponse` -- fires after assistant response (fire-and-forget, receives full response text)
- `sessionStart` -- documented but currently rejected by Cursor's validator ([forum report](https://forum.cursor.com/t/unknown-hook-type-sessionstart/149566))

**Blocking:** `{ "permission": "deny", "user_message": "..." }`

**Arg Modification:** not natively supported (Cursor does not have `updated_input`)

**Output Modification:** not supported in v1

**Session Context Injection:** `{ "additional_context": "..." }`

**Session ID Extraction Priority:**
1. `conversation_id` (stdin JSON)
2. `CURSOR_TRACE_ID` environment variable
3. Parent process ID fallback

**Platform Detection Env Vars:**
- `CURSOR_TRACE_ID` (MCP server context)
- `CURSOR_CLI` (integrated terminal context)
- `~/.cursor/` directory fallback (medium confidence)

**Configuration:**
- Project: `.cursor/hooks.json`
- User: `~/.cursor/hooks.json`
- MCP config: `.cursor/mcp.json` or `~/.cursor/mcp.json`
- **Marketplace plugin (recommended):** `.cursor-plugin/plugin.json` at the repo root auto-registers MCP, hooks, rules, and skills. Manifest explicitly points `hooks` at `./hooks/cursor/hooks.json` to avoid colliding with the Claude-format `./hooks/hooks.json`. Local install: `ln -s <repo> ~/.cursor/plugins/local/context-mode`. Plugin hook commands use `npx -y context-mode hook cursor <event>` so no global install is required.

**Plugin/native duplication:** `context-mode doctor` warns when both the plugin and `.cursor/hooks.json` register context-mode hooks (each event would otherwise fire twice). Remove one configuration to keep events single-fire.

**Hook Commands:**
```
context-mode hook cursor pretooluse
context-mode hook cursor posttooluse
context-mode hook cursor stop
```

**Known Issues / Caveats:**
- `preCompact` is intentionally not shipped in v1
- `stop` hook receives: `conversation_id`, `status`, `loop_count`, `transcript_path`; returns `followup_message` to continue
- `afterAgentResponse` is fire-and-forget (receives `text`, no return value expected)
- Hook payloads name MCP tools as `MCP:<tool>` and need adapter normalization
- Claude-compatible Cursor behavior exists, but native Cursor config is the supported path
- `additional_context` in postToolUse and sessionStart hooks is accepted but NOT surfaced to the model (Cursor upstream bug — [forum #155689](https://forum.cursor.com/t/native-posttooluse-hooks-accept-and-log-additional-context-successfully-but-the-injected-context-is-not-surfaced-to-the-model/155689), [forum #156157](https://forum.cursor.com/t/cursor-hooks-additional-context-not-injected-in-agent-context-in-posttooluse/156157)). Routing enforcement relies on `.mdc` rules file and MCP tool descriptions instead.

---

### OpenClaw

**Status:** Fully supported

**Hook Paradigm:** TS Plugin (gateway plugin via `api.registerHook()` / `api.on()`)

OpenClaw is an OpenAI-stack agent gateway. context-mode ships as a native gateway plugin that registers hooks through OpenClaw's plugin API rather than the JSON stdin/stdout wire protocol. The same plugin entry also registers context-mode as a context engine, owning compaction.

**Hook Names:**
- `tool_call:before` -- equivalent to PreToolUse
- `tool_call:after` -- equivalent to PostToolUse
- `command:new` -- equivalent to SessionStart (fires on each new gateway command)
- `before_prompt_build` -- lifecycle hook for routing instruction injection
- `registerContextEngine` (with `ownsCompaction`) -- equivalent to PreCompact

**Blocking:** `return { block: true, blockReason: "..." }` from the `tool_call:before` handler

**Arg Modification:** mutate `event.params` in the `tool_call:before` handler (or return `{ params: ... }`)

**Output Modification:** not supported (the plugin paradigm exposes args/context, not the rendered tool output)

**Context Injection:** via `before_prompt_build` (session-level) and `registerContextEngine` (compaction-level)

**Path Resolution:**
- Detection root: `~/.openclaw/`
- Plugin install: `~/.openclaw/extensions/context-mode/`
- Project config: `openclaw.json` or `.openclaw/openclaw.json`
- Global config fallback: `~/.openclaw/openclaw.json`
- Project dir: `process.cwd()` (the gateway provides no dedicated env var)
- Memory dir: project-relative `./memory`
- Session dir: `~/.openclaw/context-mode/sessions/`
- Routing instructions: `AGENTS.md`

**Configuration:**
- `openclaw.json` registers context-mode under `plugins.entries["context-mode"]` (`{ "enabled": true }`)
- `plugins.slots.contextEngine = "context-mode"` enables ownership of compaction
- No CLI hook command; OpenClaw imports the plugin module directly

**Notes / Caveats:**
- TS plugin paradigm — hooks run in-process, so there is no shell command to chmod and no platform-specific stdin/stdout quirks
- `ask` decisions are converted to `block` (with the original reason) since the gateway has no interactive confirmation path
- `context` decisions inside `tool_call:before` are dropped — context injection must be routed through `before_prompt_build` or the registered context engine
- Session ID falls back to `pid-${process.ppid}` when the gateway does not surface one

---

### Zed

**Status:** MCP-only (no hooks)

**Hook Paradigm:** MCP-only

Zed is a code editor with first-class MCP support but no hook pipeline. context-mode runs purely through Zed's `context_servers` configuration; routing enforcement falls back to the AGENTS.md instruction file (~60% compliance).

**Hook Support:**
- PreToolUse: --
- PostToolUse: --
- PreCompact: --
- SessionStart: --
- Stop: --
- Can modify args: --
- Can modify output: --
- Can inject session context: --

The hook adapter exists only to satisfy the interface contract — every parser throws `Error("Zed does not support hooks")` and every formatter returns `undefined`.

**Path Resolution:**
- Detection root: `~/.config/zed/`
- Settings file: `~/.config/zed/settings.json`
- MCP registration: `context_servers` object inside `settings.json`
- Session dir: `~/.config/zed/context-mode/sessions/`
- Routing instructions: `AGENTS.md` (sourced from `configs/zed/AGENTS.md` in the package, with an inline fallback if missing)

**Detection:**
- Auto-detected via the presence of `~/.config/zed/`
- Override via `CONTEXT_MODE_PLATFORM=zed`

**Notes / Caveats:**
- No hook adapter implies no automatic routing — the model must follow AGENTS.md voluntarily
- No marketplace or plugin registry for Zed; `getInstalledVersion()` always reports `not installed`
- `validateHooks` always returns a single `warn` row reminding the user that Zed exposes only MCP integration
- `configureAllHooks`, `setHookPermissions`, and `updatePluginRegistry` are intentional no-ops

---

### OMP (Oh My Pi)

**Status:** MCP-only (no hooks)

**Hook Paradigm:** MCP-only

[Oh My Pi (OMP)](https://github.com/can1357/oh-my-pi) is a Pi-compatible harness that stores its agent state under `~/.omp/agent/` (overridable via `OMP_PROCESSING_AGENT_DIR`). Before the dedicated adapter, OMP detection fell through to `pi` and storage rooted under another harness's directory (typically `~/.claude/`), per [issue #473](https://github.com/mksglu/context-mode/issues/473). The OMP adapter exists primarily to keep `~/.omp/context-mode/` isolated, not to provide hook integration — OMP, like Antigravity/Kiro/Zed, runs context-mode purely as an MCP server.

**Hook Support:**
- PreToolUse: --
- PostToolUse: --
- PreCompact: --
- SessionStart: --
- Stop: --
- Can modify args: --
- Can modify output: --
- Can inject session context: --

The hook adapter exists only to satisfy the interface contract — every parser throws `Error("OMP does not support hooks")` and every formatter returns `undefined`.

**Path Resolution:**
- Agent root: `$OMP_PROCESSING_AGENT_DIR` if set, else `~/.omp/agent/`
- Settings file: `<agent root>/mcp_config.json`
- MCP registration: `mcpServers` object inside `mcp_config.json`
- Session dir: `~/.omp/context-mode/sessions/` (intentionally rooted at `~/.omp/`, not the agent dir, so multiple OMP instances on one host share an index without colliding session DBs)
- Routing instructions: `PI.md` (Pi-compatible filename — OMP shares the Pi instruction surface)

**Detection (priority order, listed BEFORE `pi` so OMP is never misclassified):**
- `OMP_PROCESSING_AGENT_DIR` env var (high confidence)
- `~/.omp/` directory presence (medium confidence)
- `CONTEXT_MODE_PLATFORM=omp` override

**Notes / Caveats:**
- No hook adapter implies no automatic routing — the model must follow `PI.md` voluntarily (~60% compliance)
- No marketplace or plugin registry for OMP; `getInstalledVersion()` reports `not installed` unless an `extensions/context-mode/package.json` exists under the agent dir
- `validateHooks` always returns a single `warn` row reminding the user that OMP exposes only MCP integration
- `configureAllHooks`, `setHookPermissions`, and `updatePluginRegistry` are intentional no-ops

---

## Capability Matrix (Quick Reference)

| Capability | Claude Code | Qwen Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | GitHub Copilot CLI | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Kimi Code | Antigravity | Antigravity CLI (`agy`) | Kiro | Zed | Pi | OMP |
|-----------|:-----------:|:---------:|:----------:|:---------------:|:-----------------:|:------------------:|:------:|:--------:|:--------:|:--------:|:---------:|:---------:|:-----------:|:-----------------------:|:----:|:---:|:--:|:---:|
| PreToolUse | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes*** | Yes | -- | Bounded | Yes | -- | -- | -- |
| PostToolUse | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | Yes (capture-only) | Yes | -- | -- | -- |
| PreCompact | Yes | Yes | Yes | Yes | Yes | Yes | -- | Yes* | Yes* | Yes | Yes**** | Yes | -- | -- | -- | -- | -- | -- |
| SessionStart | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | -- | Yes | Yes | Yes | -- | -- | -- | -- | -- | -- |
| Stop | -- | -- | -- | Yes | Yes | Yes | Yes | -- | -- | -- | Yes | Yes | -- | Best-effort capture | -- | -- | -- | -- |
| Modify Args | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | Yes | -- | -- | -- | -- | -- | -- |
| Modify Output | Yes | Yes | Yes | Yes | Yes | No | No | Yes** | Yes** | No | -- | Yes | -- | -- | -- | -- | -- | -- |
| Inject Context | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | -- | Yes | Yes | Yes | -- | -- | -- | -- | -- | -- |
| Block Tools | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | Bounded | Yes | -- | -- | -- |
| MCP/native tool support | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Native plugin | Native plugin | Native plugin | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |

\* OpenCode `experimental.session.compacting` is experimental
\*\* OpenCode has a TUI rendering bug for bash tool output (#13575)
\*\*\* Codex CLI PreToolUse supports deny only (no `additionalContext`); context injection works via PostToolUse and SessionStart
\*\*\*\* Codex CLI PreCompact is runtime-gated on builds that emit the event

---

## Hook Response Format Comparison

### Blocking a Tool

| Platform | Response Format |
|----------|----------------|
| Claude Code | `{ "permissionDecision": "deny", "reason": "..." }` |
| Gemini CLI | `{ "decision": "deny", "reason": "..." }` |
| VS Code Copilot | `{ "permissionDecision": "deny", "reason": "..." }` |
| JetBrains Copilot | `{ "permissionDecision": "deny", "reason": "..." }` |
| Cursor | `{ "permission": "deny", "user_message": "..." }` |
| OpenCode | `throw new Error("...")` |
| Codex CLI | `{ "hookSpecificOutput": { "permissionDecision": "deny" } }` or exit code 2 |
| Kimi Code | `{ "hookSpecificOutput": { "permissionDecision": "deny" } }` or exit code 2 |

### Modifying Tool Input

| Platform | Response Format |
|----------|----------------|
| Claude Code | `{ "updatedInput": { ... } }` |
| Gemini CLI | `{ "hookSpecificOutput": { "tool_input": { ... } } }` |
| VS Code Copilot | `{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "updatedInput": { ... } } }` |
| JetBrains Copilot | `{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "updatedInput": { ... } } }` |
| Cursor | `{ "updated_input": { ... } }` |
| OpenCode | `{ "args": { ... } }` (mutation) |
| Codex CLI | N/A (updatedInput in schema but not implemented) |
| Kimi Code | `{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "updatedInput": { ... } } }` |

### Injecting Additional Context (PostToolUse)

| Platform | Response Format |
|----------|----------------|
| Claude Code | `{ "additionalContext": "..." }` |
| Gemini CLI | `{ "hookSpecificOutput": { "additionalContext": "..." } }` |
| VS Code Copilot | `{ "hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": "..." } }` |
| JetBrains Copilot | `{ "hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": "..." } }` |
| Cursor | `{ "additional_context": "..." }` |
| OpenCode | `{ "additionalContext": "..." }` |
| Codex CLI | `{ "hookSpecificOutput": { "additionalContext": "..." } }` |
| Kimi Code | `{ "hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": "..." } }` |

---

## CLI Hook Dispatcher

All hook-based platforms use the CLI dispatcher pattern instead of direct `node` paths:

```
context-mode hook  <event>
```

The dispatcher resolves the hook script relative to the installed package and dynamically imports it. Stdin/stdout flow through naturally since it runs in the same process.

**Advantages over `node ./node_modules/...` paths:**
- Works from any directory (no per-project `npm install` needed)
- Single global install serves all projects
- `context-mode upgrade` updates hooks in-place
- Short, portable command strings in settings files

**Supported dispatches:**

| Platform | Events |
|----------|--------|
| `claude-code` | `pretooluse`, `posttooluse`, `precompact`, `sessionstart`, `userpromptsubmit` |
| `gemini-cli` | `beforetool`, `aftertool`, `precompress`, `sessionstart` |
| `vscode-copilot` | `pretooluse`, `posttooluse`, `precompact`, `sessionstart` |
| `jetbrains-copilot` | `pretooluse`, `posttooluse`, `precompact`, `sessionstart` |
| `cursor` | `pretooluse`, `posttooluse`, `stop` |
| `codex` | `pretooluse`, `posttooluse`, `precompact`, `sessionstart`, `userpromptsubmit`, `stop` |
| `kimi` | `pretooluse`, `posttooluse`, `precompact`, `sessionstart`, `userpromptsubmit`, `stop` |
| `qwen-code` | `pretooluse`, `posttooluse`, `precompact`, `sessionstart`, `userpromptsubmit` |
| `copilot-cli` | `pretooluse`, `posttooluse`, `precompact`, `sessionstart`, `userpromptsubmit`, `stop` |
| `antigravity-cli` | `pretooluse`, `posttooluse`, `stop` |
| `kiro` | `pretooluse`, `posttooluse` |

OpenCode, KiloCode, and OpenClaw use a TS plugin paradigm (no command dispatcher). Pi and OMP register hooks through their own host APIs rather than the CLI dispatcher; Antigravity IDE and Zed are MCP-only (no hooks).

---

## SQLite Backend Selection

context-mode automatically selects the best SQLite backend at runtime based on the environment:

| Priority | Condition | Backend | Why |
|----------|-----------|---------|-----|
| 1 | Bun runtime | `bun:sqlite` | Built-in, no native addon |
| 2 | Linux + Node.js >= 22.5 | `node:sqlite` | Built-in, avoids [SIGSEGV from V8 madvise bug](https://github.com/nodejs/node/issues/62515) |
| 3 | All other environments | `better-sqlite3` | Mature native addon, prebuilt binaries |

**Why node:sqlite on Linux?** Node.js's V8 garbage collector can call `madvise(MADV_DONTNEED)` on memory ranges that overlap `better-sqlite3`'s native addon `.got.plt` section, corrupting resolved symbol addresses and causing sporadic SIGSEGV crashes (1-4/hour on Node v22-v24). `node:sqlite` is compiled into the Node.js binary itself — no separate `.node` file, no `dlopen()`, no `.got.plt` to corrupt.

**Fallback:** If `node:sqlite` is unavailable (Node < 22.5), context-mode silently falls back to `better-sqlite3`. No user configuration needed.

**Override:** Not currently supported — backend selection is automatic. If you need to force a specific backend, open an issue.

---

## Utility Commands

All platforms support utility commands via MCP meta-tools:

| Command | What it does |
|---------|-------------|
| `ctx stats` | Show context savings, call counts, and session statistics |
| `ctx doctor` | Diagnose installation: runtimes, hooks, FTS5, versions |
| `ctx upgrade` | Update from GitHub, rebuild, reconfigure hooks |
| `ctx purge` | Permanently deletes all indexed content from the knowledge base |

**How they work:** The MCP server exposes `stats`, `doctor`, `upgrade`, and `purge` tools. The `<ctx_commands>` section in routing instructions (CLAUDE.md, GEMINI.md, AGENTS.md, copilot-instructions.md) maps natural language triggers to MCP tool calls. The `doctor` and `upgrade` tools return shell commands that the LLM executes and formats as a checklist. The `purge` tool permanently deletes all indexed content from the knowledge base and is the sole reset mechanism.

---

## File: docs/UPSTREAM-CREDITS.md

# Upstream Skill Credits

context-mode references a small set of operating-discipline skills
authored by Matt Pocock (MIT). They are the operational backbone of the
[`context-mode-ops`](https://github.com/mksglu/context-mode/tree/next/.claude/skills/context-mode-ops)
skill (`/diagnose`, `/tdd`, `/grill-me`, `/grill-with-docs`,
`/improve-codebase-architecture`).

These skills are **referenced, not vendored**. Maintainers install them
locally via the upstream `skills` CLI. Shipping them to every plugin
install would pay description tokens for tools end users never invoke,
so they stay out of this repo.

## Source

- **Repository:** https://github.com/mattpocock/skills
- **License:** MIT (Copyright (c) 2026 Matt Pocock)
- **Author:** Matt Pocock

## Referenced (not vendored)

Install the upstream skills into your local Claude Code:

```bash
npx skills add mattpocock/skills \
  -s tdd grill-me grill-with-docs diagnose improve-codebase-architecture \
  -a claude-code -y
```

| Skill | Upstream path |
|-------|---------------|
| `/diagnose` | `skills/engineering/diagnose/` |
| `/tdd` | `skills/engineering/tdd/` |
| `/grill-me` | `skills/productivity/grill-me/` |
| `/grill-with-docs` | `skills/engineering/grill-with-docs/` |
| `/improve-codebase-architecture` | `skills/engineering/improve-codebase-architecture/` |

## Why reference instead of vendor?

The owner operating directive at the top of
[`context-mode-ops/SKILL.md`](https://github.com/mksglu/context-mode/tree/next/.claude/skills/context-mode-ops/SKILL.md)
treats these skills as mandatory tools, not advisory references.
Maintainers running `/context-mode-ops` install the upstream skills
once via the command above; end users do not need them. This keeps
the plugin install lean while preserving the discipline.

## License preservation

The MIT license terms travel with the source. Full license text lives
at the upstream repository
(https://github.com/mattpocock/skills). No portion is relicensed.
Skills remain MIT under Matt Pocock's copyright.