Skip to content

第九章 上下文工程(三系统对比)

← 返回目录 | 上一章: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 正是利用了这一点)。

对比总结

维度OpencodeOmOClaude 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() 启动,其上下文传递策略完全不同:

  1. 系统 Prompt 独立生成:每个 Agent 有自己的 getSystemPrompt(),而非继承父级的。getAgentSystemPrompt() 会调用 Agent 定义的 prompt 方法,再通过 enhanceSystemPromptWithEnvDetails() 追加环境信息。

  2. Fork 模式共享缓存:Fork 子代理继承父级的 Prompt Cache,因此 Prompt 是 指令式("做什么")而非 叙述式("情况是什么")。这大幅减少了重复上下文。

  3. Hook 注入附加上下文executeSubagentStartHooks() 在 Agent 启动时执行,收集 additionalContexts,以 hook_additional_context 类型的 Attachment Message 注入到对话中。

  4. 工具白名单过滤filterToolsForAgent() 根据 Agent 定义的 tools/disallowedTools 过滤可用工具集,内置的 Agent 和异步 Agent 还有额外的工具黑名单(ALL_AGENT_DISALLOWED_TOOLSASYNC_AGENT_ALLOWED_TOOLS)。

Opencode:透传式

Opencode 本身没有多 Agent 编排——它是单 Agent 架构。"信息传递"发生在插件层:Opencode 通过 chat.paramschat.message Hook 将控制权交给插件,由插件决定如何构造子 Agent 的上下文。

对比总结

维度OmOClaude CodeOpencode
传递方式结构化 6 段 Prompt独立系统 Prompt + Directive插件决定
上下文继承不继承(全新构造)Fork 可共享 Prompt CacheN/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 构建了上述的三阶段防御体系。

设计哲学对比

维度OmOClaude CodeOpencode
压缩层次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.messagetool.execute.after Hook 允许插件修改消息和工具输出。这是一种通用的旁路机制——OmO 的所有旁路通信最终都通过这些 Hook 实现。

对比

维度OmOClaude CodeOpencode
旁路格式文本标记(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 格式。模型适配留给用户或插件处理。

对比

维度OmOClaude CodeOpencode
支持模型数多模型(Claude/GPT/Gemini)仅 Claude多模型
Prompt 变体3 套(default/gemini/gpt)1 套1 套
适配策略运行时检测 + 变体选择无需适配无适配
维护成本高(每套需独立维护)低(单一目标)

9.7 小结

上下文工程是 LLM 应用中最考验工程功力的领域。通过本章的三系统对比,我们可以归纳出几个关键洞察:

  1. 复杂度与控制粒度成正比:Claude Code 的 5 级压缩层次比 OmO 的 3 阶段防御更精细,但代码量也多出 50%+。Opencode 最简洁,但也最依赖插件补充。

  2. 旁路通信是编排的基础设施:三个系统都需要在标准对话流之外注入信息,但方式从简单的文本标记(OmO)到结构化消息类型(Claude Code)不等。Claude Code 的 Attachment 方案在缓存友好性上明显优于文本注入。

  3. 持久化知识是跨会话连续性的关键:OmO 的 AGENTS.md(人工维护 + Git 跟踪)和 Claude Code 的 Memory(自动提取 + 结构化存储)代表了两种截然不同的哲学——一个信任人类维护,一个信任 AI 自我管理。

  4. 多模型支持是一把双刃剑:OmO 的模型适配让它能跨 Provider 运行,但维护 3 套 Prompt 变体的工程成本不容忽视。Claude Code 的单模型策略则让它能在 Prompt 质量上做到极致优化。


← 返回目录 | 上一章:Claude Code 独特设计与服务架构 | 下一章:工具系统完整解析 →