首页
/ goose Code Mode 实战解析:8 个关于上下文瘦身、批量执行与跨编辑器复用的关键细节

goose Code Mode 实战解析:8 个关于上下文瘦身、批量执行与跨编辑器复用的关键细节

2026-09-07 14:57:14作者:范靓好Udolf

如果你使用 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.rsPLATFORM_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 按需查找,而不是把所有定义一次性全量载入:

  1. search_modules —— 查找可用的扩展;
  2. read_module —— 了解某个扩展提供哪些工具;
  3. execute_code —— 运行调用这些工具的 JavaScript。

这一组"发现—读取—执行"的骨架在源码中有对应实现:当前 CodeExecutionClientToolDisclosure::Catalog 模式下实际暴露 list_functionsget_function_detailsexecute_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_executionreal_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" 工具调用,效果与桌面应用完全一致。

Neovim with Code Mode enabled

这种可移植性意味着:你可以在某处打磨出一套高效、省 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 至少要经过四步:

  1. 调用 search_modules 查找可用扩展;
  2. 调用 read_module 了解扩展提供的工具;
  3. 编写 JavaScript 代码;
  4. 调用 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,可以按下面的路线走:

  1. 启用扩展:桌面端点击扩展图标打开 "Code Mode";CLI 运行 goose configure 勾选 Code Mode。注意它默认关闭(default_enabled: false)。
  2. 阅读扩展指南documentation/docs/getting-started/using-extensions.md 中把 Code Mode 描述为"通过执行 JavaScript 完成工具发现与工具调用,随当前构建附带"——它是在 goose 现有扩展体系内使用。
  3. 设计你自己的对照实验:选一个真实的多步骤任务(参考作者的做法:让它修复代码缺陷并提交 PR),分别在开启与关闭 Code Mode 下各跑一遍,比较总 token 与输入 token、观察工具调用次数与上下文占用。
  4. 把它带到其他编辑器:参考第 6 节的 Neovim/avante 配置,用 goose acp --with-builtin code_execution,developer 在 ACP 客户端中复用同一套 Code Mode。
  5. 深入背景:想了解该特性的设计初衷与早期实现,可读 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 点——它解决的是"上下文规模"问题,而不是"响应速度"问题。最好的使用方式,是在你自己的任务集上跑一次对照实验,让数据替你决定。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388