CODING AGENT HARNESS · SOURCE AUDITREPORT 01 / 18
01

Goose

MCP 原生、扩展友好、单循环清楚;安全边界更依赖工具检查而非 OS 沙箱。

Rust · MCP-first 通用 AgentApache-2.0main
SOURCE
VERIFIED
Repository
aaif-goose/goose
Commit
021b0db8dbee8d6c7e9ffbab580a4143598a3560
Commit date
2026-07-27T16:41:11-04:00
Findings
18
Citations
43
Tracked files
2,422
EXECUTIVE READING

先给结论,再进入源码

核心机制

单一流式循环;完整 tool call 后并发;防失控上限

上下文

80% 结构化压缩;超窗逐级剥离工具结果

安全边界

无强制工作区边界;Auto / Approve / SmartApprove

适用建设

重连接器、桌面交互、企业工具集成

值得借鉴

  • MCP 生命周期完整
  • 权限检查顺序明确
  • 扩展与 Skills 生态兼容

需要警惕

  • 内置开发工具缺 OS 级强制边界
  • SmartApprove 仍依赖模型判断
  • 单循环对复杂调度的表达有限

直接带走

  • 统一 MCP 工具命名空间
  • 危险优先的检查流水线
  • 结构化压缩指标
00 · METHOD

研究口径:先锁提交,再沿运行链读代码

README POLICY

README 只用于定位入口、产品命名与作者自述;本账本的核心结论不得只引用 README。

FACT POLICY

实现事实优先引用运行时代码(L1)与类型/协议/配置契约(L2),并尽量用测试(L3)交叉验证。

INFERENCE POLICY

由多处代码综合得出的判断必须标为 inference、limitation 或 risk,不伪装为作者原话。

L1运行实现
L2接口契约
L3测试证明
L4文档佐证
L5明确推断

本页引用 16 个不同源码/测试文件;证据角色分布:实现 32 · 契约 7 · 测试 2 · Prompt 1 · 迁移 1。代码块是固定提交中的原文截取,长区间仅在中部折叠,首尾行号保持真实。

01 · TECHNICAL MAPS

架构总图与单轮执行链路

两张图均由本页证据账本生成,并通过 Archify showcase 9 项校验(0 error / 0 warning)。图可单独打开、搜索、缩放和追踪关系。

FIGURE 01Goose Harness 架构图全屏打开 ↗
FIGURE 02用户输入到工具回写的技术链路全屏打开 ↗
02 · COVERAGE MAP

审计维度与证据等级

入口、会话与主循环 verified L1 / L2 / L3

已逐段审计 Agent.reply/reply_internal 与 SessionManager。

Provider、流式与重试 verified L1 / L2

已审计 Provider trait、流合并和 Agent 消费逻辑。

上下文、压缩与恢复 verified L1 / L2 / L3

已审计阈值判定、工具结果裁剪、结构化摘要和溢出恢复。

工具分发与结果治理 verified L1 / L2 / L3

已审计分类、检查、审批、并发汇流、结果配对。

执行环境与沙箱 verified L1 / L2

已核对 shell/edit 的实际系统调用与路径解析;策略门不等于 OS 隔离。

权限与安全 verified L1 / L2 / L3

已审计安全、外连、对抗、权限、重复五级 inspector 顺序。

指令与 Prompt verified L1 / L2 / L3

已审计系统提示构建、项目指令、子目录 hints 与 Unicode 标签清洗。

MCP 与扩展 verified L1 / L2 / L3

已审计工具发现缓存、命名空间、HTTP/OAuth、调用分发与元数据净化。

Skills、插件与 Hooks verified L1 / L2 / L3

已审计技能目录优先级、文件逃逸防护、插件 hooks 与 MCP 注入。

子 Agent 与协作 verified L1 / L2

已审计子 Agent 的独立 Agent/Session 构造、工具选择和禁止递归委派。

持久化与观测 verified L1 / L2

已审计 SQLite/WAL、usage ledger、tracing span 与可选遥测。

测试、评测与成熟度 partial L2 / L3

关键路径测试已定位;全仓测试与 benchmark 数量统计待补。

01
DIMENSION · ENTRY-SESSION-LOOP

入口、会话与主循环

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

01
L1事实goose-loop-001

单一流式 Agent 循环驱动推理、工具和持久化

源码事实

reply_internal 在一个有界 loop 中调用 provider stream,消费增量消息,执行工具,把结果加入会话,再决定继续、重试、压缩或结束。

白话解释

它不是“模型回答一次就结束”,而是模型说一步、系统做一步、把结果再交回模型,直到满足结束条件。

对自研 Harness 的含义

Harness 的真正核心是状态机而不是提示词;重试、转向、停止钩子和上下文恢复都进入同一控制环。

边界
  • 默认最大 turn 是 1000,不代表每次任务都会接近该值;会话配置和环境参数可覆盖。
关键源码 · 实现
crates/goose/src/agents/agent.rs · L1930–L2043
 1930          let inner = Box::pin(async_stream::try_stream! {
 1931              let mut turns_taken = 0u32;
 1932              let max_turns = session_config.max_turns.unwrap_or_else(|| {
 1933                  Config::global()
 1934                      .get_param::<u32>("GOOSE_MAX_TURNS")
 1935                      .unwrap_or(DEFAULT_MAX_TURNS)
 1936              });
 1937              let mut compaction_attempts = 0;
 1938              let mut empty_turn_retries = 0u32;
 1939              let mut retrying_after_empty_turn = false;
 1940              let mut last_assistant_text = String::new();
 1941              let mut goal_check_pending = false;
 1942              let mut tool_pair_summarization_done = false;
 1943              let mut stop_hook_handled_for_exit = false;
 1944              let mut retrying_after_stop_hook_denial = false;
 1945              let mut consecutive_stop_hook_blocks = 0u32;
 1946              let stop_hook_block_cap = self.stop_hook_block_cap();
 1947              let mut can_drain_pending_steers = false;
 1948  
 1949              loop {
 1950                  if is_token_cancelled(&cancel_token) {
 1951                      break;
 1952                  }
 1953  
      … 80 lines omitted; exact range 1930–2043 …
 2034  
 2035                  let mut stream = Self::stream_response_from_provider(
 2036                      self.provider().await?,
 2037                      model_config.clone(),
 2038                      &session_config.id,
 2039                      &system_prompt,
 2040                      conversation_with_moim.messages(),
 2041                      &tools,
 2042                      &toolshim_tools,
 2043                  ).await?;
查看全部 2 处证据
  • 实现 crates/goose/src/agents/agent.rs:1930–2043 loop { ... stream_response_from_provider(...).await?; }
  • 实现 crates/goose/src/agents/agent.rs:2883–2929 逐条持久化消息,处理 pending steers 与 stop hook 后退出。
02
L1事实goose-loop-002

结束条件有防失控上限

源码事实

空模型响应最多重试 3 次;阻塞停止的插件 hook 连续超过可配置上限后会被强制覆盖,避免无限循环。

白话解释

即使模型什么都不返回,或插件一直说“还不能停”,Goose 也不会永远卡住。

对自研 Harness 的含义

所有“让 Agent 继续”的机制都需要配额和最终逃生门。

关键源码 · 契约
crates/goose/src/agents/agent.rs · L67–L79
   67  use tracing::{debug, error, info, instrument, warn};
   68  
   69  const DEFAULT_MAX_TURNS: u32 = 1000;
   70  const DEFAULT_STOP_HOOK_BLOCK_CAP: u32 = 8;
   71  const COMPACTION_PROGRESS_TEXT: &str = "goose is compacting the conversation...";
   72  const MAX_TURNS_MESSAGE: &str = "I've reached the maximum number of actions I can do without user input. Would you like me to continue?";
   73  const MAX_EMPTY_TURN_RETRIES: u32 = 3;
   74  const EMPTY_TURN_MESSAGE: &str =
   75      "The model returned an empty response. Please resend your message to continue.";
   76  const DEFAULT_FRONTEND_INSTRUCTIONS: &str = "The following tools are provided directly by the frontend and will be executed by the frontend when called.";
   77  
   78  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
   79  enum ToolCategory {
查看全部 3 处证据
  • 契约 crates/goose/src/agents/agent.rs:67–79 DEFAULT_MAX_TURNS、DEFAULT_STOP_HOOK_BLOCK_CAP、MAX_EMPTY_TURN_RETRIES。
  • 实现 crates/goose/src/agents/agent.rs:2773–2793 空响应有界重试,耗尽后产生可见消息并结束。
  • 实现 crates/goose/src/agents/agent.rs:2892–2915 stop hook 连续阻塞超过上限后强制结束。
02
DIMENSION · PROVIDER-STREAMING

Provider、流式与重试

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

03
L2事实goose-provider-001

Provider 以流式协议统一,工具调用必须完整再上送

源码事实

Provider trait 只要求实现 stream(model, system, messages, tools);MessageStream 可以增量返回文本,但工具调用以完整对象返回。

白话解释

不同模型厂商先被翻译成同一种“消息水管”。普通文字可以一个词一个词流出,但工具参数不能半截就执行。

对自研 Harness 的含义

Provider 适配层承担格式差异,Agent 主循环不需要为每家 API 复制控制逻辑。

关键源码 · 契约
crates/goose-provider-types/src/base.rs · L281–L286
  281  /// A message stream yields partial text content but complete tool calls, all within the Message object
  282  /// So a message with text will contain potentially just a word of a longer response, but tool calls
  283  /// messages will only be yielded once concatenated.
  284  pub type MessageStream = Pin<
  285      Box<dyn Stream<Item = Result<(Option<Message>, Option<ProviderUsage>), ProviderError>> + Send>,
  286  >;
查看全部 2 处证据
  • 契约 crates/goose-provider-types/src/base.rs:281–286 partial text content but complete tool calls
  • 契约 crates/goose-provider-types/src/base.rs:394–427 Provider::stream 与可覆盖 get_context_limit。
03
DIMENSION · CONTEXT-COMPACTION

上下文、压缩与恢复

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

04
L1事实goose-context-001

80% 阈值触发结构化压缩

源码事实

当 Agent 自管上下文且估算 token/上下文上限大于阈值(默认 0.8)时触发压缩;压缩保留最新用户文本,并插入结构化摘要与继续指令。

白话解释

快塞满模型记忆时,它不会粗暴删掉全部历史,而是把旧进展整理成一张交接单,再把用户最新要求放回去。

对自研 Harness 的含义

压缩的目标不是“越短越好”,而是保留任务状态、文件改动、错误、待办和下一步。

关键源码 · 契约
crates/goose/src/context_mgmt/mod.rs · L26–L49
   26  pub const DEFAULT_COMPACTION_THRESHOLD: f64 = 0.8;
   27  
   28  const TOOLCALL_SUMMARIZATION_BATCH_SIZE: usize = 10;
   29  
   30  fn tool_pair_summarization_enabled() -> bool {
   31      Config::global()
   32          .get_param::<bool>("GOOSE_TOOL_PAIR_SUMMARIZATION")
   33          .unwrap_or(true)
   34  }
   35  
   36  const CONVERSATION_CONTINUATION_TEXT: &str =
   37      "Your context was compacted. The previous message contains a summary of the conversation so far.
   38  Do not mention that you read a summary or that conversation summarization occurred.
   39  Just continue the conversation naturally based on the summarized context.";
   40  
   41  const TOOL_LOOP_CONTINUATION_TEXT: &str =
   42      "Your context was compacted. The previous message contains a summary of the conversation so far.
   43  Do not mention that you read a summary or that conversation summarization occurred.
   44  Continue calling tools as necessary to complete the task.";
   45  
   46  const MANUAL_COMPACT_CONTINUATION_TEXT: &str =
   47      "Your context was compacted at the user's request. The previous message contains a summary of the conversation so far.
   48  Do not mention that you read a summary or that conversation summarization occurred.
   49  Just continue the conversation naturally based on the summarized context.";
查看全部 3 处证据
  • 契约 crates/goose/src/context_mgmt/mod.rs:26–49 默认 compaction threshold 为 0.8,并定义 Agent 可见但用户隐藏的继续消息。
  • 实现 crates/goose/src/context_mgmt/mod.rs:78–188 旧消息改可见性,插入摘要和 continuation,再恢复最新用户消息。
  • 契约 crates/goose/src/context_mgmt/structured.rs:13–38 摘要字段覆盖意图、概念、文件、错误、待办、当前工作与下一步。
05
L1事实goose-context-002

上下文超限采用渐进式工具结果剥离和一次恢复重试

源码事实

压缩会依次尝试移除 0%、10%、20%、50%、100% 的中间工具响应;Provider 明确报上下文超限时执行恢复压缩,第二次仍超限则终止。

白话解释

先保留尽可能多的工具证据,实在放不下才逐级清掉旧工具输出;不能靠无限压缩掩盖超限。

对自研 Harness 的含义

Harness 应区分账单 token、保留 token 和可牺牲的工具噪声,并给恢复动作设次数上限。

关键源码 · 实现
crates/goose/src/context_mgmt/mod.rs · L319–L398
  319  async fn do_compact(
  320      provider: &dyn Provider,
  321      model_config: &ModelConfig,
  322      session_id: &str,
  323      messages: &[Message],
  324  ) -> Result<(Message, ProviderUsage), anyhow::Error> {
  325      let agent_visible_messages =
  326          Conversation::new_unvalidated(messages.iter().cloned()).agent_visible_messages();
  327  
  328      // Try progressively removing more tool response messages from the middle to reduce context length
  329      let removal_percentages = [0, 10, 20, 50, 100];
  330  
  331      for (attempt, &remove_percent) in removal_percentages.iter().enumerate() {
  332          let filtered_messages = filter_tool_responses(&agent_visible_messages, remove_percent);
  333  
  334          let messages_text = filtered_messages
  335              .iter()
  336              .map(|&msg| format_message_for_compacting(msg))
  337              .collect::<Vec<_>>()
  338              .join("\n");
  339  
  340          let context = SummarizeContext {
  341              messages: messages_text,
  342          };
      … 46 lines omitted; exact range 319–398 …
  389                  }
  390                  return Err(e.into());
  391              }
  392          }
  393      }
  394  
  395      Err(anyhow::anyhow!(
  396          "Unexpected: exhausted all attempts without returning"
  397      ))
  398  }
查看全部 2 处证据
  • 实现 crates/goose/src/context_mgmt/mod.rs:319–398 按多个比例过滤工具响应并重试快速摘要。
  • 实现 crates/goose/src/agents/agent.rs:2533–2591 ContextLengthExceeded 后恢复压缩;第二次仍失败就给出终止通知。
04
DIMENSION · TOOL-DISPATCH

工具分发与结果治理

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

06
L1事实goose-tools-001

同一模型 turn 的多个工具经检查后并发执行

源码事实

工具请求先统一通过 inspector 和许可分组;已批准及用户批准的工具流随后用 stream::select_all 合并,结果按 request_id 配回各自响应。

白话解释

模型一次要读三个文件时,不必傻等第一个完成再做第二个;但每个调用先过安全和审批门。

对自研 Harness 的含义

并发提高吞吐,但必须用稳定 ID 做结果配对,并允许单个工具发通知、请求交互或取消。

关键源码 · 实现
crates/goose/src/agents/agent.rs · L2210–L2265
 2210                                      // Run all tool inspectors
 2211                                      let inspection_results = self.tool_inspection_manager
 2212                                          .inspect_tools(
 2213                                              &session_config.id,
 2214                                              &remaining_requests,
 2215                                              conversation.messages(),
 2216                                              goose_mode,
 2217                                          )
 2218                                          .await?;
 2219  
 2220                                      let permission_check_result = self.tool_inspection_manager
 2221                                          .process_inspection_results_with_permission_inspector(
 2222                                              &remaining_requests,
 2223                                              &inspection_results,
 2224                                          )
 2225                                          .unwrap_or_else(|| {
 2226                                              let mut result = PermissionCheckResult {
 2227                                                  approved: vec![],
 2228                                                  needs_approval: vec![],
 2229                                                  denied: vec![],
 2230                                              };
 2231                                              result.needs_approval.extend(remaining_requests.iter().cloned());
 2232                                              result
 2233                                          });
      … 22 lines omitted; exact range 2210–2265 …
 2256                                              &mut request_to_response_map,
 2257                                              cancel_token.clone(),
 2258                                              &session,
 2259                                              &inspection_results,
 2260                                          );
 2261  
 2262                                          while let Some(msg) = tool_approval_stream.try_next().await? {
 2263                                              yield AgentEvent::Message(msg);
 2264                                          }
 2265                                      }
查看全部 2 处证据
  • 实现 crates/goose/src/agents/agent.rs:2210–2265 inspect_tools 后分为 approved、needs_approval、denied。
  • 实现 crates/goose/src/agents/agent.rs:2267–2337 用 stream::select_all 汇流工具结果,并按 request_id 处理。
05
DIMENSION · PERMISSIONS-SECURITY

权限与安全

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

07
L1事实goose-security-001

工具检查顺序体现“危险优先”

源码事实

检查器固定按 Security、Egress、Adversary、Permission、Repetition 顺序注册。

白话解释

先看是否像恶意命令和数据外传,再做额外对抗审查,然后才判断用户是否需要点批准,最后检查重复循环。

对自研 Harness 的含义

审批不是唯一防线;危险检测应在便利性策略之前执行。

边界
  • 具体 inspector 是否生效还受配置控制;例如模式扫描可被关闭。
关键源码 · 实现
crates/goose/src/agents/agent.rs · L659–L688
  659      /// Create a tool inspection manager with default inspectors
  660      fn create_tool_inspection_manager(
  661          permission_manager: Arc<PermissionManager>,
  662          provider: SharedProvider,
  663          session_manager: Arc<SessionManager>,
  664      ) -> ToolInspectionManager {
  665          let mut tool_inspection_manager = ToolInspectionManager::new();
  666  
  667          // Add security inspector (highest priority - runs first)
  668          tool_inspection_manager.add_inspector(Box::new(SecurityInspector::new()));
  669          tool_inspection_manager.add_inspector(Box::new(EgressInspector::new()));
  670  
  671          // Add adversary inspector (LLM-based review, enabled by ~/.config/goose/adversary.md)
  672          tool_inspection_manager.add_inspector(Box::new(AdversaryInspector::new(
  673              provider.clone(),
  674              session_manager.clone(),
  675          )));
  676  
  677          // Add permission inspector (medium-high priority)
  678          tool_inspection_manager.add_inspector(Box::new(PermissionInspector::new(
  679              permission_manager,
  680              provider,
  681              session_manager,
  682          )));
  683  
  684          // Add repetition inspector (lower priority - basic repetition checking)
  685          tool_inspection_manager.add_inspector(Box::new(RepetitionInspector::new(None)));
  686  
  687          tool_inspection_manager
  688      }
查看全部 1 处证据
  • 实现 crates/goose/src/agents/agent.rs:659–688 五类 inspector 的固定注册顺序。
08
L1事实goose-permission-001

Auto、Approve、SmartApprove 是不同权限语义

源码事实

Auto 模式允许所有调用;Approve/SmartApprove 尊重显式许可,SmartApprove 还会依据工具注解或 LLM 判断只读调用是否可自动放行,未知调用默认要求审批。

白话解释

“智能批准”不是无条件执行:读文件一类操作可能自动过,写文件或判断不清的操作仍会问人。

对自研 Harness 的含义

权限模式必须在 UI 和审计记录中显式呈现,不能用一个笼统的“安全模式”标签。

关键源码 · 实现
crates/goose/src/permission/permission_inspector.rs · L159–L268
  159                  let action = match goose_mode {
  160                      GooseMode::Chat => continue,
  161                      GooseMode::Auto => InspectionAction::Allow,
  162                      GooseMode::Approve | GooseMode::SmartApprove => {
  163                          // 1. Check user-defined permission first
  164                          if let Some(level) = permission_manager.get_user_permission(tool_name) {
  165                              match level {
  166                                  PermissionLevel::AlwaysAllow => InspectionAction::Allow,
  167                                  PermissionLevel::NeverAllow => InspectionAction::Deny,
  168                                  PermissionLevel::AskBefore => {
  169                                      InspectionAction::RequireApproval(None)
  170                                  }
  171                              }
  172                          // 2. Check for a read-only annotation in SmartApprove mode
  173                          } else if goose_mode == GooseMode::SmartApprove
  174                              && self.is_readonly_annotated_tool(tool_name)
  175                          {
  176                              InspectionAction::Allow
  177                          // 3. Special case for extension management
  178                          } else if tool_name == MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE {
  179                              InspectionAction::RequireApproval(Some(
  180                                  "Extension management requires approval for security".to_string(),
  181                              ))
  182                          // 4. Defer to LLM detection (SmartApprove, uncached or legacy cached allow)
      … 76 lines omitted; exact range 159–268 …
  259                          "Tool requires user approval".to_string()
  260                      },
  261                      confidence: 1.0, // Permission decisions are definitive
  262                      inspector_name: self.name().to_string(),
  263                      finding_id: None,
  264                  });
  265              }
  266          }
  267  
  268          Ok(results)
查看全部 1 处证据
  • 实现 crates/goose/src/permission/permission_inspector.rs:159–268 按 GooseMode 分支,结合显式许可、工具注解与只读判断返回 inspection result。
06
DIMENSION · EXECUTION-SANDBOX

执行环境与沙箱

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

09
L1限制goose-sandbox-001

内置开发者工具没有强制工作区边界

源码事实

shell 工具最终启动系统 bash/sh/PowerShell;Flatpak 下使用 flatpak-spawn --host。edit 工具解析并接受绝对路径,随后直接 fs::write。

白话解释

Goose 会先决定“该不该执行”,但一旦放行,命令通常是在真实电脑环境里跑,不是在一个只能碰项目目录的小盒子里。

对自研 Harness 的含义

Goose 的默认安全核心是策略检查与人类审批,不是内核级隔离;部署到高风险环境应再叠加容器、VM 或受限执行器。

边界
  • 外层桌面打包、企业运行平台或用户自行容器化可能增加隔离;本结论针对仓库内置 developer shell/edit 的实现。
关键源码 · 实现
crates/goose/src/agents/platform_extensions/developer/shell.rs · L25–L49
   25  /// Check if the current process is running inside a Flatpak sandbox.
   26  ///
   27  /// When inside Flatpak, shell commands must be wrapped with `flatpak-spawn --host`
   28  /// to execute on the host system rather than inside the sandbox.
   29  #[cfg(not(windows))]
   30  pub(crate) fn is_flatpak() -> bool {
   31      std::path::Path::new("/.flatpak-info").exists()
   32  }
   33  
   34  #[cfg(not(windows))]
   35  const FLATPAK_HOST_ARGS: [&str; 2] = ["--host", "--watch-bus"];
   36  
   37  #[cfg(not(windows))]
   38  pub(crate) fn flatpak_spawn_command() -> tokio::process::Command {
   39      let mut command = tokio::process::Command::new("flatpak-spawn");
   40      command.args(FLATPAK_HOST_ARGS);
   41      command
   42  }
   43  
   44  #[cfg(not(windows))]
   45  fn flatpak_spawn_process() -> std::process::Command {
   46      let mut command = std::process::Command::new("flatpak-spawn");
   47      command.args(FLATPAK_HOST_ARGS);
   48      command
   49  }
查看全部 3 处证据
  • 实现 crates/goose/src/agents/platform_extensions/developer/shell.rs:25–49 Flatpak 环境配置 flatpak-spawn --host --watch-bus。
  • 实现 crates/goose/src/agents/platform_extensions/developer/shell.rs:633–700 构造系统 shell,设置 cwd/PATH/会话环境后执行。
  • 实现 crates/goose/src/agents/platform_extensions/developer/edit.rs:47–105 解析路径、创建父目录、直接 fs::write。
07
DIMENSION · EXTENSIONS-MCP

MCP 与扩展

本章共 3 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

10
L1事实goose-mcp-001

MCP 工具被统一命名空间化、缓存和动态刷新

源码事实

ExtensionManager 从所有客户端分页拉取工具,按 extension__tool 前缀公开(特殊配置可不加前缀),缓存聚合结果;扩展增删会提升版本并失效缓存。

白话解释

各插件都可能有一个叫 search 的工具,所以 Goose 默认把它们改成“插件名__search”,避免撞名,并缓存工具清单减少重复查询。

对自研 Harness 的含义

工具注册表既是性能组件也是一致性组件;热插拔后必须原子失效。

关键源码 · 实现
crates/goose/src/agents/extension_manager.rs · L1271–L1342
 1271      /// Get all tools from all clients with proper prefixing
 1272      pub async fn get_prefixed_tools(
 1273          &self,
 1274          session_id: &str,
 1275          extension_name: Option<String>,
 1276      ) -> ExtensionResult<Vec<Tool>> {
 1277          let all_tools = self.get_all_tools_cached(session_id).await?;
 1278          Ok(self.filter_tools(&all_tools, extension_name.as_deref(), None))
 1279      }
 1280  
 1281      pub async fn get_prefixed_tools_excluding(
 1282          &self,
 1283          session_id: &str,
 1284          exclude: &str,
 1285      ) -> ExtensionResult<Vec<Tool>> {
 1286          let all_tools = self.get_all_tools_cached(session_id).await?;
 1287          Ok(self.filter_tools(&all_tools, None, Some(exclude)))
 1288      }
 1289  
 1290      fn filter_tools(
 1291          &self,
 1292          tools: &[Tool],
 1293          extension_name: Option<&str>,
 1294          exclude: Option<&str>,
      … 38 lines omitted; exact range 1271–1342 …
 1333          {
 1334              let mut cache = self.tools_cache.lock().await;
 1335              let version_after = self.tools_cache_version.load(Ordering::SeqCst);
 1336              if version_after == version_before && cache.is_none() {
 1337                  *cache = Some(Arc::clone(&tools));
 1338              }
 1339          }
 1340  
 1341          Ok(tools)
 1342      }
查看全部 2 处证据
  • 实现 crates/goose/src/agents/extension_manager.rs:1271–1342 过滤、缓存和版本校验。
  • 实现 crates/goose/src/agents/extension_manager.rs:1391–1455 分页列举客户端工具并添加扩展命名空间。
11
L1事实goose-mcp-002

远端 MCP 支持 HTTP、OAuth 和交互通知

源码事实

扩展可通过 streamable HTTP(Unix 上还可走 socket)连接,复用凭证存储和 OAuth;工具执行可同时返回通知流、ActionRequired 流与最终结果。

白话解释

插件不一定是本机子进程,也可以是要登录的远程服务;执行过程中还能弹出“需要用户操作”的中间事件。

对自研 Harness 的含义

Connector 模型必须同时处理认证生命周期、取消、通知和最终值,不能只把工具当普通函数。

关键源码 · 实现
crates/goose/src/agents/extension_manager.rs · L612–L730
  612  async fn connect_with_auth(
  613      auth_manager: rmcp::transport::AuthorizationManager,
  614      uri: &str,
  615      timeout: Duration,
  616      headers: &HashMap<String, String>,
  617      provider: SharedProvider,
  618      client_name: String,
  619      capabilities: GooseMcpClientCapabilities,
  620      roots_dir: &std::path::Path,
  621  ) -> ExtensionResult<Box<dyn McpClientTrait>> {
  622      let mut auth_headers = HeaderMap::new();
  623      auth_headers.insert(reqwest::header::USER_AGENT, GOOSE_USER_AGENT);
  624      for (key, value) in headers {
  625          auth_headers.insert(
  626              HeaderName::try_from(key)
  627                  .map_err(|_| ExtensionError::ConfigError(format!("invalid header: {}", key)))?,
  628              value.parse().map_err(|_| {
  629                  ExtensionError::ConfigError(format!("invalid header value: {}", key))
  630              })?,
  631          );
  632      }
  633      #[allow(unused_mut)]
  634      let mut auth_client_builder = reqwest::Client::builder().default_headers(auth_headers);
  635      #[cfg(target_os = "linux")]
      … 85 lines omitted; exact range 612–730 …
  721      let transport = StreamableHttpClientTransport::with_client(
  722          http_client,
  723          StreamableHttpClientTransportConfig::with_uri(uri),
  724      );
  725  
  726      // If we have stored OAuth credentials, try refreshing and connecting directly.
  727      // This avoids the unnecessary 401 → browser re-auth cycle on every new session.
  728      if credential_store.load().await.is_ok_and(|c| c.is_some()) {
  729          match oauth_flow(&uri.to_string(), &name.to_string()).await {
  730              Ok(auth_manager) => {
查看全部 2 处证据
  • 实现 crates/goose/src/agents/extension_manager.rs:612–730 构建带请求头的 Streamable HTTP transport,并复用 OAuth 凭证。
  • 实现 crates/goose/src/agents/extension_manager.rs:1786–1895 解析工具归属,调用 MCP client,暴露通知与 ActionRequired stream。
12
L1事实goose-mcp-003

MCP UI 元数据采用“先剥离、再可信补写”

源码事实

客户端返回的 goose.mcpApp 和内部工具更新元数据会先被删除;只有宿主主动读取关联资源后才插入可信附件。

白话解释

插件不能只靠自己声称“这是可信 UI”就让前端照单全收,Goose 会清掉敏感标记并由宿主重新验证、重新封装。

对自研 Harness 的含义

所有能改变宿主 UI/行为的扩展元数据都应有信任边界。

关键源码 · 实现
crates/goose/src/agents/extension_manager.rs · L322–L346
  322  fn remove_untrusted_mcp_app_meta(result: &mut CallToolResult) {
  323      let Some(meta) = result.meta.as_mut() else {
  324          return;
  325      };
  326  
  327      meta.0.remove(TRUSTED_TOOL_UPDATE_META_KEY);
  328  
  329      let remove_goose = meta
  330          .0
  331          .get_mut("goose")
  332          .and_then(Value::as_object_mut)
  333          .map(|goose_meta| {
  334              goose_meta.remove("mcpApp");
  335              goose_meta.is_empty()
  336          })
  337          .unwrap_or(false);
  338  
  339      if remove_goose {
  340          meta.0.remove("goose");
  341      }
  342  
  343      if meta.0.is_empty() {
  344          result.meta = None;
  345      }
  346  }
查看全部 3 处证据
  • 实现 crates/goose/src/agents/extension_manager.rs:322–346 remove_untrusted_mcp_app_meta 清理保留键。
  • 实现 crates/goose/src/agents/extension_manager.rs:1866–1880 调用后先清理,再由宿主 hydrate 资源并写回可信 metadata。
  • 测试 crates/goose/src/agents/extension_manager.rs:2881–2904 测试伪造 payload 会被剥离。
08
DIMENSION · INSTRUCTIONS-PROMPTS

指令与 Prompt

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

13
L1事实goose-prompt-001

系统提示由稳定骨架、扩展说明和路径 hints 组合

源码事实

PromptBuilder 聚合时间、工作目录、扩展说明、可用工具、模式、系统 extras 与 hints;工具排序稳定以利提示缓存,并清洗扩展提供的 Unicode 标签。

白话解释

提示词不是一整块写死文本,而是像模板一样拼装;插件能加说明,但会先清理可能伪装成系统标签的字符。

对自研 Harness 的含义

指令装配应有稳定顺序、来源边界和可测试的净化规则,否则缓存命中与安全都会漂移。

关键源码 · 实现
crates/goose/src/agents/prompt_manager.rs · L95–L165
   95      pub fn with_hints(mut self, working_dir: &Path) -> Self {
   96          let hints_filenames = get_context_filenames();
   97          let ignore_patterns = build_gitignore(working_dir);
   98  
   99          let hints = load_hint_files(working_dir, &hints_filenames, &ignore_patterns);
  100  
  101          if !hints.is_empty() {
  102              self.hints = Some(hints);
  103          }
  104          self
  105      }
  106  
  107      pub fn with_enable_subagents(mut self, subagents_enabled: bool) -> Self {
  108          self.subagents_enabled = subagents_enabled;
  109          self
  110      }
  111  
  112      pub fn with_goose_mode(mut self, mode: GooseMode) -> Self {
  113          self.goose_mode = Some(mode);
  114          self
  115      }
  116  
  117      pub fn build(self) -> String {
  118          let mut extensions_info = self.extensions_info;
      … 37 lines omitted; exact range 95–165 …
  156              code_execution_mode: self.code_execution_mode,
  157              moim_system_prompt_block: moim::system_prompt_block(),
  158          };
  159  
  160          let base_prompt = if let Some(override_prompt) = &self.manager.system_prompt_override {
  161              let sanitized_override_prompt = sanitize_unicode_tags(override_prompt);
  162              prompt_template::render_string(&sanitized_override_prompt, &context)
  163          } else {
  164              prompt_template::render_template("system.md", &context)
  165          }
查看全部 3 处证据
  • 实现 crates/goose/src/agents/prompt_manager.rs:95–165 读取 hints、稳定排序工具、清洗标签并渲染系统模板。
  • 实现 crates/goose/src/agents/prompt_manager.rs:170–273 追加 extras 与 hints,固定小时级时间用于缓存。
  • 测试 crates/goose/src/agents/prompt_manager.rs:289–360 覆盖 Unicode 标签清洗和提示构建。
09
DIMENSION · SKILLS-PLUGINS-HOOKS

Skills、插件与 Hooks

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

14
L1事实goose-skills-001

Skills 采用渐进加载和多生态目录兼容

源码事实

项目级 .agents/.goose/.claude 目录先于全局目录发现;系统提示只列技能名与描述,真正调用时才载入 SKILL.md 或支持文件,并校验支持文件解析后仍位于技能目录内。

白话解释

模型一开始只拿到“技能目录”,需要时再打开说明书和附件,既省上下文,也防止通过 ../ 偷读技能目录外文件。

对自研 Harness 的含义

技能系统应把发现、选择、加载分开,并对支持文件做 canonical path 校验。

关键源码 · 实现
crates/goose/src/skills/mod.rs · L313–L341
  313  /// Every directory the agent reads skills from, paired with whether each is a
  314  /// global (home-rooted) location. Order matches discovery precedence: project
  315  /// dirs first, then global dirs.
  316  pub fn all_skill_dirs(working_dir: Option<&Path>) -> Vec<(PathBuf, bool)> {
  317      let mut dirs: Vec<(PathBuf, bool)> = Vec::new();
  318  
  319      if let Some(wd) = working_dir {
  320          dirs.push((wd.join(".agents").join("skills"), false));
  321          dirs.push((wd.join(".goose").join("skills"), false));
  322          dirs.push((wd.join(".claude").join("skills"), false));
  323      }
  324  
  325      let home = dirs::home_dir();
  326      if let Some(h) = home.as_ref() {
  327          dirs.push((h.join(".agents").join("skills"), true));
  328      }
  329      dirs.push((Paths::config_dir().join("skills"), true));
  330      if let Some(h) = home.as_ref() {
  331          dirs.push((h.join(".claude").join("skills"), true));
  332          dirs.push((h.join(".config").join("agents").join("skills"), true));
  333      }
  334  
  335      dirs.extend(
  336          installed_plugin_skill_dirs()
  337              .into_iter()
  338              .map(|dir| (dir, true)),
  339      );
  340  
  341      dirs
查看全部 3 处证据
  • 实现 crates/goose/src/skills/mod.rs:313–341 项目与全局技能目录及其发现优先级。
  • 实现 crates/goose/src/skills/client.rs:132–187 按名称加载技能或支持文件,并拒绝解析到目录外。
  • 实现 crates/goose/src/skills/client.rs:245–266 系统指令只列出技能名和描述。
15
L1事实goose-hooks-001

插件 Hooks 覆盖工具、文件、Shell、会话和停止事件

源码事实

HookManager 从启用插件的 hooks/hooks.json 加载命令动作;PreToolUse 和 Stop 可阻塞,其他 hook 失败默认放行;Stop 阻塞另有连续次数上限。

白话解释

组织可以在“运行命令前”“改文件后”“会话开始”“Agent 想结束”等时点执行自己的脚本,但普通通知脚本坏掉不会让整个 Agent 瘫痪。

对自研 Harness 的含义

Hook 的失败语义必须逐事件定义;治理门可以 fail-closed,旁路观测更适合 fail-open。

关键源码 · 契约
crates/goose/src/hooks/mod.rs · L64–L95
   64  impl HookEvent {
   65      fn name(&self) -> &'static str {
   66          match self {
   67              HookEvent::PreToolUse => "PreToolUse",
   68              HookEvent::PostToolUse => "PostToolUse",
   69              HookEvent::PostToolUseFailure => "PostToolUseFailure",
   70              HookEvent::SessionStart => "SessionStart",
   71              HookEvent::SessionEnd => "SessionEnd",
   72              HookEvent::UserPromptSubmit => "UserPromptSubmit",
   73              HookEvent::BeforeReadFile => "BeforeReadFile",
   74              HookEvent::AfterFileEdit => "AfterFileEdit",
   75              HookEvent::BeforeShellExecution => "BeforeShellExecution",
   76              HookEvent::AfterShellExecution => "AfterShellExecution",
   77              HookEvent::Stop => "Stop",
   78          }
   79      }
   80  
   81      fn from_name(name: &str) -> Option<Self> {
   82          Some(match name {
   83              "PreToolUse" => HookEvent::PreToolUse,
   84              "PostToolUse" => HookEvent::PostToolUse,
   85              "PostToolUseFailure" => HookEvent::PostToolUseFailure,
   86              "SessionStart" => HookEvent::SessionStart,
   87              "SessionEnd" => HookEvent::SessionEnd,
   88              "UserPromptSubmit" => HookEvent::UserPromptSubmit,
   89              "BeforeReadFile" => HookEvent::BeforeReadFile,
   90              "AfterFileEdit" => HookEvent::AfterFileEdit,
   91              "BeforeShellExecution" => HookEvent::BeforeShellExecution,
   92              "AfterShellExecution" => HookEvent::AfterShellExecution,
   93              "Stop" => HookEvent::Stop,
   94              _ => return None,
   95          })
查看全部 3 处证据
  • 契约 crates/goose/src/hooks/mod.rs:64–95 HookEvent 枚举覆盖 Pre/PostTool、Session、Read/Edit、Shell、Stop。
  • 实现 crates/goose/src/hooks/mod.rs:237–284 从启用插件加载 hooks.json。
  • 实现 crates/goose/src/hooks/mod.rs:292–425 普通 emit 失败放行;blocking emit 解释阻塞退出码或 JSON。
10
DIMENSION · SUBAGENTS-COLLABORATION

子 Agent 与协作

本章共 1 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

16
L1事实goose-subagent-001

子 Agent 是独立 Agent 与子会话,不是主提示词里的角色扮演

源码事实

SubagentHandler 新建 Agent,继承/覆盖 provider 与 model,按配置装载扩展和响应 schema,创建 SubAgent 类型会话,并运行标准 Agent.reply;子 Agent 系统提示明确禁止继续创建子 Agent。

白话解释

主 Agent 真正启动了另一个有独立历史和工具集的执行循环,而不是在同一段对话里假装分身;但只允许一层委派。

对自研 Harness 的含义

独立会话便于隔离上下文、统计成本和回传结果;禁止递归可控制爆炸式 fan-out。

关键源码 · 实现
crates/goose/src/agents/subagent_handler.rs · L121–L230
  121  fn get_agent_messages(params: SubagentRunParams) -> AgentMessagesFuture {
  122      Box::pin(async move {
  123          let SubagentRunParams {
  124              config,
  125              recipe,
  126              task_config,
  127              session_id,
  128              cancellation_token,
  129              on_message,
  130              notification_tx,
  131              ..
  132          } = params;
  133  
  134          let system_instructions = recipe.instructions.clone().unwrap_or_default();
  135          let user_task = recipe
  136              .prompt
  137              .clone()
  138              .unwrap_or_else(|| "Begin.".to_string());
  139  
  140          let agent = Arc::new(Agent::with_config(config));
  141  
  142          agent
  143              .update_provider(
  144                  task_config.provider.clone(),
      … 76 lines omitted; exact range 121–230 …
  221                      tracing::error!("Error receiving message from subagent: {}", e);
  222                      break;
  223                  }
  224              }
  225          }
  226  
  227          let final_output = get_final_output(&agent, has_response_schema).await;
  228  
  229          Ok((conversation, final_output))
  230      })
查看全部 2 处证据
  • 实现 crates/goose/src/agents/subagent_handler.rs:121–230 构造独立 Agent、扩展、响应 schema、会话配置并运行 reply。
  • Prompt crates/goose/src/prompts/subagent_system.md:1–38 子 Agent 有边界任务、最大 turns,并被禁止再生成 subagents。
11
DIMENSION · OBSERVABILITY-PERSISTENCE

持久化与观测

本章共 2 个可定位结论;结论按“实现事实 → 白话解释 → 工程影响 → 源码摘录”展开。

17
L1事实goose-session-001

会话、消息、成本与压缩指标落到 SQLite/WAL

源码事实

SessionManager 使用 SQLite、WAL 和迁移管理持久化 sessions、messages、usage_ledger;会话记录 provider/model/mode/project/parent,usage ledger 区分输入输出、缓存、总成本与压缩后保留 token。

白话解释

对话不只是屏幕上的临时文本:每条消息、用的模型、父子会话、花费和压缩前后 token 都能落盘追踪。

对自研 Harness 的含义

成本与上下文恢复应成为一等数据模型,而不是散落在日志字符串里。

关键源码 · 契约
crates/goose/src/session/session_manager.rs · L45–L96
   45  pub enum SessionType {
   46      #[default]
   47      User,
   48      Scheduled,
   49      SubAgent,
   50      Hidden,
   51      Terminal,
   52      Gateway,
   53      Acp,
   54  }
   55  
   56  static SESSION_STORAGE: LazyLock<Arc<SessionStorage>> =
   57      LazyLock::new(|| Arc::new(SessionStorage::new(Paths::data_dir())));
   58  
   59  #[derive(Debug, Clone, Serialize, Deserialize)]
   60  pub struct Session {
   61      pub id: String,
   62      pub working_dir: PathBuf,
   63      #[serde(alias = "description")]
   64      pub name: String,
   65      #[serde(default)]
   66      pub user_set_name: bool,
   67      #[serde(default)]
   68      pub session_type: SessionType,
      … 18 lines omitted; exact range 45–96 …
   87      pub goose_mode: GooseMode,
   88      #[serde(default)]
   89      pub archived_at: Option<DateTime<Utc>>,
   90      #[serde(default)]
   91      pub project_id: Option<String>,
   92      #[serde(default)]
   93      pub parent_session_id: Option<String>,
   94      #[serde(default)]
   95      pub last_message_snippet: Option<String>,
   96  }
查看全部 3 处证据
  • 契约 crates/goose/src/session/session_manager.rs:45–96 SessionType 与 Session 持久字段。
  • 实现 crates/goose/src/session/session_manager.rs:843–890 SQLite 路径、WAL、foreign keys 与 migrations。
  • 迁移 crates/goose/src/session/session_manager.rs:893–1031 sessions/messages/usage_ledger 表和 parent_session 索引。
18
L1事实goose-observe-001

Tracing 以 goose:: span 生成 trace/span 观测事件

源码事实

ObservationLayer 仅处理 target 以 goose:: 开头的 tracing 事件,跟踪 span 层级、输入输出和批次,并可把 reply 最终文本记录为 trace_output。

白话解释

内部关键步骤会形成一棵调用轨迹,不是只打一长串平面日志;同时避免把所有依赖库噪声都收进来。

对自研 Harness 的含义

观测层和业务层通过结构化 span 字段连接,适合接入 Langfuse 等后端,但敏感输出治理仍需部署方关注。

关键源码 · 实现
crates/goose/src/tracing/observation_layer.rs · L103–L184
  103  impl ObservationLayer {
  104      pub async fn handle_span(&self, span_id: u64, span_data: SpanData) {
  105          let observation_id = span_data.observation_id.clone();
  106  
  107          {
  108              let mut spans = self.span_tracker.lock().await;
  109              spans.add_span(span_id, observation_id.clone());
  110          }
  111  
  112          // Get parent ID if it exists
  113          let parent_id = if let Some(parent_span_id) = span_data.parent_span_id {
  114              let spans = self.span_tracker.lock().await;
  115              spans.get_span(parent_span_id).cloned()
  116          } else {
  117              None
  118          };
  119  
  120          let trace_id = self.ensure_trace_id().await;
  121  
  122          // Create the span observation
  123          let mut batch = self.batch_manager.lock().await;
  124          batch.add_event(
  125              "observation-create",
  126              json!({
      … 48 lines omitted; exact range 103–184 …
  175                  "timestamp": Utc::now().to_rfc3339(),
  176                  "input": {},
  177                  "metadata": {},
  178                  "tags": [],
  179                  "public": false
  180              }),
  181          );
  182  
  183          trace_id
  184      }
查看全部 3 处证据
  • 实现 crates/goose/src/tracing/observation_layer.rs:103–184 创建与更新 trace/span 事件。
  • 实现 crates/goose/src/tracing/observation_layer.rs:196–336 记录输入输出,仅过滤 goose:: targets。
  • 实现 crates/goose/src/agents/agent.rs:2922–2924 把最终助手文本记录到当前 span 的 trace_output。
APPENDIX · SOURCE INDEX

本报告引用过的实现文件

这是一份代码阅读索引,不是仓库文件总表。机器候选扫描覆盖整个仓库;进入结论的文件必须经人工沿调用链复核。

  1. 01crates/goose/src/agents/agent.rsL1930–2043, 2883–2929, 67–79, 2773–2793, 2892–2915, 2533–2591, 2210–2265, 2267–2337, 659–688, 2922–2924
  2. 02crates/goose-provider-types/src/base.rsL281–286, 394–427
  3. 03crates/goose/src/context_mgmt/mod.rsL26–49, 78–188, 319–398
  4. 04crates/goose/src/context_mgmt/structured.rsL13–38
  5. 05crates/goose/src/permission/permission_inspector.rsL159–268
  6. 06crates/goose/src/agents/platform_extensions/developer/shell.rsL25–49, 633–700
  7. 07crates/goose/src/agents/platform_extensions/developer/edit.rsL47–105
  8. 08crates/goose/src/agents/extension_manager.rsL1271–1342, 1391–1455, 612–730, 1786–1895, 322–346, 1866–1880, 2881–2904
  9. 09crates/goose/src/agents/prompt_manager.rsL95–165, 170–273, 289–360
  10. 10crates/goose/src/skills/mod.rsL313–341
  11. 11crates/goose/src/skills/client.rsL132–187, 245–266
  12. 12crates/goose/src/hooks/mod.rsL64–95, 237–284, 292–425
  13. 13crates/goose/src/agents/subagent_handler.rsL121–230
  14. 14crates/goose/src/prompts/subagent_system.mdL1–38
  15. 15crates/goose/src/session/session_manager.rsL45–96, 843–890, 893–1031
  16. 16crates/goose/src/tracing/observation_layer.rsL103–184, 196–336