主题
第九章 上下文工程(三系统对比)
← 返回目录 | 上一章:Claude Code 独特设计与服务架构 | 下一章:工具系统完整解析 →
背景:上下文窗口是所有 LLM 应用的核心约束。三个系统在"如何让 Agent 在有限窗口内获得最有效信息"这个问题上,走出了截然不同的路径。
9.1 系统提示的构建策略
OmO:动态组装式
OmO 不使用静态系统提示。每次 LLM 调用前,系统提示由 dynamic-agent-prompt-builder.ts 动态组装:
最终系统提示 =
基础 Agent 指令(角色定义、行为准则)
+ 已加载 Skill 的指令内容
+ 动态生成的编排指南(根据当前可用 Agent 和工具)
+ 环境信息(OS、时区、当前目录、Git 状态)
+ 系统提醒(后台任务通知、验证提醒等)生成的内容包括:关键触发器检查规则、工具选择指南表、Explore/Librarian 使用模板、反重复规则、Category + Skill 委派协议、硬性约束列表。
Claude Code:分层拼接式
Claude Code 的系统提示由 buildEffectiveSystemPrompt() 从多个来源拼接:
最终系统提示 =
默认系统提示(getSystemPrompt() — 基于可用工具集动态生成)
+ 自定义系统提示(用户/项目级 customSystemPrompt)
+ 追加系统提示(appendSystemPrompt)
+ CLAUDE.md 指令文件内容
+ Memory 系统中的相关记忆
+ 用户上下文 + 系统上下文与 OmO 不同,Claude Code 的系统提示不包含编排指南——编排逻辑(Coordinator 模式)是内嵌在代码中的,不需要通过 prompt 教模型如何委派。
Opencode:相对静态
Opencode 的基础系统提示较为固定,主要包含工具描述和基本行为指引。它的扩展点在插件层——通过 chat.params Hook 可以修改系统提示(OmO 正是利用了这一点)。
对比总结
| 维度 | Opencode | OmO | Claude Code |
|---|---|---|---|
| 系统提示生成 | 静态 + 插件 Hook | 全动态组装 | 分层拼接 |
| 编排指令 | 无 | 在 prompt 中教模型 | 在代码中硬编码 |
| 指令来源 | 内置 + 插件注入 | 内置 + Skill + AGENTS.md | 内置 + CLAUDE.md + Memory |
| prompt 膨胀风险 | 低(简洁) | 高(全量注入) | 中(按需加载) |
9.2 Agent 间信息传递
当一个 Agent 需要委派子任务时,"传递什么信息"和"如何传递"直接决定子 Agent 的执行质量。三个系统在这个问题上采用了风格迥异的方案。
OmO:结构化 Prompt 协议
OmO 要求所有委派都遵循严格的 6 段式 Prompt 结构:
1. TASK: 原子化目标(每次委派只做一件事)
2. EXPECTED OUTCOME: 具体可交付物 + 成功标准
3. REQUIRED TOOLS: 显式工具白名单(防止工具滥用)
4. MUST DO: 穷尽式需求列表(不留隐含假设)
5. MUST NOT DO: 禁止行为列表(预判并阻止偏离)
6. CONTEXT: 文件路径、已有模式、约束条件这个结构在 delegate-task/constants.ts 中强制定义,如果 Prompt 缺少必要段落,系统会提示重写。OmO 还通过 token-limiter.ts 对注入内容做 Token 预算管理——当 Skill 内容 + AGENTS.md 上下文 + Category 附加 Prompt 超过预算时,按优先级逐段截断(先裁 Skill 内容,再裁 AGENTS.md,最后裁 Category Prompt)。
对于本地/免费模型,Token 预算硬限为 24,000——这是一个务实的设计:小模型的有效窗口远小于标称窗口,塞太多指令反而降低遵循度。
Claude Code:继承式上下文 + Directive Prompt
Claude Code 的 Agent(子代理)通过 runAgent() 启动,其上下文传递策略完全不同:
系统 Prompt 独立生成:每个 Agent 有自己的
getSystemPrompt(),而非继承父级的。getAgentSystemPrompt()会调用 Agent 定义的 prompt 方法,再通过enhanceSystemPromptWithEnvDetails()追加环境信息。Fork 模式共享缓存:Fork 子代理继承父级的 Prompt Cache,因此 Prompt 是 指令式("做什么")而非 叙述式("情况是什么")。这大幅减少了重复上下文。
Hook 注入附加上下文:
executeSubagentStartHooks()在 Agent 启动时执行,收集additionalContexts,以hook_additional_context类型的 Attachment Message 注入到对话中。工具白名单过滤:
filterToolsForAgent()根据 Agent 定义的tools/disallowedTools过滤可用工具集,内置的 Agent 和异步 Agent 还有额外的工具黑名单(ALL_AGENT_DISALLOWED_TOOLS、ASYNC_AGENT_ALLOWED_TOOLS)。
Opencode:透传式
Opencode 本身没有多 Agent 编排——它是单 Agent 架构。"信息传递"发生在插件层:Opencode 通过 chat.params 和 chat.message Hook 将控制权交给插件,由插件决定如何构造子 Agent 的上下文。
对比总结
| 维度 | OmO | Claude Code | Opencode |
|---|---|---|---|
| 传递方式 | 结构化 6 段 Prompt | 独立系统 Prompt + Directive | 插件决定 |
| 上下文继承 | 不继承(全新构造) | Fork 可共享 Prompt Cache | N/A |
| 工具控制 | REQUIRED TOOLS 白名单 | 定义级白/黑名单过滤 | 无限制 |
| Token 预算 | 显式限制 + 逐段截断 | 无显式限制 | 无 |
| Hook 扩展 | Skill + AGENTS.md 注入 | SubagentStart Hook 注入 | chat.params Hook |
9.3 上下文窗口管理与压缩
上下文窗口是 LLM 应用的核心物理约束。三个系统在"如何在窗口耗尽前保持 Agent 有效运行"这个问题上,发展出了复杂度差异极大的方案。
OmO:三阶段防御体系
OmO 的上下文管理按时间线分为预警 → 预防 → 恢复三个阶段:
阶段一:预警监控(70% 阈值)
context-window-monitor.ts 在每次工具调用后检查 Token 使用量。当使用率超过 70% 时,向工具输出追加提醒——告诉模型"你还有 X% 的空间",防止模型因恐慌而跳过任务。这个提醒只触发一次(per session),避免反复干扰。
阶段二:预防性压缩(78% 阈值)
preemptive-compaction.ts 在使用率达到 78% 时主动触发 Opencode 的 session.summarize() 进行压缩。关键设计细节:
- 120 秒超时保护(压缩本身也消耗 Token,不能无限等待)
- 60 秒冷却期(避免频繁触发)
- 压缩后监控降级(
degradation-monitor)——如果压缩后模型行为明显变差,系统会检测到
阶段三:错误恢复(窗口溢出后)
这是 OmO 最复杂的子系统之一(31 文件,约 2232 行代码)。当 API 返回 Token 超限错误时,anthropic-context-window-limit-recovery 按优先级执行五个恢复策略:
1. 空内容修复 — 处理消息中的 null/空 content 块
2. 去重 — 移除上下文中重复的工具输出
3. 目标 Token 截断 — 将最大的工具输出截断到窗口 50% 的目标比例
4. 激进截断 — 最后手段,仅保留最少量输出
5. 摘要重试 — 触发完整压缩(摘要化),然后重试请求每个策略最多尝试 2 次,退避策略为指数增长(2s 基数,×2 倍增,上限 30s)。截断最多尝试 20 轮。
Claude Code:五级压缩层次体系
Claude Code 的上下文压缩是我们分析的三个系统中最精细的。它有五个独立的压缩机制,按粒度从小到大排列:
Level 1:Snip(内容裁剪)
最轻量的操作。对单条工具输出进行内容裁剪——移除不必要的细节,保留结构性信息。通常在工具输出时即时执行。
Level 2:Microcompact(微压缩,530 行)
microCompact.ts 是一种"擦除"操作:对已经处理过的工具输出执行 content-clear,只保留最近的 N 条完整消息。有两种触发模式:
- 计数触发:当消息数超过阈值时,清除早期工具输出的具体内容
- 时间触发:当距上一条消息的间隔超过阈值时触发(此时 API 端的 Prompt Cache 已过期,保留完整内容没有缓存收益)
Microcompact 的精妙之处在于它不改变消息结构——消息仍在那里,只是内容被清空了。这避免了模型因消息消失而产生困惑。
Level 3:Context Collapse(上下文折叠)
对对话中的一段连续消息进行摘要,将多轮对话折叠为一条摘要消息。比 Microcompact 更激进,但仍然保留对话的因果链。
Level 4:Autocompact(自动压缩,351 行)
autoCompact.ts 是 Claude Code 的主力压缩机制。当上下文使用率超过阈值时自动触发,对整个对话历史进行摘要压缩。它会保留最近的消息原文,但将早期对话压缩为结构化摘要。
Level 5:Session Memory Compact(会话记忆压缩,630 行)
这是最激进的压缩级别。sessionMemoryCompact.ts 不仅压缩当前对话,还会将关键信息提取到 Memory 系统中持久化存储。这意味着即使整个会话被重置,模型仍然可以通过 Memory 恢复关键知识。
Opencode:委托式
Opencode 的原生压缩能力相对简单——它提供了 session.summarize() API,实现基本的对话摘要压缩。更精细的控制权交给插件层,OmO 正是利用这个 API 构建了上述的三阶段防御体系。
设计哲学对比
| 维度 | OmO | Claude Code | Opencode |
|---|---|---|---|
| 压缩层次 | 3 阶段(预警/预防/恢复) | 5 级精细层次 | 1 级(摘要) |
| 触发方式 | 阈值驱动(70%/78%/溢出) | 多信号(计数/时间/使用率/溢出) | 插件触发 |
| 最小操作 | 工具输出截断 | 单条内容擦除(Microcompact) | 全对话摘要 |
| 信息保留 | 去重 + 分级截断 | 内容擦除但保留结构 | 摘要替代 |
| 持久化恢复 | 无(压缩后信息丢失) | Session Memory → 持久 Memory | 无 |
| 代码规模 | ~2400 行 | ~3500+ 行 | ~100 行 |
两个系统的核心差异在于压缩粒度:OmO 的最小操作单位是"截断一条工具输出",而 Claude Code 可以做到"擦除一条消息的内容但保留其骨架"。这让 Claude Code 能更平滑地降级——模型看到的对话结构没有断裂,只是细节逐渐模糊。
9.4 旁路通信机制
LLM 对话的标准通道是 user/assistant 消息交替。但在复杂编排场景中,系统需要在"正常对话流"之外向模型注入信息——我们称之为"旁路通信"。
OmO:System Directive + System Reminder
OmO 使用两种旁路注入方式:
System Directive:格式化的系统指令,通过 [SYSTEM DIRECTIVE: OH-MY-OPENCODE - {TYPE}] 前缀标识。定义了 8 种类型:TODO_CONTINUATION(续做提醒)、RALPH_LOOP(循环控制)、BOULDER_CONTINUATION(Boulder 延续)、DELEGATION_REQUIRED(委派要求)等。这些指令被注入到工具输出或消息中,引导模型行为。
System Reminder(<system-reminder>):XML 标签包裹的提醒信息,注入到工具输出末尾。典型场景包括:
- 后台任务完成通知("任务 X 已完成,使用 background_output 获取结果")
- Agent 可用性提醒("你有 explore/librarian Agent 可用")
- Category + Skill 委派提醒
- AGENTS.md 文件内容注入
关键设计:keywordDetector 和其他 Hook 会主动识别并跳过 System Directive/Reminder,避免系统自己的注入触发模式检测(例如防止系统提醒中的关键词误触发 ultrawork 模式)。
Claude Code:Attachment Message
Claude Code 使用 attachments.ts(约 3900 行,系统中最大的单文件之一)管理旁路信息。Attachment 是一种特殊的消息类型(type: 'attachment'),插入到对话流中但不计入标准 user/assistant 轮次。
已知的 Attachment 类型包括:
relevant_memories:从 Memory 系统检索的相关记忆hook_additional_context:Hook 注入的附加上下文agent_listing_delta:Agent 列表变更通知(从工具描述中抽出,避免缓存失效)file_context:文件内容注入- CLAUDE.md 指令内容
Attachment 的一个精巧优化:Agent 列表原先嵌在 AgentTool 的描述中,但描述变化会导致 Prompt Cache 失效(约占 10.2% 的缓存创建 Token)。将其抽为 Attachment Message 后,Agent 列表的变化不再触发整个工具 Schema 的缓存重建。
Opencode:Hook 注入
Opencode 通过 chat.message 和 tool.execute.after Hook 允许插件修改消息和工具输出。这是一种通用的旁路机制——OmO 的所有旁路通信最终都通过这些 Hook 实现。
对比
| 维度 | OmO | Claude Code | Opencode |
|---|---|---|---|
| 旁路格式 | 文本标记(Directive + XML Tag) | 独立消息类型(Attachment) | Hook 修改 |
| 类型化 | 8 种 Directive 类型 | 多种 Attachment 类型 | 无类型 |
| 缓存友好 | 不考虑缓存影响 | 主动优化缓存失效 | 不涉及 |
| 过滤机制 | isSystemDirective() 跳过检测 | type === 'attachment' 结构化区分 | 无 |
9.5 持久化知识系统
上下文窗口是瞬态的——会话结束就消失。持久化知识系统让模型能跨会话积累和利用项目知识。
OmO:AGENTS.md 层级知识库
OmO 使用 AGENTS.md 文件构建分层知识体系。每个目录下可以放置一个 AGENTS.md 文件,描述该目录的职责、关键文件、约定和反模式。
注入机制:directory-agents-injector Hook 在工具读取或操作文件时,自动检查文件所在目录是否有 AGENTS.md,如果有则将其内容以 <system-reminder> 标签注入到工具输出中。注入是幂等的——已注入过的路径不会重复注入(通过 session-injected-paths 存储跟踪)。
生成机制:OmO 提供 /init-deep 命令,派出多个 Explore Agent 并行扫描项目结构,然后根据发现自动生成各目录的 AGENTS.md。一个典型的 OmO 项目可能有 39+ 个 AGENTS.md 文件形成完整的知识层级。
设计权衡:AGENTS.md 的优势是版本可控(纳入 Git)且人可编辑。劣势是维护成本高——代码变化后 AGENTS.md 可能过时,且注入时机依赖于工具调用路径,不保证模型总能看到。
Claude Code:CLAUDE.md + Memory 双系统
Claude Code 有两套互补的持久化知识机制:
CLAUDE.md(项目指令文件):类似 AGENTS.md,但更简单——通常只有项目根目录的一个文件,包含编码规范、项目约定、常用命令等。CLAUDE.md 的内容在每次会话开始时加载到系统提示中,不依赖工具调用触发。
Memory 系统(memdir/,8 文件):这是 Claude Code 独有的自动知识管理系统。Memory 分为多个类型:
- 用户反馈记忆:记录用户的工作偏好和纠正("不要用
any类型"、"用 snake_case 命名") - 团队记忆:通过
teamMemPaths.ts管理团队级别的共享知识 - 自动记忆:Session Memory Compact(第 5 级压缩)过程中自动提取的关键信息
Memory 的读取通过 findRelevantMemories.ts 实现——它使用一个专用的 LLM 调用来判断哪些记忆与当前查询相关(最多返回 5 条),然后作为 Attachment 注入到对话中。
Memory 系统内置了时效性验证要求:记忆记录可能过时,系统提示中明确指示模型"在基于记忆行动前,先验证当前状态"。
Opencode:无原生持久化
Opencode 本身不提供项目知识持久化机制。它支持读取配置文件中的自定义指令,但没有层级知识库或自动记忆系统。这部分能力完全留给插件实现。
对比
| 维度 | OmO (AGENTS.md) | Claude Code (CLAUDE.md + Memory) | Opencode |
|---|---|---|---|
| 知识格式 | 分层 Markdown(per-directory) | 单文件指令 + 结构化 Memory | 无 |
| 加载时机 | 工具调用时按需注入 | 会话开始时 + 按需检索 | N/A |
| 自动生成 | /init-deep 批量生成 | Session Memory Compact 自动提取 | 无 |
| 版本控制 | Git 跟踪 | CLAUDE.md 可 Git,Memory 不跟踪 | N/A |
| 跨会话 | 是(文件持久) | 是(Memory 持久) | 否 |
| 团队共享 | 通过 Git 共享 | 专用团队 Memory 路径 | 无 |
| 时效性 | 人工维护 | 系统级验证提示 | N/A |
9.6 模型特定适配
不同 LLM 有不同的"偏好"——相同的指令用不同的表述方式,遵循度差异显著。
OmO:多模型 Prompt 变体
OmO 为核心 Agent(Sisyphus)维护了三套 Prompt 变体:
- default.ts(Claude 及通用模型):使用 XML 标签(
<Behavior_Instructions>、<Constraints>)组织指令,层次分明,适合 Claude 的指令跟随特性 - gemini.ts:针对 Gemini 的"激进倾向"添加纠正覆盖层——强制工具使用、强化委派要求、补充验证覆盖。Gemini 倾向于直接回答而不使用工具,因此需要额外的强制措施
- gpt-5-4.ts:原生 GPT-5.4 Prompt,使用块结构化(block-structured)指导风格。GPT 模型对原则驱动的 Prompt 响应更好——简洁原则 + XML 标签结构 + 明确决策标准
dynamic-agent-prompt-builder.ts 在运行时检测当前模型,选择对应的 Prompt 变体组装最终系统提示。这种设计让 OmO 能在多个 LLM Provider 之间切换而不损失编排质量。
Claude Code:单模型优化
Claude Code 仅针对 Claude 模型优化(它是 Anthropic 的产品,这很自然)。所有 Prompt 都假设底层模型是 Claude,不需要模型适配层。这带来了更简洁的 Prompt——不需要为不同模型的怪癖添加纠正措施。
Opencode:模型无关
Opencode 的 Prompt 层不做模型特定适配。它支持多个 Provider,但使用统一的 Prompt 格式。模型适配留给用户或插件处理。
对比
| 维度 | OmO | Claude Code | Opencode |
|---|---|---|---|
| 支持模型数 | 多模型(Claude/GPT/Gemini) | 仅 Claude | 多模型 |
| Prompt 变体 | 3 套(default/gemini/gpt) | 1 套 | 1 套 |
| 适配策略 | 运行时检测 + 变体选择 | 无需适配 | 无适配 |
| 维护成本 | 高(每套需独立维护) | 低(单一目标) | 低 |
9.7 小结
上下文工程是 LLM 应用中最考验工程功力的领域。通过本章的三系统对比,我们可以归纳出几个关键洞察:
复杂度与控制粒度成正比:Claude Code 的 5 级压缩层次比 OmO 的 3 阶段防御更精细,但代码量也多出 50%+。Opencode 最简洁,但也最依赖插件补充。
旁路通信是编排的基础设施:三个系统都需要在标准对话流之外注入信息,但方式从简单的文本标记(OmO)到结构化消息类型(Claude Code)不等。Claude Code 的 Attachment 方案在缓存友好性上明显优于文本注入。
持久化知识是跨会话连续性的关键:OmO 的 AGENTS.md(人工维护 + Git 跟踪)和 Claude Code 的 Memory(自动提取 + 结构化存储)代表了两种截然不同的哲学——一个信任人类维护,一个信任 AI 自我管理。
多模型支持是一把双刃剑:OmO 的模型适配让它能跨 Provider 运行,但维护 3 套 Prompt 变体的工程成本不容忽视。Claude Code 的单模型策略则让它能在 Prompt 质量上做到极致优化。