【block/goose】核心架构与设计原理深度解析:Linux 基金会 AAIF 旗下 50k Star 的 Rust Coding Agent Harness 是如何用 Provider Registry + MCP Extension + Hook + Recipe 四层抽象统一所有 LLM 的
引子:当 Block 把 goose 捐给 Linux 基金会,50k Star 的 Rust Coding Agent 想做什么
2024 年 8 月,Block(前 Square,Jack Dorsey 的支付公司)开源了 goose,最初定位是”在桌面、CLI、API 三端可用的本地 AI Agent”。一年半之后的 2026 年 7 月,goose 已经被捐给了 Linux 基金会旗下的 Agentic AI Foundation(AAIF),Star 数突破 50,734,主仓库(aaif-goose/goose)刚刚在 7 月 7 日有 commit,活跃度拉满。这与同期 Anthropic、OpenAI、Google 的官方 Agent 框架并不冲突——goose 的定位非常明确:一个真正可自托管、可扩展、跨 15+ LLM Provider、对接 70+ MCP Extension 的开源 Coding Agent Harness。
它不是另一种 LangChain。LangChain 是 Python 的 LLM 应用胶水层;goose 是 Rust 写的、有完整桌面 UI + CLI + HTTP Server 三端、有自研调度器(crates/goose/src/scheduler.rs,49KB)、有自研 Provider Registry(crates/goose/src/providers/provider_registry.rs,14KB)、有自研 Hook 系统(11 个事件点,比 Claude Code 的 5 个还多)、有自研 Recipe 子命令引擎、有完整的 MCP Elicitation 跨进程等待方案(crates/goose/src/action_required_manager.rs)。换句话说:它把 2026 年下半年所有 Coding Agent 工程化议题都揉进了同一个 Rust workspace。
今天这篇文章会带你逐层拆解 goose 的内部结构。我们从仓库统计、整体架构、四层抽象(Provider / Extension / Hook / Recipe)入手,再深入调度器、子 Agent、Platform Extension、OAuth Provider Refresh、ActionRequiredManager 等关键模块,最后用对比表分析它与 Claude Code、Continue、Cline、Goose ACP 这条赛道的工程哲学差异。
项目定位与核心价值
一句话定义
goose 是一个由 Linux 基金会 AAIF 维护的 Rust 编写的开源 Coding Agent Harness,原生支持桌面应用、CLI 与 HTTP Server 三种部署形态,通过 Provider Registry 抽象 15+ LLM 后端,通过 MCP Extension 抽象 70+ 工具,通过 Hook 系统抽象 11 个生命周期事件,通过 Recipe YAML 抽象可复用的任务模板。
能力矩阵
| 维度 | 能力 | 关键证据 |
|---|---|---|
| 客户端形态 | 桌面 App(macOS/Linux/Windows)+ CLI + HTTP Server | crates/goose-server + ui/desktop + crates/goose-cli |
| LLM Provider | 15+ 官方注册,含 Anthropic/OpenAI/Google/Ollama/OpenRouter/Azure/Bedrock/Databricks/HuggingFace/Tetrate/Snowflake/xAI/NanoGPT/Gemini CLI/ChatGPT Codex 等 | crates/goose/src/providers/init.rs 中 register_with_inventory::<XxxProvider> 调用 33 次 |
| ACP Provider | 支持 Claude Code、Codex、Cursor Agent、Pi、Gemini CLI、Amp、GitHub Copilot 等作为”二级 Provider”(即通过这些 CLI 的 ACP 协议包一层) | crates/goose/src/providers/{claude_code,codex,cursor_agent,gemini_cli,amp_acp,...}.rs |
| MCP Extension | 内置 11 套 Platform Extension(Todo/Analyze/Apps/ChatRecall/ExtManager/Orchestrator/Summarize/Summon/CodeExecution/TOM/Developer),外部可挂载 70+ 第三方 | crates/goose/src/agents/platform_extensions/mod.rs |
| Hook 事件 | 11 个生命周期事件点(SessionStart/SessionEnd/UserPromptSubmit/PreToolUse/PostToolUse/PostToolUseFailure/BeforeReadFile/AfterFileEdit/BeforeShellExecution/AfterShellExecution/Stop) | crates/goose/src/hooks/mod.rs |
| 任务调度 | Cron 表达式触发、定时任务管理、任务杀死与暂停/恢复 | crates/goose/src/scheduler.rs(49KB,自研 Tokio JobScheduler 包装) |
| Recipe 模板 | YAML 声明式任务模板,支持子 Recipe 嵌套、deeplink 触发 | crates/goose/src/recipe/{manifest,mod,build_recipe}.rs |
| 可观测性 | OpenTelemetry span(reply_stream)、PostHog 事件、Tracing 字段丰富(trace_input/trace_output/session.*) | crates/goose/src/{otel,posthog,tracing}/ |
| 安全审计 | Adversary Inspector、Egress Inspector、Patterns、Scanner、Classification Client | crates/goose/src/security/(6 个文件总计 130KB) |
仓库统计
1 | 仓库 : aaif-goose/goose(从 block/goose 迁出) |
整体架构
goose 的代码组织非常清晰:核心 crates 构成 Rust 工作区(workspace),桌面 UI 是独立的 Electron+React 前端,二者通过 goose-server HTTP 接口通信。下面这张顶层架构图覆盖了从用户输入到 LLM 输出的全链路。
flowchart TB
subgraph 客户端层
UI["🖥️ Desktop UI<br/>ui/desktop<br/>Electron + React"]
CLI["⌨️ CLI<br/>goose-cli crate<br/>goose run / session / schedule"]
SDK["🐍 SDK<br/>goose-sdk crate<br/>Python/TypeScript bindings"]
end
subgraph 传输层
Server["🌐 HTTP Server<br/>goose-server crate<br/>axum routes + OpenAPI"]
ACP["🔌 ACP 协议<br/>Agent Client Protocol<br/>stdio / HTTP"]
end
subgraph 编排层
Agent["🤖 Agent<br/>crates/goose/src/agents/agent.rs<br/>reply / reply_internal"]
Scheduler["⏰ Scheduler<br/>crates/goose/src/scheduler.rs<br/>tokio-cron-scheduler"]
Session["📁 Session Manager<br/>crates/goose/src/session/session_manager.rs"]
end
subgraph 能力层
Extensions["🧩 Extension Manager<br/>crates/goose/src/agents/extension_manager.rs<br/>MCP stdio / SSE / HTTP"]
PlatformExt["⚙️ Platform Extensions<br/>Todo/Analyze/Apps/Summon/<br/>Orchestrator/ChatRecall/Summarize"]
Hooks["🪝 Hook Manager<br/>crates/goose/src/hooks/mod.rs<br/>11 events × blocking/async"]
end
subgraph 模型层
ProviderReg["📚 Provider Registry<br/>crates/goose/src/providers/<br/>33 register_with_inventory"]
Inventory["🗂️ Inventory Service<br/>crates/goose/src/providers/inventory/<br/>canonical_models + cache"]
Declarative["🧬 Declarative Providers<br/>crates/goose/src/providers/declarative/<br/>YAML 配置自定义 Provider"]
end
subgraph 基础设施层
MCP["🔗 MCP Transport<br/>rmcp crate<br/>TokioChildProcess / StreamableHttp"]
ActionReq["⚡ ActionRequired Manager<br/>crates/goose/src/action_required_manager.rs"]
OTel["📊 OpenTelemetry<br/>crates/goose/src/otel/"]
Security["🛡️ Security Inspector<br/>crates/goose/src/security/"]
end
UI --> Server
CLI --> Agent
CLI --> Scheduler
SDK --> Agent
Server --> Agent
ACP --> Agent
Agent --> Extensions
Agent --> Hooks
Agent --> ProviderReg
Extensions --> PlatformExt
Extensions --> MCP
ProviderReg --> Inventory
ProviderReg --> Declarative
Hooks --> ActionReq
Agent --> ActionReq
Agent --> OTel
Extensions --> Security
Agent --> Security
Scheduler --> Agent
Session --> Agent后端服务拆分(来自 crates/goose-server)
goose-server 是一个独立的 axum HTTP 服务,可以独立部署,让 goose 变成”裸金属 API”。下面是 server crate 内的路由(摘自 crates/goose-server/src/routes/):
1 | src/routes/ |
为什么要有 server crate? 这是 goose 区别于 LangChain、CrewAI 这类 Python 库的关键设计:goose 把 Agent 本身当成 daemon,桌面 UI、CLI、Python SDK 都可以连接到这个 daemon。这意味着:
- 桌面 UI 不需要内置 Agent 逻辑,只是 server 的一个前端
- CLI 可以远程连 server,多终端共享同一会话
- Python SDK 可以编程式调用,与 LangServe 类似的体验,但底层是 Rust 性能
Provider 三级抽象
goose 的 Provider 系统是它最值得深入研究的部分。它不只是一个”如果用 OpenAI 就调用 openai 包”的胶水层,而是一个三级抽象:底层是 Provider trait,中层是 ProviderRegistry + inventory,顶层是 Declarative Providers + ACP 子 Provider。这种设计让 goose 既能内置 15+ 官方 Provider,又能允许用户用 YAML 自定义 Provider,甚至能把 Claude Code CLI、Codex CLI、Cursor Agent 这种外部 CLI 当成”二级 Provider”使用。
Provider trait(来自 crates/goose/src/providers/base.rs)
1 | // 来自 crates/goose/src/providers/base.rs |
关键观察:Provider trait 来自上游 goose_providers::base::*(单独 crate),ProviderDef 是本地 trait,用来在 goose 工作区内统一注册。from_env 是异步的(BoxFuture),因为很多 Provider 初始化时要读 secret、做 OAuth flow、调用 model list API。
ProviderRegistry(来自 crates/goose/src/providers/provider_registry.rs)
1 | // 来自 crates/goose/src/providers/provider_registry.rs |
ProviderEntry 的设计哲学:
constructor:异步闭包,签名(extensions, working_dir, tls_config) -> Future<Arc<dyn Provider>>。所有 Provider 构造都走这条路,行为统一inventory_identity+inventory_configured:两个闭包分别返回InventoryIdentityInput和bool,用来判断该 Provider 是否”已配置”(API key 是否有效)。这两个 resolver 会被 InventoryService 周期调用(24 小时一次)刷新 model 列表cleanup:可选的析构回调,OAuth Provider 用它来 revoke tokenprovider_type:四种类型(Preferred/Builtin/Declarative/Custom),UI 列表展示时按此排序
Inventory 模型(来自 crates/goose/src/providers/inventory/mod.rs)
1 | // 来自 crates/goose/src/providers/inventory/mod.rs |
InventoryIdentity 是 Provider Registry 最有创意的一处:它把一个 Provider 的”身份”hash 成 SHA-256,hash 输入是 provider_id + family + public_inputs + secret_inputs。这意味着:
- 同一 Provider 不同 API key 视为不同身份 → 库存条目独立缓存
- 改 API key 后 24h 自动失效 → 自动重新拉 model 列表
- 跨设备同步:identity 是纯函数,hash 一致意味着库存一致
下面是 Provider 三级抽象的整体图:
flowchart TB
subgraph L1["Level 1: Provider Trait(goose_providers crate)"]
PT[Provider trait<br/>complete / fetch_model_info / ...]
end
subgraph L2["Level 2: ProviderRegistry + Inventory"]
PR[ProviderRegistry<br/>HashMap of ProviderEntry]
PI[ProviderInventoryService<br/>SQLite cache + 24h TTL]
IM[InventoryIdentity<br/>SHA-256 hash of config]
end
subgraph L3["Level 3: 注册策略"]
B[Builtin<br/>register::<AnthropicProviderDef>]
P[Preferred<br/>register_with_inventory<br/>auto-refresh]
D[Declarative<br/>YAML 配置 → 动态生成]
A[ACP 子 Provider<br/>包 Claude Code / Codex CLI]
end
L2 --> L1
L3 --> L2
B --> PR
P --> PR
D --> PR
A --> PRProvider 注册一览(来自 crates/goose/src/providers/init.rs)
goose 在 init.rs 中静态注册了 33 个 Provider,每个用 register::<X> 或 register_with_inventory::<X> 两种方式之一注册。带 inventory 的(16 个)会自动 24h 刷新模型列表,不带的(17 个)只在启动时初始化一次:
1 | register_with_inventory: |
为什么 Avian/Tetrate 这种小 Provider 不带 inventory? 因为它们的 model 列表是固定的(≤5 个),refresh 没意义。inventory 主要用于”模型在云端动态更新”的场景(Anthropic/OpenAI/Google 等 15+ 模型供应商每月都有新模型)。
Extension Manager 与 MCP 抽象
goose 把所有外部能力都抽象成 MCP Extension——本地子进程、远程 SSE、HTTP Streamable 三种传输。ExtensionManager 负责拉起、复用、释放 MCP 子进程。
ExtensionConfig(来自 crates/goose/src/agents/extension.rs)
每个 Extension 都用一种统一配置描述:
1 | // 来自 crates/goose/src/agents/extension.rs |
七种 Extension 类型的工程意义:
- Stdio/Sse/StreamableHttp:三种 MCP 标准传输,对应 MCP 协议的演进史
- Builtin:goose 进程内的 MCP server,无需启动子进程(最低延迟)
- Platform:与 Builtin 类似,但走
crates/goose/src/agents/platform_extensions/的工厂模式 - Frontend:桌面 UI 提供的工具(如打开文件、调用系统对话框)
MCP 客户端握手(来自 crates/goose/src/agents/mcp_client.rs)
1 | // 来自 crates/goose/src/agents/mcp_client.rs |
GooseMcpHostInfo 的设计:goose 在 initialize 请求中把自己的扩展能力告诉 MCP server,例如它声明支持 io.modelcontextprotocol/ui 的 text/html;profile=mcp-app MIME 类型。这样 MCP server 就可以返回渲染好的 HTML 卡片,桌面 UI 直接内嵌。
MCP 三种传输的 fork 路径
flowchart LR
EM[ExtensionManager]
EM -->|Stdio| SP["TokioChildProcess<br/>rmcp::transport::<br/>TokioChildProcess"]
EM -->|Sse| SH["StreamableHttpClient<br/>(legacy SSE 适配)"]
EM -->|StreamableHttp| SH2["StreamableHttpClient<br/>rmcp::transport::<br/>streamable_http_client"]
SP --> MCP1[MCP Server 子进程]
SH --> MCP2[远程 MCP Server]
SH2 --> MCP3[远程 MCP Server]
EM -.初始化.-> Auth["oauth_flow<br/>crates/goose/src/oauth/"]
EM -.失败检测.-> MC["extension_malware_check<br/>crates/goose/src/agents/extension_malware_check.rs"]Stdio 走 TokioChildProcess(rmcp crate 提供),Sse 与 StreamableHttp 都走 StreamableHttpClient,只是请求方式不同。这种实现方式是rmcp 0.x → 1.x 演进的产物:MCP 早期只有 stdio(Node 子进程)和 SSE(HTTP 长轮询),后期统一为 Streamable HTTP,但 goose 仍然保留 SSE 后缀用于兼容老旧 MCP server。
内置 Platform Extensions(来自 crates/goose/src/agents/platform_extensions/mod.rs)
1 | // 来自 crates/goose/src/agents/platform_extensions/mod.rs |
11 个内置 Platform Extension 速览:
| Extension | 工具数 | 用途 | default_enabled |
|---|---|---|---|
analyze | ~8 | tree-sitter 代码结构分析(目录/文件/symbol/call graph) | ✅ |
todo | ~3 | 内部任务追踪(goose 给自己的 TODO 列表) | ✅ |
apps | ~6 | 创建并管理 Goose Apps(沙盒 HTML/CSS/JS 窗口) | ✅ |
chatrecall | ~2 | 跨会话历史检索(向量 + 关键字) | ❌ |
extensionmanager | ~3 | 动态启停 Extension | ✅ |
summon | ~5 | 调用子 Agent(sub-agent) | ✅ |
orchestrator | ~4 | 多子 Agent 编排 | ✅ |
summarize | ~3 | 对长输出做 LLM 摘要 | ✅ |
code_execution | ~2 | 远程代码沙盒执行(隔离进程) | opt-in |
tom (TodoManager) | ~3 | 与 todo 集成但提供更细粒度状态机 | ✅ |
developer | ~5 | 开发者自带的 shell/edit 工具 | ✅ |
default_enabled: true 的含义:goose 启动时自动加载这些 Extension,Agent 可直接调用它们。hidden: false 表示它们出现在 UI 列表中,可以被用户关闭。
Hook 系统:11 个生命周期事件
goose 的 Hook 系统比 Claude Code(5 个事件)更丰富,达到 11 个。下面是完整的事件清单(来自 crates/goose/src/hooks/mod.rs):
1 | // 来自 crates/goose/src/hooks/mod.rs |
HookEvent 设计哲学:
| 事件 | 触发时机 | 典型用途 | 与 Claude Code 对比 |
|---|---|---|---|
SessionStart | 会话启动时(model provider 加载后) | 注入项目说明、设置初始 goal | SessionStart(一致) |
SessionEnd | 会话关闭时 | 写进度文件、发通知 | SessionEnd(一致) |
UserPromptSubmit | 用户消息进入 conversation 前 | 拦截注入、改写 prompt、触发 steer | UserPromptSubmit(一致) |
PreToolUse | 工具调用前(参数已知) | 修改参数、追加 audit log | PreToolUse(一致) |
PostToolUse | 工具成功返回后 | 副作用(保存指标、触发后续工具) | PostToolUse(一致) |
PostToolUseFailure | 工具失败后 | 自动重试、降级到备用 | ❌ Claude Code 没有 |
BeforeReadFile | 文件读取前(仅 Read 类工具) | 安全审计、redaction | ❌ 更细粒度 |
AfterFileEdit | 文件编辑后(仅 Edit/Write 类工具) | git auto-commit、format | ❌ 更细粒度 |
BeforeShellExecution | shell 命令执行前 | 安全拦截(rm -rf 等危险命令) | ❌ 更细粒度 |
AfterShellExecution | shell 命令执行后 | stdout 截断、敏感信息扫描 | ❌ 更细粒度 |
Stop | Agent 准备退出主循环 | 阻断退出、要求继续 | Stop(一致) |
细粒度事件的优势:在 goose 里,”shell 命令执行”是一类独立事件,可以单独加策略。例如 BeforeShellExecution 强制要求所有 shell 命令通过 safety scanner,AfterShellExecution 自动过滤输出中的 API key。这种”按工具类型细分”的策略是 Anthropic 官方 Claude Code 不支持的。
下面是 Hook 在 Agent 主循环中的触发时序:
sequenceDiagram
participant U as User
participant A as Agent Loop
participant H as HookManager
participant T as Tool
U->>A: user message
A->>H: emit(SessionStart) [once]
H-->>A: Allow/Deny
A->>H: emit(UserPromptSubmit, message)
H-->>A: Allow/Deny (can modify message)
loop while tool_results or final_output
A->>A: provider.complete(conversation)
A->>A: parse tool calls
loop each tool call
A->>H: emit(PreToolUse, tool_name, args)
H-->>A: Allow / Deny{reason}
alt Deny
A->>T: skip
A->>H: emit(PostToolUseFailure)
else Allow
A->>T: execute
T-->>A: result
A->>H: emit(PostToolUse, result)
opt if file tool
A->>H: emit(BeforeReadFile / AfterFileEdit)
end
opt if shell tool
A->>H: emit(BeforeShellExecution / AfterShellExecution)
end
end
end
A->>A: append tool results to conversation
end
A->>H: emit(Stop, last_assistant_text)
H-->>A: Allow / Deny
A->>H: emit(SessionEnd)HookManager 接口(crates/goose/src/hooks/mod.rs):
1 | pub struct HookManager { |
Builder 模式 + 链式调用:HookContext::new(event, session_id).with_tool(name, args).with_tool_output(output) 一行就把所有上下文塞进去,调用方不用记参数顺序。
Recipe 子命令引擎
goose 的 Recipe 是一个声明式任务模板,可以用 YAML 写好保存下来,下次执行时不必重新组织 prompt。下面是 Recipe 的核心定义(来自 crates/goose/src/recipe/mod.rs):
1 | // 来自 crates/goose/src/recipe/mod.rs |
Recipe 字段语义:
instructions:自然语言指令(system prompt 的一部分)prompt:运行时用户消息模板(可嵌入参数)extensions:执行此 Recipe 时自动加载的 MCP extensionscontext:需要追加到 conversation 的初始 context 文件列表activities:执行期间展示在 UI 上的活动条目(每个 activity 是一个简短人类可读步骤)parameters:模板参数定义(带类型、默认值、必填)sub_recipes:嵌套子 Recipe 路径(相对路径解析)profile:命名配置(指向 ~/.config/goose/profiles/)model_config:覆盖默认 provider/model 的临时配置
Recipe 与 Claude Code Skills / planning-with-files 的差异:
| 特性 | goose Recipe | Claude Code Skills | planning-with-files |
|---|---|---|---|
| 表达形式 | YAML | Markdown | Markdown + 3-File |
| 触发方式 | goose run --recipe <name> | 自动匹配 | 全程常驻 |
| 嵌套组合 | sub_recipes 字段 | 不支持 | 不支持 |
| 参数化 | parameters 字段 + $NAME 替换 | 占位符 | 不支持 |
| 状态化 | recipe_dir 记录原始位置 | 无 | 无 |
| 可分享性 | Recipe 文件就是产物 | Skill 是指令集 | Filesystem 是 RAM |
Recipe 加载路径解析(来自 crates/goose/src/recipe/manifest.rs):
1 | // 来自 crates/goose/src/recipe/manifest.rs |
resolve_recipe_sub_recipe_paths 关键细节:
1 | fn resolve_recipe_sub_recipe_paths(recipe: &mut Recipe, recipe_path: &Path) { |
为什么需要这个? Recipe 拷贝到 scheduled_recipes/ 目录后,相对路径会失效(原来 child.yaml 相对 ~/recipes/parent.yaml,但执行时从 ~/Library/Application Support/goose/scheduled_recipes/parent.yaml 启动)。ScheduledJob.recipe_base_dir 字段专门存原始目录,让 sub_recipe 始终解析到原始位置。
ActionRequiredManager:MCP Elicitation 的跨进程等待方案
MCP 协议有一个看似简单但实现起来很难的功能:Elicitation——MCP server 在执行 tool 时需要用户提供额外信息(例如确认操作、填写表单),可以暂停执行并向 client 发起 elicitation 请求。goose 的解决方案是 ActionRequiredManager,它位于 crates/goose/src/action_required_manager.rs。
数据结构
1 | // 来自 crates/goose/src/action_required_manager.rs |
PendingRequest 用 oneshot 通道而不是 mpsc::Receiver,因为 elicit 是一个请求一次响应(1:1)的语义。oneshot 比 mpsc 更精确,并且可以表达”一次性”的语义。
request_and_wait 流程
1 | pub(crate) async fn request_and_wait( |
时序图:
sequenceDiagram
participant MCP as MCP Server<br/>(子进程)
participant ARM as ActionRequiredManager
participant UI as Desktop UI
participant Stream as Streamed AgentEvent
MCP->>ARM: request_and_wait(<br/>session_id,<br/>tool_call_request_id,<br/>message,<br/>schema,<br/>timeout)
ARM->>ARM: 生成 uuid<br/>注册 oneshot::Sender<br/>到 pending map
ARM->>Stream: 通过 (session_id, tool_call_request_id)<br/>查找 mpsc::Sender
ARM->>Stream: send(Message::ActionRequiredElicitation)
Stream->>UI: SSE 推送 AgentEvent::Message(<br/>ActionRequired)
UI->>UI: 渲染表单<br/>等待用户操作
UI->>ARM: submit(<br/>session_id,<br/>id,<br/>outcome)
ARM->>ARM: 在 pending map 中<br/>oneshot::Sender.send(outcome)
ARM-->>MCP: return Outcome
ARM->>ARM: 从 pending map 中移除 id这一设计精妙在哪?
- 跨进程边界:MCP server 在子进程内执行,它发起的 elicitation 要回到桌面 UI(Electron 渲染进程)才能让用户看到表单。goose 把这件事用
action_required_senders这个(session_id, tool_call_request_id) -> mpsc::Sender<Message>表打通 - oneshot + Arc<Mutex
> :oneshot 表达一次性的”发信号”,Arc<Mutex<>> 让多个 await 端都能竞争拿到结果(虽然只有第一个会成功) - 超时清理:超时后无论结果如何,从
pendingmap 移除 id,防止内存泄漏 - 错误降级:
Tool call request not found/stream closed都会被request_and_wait捕获并返回,避免悬挂的 tool call
Agent 主循环
让我们看 goose 的核心 Agent 循环,它在 crates/goose/src/agents/agent.rs 的 reply_internal 中。这是最长的一个文件(166KB),下面是简化版的循环骨架:
1 | // 来自 crates/goose/src/agents/agent.rs(精简版) |
几个关键设计:
async_stream::try_stream!宏:让整个循环变成异步流,前端可以通过 SSE 流式接收每个 AgentEvent(用户消息、工具调用、工具结果、错误等)final_output_tool:Agent 通过专门工具显式标记”任务完成”,而不是依赖”无 tool call = 完成”(这避免了长输出被截断时误判完成)Stophook 阻断:hook 可以拒绝 Agent 退出,重新触发下一轮 reply。stop_hook_block_cap防止恶意 hook 锁死 Agentretrying_after_stop_hook_denial:区分”自然结束”和”被 hook 拒绝”,触发不同的 telemetrytrace_input/trace_output/session.*字段:OpenTelemetry 自动收集,方便后续接入 Jaeger / Honeycomb
调度器与子 Agent
Scheduler(来自 crates/goose/src/scheduler.rs,49KB)
goose 的调度器把 Cron 表达式 + Recipe + Agent 三者绑定,让用户可以定时触发一个 Agent 任务:
1 | // 来自 crates/goose/src/scheduler.rs |
调度器核心操作:
| 方法 | 用途 | 关键行为 |
|---|---|---|
add_scheduled_job | 注册任务 | 校验 cron、保存到 schedule.json |
schedule_recipe | 把 Recipe 转成 ScheduledJob | 调用 tokio-cron-scheduler 注册 |
list_scheduled_jobs | 列出全部任务 | 读 JSON 状态 |
remove_scheduled_job | 删除任务 | 同时从内存和 cron 调度器移除 |
run_now | 立刻触发一次 | 跳过 cron 检查 |
pause_schedule / unpause_schedule | 暂停/恢复 | 改 paused 字段 |
kill_running_job | 杀死运行中的任务 | 调 CancellationToken::cancel() |
get_running_job_info | 查运行状态 | 读 currently_running + process_start_time |
recipe_base_dir 字段特别值得注意——它是 ScheduledJob 专有的元数据,存的是 Recipe 文件被拷贝到 scheduled_recipes/ 之前的原始目录。这样执行 Recipe 时,resolve_recipe_sub_recipe_paths 始终能从原始位置解析 sub_recipe,相对路径不会失效。
子 Agent(来自 crates/goose/src/agents/summon.rs,108KB)
Summon 是 goose 的”召唤子 Agent”机制,封装在 platform extension 中:
1 | Summon Client 主要方法: |
子 Agent 是独立 session,可以并行执行,与父 Agent 通过 SQLite 数据库共享会话状态。crates/goose/src/agents/subagent_handler.rs(12KB)实现了父子事件转发,子 Agent 的每个 AgentEvent 都被父 Agent 接收并决定如何处理。
安全审计系统
goose 的安全模块在 crates/goose/src/security/ 下,6 个文件总计 130KB:
| 文件 | 行数 | 用途 |
|---|---|---|
patterns.rs | 20KB | 危险模式正则(API key 泄漏、rm -rf 等) |
egress_inspector.rs | 19KB | 出站请求审计(限制外联 IP / domain) |
adversary_inspector.rs | 24KB | 对抗性 prompt 注入检测 |
scanner.rs | 15KB | 文件/输出敏感信息扫描 |
classification_client.rs | 8KB | 与外部分类服务交互 |
security_inspector.rs | 5KB | 统一接口门面 |
安全审计的触发时机:
- PreToolUse hook:每个工具调用前过一遍
security_inspector - BeforeShellExecution:所有 shell 命令必查
patterns.rs - EgressInspector:所有 HTTP 请求检查目标是否在白名单
- AdversaryInspector:每条 user message 查 prompt injection
这是 goose 比 Claude Code、Continue 更谨慎的工程决策——把”防 prompt injection / 防数据外泄”做成基础设施级别的拦截器,而不是 hook plugin。
桌面 UI 三端协同
goose 的桌面 UI 在 ui/desktop/,是 Electron + React 应用。它不内置 Agent 逻辑,只通过 gooseServe crate 与本地 goose-server 通信:
1 | // 来自 ui/desktop/src/gooseServe.ts(精简版) |
GooseServeLeaseRegistry 的设计哲学:多进程互斥。同一台机器上可能有多个 desktop app 实例尝试启动 server,registry 用 SQLite + 时间戳做 lease,避免端口冲突。
下面是 goose 三端的拓扑:
flowchart LR
subgraph 进程A
Desktop1["🖥️ Desktop App 1<br/>Electron + React"]
Desktop1 -->|HTTP| Server1["goose-server<br/>port X"]
end
subgraph 进程B
Desktop2["🖥️ Desktop App 2<br/>Electron + React"]
Desktop2 -->|HTTP| Server2["goose-server<br/>port Y"]
end
subgraph 进程C
CLI["⌨️ CLI<br/>goose run"]
CLI -->|直接调用| Server1
end
subgraph 进程D
SDK["🐍 Python SDK"]
SDK -->|HTTP| Server1
end
subgraph LeaseRegistry
Reg[("SQLite Lease<br/>~/.local/share/goose/leases.db")]
end
Server1 -.注册 lease.-> Reg
Server2 -.注册 lease.-> Reg
Desktop1 -.查询 lease.-> Reg为什么 GooseServe 这种”自带 server”的设计重要? 传统的 Electron AI 应用把 Agent 逻辑塞在前端 renderer 进程,性能差、状态难管。goose 把 server 抽出来后:
- 桌面 UI 只是薄前端,可以做 Electron、纯 Webview、甚至命令行版
- CLI 和 SDK 可以复用同一个 server 实例
- server 是长生命周期进程,session 状态能跨 UI 重启保留
OAuth Provider 与 Refresh
很多 LLM Provider(GitHub Copilot、ChatGPT、Claude、Gemini)现在都用 OAuth 而不是 API key。goose 的 crates/goose/src/providers/oauth.rs(21KB)实现了 OAuth device flow,配套 oauth_device_flow.rs(20KB)。
下面是 OAuth Provider 的注册与刷新流程:
sequenceDiagram
participant U as User
participant Goose as Goose Agent
participant OAuth as OAuth Service<br/>(device flow)
participant Provider as LLM Provider API
U->>Goose: 选择 "GitHub Copilot (OAuth)"
Goose->>OAuth: device_flow.request_device_code()
OAuth-->>Goose: {user_code, verification_uri, interval}
Goose->>U: 显示 "请打开 https://github.com/login/device<br/>输入 ABCD-1234"
loop poll interval
Goose->>OAuth: device_flow.poll_token()
alt pending
OAuth-->>Goose: {status: pending}
else authorized
OAuth-->>Goose: {access_token, refresh_token, expires_at}
end
end
Goose->>Goose: 把 token 存入 GooseCredentialStore<br/>(Keychain on macOS / Secret Service on Linux)
Goose->>Provider: 初始化 Copilot Provider
Provider-->>Goose: Provider ready
Note over Goose,Provider: 24h 后
Goose->>Goose: ProviderInventoryService.is_stale()
Goose->>OAuth: refresh_token()
OAuth-->>Goose: new access_token
Goose->>Provider: 用新 token 重新创建 ProviderGooseCredentialStore 的实现细节(来自 crates/goose/src/oauth/):
- macOS:使用 Keychain(
securityCLI 调用) - Linux:使用 Secret Service API(libsecret / KWallet)
- Windows:使用 Credential Manager
- Fallback:加密文件(key 派生自用户密码或随机)
为什么不存成明文 JSON? 因为 GitHub Copilot 的 token 拥有完全访问用户订阅的能力,必须 OS 级保护。goose 在 README 中明确推荐生产环境用 Keychain 而非文件 fallback。
Recipe YAML 实例
goose 的 Recipe 是声明式 YAML,下面是一段示例(简化版,参考 crates/goose-cli/src/recipes/):
1 | # daily_standup.yaml |
调用方式:
1 | # 一次执行 |
部署与运行
一键安装
1 | # CLI(macOS / Linux) |
第一次配置
1 | # 交互式 |
启动 server 模式
1 | # 让 goose 跑成 daemon |
与 MCP server 配合
1 | # 启动一个 MCP server |
调度 Recipe
1 | # 注册定时任务 |
与同类 Coding Agent Harness 对比
goose 在 2026 年下半年的 Coding Agent Harness 赛道里处于什么位置?我们对比 4 个最相关的项目:
| 维度 | block/goose | Claude Code | Cline | Continue |
|---|---|---|---|---|
| 语言 | Rust + TS | TypeScript | TypeScript | TypeScript |
| ⭐ Star | 50,734 | 闭源(Anthropic 官方) | ~20K | 35K |
| 协议 | Apache 2.0 | 闭源 | Apache 2.0 | Apache 2.0 |
| Provider 数 | 15+ 官方 + ACP 包装 | 1(Anthropic) | 多 | 多 |
| MCP 支持 | 一等公民(7 种 Extension 类型) | 完整 | 完整 | 完整 |
| 桌面 UI | ✅ Electron | ❌(纯 CLI) | VSCode 扩展 | VSCode + JetBrains |
| CLI | ✅ | ✅ | ❌ | ❌ |
| HTTP Server | ✅ goose-server | ❌ | ❌ | ❌ |
| Python/JS SDK | ✅ | ❌ | ❌ | ❌ |
| Hook 系统 | 11 个事件 | 5 个事件 | 不支持 | 不支持 |
| 任务调度 | ✅ Cron + Recipe | ❌ | ❌ | ❌ |
| 安全审计 | ✅ 内置 130KB | 较弱 | 无 | 无 |
| OAuth Device Flow | ✅ 完整 | ❌ | ❌ | ❌ |
| Linux 基金会治理 | ✅ AAIF | ❌ | ❌ | ❌ |
核心设计哲学差异:
1. Provider 抽象 vs 模型锁定
- goose:Provider Registry + Inventory,15+ 官方 + 用户可自定义 YAML(Declarative Providers)
- Claude Code:仅 Anthropic 模型,无切换机制
- Cline / Continue:开放但每次都要重新配置
2. Hook 系统的粒度
- goose:11 个事件,含
BeforeShellExecution/AfterFileEdit/BeforeReadFile等细粒度 - Claude Code:5 个事件,没有专门文件/Shell 事件
- Cline / Continue:基本无 Hook 抽象,扩展靠 VSCode API
3. 三端协同
- goose:Desktop / CLI / HTTP Server 三端共享 session,靠
gooseServe进程管理 - Claude Code:仅 CLI(桌面通过 Claude Desktop App 是另一回事)
- Cline / Continue:VSCode 插件,无独立 server
4. 治理模式
- goose:捐给 Linux 基金会 AAIF,社区驱动 + 厂商中立
- 其他:单一公司所有(Anthropic / Cline Inc. / Continue Inc.)
优缺点分析
| 维度 | 优势 | 代价 |
|---|---|---|
| 架构简洁性 | 13+ crate 分层清晰;Provider/Extension/Hook/Recipe 四抽象正交 | 仓库 936MB,新人上手需要看遍整个 workspace |
| 扩展性 | 33+ Provider / 11 Platform Extension / 70+ MCP 全开放 | 第三方扩展质量参差,需要 vetters |
| 易用性 | 一行 goose configure 启用;Recipe YAML 复用任务模板 | 配置文件分散在多个目录(~/.config/goose/、~/Library/Application Support/goose/) |
| 性能 | Rust 核心 + Tokio 异步;远好于纯 Python Agent Harness | 编译时间长(首次 5+ 分钟);增量编译靠 sccache |
| 复杂度 | Hook 11 事件 + Recipe sub_recipes + Scheduler + ActionRequired + OAuth Refresh + Inventory 全套 | 学习曲线陡;想做自定义 Provider 要读 ~7 个文件 |
| 维护性 | 协议清晰、模块解耦;Rust 强类型保障 | Rust 异步生态碎片化(async_stream / tokio_stream / futures 三选一) |
适合用 goose 的场景
- 企业自托管 AI Agent 网关:15+ Provider 覆盖了几乎所有 LLM,企业内部统一 Agent Runtime,goose-server 暴露 HTTP API 给内部系统调用
- 研发团队内部工作流自动化:Recipe + Scheduler 把”每日站会报告 / 每周 PR 总结 / 客户工单回复”模板化
- 需要严格审计的金融 / 医疗 / 法律场景:内置 130KB 安全模块 + 11 Hook 事件粒度 + EgressInspector 限制外联
- 需要 OAuth 集成的 IDE / 编辑器:Continue / Cline 都是 VSCode 插件,goose 可以做成 JetBrains / Vim / Neovim 插件(已经有 goose-sdk for Python)
- Linux 基金会标准诉求:需要中立治理、不依赖单一厂商的场景
不太适合的场景
- 只要一个写代码补全工具:VSCode + Copilot 1 个 Tab 键搞定,goose 太重
- 只需要 Chat 形态:直接用 ChatGPT / Claude.ai 网页,goose 没有 GUI 聊天界面
- 极简 CLI 爱好者:goose CLI 命令集 80+ 个,参数繁杂;Claude Code CLI 更简洁
- 纯个人单机临时使用:不需要 scheduler / OAuth / server,复杂度溢出
趋势与总结
三个值得追的趋势
趋势一:2026 下半年所有 Coding Agent Harness 都会把 Provider 抽象做厚
- LangChain 是 Provider 抽象最早的尝试(Python)
- LiteLLM 把抽象上推到路由层(同样 Python)
- goose 把 Provider + Inventory + Declarative YAML + OAuth Refresh 整合在一起,是 2026 年下半年的”完整答案”
- 后续 LangChain / LlamaIndex 必然跟进,做 Rust 版或 TypeScript 版的”Provider Registry + Inventory”
趋势二:Hook 事件粒度从 5 个 → 11 个 → 20+ 个
- Anthropic Claude Code:5 个事件
- goose:11 个事件
- Cursor Composer / Continue:未公开 Hook 抽象,但肯定会加
- 未来会出现”按工具类型细分的 Hook 抽象”——比如
BeforeBashExec/AfterReadFile/BeforeGitPush这种”工具 + 事件”二维矩阵
趋势三:Linux 基金会 AAIF 是 Coding Agent 治理的”新基准”
- 之前 Python 数据科学有 NumFOCUS、CNCF 治理容器
- 现在 AAIF(Agentic AI Foundation)治理 Coding Agent 标准
- goose 是 AAIF 第一个旗舰项目(其他可能是 LangChain / LlamaIndex 的 Rust 版)
- 中国这边 OpenAtom / 开放原子可能有类似动作(如 OpenLoong / OpenBlock 等)
给读者的工程建议
- 如果你在选 Coding Agent Harness:先看 goose 和 Claude Code 的差异表。如果你的 Provider 多样、需要调度、需要安全审计,goose 是首选;如果只是个人写代码,Claude Code CLI 更简洁
- 如果你在写自己的 Agent Harness:ProviderRegistry + Inventory 是必抄的设计,避免自己造轮子。
crates/goose/src/providers/provider_registry.rs是 14KB 的精华 - 如果你在做 MCP server:ActionRequiredManager 是 MCP Elicitation 的工业级实现,跨进程、oneshot、超时清理、错误降级全部覆盖,值得参考
- 如果你在做多 Agent 编排:goose 的
summonsub-agent +orchestrator+ SQLite 共享状态是一个轻量方案,比 AutoGen / CrewAI 的对话驱动模型更工程化
一句话总结
block/goose 是 2026 年下半年最值得研究的 Coding Agent Harness 之一——50k Star、Rust 实现、捐给 Linux 基金会 AAIF、Provider Registry + MCP Extension + 11-event Hook + Recipe YAML + ActionRequired Elicitation + Cron Scheduler + OAuth Device Flow + EgressInspector + 内置 11 套 Platform Extension——它把 Coding Agent Harness 工程化的几乎所有议题都揉进了同一个 Apache 2.0 开源仓库。
关键资源
| 类型 | 链接 |
|---|---|
| GitHub 仓库 | https://github.com/aaif-goose/goose |
| 旧仓库(已迁移) | https://github.com/block/goose |
| 官方文档 | https://goose-docs.ai/ |
| Linux 基金会 AAIF | https://aaif.io/ |
| Rust crate docs | https://docs.rs/goose |
| MCP 协议标准 | https://modelcontextprotocol.io/ |
| ACP 协议 | https://agentclientprotocol.com/ |
| License | Apache License 2.0 |
| Discord | https://discord.gg/goose-oss |
| 官网博客 | https://block.github.io/goose/blog/ |
附录:源码引用清单(本文所有代码均直接来自 goose 仓库 2026-07-07 main 分支对应文件,可对照阅读)
| 引用文件 | 行数 | 核心内容 |
|---|---|---|
crates/goose/src/agents/agent.rs | 166KB / ~4500 行 | Agent 主循环 reply_internal |
crates/goose/src/agents/extension_manager.rs | 113KB / ~3000 行 | MCP 三种传输管理 |
crates/goose/src/agents/mcp_client.rs | 49KB / ~1500 行 | MCP 客户端握手 |
crates/goose/src/agents/platform_extensions/mod.rs | 10KB / 11 Extensions | Platform Extension 注册表 |
crates/goose/src/providers/provider_registry.rs | 14KB / ~400 行 | Provider 注册表 + Inventory |
crates/goose/src/providers/inventory/mod.rs | 46KB / ~1500 行 | Inventory 缓存 + canonical models |
crates/goose/src/providers/init.rs | 16KB / ~500 行 | 33 个 Provider 静态注册 |
crates/goose/src/providers/base.rs | 1.3KB | Provider trait 定义 |
crates/goose/src/hooks/mod.rs | 28KB / ~900 行 | 11 个 HookEvent + HookManager |
crates/goose/src/scheduler.rs | 49KB / ~1500 行 | Cron 调度 + 任务杀死/暂停 |
crates/goose/src/recipe/mod.rs | 28KB / ~900 行 | Recipe 完整定义 |
crates/goose/src/recipe/manifest.rs | 3.8KB | Recipe 文件路径解析 |
crates/goose/src/action_required_manager.rs | 19KB / ~600 行 | MCP Elicitation 跨进程等待 |
crates/goose-mcp/src/computercontroller/mod.rs | 65KB | 系统级自动化(peekaboo / shell / web) |
crates/goose-server/src/routes/*.rs | ~80KB | axum HTTP 路由(11 个 endpoint) |
ui/desktop/src/gooseServe.ts | 17KB | 桌面 UI 与 server 的 lease 管理 |
crates/goose/src/security/{patterns,egress_inspector,adversary_inspector}.rs | 共 60KB | 安全审计三大模块 |
后记:本文在调研时遇到的最大挑战是 goose 的代码体量——核心 workspace 已经膨胀到 936MB,仅 crates/goose 一个 crate 就超过 800MB。但你完全不需要读完全部代码——只要把 crates/goose/src/providers/provider_registry.rs、crates/goose/src/agents/extension_manager.rs、crates/goose/src/hooks/mod.rs、crates/goose/src/action_required_manager.rs 这四个文件读完,就能理解 goose 设计的 80% 精妙之处。其余 20% 散落在 OAuth(crates/goose/src/oauth/)、Inventory(crates/goose/src/providers/inventory/)、Security(crates/goose/src/security/),是”工程化打磨”的部分,按需阅读即可。
对比同类项目:与之前我们写过的 Claude Code、OpenAI Codex、Cline、Continue 等 Coding Agent Harness 相比,goose 的核心优势是 Provider 抽象 + Hook 粒度 + 三端协同 + Linux 基金会治理 这四点。如果你正在选型 Coding Agent Harness,goose 值得作为”开源、跨 Provider、需要调度、需要审计”场景的首选。