2.6 Attachment System¶
本节摘要
Attachment System 是 Claude Code 中 system prompt 和对话消息之外的第三条状态注入通道。它在每个用户 turn 动态计算 30+ 类 Context 信息(Skill 列表、文件变更、Tool 变更、任务提醒、token 用量等),以 <system-reminder> 标签包裹的 user message 形式注入对话。这是 Claude Code 中变化最频繁的状态注入机制——与会话级缓存的 system prompt 形成互补。
一、为什么需要 Attachment System¶
Claude Code 的状态注入有三个通道,各自服务于不同的变化频率:
| 通道 | 变化频率 | 缓存行为 | 典型内容 |
|---|---|---|---|
| System Prompt | 会话级(几乎不变) | 静态区域跨组织缓存 | 身份、规则、安全指令 |
| Tool Definitions | 会话级(偶尔变化) | 随 system prompt 一起发送 | Tool Schema |
Attachment(<system-reminder>) |
每 turn 变化 | 不缓存,每次重算 | Skill 列表、文件变更、任务提醒 |
System prompt 追求稳定以命中 Prompt Cache;Attachment System 则承担所有需要每 turn 更新的动态状态,且不破坏 system prompt 的缓存。
二、触发时机与调用点¶
Attachment System 在两个时机被调用:
时机 1:用户输入时(Turn 开始)¶
用户发送消息后、LLM 被调用前,getAttachmentMessages(input, ...) 计算初始 attachment 集合。此时 input 非空,可以触发用户输入相关的 attachment(如 @-mention 文件解析、Skill 发现)。
时机 2:Tool 执行后(Turn 中间)¶
每次 LLM 返回 tool call 并执行完毕后,在下一轮 API 调用前再次运行 getAttachmentMessages(null, ...)(query.ts:1580)。此时文件变更、任务状态、动态 Skill 等可能因 Tool 执行而发生变化。
一个 turn 内可能触发多次
一个 agentic turn 可能包含多轮 tool call。每轮执行后都会重新计算 attachment——因此 Skill 列表、文件变更通知等会在 turn 内增量更新。
三、完整 Attachment 类型清单¶
getAttachments()(attachments.ts:743)并行计算三组 attachment,涵盖 30+ 类型:
第一组:用户输入相关(仅 Turn 开始时)¶
| 类型 | 触发条件 | 注入内容 | 影响的组件 |
|---|---|---|---|
at_mentioned_files |
用户 @文件名 | 文件内容 | Context |
mcp_resources |
用户 @MCP 资源 | MCP 资源内容 | Context |
agent_mentions |
用户 @agent 名 | Agent 信息 | Sub-agent |
skill_discovery |
Turn 0 用户输入 | 发现的相关 Skill | Skill |
第二组:全线程通用(每次 Tool 执行后都计算)¶
| 类型 | 触发条件 | 注入内容 | 影响的组件 |
|---|---|---|---|
skill_listing |
SkillTool 可用且有新 Skill | Skill 列表(增量) | Skill |
dynamic_skill |
文件操作发现新 Skill 目录 | 新 Skill 名称(仅 UI) | Skill |
changed_files |
自上次 turn 以来文件被修改 | 文件变更摘要 | Context |
nested_memory |
CWD 下有嵌套 CLAUDE.md | 嵌套目录的 Memory 文件 | Memory |
deferred_tools_delta |
Tool 集发生延迟变更 | 新增或移除的 Tool 名 | Tool |
agent_listing_delta |
可用 Agent 列表变化 | 新增或移除的 Agent | Sub-agent |
mcp_instructions_delta |
MCP 服务器指令变化 | 新增或移除的 MCP 指令 | Tool |
queued_commands |
有排队的命令或通知 | 命令内容 | Context |
date_change |
日期跨天 | 新日期 | Context |
ultrathink_effort |
用户触发深度思考 | effort 级别 | Context |
plan_mode |
进入或退出 Plan 模式 | 模式指令 | Context |
auto_mode |
进入或退出 Auto 模式 | 模式指令 | Context |
todo_reminders |
N 轮未更新 todo | 当前待办列表 | Task |
teammate_mailbox |
有未读团队消息 | 消息内容 | Sub-agent |
team_context |
Swarm 模式下 | 团队信息 | Sub-agent |
agent_pending_messages |
Sub-agent 有待处理消息 | 消息内容 | Sub-agent |
critical_system_reminder |
有紧急系统提醒 | 提醒内容 | Context |
compaction_reminder |
Context 接近满 | 压缩状态 | Context |
companion_intro |
Buddy 模式首次出现 | 伴侣介绍 | Context |
第三组:主线程独占(Sub-agent 中不计算)¶
| 类型 | 触发条件 | 注入内容 | 影响的组件 |
|---|---|---|---|
ide_selection |
IDE 有选中文本 | 选中行内容 | Context |
ide_opened_file |
IDE 有打开文件 | 文件路径 | Context |
output_style |
有输出风格配置 | 风格指令 | Context |
diagnostics |
有诊断信息 | 错误或警告 | Context |
lsp_diagnostics |
LSP 有诊断 | LSP 错误 | Context |
unified_tasks |
有后台任务 | 任务状态 | Task |
token_usage |
每 turn | 已用或剩余 token | Context |
budget_usd |
有预算限制 | 已用或剩余预算 | Context |
output_token_usage |
每 turn | 输出 token 用量 | Context |
verify_plan_reminder |
有待验证的计划 | 验证提醒 | Task |
四、序列化机制¶
步骤 1:计算¶
getAttachments() 并行计算所有 attachment,每个通过 maybe(name, fn) 包裹——如果 fn 抛异常或超时(1 秒),该 attachment 被安全跳过。
步骤 2:序列化¶
normalizeAttachmentForAPI(attachment) 将每个 attachment 转化为 isMeta: true 的 user message,用 wrapInSystemReminder() 包裹:
function wrapInSystemReminder(content: string): string {
return `<system-reminder>\n${content}\n</system-reminder>`
}
步骤 3:合并¶
attachment message 不是独立消息——在 normalizeMessagesForAPI 中被合并进相邻的 user message(mergeUserMessages)。smooshSystemReminderSiblings 进一步将 <system-reminder> 文本折叠到 tool_result 旁边。最终 LLM 看到的是一条 user message 中混合了真实用户输入和多个 <system-reminder> 块。
步骤 4:模型感知¶
System prompt 静态区域中告知模型注意这些标签:
Tool results and user messages may include
<system-reminder>tags. Tags contain information from the system. They bear no direct relation to the specific tool results or user messages in which they appear.
五、增量更新(Delta 语义)¶
部分 attachment 采用增量语义——只发送自上次以来的变化:
| Attachment | Delta 机制 | 追踪方式 |
|---|---|---|
skill_listing |
只发新增的 Skill | sentSkillNames: Map<agentKey, Set<name>> |
deferred_tools_delta |
只发新增或移除的 Tool | getDeferredToolsDelta() 比较前后快照 |
agent_listing_delta |
只发新增或移除的 Agent | 比较前后快照 |
mcp_instructions_delta |
只发新增或移除的 MCP 指令 | getMcpInstructionsDelta() |
频率控制¶
部分 attachment 有稀疏化策略(不是每 turn 都发完整内容):
| 配置 | 值 | 说明 |
|---|---|---|
TODO_REMINDER_CONFIG.TURNS_SINCE_WRITE |
10 | 10 轮未写 todo 才提醒 |
TODO_REMINDER_CONFIG.TURNS_BETWEEN_REMINDERS |
10 | 两次提醒间隔至少 10 轮 |
PLAN_MODE_ATTACHMENT_CONFIG.TURNS_BETWEEN_ATTACHMENTS |
5 | Plan 模式每 5 轮提醒 |
PLAN_MODE_ATTACHMENT_CONFIG.FULL_REMINDER_EVERY_N_ATTACHMENTS |
5 | 每 5 次中有 1 次是完整版 |
六、对其他组件的影响¶
对 Context Management 的影响¶
- 占用 Context Window:attachment 序列化后成为 messages 的一部分,占用 token
- 触发压缩:大量 attachment 会加速 Context Window 消耗,间接触发 autocompact
- 压缩后丢失与恢复:attachment 内容在压缩时被摘要替代;部分关键信息通过
postCompactCleanup恢复(如invoked_skills重新注入已加载的 Skill 内容)
对 Memory System 的影响¶
nested_memory将嵌套目录的 CLAUDE.md 内容注入对话relevant_memories将 Sonnet 召回的 Memory 文件注入对话changed_files通知模型文件已变化(可能使之前读取的 Memory 过时)
对 Tool 和 Skill 的影响¶
deferred_tools_delta通知模型 Tool 集的变化mcp_instructions_delta通知模型 MCP 服务器指令的变化skill_listing是 Skill 列表到达 LLM 的唯一通道
对 Sub-agent 的影响¶
- Sub-agent 共享第二组(全线程通用)attachment,但不获取第三组(主线程独占)
agent_listing_delta通知 Agent 可用列表变化
对 Prompt Cache 的影响¶
- 不破坏 Prompt Cache:attachment 位于 user message 中,不在 system prompt 静态区域内
关键文件索引¶
| 文件 | 职责 |
|---|---|
src/utils/attachments.ts |
核心文件(3,998 行):30+ 类 attachment 的计算逻辑 |
src/utils/messages.ts |
normalizeAttachmentForAPI() 序列化、wrapInSystemReminder() |
src/query.ts |
两个调用点:用户输入后 + Tool 执行后 |
src/services/compact/compact.ts |
压缩后 attachment 恢复 |
src/tools/SkillTool/prompt.ts |
formatCommandsWithinBudget() Skill listing 格式化 |