context-mode 在 Gemini CLI 中的强制路由规则:GEMINI.md 指令全解与上下文窗口守护机制
【免费下载链接】context-modeContext window optimization for AI coding agents. Sandboxes tool output (98% reduction), persists session memory, and enforces routing across 17 platforms via MCP + hooks.项目地址: https://gitcode.com/GitHub_Trending/cl/context-mode
context-mode 通过 Gemini CLI 的 GEMINI.md 指令文件注入一组强制路由规则(MANDATORY routing rules),把模型对工具的使用路径收敛到沙箱化的 MCP 工具之上,从而防止未路由的命令把海量输出灌入上下文窗口。本文以configs/gemini-cli/GEMINI.md为骨架,结合src/adapters/gemini-cli/、hooks/gemini-cli/的源码与configs/gemini-cli/settings.json的钩子配置,逐条拆解规则背后的机制、工具选择协议与并发批次用法,并给出可直接落地的实操指引。
为什么需要强制路由:一个未路由命令就灌入 56 KB
GEMINI.md 开篇就给出了这条规则的动机:一条未路由的命令会把 56 KB 原始输出直接塞进上下文窗口。Gemini CLI 的原生工具(如run_shell_command、read_file、web_fetch)返回的都是未经处理的原始文本,反复调用会迅速耗尽上下文预算,导致模型"忘记"早期指令、产生幻觉或在长会话中退化。
context-mode 的解决思路不是禁止工具,而是路由:把"会吐大量数据的操作"从原生工具重定向到专用的 MCP 沙箱工具,只让经过提炼的结果(通常是 stdout 里的一行答案)进入上下文。这套路由由两个层面共同实现:
- 钩子层拦截:
hooks/gemini-cli/beforetool.mjs调用routePreToolUse()(定义于 hooks/core/routing.mjs),对run_shell_command|read_file|read_many_files|grep_search|search_file_content|web_fetch|activate_skill|mcp__plugin_context-mode以及外部mcp__命名空间(负向前瞻mcp__(?!.*context-mode),见 src/adapters/gemini-cli/hooks.ts)做前置审查,命中则返回decision: "deny"或参数改写; - 指令层引导:GEMINI.md 告诉模型"哪些操作被拦截、应该改用哪个工具",从模型决策源头减少违规尝试。
Think in Code:用一段脚本替代十次工具调用
GEMINI.md 的第一条强制规则是Think in Code:凡是分析、计数、过滤、比较、搜索、解析、变换数据的任务,都必须通过mcp__context-mode__ctx_execute(language, code)写代码完成,只用console.log()输出答案,禁止把原始数据读进上下文。
这条规则背后的原理("PROGRAM the analysis, not COMPUTE it")在于:原生工具链的每次调用都是一次往返,每次都把全量数据带回上下文;而把分析写成一段 JavaScript 代码放进沙箱,数据在沙箱内完成计算,唯一进入上下文的只有console.log打印的那一行结果。约束如下:
- 使用纯 JavaScript,仅允许 Node.js 内置模块(
fs、path、child_process); - 必须写
try/catch,处理null/undefined; - 一段脚本替换十次工具调用。
代码形式的补充说明在 skills/context-mode/SKILL.md 中有更细的映射表,例如"命中 API 端点"用ctx_execute执行fetch('http://localhost:3000/api/orders'),"读取日志/数据文件做分析"用ctx_execute_file(文件加载进FILE_CONTENT而非上下文)。
BLOCKED:被强制拦截的三类操作
GEMINI.md 明确列出了三类被拦截的操作,模型不得重试,只能走指定替代路径:
| 被拦截操作 | 拦截特征 | 替代工具 |
|---|---|---|
| curl / wget | Shell 中的curl/wget被拦截 | ctx_fetch_and_index(url, source)或ctx_execute(language: "javascript", code: "const r = await fetch(...)") |
| 内联 HTTP | fetch('http、requests.get(、requests.post(、http.get(、http.request( | ctx_execute(language, code)——只有 stdout 进入上下文 |
| WebFetch / 网页浏览 | Gemini 原生web_fetch工具 | ctx_fetch_and_index(url, source)后再ctx_search(queries) |
这些拦截由 hooks/gemini-cli/beforetool.mjs 的routePreToolUse实际执行,并经由 src/adapters/gemini-cli/index.ts 的formatPreToolUseResponse转译为 Gemini CLI 的响应格式——拦截时返回{ decision: "deny", reason: ... }(注意 Gemini CLI 用的是decision字段而非 Claude Code 的permissionDecision)。
ctx_fetch_and_index与ctx_execute的价值在于"原始 HTML/网络响应永不进入上下文":抓取与索引在服务端完成,模型拿到的只有后续ctx_search的检索结果。
REDIRECTED:三类操作改走沙箱
对于未被完全封死、但会产生大量输出的操作,GEMINI.md 采取"重定向到沙箱"策略:
- Shell(输出超过 20 行):Shell 仅保留给
git、mkdir、rm、mv、cd、ls、npm install、pip install这类低噪声命令;其余一律改用ctx_batch_execute(commands, queries)或ctx_execute(language: "javascript", code: "...")。只有当代码与宿主机 shell 匹配时才使用language: "shell"。 - read_file(用于分析):区分意图——为了编辑而读文件用
read_file正确;为了分析/探索/总结而读文件,改用ctx_execute_file(path, language, code),让文件内容进入沙箱的FILE_CONTENT而不进入上下文。 - grep / search(结果量大):用
ctx_execute(language: "javascript", code: "...")在沙箱内做可移植的过滤与计数。
从源码结构看,这种"编辑 vs 分析"的二分是为了保住read_file在写代码工作流中的低延迟体验,同时把分析型读取挡在上下文之外——这与 skills/context-mode/SKILL.md 中"处理大数据一律用ctx_execute/ctx_execute_file而非 Bash/cat"的总体约定一致。
工具选择协议:六步工作流
GEMINI.md 给出了一套编号的工具选择顺序,用于规范整个会话中的工具使用节奏:
- MEMORY:
ctx_search(sort: "timeline")——恢复会话后、向用户提问之前,先检索既往上下文; - GATHER:
ctx_batch_execute(commands, queries)——一次性运行所有命令、自动建索引、返回搜索结果,一次调用替代 30+ 次调用;每条命令形如{label: "header", command: "..."}; - FOLLOW-UP:
ctx_search(queries: ["q1", "q2", ...])——所有追问放进一个数组、一次调用(默认相关性模式); - PROCESSING:
ctx_execute(language, code)/ctx_execute_file(path, language, code)——沙箱处理,只有 stdout 进入上下文; - WEB:
ctx_fetch_and_index(url, source)后接ctx_search(queries)——原始 HTML 永不进入上下文; - INDEX:
ctx_index(content, source)——把内容存入 FTS5 全文索引,供后续检索。
这套协议的核心思想是批量化:把"问一句答一句"的对话式工具使用,改造成"一次收集、多次检索、沙箱加工"的管道式处理,显著减少上下文往返次数。
并行 I/O 批次:concurrency 参数的正确用法
对于多 URL 抓取或多 API 调用,GEMINI.md 要求始终携带concurrency: N(1-8):
ctx_batch_execute(commands: [3+ network commands], concurrency: 5)——适用于 gh、curl、dig、docker inspect、多区域云查询等网络密集型命令;ctx_fetch_and_index(requests: [{url, source}, ...], concurrency: 5)——多 URL 批量抓取。
并发的选型有明确的取舍规则:
- I/O 密集(网络调用、API 查询)用concurrency 4-8;
- CPU 密集(npm test、build、lint)或共享状态(端口、锁文件、同一仓库写入)保持 concurrency 1;
- GitHub API 有速率限制:
gh调用上限设为 4。
输出与会话连续性:写文件、打标签、先搜索再提问
GEMINI.md 对输出规范的要求是:
- 产物写入文件,绝不打成内联文本——返回"文件路径 + 一行描述";
- 为
search(source: "label")提供描述性来源标签,便于事后检索定位; - 技能、角色与决策在整个会话期间持续生效,不随对话变长而放弃。
会话记忆(Memory)是这套机制的另一根支柱。会话历史是持久化且可搜索的,恢复会话后必须"先搜索、再提问":
| 需求 | 命令 |
|---|---|
| 我们之前在做什么? | ctx_search(queries: ["summary"], source: "compaction", sort: "timeline") |
| 我们决定了什么? | ctx_search(queries: ["decision"], source: "decision", sort: "timeline") |
| 什么不要重复做? | ctx_search(queries: ["rejected"], source: "rejected-approach") |
| 存在哪些约束? | ctx_search(queries: ["constraint"], source: "constraint") |
若搜索返回 0 条结果,则按全新会话处理。注意:用户提示词历史不可用(Note: user-prompt history not available),因此不能依赖提示词回放,必须靠事件索引。
这套"先搜索再提问"的约定在实现层面由 hooks/gemini-cli/sessionstart.mjs 支撑:SessionStart 钩子按source(startup/compact/resume/clear)分派——startup清理旧会话并把~/.gemini/GEMINI.md与项目根GEMINI.md的规则内容写入会话事件库;compact写入事件文件并注入会话知识指令;resume加载此前会话事件并注入恢复指令。而 hooks/gemini-cli/beforeagent.mjs 则扮演 UserPromptSubmit 的等价物:捕获每条真实用户提示(跳过<system-reminder>等系统消息),做决策/角色/意图抽取并写入 SQLite 会话库,从而让 LLM 在 compact/resume 之后能从用户离开的确切位置续上——该钩子被要求极快(单次 SQLite 写入,目标 <10ms),失败时静默降级绝不阻塞会话。
ctx 命令:四条运维子命令
GEMINI.md 把四条上下文运维命令映射到了对应的 MCP 工具:
| 命令 | 动作 |
|---|---|
ctx stats | 调用statsMCP 工具,原样显示完整输出 |
ctx doctor | 调用doctorMCP 工具,运行其返回的 shell 命令,以清单形式展示 |
ctx upgrade | 调用upgradeMCP 工具,运行其返回的 shell 命令,以清单形式展示 |
ctx purge | 调用purgeMCP 工具(confirm: true),清空知识库前会先警告 |
其中ctx doctor对 Gemini CLI 的健康检查覆盖全部六个钩子脚本(BeforeAgent/BeforeTool/AfterTool/AfterModel/PreCompress/SessionStart),见 src/adapters/gemini-cli/index.ts 的getHealthChecks——它直接探测<pluginRoot>/hooks/gemini-cli/<scriptName>的文件存在性,避免经过命令字符串解析的间接路径。
另外注意:/clear或/compact之后,知识库与会话统计仍然保留。如需彻底清空、从零开始,必须显式执行ctx purge。
环境配置:钩子与 MCP 的两份配置文件
要让 GEMINI.md 中的规则真正生效,Gemini CLI 环境需要完成两层配置:
1. 钩子注册(settings.json)——configs/gemini-cli/settings.json 展示了完整的钩子清单,用户级配置位于~/.gemini/settings.json,项目级位于项目根.gemini/settings.json(适配器注释见 src/adapters/gemini-cli/index.ts):
{ "hooks": { "BeforeTool": [ { "matcher": "run_shell_command|read_file|read_many_files|grep_search|search_file_content|web_fetch|activate_skill|mcp__plugin_context-mode", "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli beforetool" }] } ], "AfterTool": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli aftertool" }] }], "AfterModel": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli aftermodel" }] }], "PreCompress": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli precompress" }] }], "SessionStart": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli sessionstart" }] }] } }其中的 BeforeTool matcher 在适配器实现里(src/adapters/gemini-cli/index.ts)还会追加mcp__context-mode前缀与外部 MCP 捕获模式mcp__(?!.*context-mode)(对应 issue #529:没有它,slack/telegram/gdrive/notion 等外部 MCP 的大体积输出会绕过路由提示直接淹没模型上下文)。六个钩子分别对应hooks/gemini-cli/下的beforeagent.mjs、beforetool.mjs、aftertool.mjs、aftermodel.mjs、precompress.mjs、sessionstart.mjs(映射表见 src/adapters/gemini-cli/hooks.ts)。ctx upgrade会通过configureAllHooks(src/adapters/gemini-cli/index.ts)自动补齐或更新这些钩子条目,并为脚本设置 0755 可执行权限。
2. MCP 服务器注册(mcp.json)——configs/gemini-cli/mcp.json 的内容极简:
{ "mcpServers": { "context-mode": { "command": "context-mode" } } }注册后,mcp__context-mode__ctx_execute、ctx_batch_execute、ctx_fetch_and_index、ctx_search、ctx_index、ctx_execute_file等工具才会出现在模型中,GEMINI.md 中的全部路由指令才有对应的落点。
源码级原理补充:Gemini CLI 适配器的能力边界
从 src/adapters/gemini-cli/index.ts 的能力声明可以看到 Gemini CLI 平台支持:preToolUse、postToolUse、preCompact、sessionStart全开,且canModifyArgs(通过hookSpecificOutput.tool_input合并改写参数)与canModifyOutput(decision: "deny"+ reason 替换输出)都为 true。但它也有明确限制:
- 无
decision: "ask"支持——需要用户确认的动作会被降级为deny(安全策略默认拦截,见 src/adapters/gemini-cli/index.ts); - PreCompress 仅为 advisory(异步、不能阻塞);
- 钩子暂不对子代理(subagents)触发;
- 项目目录解析优先级为
input.cwd>GEMINI_PROJECT_DIR>CLAUDE_PROJECT_DIR>process.cwd()(src/adapters/gemini-cli/index.ts),会话 ID 优先取session_id字段,缺失时回退pid-<ppid>。
这些边界决定了 GEMINI.md 中"BLOCKED 即 deny、REDIRECTED 即改写参数"的实际行为模式,也解释了为何规则要写得如此强硬——因为 Gemini CLI 平台本身不提供温和的"询问用户"通道。
结语:一份可执行的"上下文预算管理手册"
configs/gemini-cli/GEMINI.md本质上是一份写给模型阅读的上下文预算管理手册:它把"工具输出不可控"这个 Agent 工程中的经典难题,拆解为 BLOCKED(硬拦截)、REDIRECTED(沙箱重定向)、工具选择协议、并行批次、文件化输出与先搜索再提问六组可执行规则。对使用者而言,只需保证~/.gemini/settings.json的钩子注册与 MCP 服务器配置完整(可运行ctx doctor自检),模型就会在 GEMINI.md 的持续约束下,把绝大多数数据密集操作收敛进沙箱,让 56 KB 的原始输出变成一行答案——这正是 context-mode 在 Gemini CLI 上守护上下文窗口的工作方式。
【免费下载链接】context-modeContext window optimization for AI coding agents. Sandboxes tool output (98% reduction), persists session memory, and enforces routing across 17 platforms via MCP + hooks.项目地址: https://gitcode.com/GitHub_Trending/cl/context-mode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考