context-mode Documentation

Context window optimization for AI coding agents. Sandboxes tool output (98% reduction), persists session memory, and enforces routing across 17 platforms...

RAW Doc

File: README.md

Context Mode

The other half of the context problem.



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) โ€” the routing block stays focused on where data goes, not on how the model talks.

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:

text
/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) 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.

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:

text
/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

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

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

Full setup guide: 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 for the full reference. Tracking: #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 / #489.

After Cursor lists context-mode in the 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). 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). Routing relies on the .mdc rules file instead of hook context injection.

Full configs: configs/cursor/hooks.json | configs/cursor/mcp.json | 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 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, #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/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 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), Node.js 22+.

context-mode runs as a native 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.

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() (lifecycle) and api.registerHook() (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. 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

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. 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

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/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 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).

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/agent.json | 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). 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). 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 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, 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/SYSTEM.md | plugin source: src/adapters/omp/plugin.ts

Build Prerequisites (CentOS, RHEL, Alpine)

Context Mode uses 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 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) โ€” 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, #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), 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

text
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.

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).

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). 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.

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). 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 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, #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 ~98% saved ~60% saved
Gemini CLI Yes GEMINI.md ~98% saved ~60% saved
VS Code Copilot Yes copilot-instructions.md ~98% saved ~60% saved
JetBrains Copilot Yes copilot-instructions.md ~98% saved ~60% saved
GitHub Copilot CLI Yes copilot-instructions.md ~98% saved ~60% saved
Cursor Yes context-mode.mdc ~98% saved ~60% saved
OpenCode Plugin AGENTS.md ~98% saved ~60% saved
OpenClaw Plugin AGENTS.md ~98% saved ~60% saved
Codex CLI Yes AGENTS.md ~98% saved ~60% saved
Antigravity -- GEMINI.md -- ~60% saved
Antigravity CLI (agy) Bounded routing rule + skill (rules, skill) bounded Bash/Read/Grep/WebFetch enforcement ~60% saved
Kiro Yes KIRO.md ~98% saved ~60% saved
Zed -- AGENTS.md -- ~60% saved
Pi โœ“ AGENTS.md ~98% saved ~60% saved
OMP Plugin 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 for the full capability comparison.

Utility Commands

Inside any AI session โ€” just type the command. The LLM calls the MCP tool automatically:

text
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 โ†’

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)

text
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

text
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

text
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)

text
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

text
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

text
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 โ†’

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 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) fired only once per session, which got lost after context compaction in MCP-heavy sessions (e.g. 50+ Jira/Slack/Notion calls โ€” see #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 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 (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:

    text
    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:

    text
    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:

text
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 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 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 โ€” Compaction hooks not firing. Closed as duplicate of #3728; fix merged.
  • #5513 โ€” api.on() hooks not invoked for tool lifecycle events. Fixed in PR #9761.

Minimum Version

Required: OpenClaw >2026.1.29

This is the first release that includes the api.on() fix from PR #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

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

Upgrade

Update context-mode to the latest version:

text
context-mode upgrade

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

Verification

Run the diagnostics to verify everything is working:

text
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:

text
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:

text
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:

text
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.

  • 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:

text
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:

text
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:


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)
  • 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:

text
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:


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:

text
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:

text
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:

text
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:


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)

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:

text
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, forum #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) 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. 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:

text
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
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
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

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
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.