goose Code Mode 实战解析:8 个关于上下文瘦身、批量执行与跨编辑器复用的关键细节
如果你使用 goose(一个开源的、可扩展的 AI Agent)进行长时间编码会话,你迟早会遇到同一个敌人——context rot(上下文腐烂):随着会话变长,Agent 有限的记忆窗口里塞满了工具定义与历史消息,早期指令逐渐被"挤"出有效上下文,输出开始变得不可靠,甚至在代码库里埋下隐蔽但严重的错误。Code Mode 正是针对这一问题的工程解法:它不再把几十个工具的 JSON 定义一次性塞给模型,而是让 Agent"写代码去调用工具",从而把模型需要同时记住的内容降到最低。
本文以 goose 社区作者的真实实验为基础(修复 goose 自身在 Gemini 图像处理上的缺陷并提交 PR,同样的任务分别在开启 / 关闭 Code Mode 下各跑一遍),结合仓库内 code_execution.rs 等源码,拆解 Code Mode 的底层原理、实测收益、适用边界与启用方式。读完你将掌握:Code Mode 与 MCP 的真实关系、它如何节省 30% 的 token、为什么它能自动纠正 Agent 的工具选择错误、以及如何通过 ACP 在 Neovim 等编辑器中复用同一套 Code Mode 工作流。
背景:长会话为什么需要 Code Mode
Agent 已经改变了编程方式——开发者不再需要在多个专业工具之间反复切换、也不再依赖其他团队,一个 Agent 就能执行复杂的多步骤任务。但随之而来的是新挑战:如何在长时间会话中让 Agent 保持可靠。
问题根源在于模型记忆有限。一段运行过久的会话会让模型"忘记"早先的指令,导致不可靠的输出、挫败感,以及对代码库"微妙但致命"的破坏。传统思路是逐个排查并禁用不用的 MCP 服务器,但这既繁琐又粗暴。
Code Mode 提供了另一种思路:与其向 LLM 描述几十个彼此独立的工具,不如让 Agent 编写代码,用代码去程序化地调用这些工具。模型因此无需在上下文中一次性承载所有工具定义。这个概念最早由 Cloudflare 提出、Anthropic 跟进实践,goose 团队则将其做成了开源实现(详见 documentation/blog/2025-12-15-code-mode-mcp/index.md),并在 v1.17.0 起作为平台扩展随 goose 发布。
下面就是作者在数月的日常使用与对照实验中总结出的 8 个关键事实。
1. Code Mode 不是 MCP 的"杀手"
很多人在 Code Mode 概念刚出现时宣称它将取代 MCP,实际上 Code Mode 内部依然使用 MCP。
MCP 是一套标准,让 AI Agent 连接外部工具与数据源。当你给 Agent 安装一个 MCP 服务器(在 goose 生态里叫"扩展 / extension")后,该服务器会把自身能力以 MCP 工具的形式暴露出来。例如 goose 内置的核心扩展 developer 就暴露了 shell(执行命令)与 text_editor(查看和编辑文件)等工具。
Code Mode 做的是把你的 MCP 工具包装成 JavaScript 模块,让 Agent 能把多次工具调用合并为一次执行。用 HTTP / REST 的关系来类比最贴切:MCP 是底层协议,负责通信;Code Mode 是基于协议之上的一种交互模式,让 Agent 更高效地使用这些工具。在 goose 的生态里,Code Mode 本身甚至就被实现为一个 MCP 服务器(扩展),见下述第 2 点。
2. goose 原生支持 Code Mode:v1.17.0 起随平台扩展发布
Code Mode 支持于 2025 年 12 月随 goose v1.17.0 落地。它以名为 "Code Mode" 的平台扩展形式发布,桌面应用与 CLI 均可启用:
- 桌面应用:点击扩展(extensions)图标,打开 "Code Mode" 开关;
- CLI:运行
goose configure,启用 Code Mode 扩展。
从源码看,Code Mode 以 feature flag 形式编译进 goose。crates/goose/Cargo.toml 中定义了 code-mode = ["dep:pctx_code_mode"] 特性,依赖 pctx_code_mode crate(v0.5.0)提供核心代码执行能力。
在 platform_extensions/mod.rs 的 PLATFORM_EXTENSIONS 注册表中,可以清楚看到这条记录:
name: "code_execution" (扩展注册名,即 EXTENSION_NAME)
display_name: "Code Mode"
description: "Goose will make extension calls through code execution, saving tokens"
default_enabled: false (默认关闭,需手动启用)
unprefixed_tools: true (工具不以扩展名前缀暴露,作为一等公民使用)
也就是说,当你在 UI 中打开 "Code Mode" 时,实际加载的是名为 code_execution 的平台扩展。它 default_enabled: false 意味着用户必须显式开启——这也与博客作者"在桌面应用 / CLI 中手动启用"的建议一致。
从实现演进看,2025-12-15 的发布博客记载最初基于可嵌入的 JavaScript 引擎编写;而当前 code_execution.rs 的注释显示执行已构建在具备 Deno/V8 特性的运行时之上(通过 pctx_code_mode),并围绕该引擎做了精心的超时与取消设计(见第 4 节)。自初版发布以来,该功能已持续收获大量改进。
3. Code Mode 让你的上下文窗口保持干净
每安装一个 MCP 服务器(扩展),都会向 Agent 的内存注入可观的数据:每个工具都带一份描述其用途、接受参数与返回结果的 工具定义。这些定义会持续占用上下文窗口。
例如:一份定义占 500 token、一个扩展有 5 个工具,还没开工就已消耗 2,500 token;如果同时启用多个扩展,这个数字轻松翻倍甚至达到 10 倍。Code Mode 开启前,一次典型会话的上下文开头可能长这样:
[System prompt: ~1,000 tokens]
[Tool: developer__shell - 500 tokens]
[Tool: developer__text_editor - 600 tokens]
[Tool: developer__analyze - 400 tokens]
[Tool: slack__send_message - 450 tokens]
[Tool: slack__list_channels - 400 tokens]
[Tool: googledrive__search - 500 tokens]
[Tool: googledrive__download - 450 tokens]
... and so on for every tool in every extension
随着会话推进,你真正关心的内容——正在讨论的代码、要解决的问题、早先给出的指令——反而被这些"没在用"的工具定义挤出窗口,导致性能下降和记忆丢失。
作者过去推荐手动禁用不用的 MCP 服务器,但 Code Mode 给出了更优雅的方案:它只暴露少数几个发现型工具,让 Agent 按需查找,而不是把所有定义一次性全量载入:
search_modules—— 查找可用的扩展;read_module—— 了解某个扩展提供哪些工具;execute_code—— 运行调用这些工具的 JavaScript。
这一组"发现—读取—执行"的骨架在源码中有对应实现:当前 CodeExecutionClient 在 ToolDisclosure::Catalog 模式下实际暴露 list_functions、get_function_details 与 execute_typescript 三个工具(见 code_execution.rs),同时在注入的指令(catalog_disclosure_moim)中明确告诫模型:"这些回调函数只能在 execute_typescript 内部使用,不要直接把回调函数名当工具调用;先用 list_functions / get_function_details 检视签名。"(code_execution.rs)。工具名在不同版本与 disclosure 模式下略有差异,但"按需发现、聚合成单次执行"的设计目的一致。
作者的对照实验量化了这一收益:同样的"修复 bug 并提 PR"任务,开启 Code Mode 后 token 消耗降低约 30%:
| Metric | With Code Mode | Without Code Mode |
|---|---|---|
| Total tokens | 23,339 | 33,648 |
| Input tokens | 23,128 | 33,560 |
4. Code Mode 把多次操作批量为一次工具调用
Token 节省并不只来自"前置加载的工具定义变少",还来自对对话"活跃侧"的**批量化(batching)**处理。
当你让 Agent 做一件事时,它通常会把请求拆成一个个独立步骤,每步一次工具调用。例如对 goose 说"查看当前分支、展示 diff、跑测试",它可能执行四条独立命令:
▶ developer__shell → git branch --show-current
▶ developer__shell → git status
▶ developer__shell → git diff
▶ developer__shell → cargo test
每一次调用都会给 goose 需要追踪的会话历史增加一层。批量化则把这些合并进一次执行。开启 Code Mode 后,同样的提示词在界面上只会出现一次工具调用:
▶ Code Execution: Execute Code
generating...
在这次执行内部,所有命令被编排进一个脚本:
import { shell } from "developer";
const branch = shell({ command: "git branch --show-current" });
const status = shell({ command: "git status" });
const diff = shell({ command: "git diff" });
const tests = shell({ command: "cargo test" });
对用户而言结果完全相同,但 Agent 只需要记住一次交互而不是四次。往返次数减少,会话历史保持精简,Agent 得以始终聚焦当前任务。
源码层面,"必须批量化"并非软性建议而是硬性指令。CodeExecutionClient::get_moim 注入的提示原文如此(code_execution.rs):
ALWAYS batch multiple tool operations into ONE execute_typescript call.
- WRONG: Separate execute_typescript calls for read file, then write file
- RIGHT: One execute_typescript with an async run() function that reads AND writes AND logs/returns as little information as needed for the next step.
值得注意的是,批量执行的背后是完整的回调分发机制:build_callback_registry 会把当前会话中每个对模型可见的工具注册为一个 JavaScript 可调用的回调(code_execution.rs),脚本里的 shell(...) 等调用最终经 ExtensionManager::dispatch_tool_call 路由回真实的 MCP 工具。任何工具在一次执行中出错,其退出码与 stdout/stderr 都会被打包返回给模型继续处理。
工程上还有一个值得留意的细节:由于执行引擎不是 Send 类型,goose 把代码执行放入独立 tokio 运行时,并用"执行超时 + 取消令牌"双保险防止某个卡死的脚本把整条会话堵死(code_execution.rs)。超时/取消时会通过子令牌(dispatch token)通知仍在飞行中的嵌套工具调用停止,避免后台残留进程——这正是对应仓库测试 run_in_deno_runtime_times_out_on_hung_execution、real_v8_hung_script_times_out_and_frees_the_runtime 所验证的行为。执行总时长由全局配置的扩展默认超时(DEFAULT_EXTENSION_TIMEOUT)兜底。
5. Code Mode 会做出更聪明的工具选择
当 Agent 面对几十个工具时,它有时会做出"听起来合理、但技术上是错的"选择。因为在标准模式下,Agent 依据的是扁平列表加一段简短文本描述,于是很可能挑中一个"名字像那么回事、实则缺上下文"的工具,白白浪费大量时间与 token。
作者在实验中就撞上了这一幕:他启用了名为 agent-task-queue 的扩展(用于带超时执行后台任务)。当请 goose 运行 PR 测试时,Agent 审视可用工具后看到 agent-task-queue,它判断"测试套件属于长时间运行的任务",于是选用了这个专用工具而不是通用的 shell。结果调用立刻失败:
FAILED exit=127 0.0s
/bin/sh: cargo: command not found
作者的环境并没有为当前工具链配置那个扩展——goose 基于描述做出了"合理"的选择,但对真实环境而言却用错了工具。
同样的任务在 Code Mode 会话里从未发生。原因在于 Code Mode 要求显式的 import 语句——Agent 不再"浏览一堆名字",而必须明确自己要用哪个模块。它选择了从 developer 模块导入:
import { shell } from "developer";
const test = shell({ command: "cargo test -p goose --lib formats::google" });
显式地 import ... from "developer" 确保测试真正运行在作者自己的 shell 环境里。这正呼应了第 5 点背后更深的设计意图:工具选择从"靠名字猜测"变成"靠模块显式声明",Agent 必须对自己的调用负责。
6. Code Mode 可以跨编辑器移植
goose 不只是 Agent,它同时也是 ACP(Agent Client Protocol)服务器——这意味着你可以把它接入任何支持 ACP 的编辑器,例如 Zed 或 Neovim;而且在 goose 里配置过的 MCP 服务器同样在这些编辑器中可用。更棒的是,连 Code Mode 本身也可以一起"带过去"。
作者亲自在 Neovim 中尝试:把 avante.nvim 连接到 goose,并且开启 Code Mode。配置如下:
{
"yetone/avante.nvim",
build = "make",
event = "VeryLazy",
opts = {
provider = "goose",
acp_providers = {
["goose"] = {
command = "goose",
args = { "acp", "--with-builtin", "code_execution,developer" },
},
},
},
dependencies = {
"nvim-lua/plenary.nvim",
"MunifTanjim/nui.nvim",
},
}
关键就在编辑器配置内直接开启 Code Mode 的这一行:
args = { "acp", "--with-builtin", "code_execution,developer" },
这行命令里的两个名字正好对应前面第 2 节提到的平台扩展:code_execution(即 Code Mode 的注册名)与 developer(提供 shell / text_editor 的核心开发扩展)。作者让 goose 列出 Rust 文件并统计代码行数——Neovim 缓冲区里没有出现一长串零散的 shell 命令,而是单独一个 "Code Execution" 工具调用,效果与桌面应用完全一致。
这种可移植性意味着:你可以在某处打磨出一套高效、省 token 的 Agent 工作流,然后带着它走进任何你感到舒适的编辑器环境。有关 goose 作为 ACP 服务器接入编辑器的更多配置,可参考 documentation/docs/guides/acp-providers.md。
7. Code Mode 在不同 LLM 上的表现差异很大
作者实验所用的模型是 Claude Opus 4.5,结果会因模型而异。Code Mode 对模型提出了四类要求,而并非所有模型都同样擅长:
- 写出合法 JavaScript:模型必须生成语法正确的代码。代码生成能力更强的模型,报错会更少;
- 遵循 import 模式:Code Mode 期望模型用
import { shell } from "developer"这样的方式从模块导入工具。有些模型会绕过 import 直接调用工具名——这必然失败; - 善用发现工具:写代码之前,模型应当先调用
search_modules/read_module(或实现中的list_functions/get_function_details)弄清可用工具。跳过这一步直接猜的模型,容易凭空捏造出根本不存在的工具名; - 优雅地处理错误:一次代码执行失败后,模型需要阅读错误、理解原因并重试。不同模型在这种"反馈闭环"上的表现差异明显。
如果 Code Mode 在你的配置下表现不佳,先试试换模型。擅长代码生成与指令遵循的模型,通常比那些为其他任务优化的模型更适合 Code Mode。
8. Code Mode 并不适合所有任务
Code Mode 有额外开销。真正执行任何操作之前,LLM 至少要经过四步:
- 调用
search_modules查找可用扩展; - 调用
read_module了解扩展提供的工具; - 编写 JavaScript 代码;
- 调用
execute_code运行它。
对简单、单工具的任务来说,这套开销并不划算——如果只是想跑一条 shell 命令或看一眼文件,常规工具调用反而更快。这也可以从代码层面解释:在 ToolDisclosure::Catalog(默认)模式下,模型被要求先通过 list_functions/get_function_details 检视签名再写 execute_typescript;而每个回调工具只在执行环境内部可用,模型无法"即点即用"。
结合实验,作者给出了清晰的取舍建议:
| Use Code Mode When | Skip Code Mode When |
|---|---|
| You have multiple extensions enabled | You only have 1-2 extensions |
| Your task involves multi-step orchestration | Your task is a single tool call |
| You want longer sessions without context rot | Speed matters more than context longevity |
| You are working across multiple editors | You are doing a quick one-off task |
翻译成实践准则:扩展数量多、任务需多步编排、追求长会话不腐坏、在多编辑器间复用时,Code Mode 是优选;反之当扩展少、单次工具调用、追求即时速度、只做一次性任务时,关掉它更明智。
自己动手:从启用到跑通实验
想亲自验证 Code Mode,可以按下面的路线走:
- 启用扩展:桌面端点击扩展图标打开 "Code Mode";CLI 运行
goose configure勾选 Code Mode。注意它默认关闭(default_enabled: false)。 - 阅读扩展指南:documentation/docs/getting-started/using-extensions.md 中把 Code Mode 描述为"通过执行 JavaScript 完成工具发现与工具调用,随当前构建附带"——它是在 goose 现有扩展体系内使用。
- 设计你自己的对照实验:选一个真实的多步骤任务(参考作者的做法:让它修复代码缺陷并提交 PR),分别在开启与关闭 Code Mode 下各跑一遍,比较总 token 与输入 token、观察工具调用次数与上下文占用。
- 把它带到其他编辑器:参考第 6 节的 Neovim/avante 配置,用
goose acp --with-builtin code_execution,developer在 ACP 客户端中复用同一套 Code Mode。 - 深入背景:想了解该特性的设计初衷与早期实现,可读 documentation/blog/2025-12-15-code-mode-mcp/index.md;想弄清它为何"不取代 MCP",可读 documentation/blog/2025-12-21-code-mode-doesnt-replace-mcp/index.md。
如果你手头的扩展数量正在膨胀、长会话频繁"失忆",或者希望把同一套高效 Agent 工作流带到不同编辑器,那么 Code Mode 值得成为你默认开启的选项;但请记住第 8 点——它解决的是"上下文规模"问题,而不是"响应速度"问题。最好的使用方式,是在你自己的任务集上跑一次对照实验,让数据替你决定。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
