goose/source-notesupstream ↗
goose · version 1.45.0 · Rust 1.94.1

第 4 章:消息、可见性与会话——一份数据服务三类消费者

4.1 是什么:Message 不是“聊天气泡”

Messageidrolecreatedcontentmetadata 构成。MessageContentBlock 可以是 text、image、tool request/response、thinking、redacted thinking、action required、frontend tool 和 system notification。它同时承载:

metadata 中最重要的两个布尔值是 user_visibleagent_visible。这不是一个简单的“隐藏消息”开关,而是两条独立的投影。

flowchart TB
  Raw[同一 Message] --> UserView[user_visible_content\n给 UI]
  Raw --> AgentView[agent_visible_content\n给模型上下文]
  Raw --> Store[原始 JSON\n持久化]
  Raw --> Events[AgentEvent\n增量渲染]

4.2 源码怎么做:双可见性 + typed content

Message::user_visible_content() 会按 MCP annotation 和消息元数据过滤内容;agent_visible_content() 则生成给 Assistant audience 的投影。比如:

ConversationVec<Message> 的受控封装。push 会按 message ID 合并增量文本和 thinking;fix_conversation 则在发给 LLM 前运行一组卫生处理:合并 text、去掉空消息、修复 tool pair、合并连续消息、去重 signed thinking,并确保 Agent 可见消息的首尾结构合法。

这解释了一个看似奇怪的事实:流式响应可以产生许多 AgentEvent::Message,但 session 最终只需要一组可重放、可校验的消息。

4.3 Tool request 为什么是“消息内容”,而不是外部任务对象

ToolRequest 记录 request id、解析后的 CallToolRequestParams、provider metadata 和 tool meta。对应的 ToolResponse 保留同一 id。把二者嵌入消息有三个好处:

  1. Provider formatter 可以按照自己的协议把 tool pair 重建出来。
  2. Compaction 可以把工具历史作为上下文的一部分压缩。
  3. Session export/import 不需要另外理解一套任务数据库。

对于解析失败的 tool call,Agent 仍会把合法 placeholder 写入历史,把真实解析错误放在配对的工具响应中。这样每个 provider 的正常 formatter 路径不需要分别处理“历史里有 Err”这种异常形状。

4.4 Session:消息之外的运行时快照

Session 不只保存 conversation,还保存 working_dir、session_type、provider/model config、extension data、recipe、schedule、usage、project_id、parent_session_id 和归档信息。SessionType 区分 user、scheduled、sub-agent、hidden、terminal、gateway、acp 等来源。

Session 的一个设计取舍是:摘要信息放在 sessions 表,完整消息另放 messages 表;读取列表时可以不加载完整 conversation,从而让会话列表和历史搜索更轻。session name 还可以由 provider 异步生成,但一旦 user_set_name 就不再覆盖。

源码定位