chapter 04 / 上下文管理层
上下文工程
上下文是稀缺资源:腐化、焦虑、压缩与缓存
第 1 章说过:模型是无状态的,所有「记忆」都是 Harness 每轮喂进去的。这一章讲的就是「喂什么」这门手艺——上下文工程。它可能是 Harness 各层中投入产出比最高的一层。
上下文是稀缺资源
上下文窗口再大也是有限的,而且并非越满越好。Anthropic 的工程实践把这一点说得很直白:上下文是需要策展(curate)的注意力预算——系统提示、工具定义、示例、历史消息、检索结果都在争夺同一份预算。
Effective context engineering for AI agents ↗
上下文是稀缺资源:如何在系统提示、工具、示例、检索之间策展有限的注意力预算——上下文工程是 Harness 的核心职责之一。
在循环模拟器里你已经见过:上下文只增不减。多轮文件读取、命令输出、日志打印天然膨胀,编码 Agent 尤其严重。不加管理的后果不是「变慢」,而是输出质量塌方。
两种典型的失败模式
Context Rot:注意力被塞爆
Context Rot(上下文腐烂):Agent 被文档结构引发的探索行为吸引,打开几十个无关文档、塞入几十万 token,输出反而变差。两种常见触发:
- 架构总览写太多 → Agent 想「更好地理解架构」,把相关文档全部展开;
- 「别做」清单太长(30–50 条无替代方案)→ Agent 逐条检查是否违规,翻出迁移脚本、兼容层等一堆不相关代码。
Context Anxiety:模型自己先怂了
Context Anxiety(上下文焦虑):模型感知自己接近窗口极限时提前收工——到第 8 个子任务开始跳过测试、简化实现、写 TODO 注释。不是任务变难了,而是上下文太长了。
有意思的是:原地压缩不能完全解决它,因为模型仍然感知到「这是段长对话」。更有效的是上下文重置——完全清空 + 结构化交接文件,让新 Agent 在干净状态下继续(这也是 Ralph Loop 的思路:文件系统在窗口之间提供连续性)。
Harness 的应对工具箱
- 上下文压缩:Clipping(限制单个工具输出的最大长度)+ Transcript Reduction(历史压成「近事浓、远事淡」的摘要)+ 去重(同一文件多次读取只留最近一次)。Claude Code 的
/compact就是手动触发的压缩。 - Prompt Prefix Caching:把稳定的前缀(系统提示、工具定义)缓存起来,每轮只为增量付费——这要求 Harness 组装上下文时保持前缀稳定,别随手往开头插东西。
- 工作记忆与完整会话分离:窗口里只放工作记忆,完整历史放在窗口之外的会话对象里(Session as Context Object),需要时检索回来。
- 长期记忆:跨会话的知识沉淀交给专门的记忆系统。openJiuwen 有独立的 agent-memory 仓库(
jiuwen_memory/memory_core/long_term_memory.py),提供抽取、存储、图谱化检索。
Sebastian Raschka 的判断值得贴在墙上:"看起来是模型能力问题,实际上大多是 context quality 问题。"
源码走读:ContextEngine
openJiuwen 把上下文管理拆成了独立引擎——注意它的职责清单里没有一条是「生成回答」:
source walkthrough
ContextEngine:上下文的创建、压缩与持久化
openJiuwen-ai/agent-core @ 1e3a5c7a3d · openjiuwen/core/context_engine/context_engine.py:25-322 · 提取于 2026-07-18
openJiuwen 把上下文管理从 Agent 中拆出来做成了独立引擎:处理器链负责窗口截断与压缩,上下文状态随会话持久化。三段代码分别展示引擎的职责边界、主动压缩的结果语义、以及状态存档。
- 段 1L25–39职责边界:注册处理器、创建隔离上下文、执行处理链
class ContextEngine: """ Manages the lifecycle and processing of conversational context. ContextEngine acts as the central entry-point for: 1. Registering and configuring message processors. 2. Creating isolated ModelContext instances tied to a session. 3. Applying processor chains to enforce window limits, compression, etc. Parameters ---------- config : ContextEngineConfig, optional Global engine settings (message/token limits, processor defaults). If omitted, a default configuration is used. """注意这个类的三条职责没有一条是「生成回答」——上下文管理是纯粹的资源管理问题:窗口限制、压缩策略都被抽象成可插拔的 processor 链。每个 ModelContext 与 session 绑定并相互隔离,这是防止「上下文污染」的结构性手段。
- 段 2L158–187主动压缩:busy / compressed / noop 三种结果
async def compress_context( self, context_id: str = "default_context_id", session: Session = None, *, session_id: str = None, processor_types: List[str] = None, **kwargs, ) -> str | dict[str, Any]: """ Actively run registered compression processors for an existing context. Returns: Compression result code: - ``"busy"``: passive compression is already in progress. - ``"compressed"``: active compression ran and changed context. - ``"noop"``: active compression ran but nothing changed, or no compression processor is registered. A successful compression is saved to the resolved session and committed before this method returns. Contexts without a session remain in-memory only. """对应 Claude Code 的 /compact:压缩既可以被动触发(接近窗口上限时),也可以主动调用。三种返回值把并发语义讲得很清楚——被动压缩正在跑时返回 busy 而不是叠加执行;压缩成功后立即持久化并 commit。「压缩」是 Harness 预防 Context Anxiety 的主动手段。
- 段 3L284–322状态存档:消息、窗口位置、token 统计一起保存
@_fw.emit_after(ContextEvents.CONTEXT_OFFLOADED, result_key="result") async def save_contexts(self, session: Session, context_ids: List[str] = None ): """ Batch-persist multiple contexts and their runtime states. Each context's messages, sliding-window position, token count and statistics are saved locally. """ if not session: context_engine_logger.warning( "Save context failed, session cannot be None", event_type=LogEventType.CONTEXT_SAVE, ) return session_id = session.get_session_id() states = dict() if context_ids is None: context_ids = [ context.context_id() for context_id, context in self._context_pool.items() if context.session_id() == session_id ] for context_id in context_ids: context_id = self._process_context_id(context_id) full_context_id = f"{session_id}_{context_id}" context = self._context_pool.get(full_context_id) if context is None or not hasattr(context, "save_state"): continue context_state = context.save_state() states[context_id] = context_state self._save_state_to_session(session, states) return states第 2 章循环里反复出现的 save_contexts 就是这里。存的不只是消息,还有滑动窗口位置和 token 统计——恢复时压缩策略能接着上次的状态算。装饰器 emit_after 发出 CONTEXT_OFFLOADED 事件,可观测性挂在事件总线上而不是散落在业务代码里。
回看第 2 章的 ReAct 循环:每次模型调用前 get_context_window(...) 组装窗口、每轮结束后 save_contexts(...) 存档——现在你知道这两个调用背后是整个引擎在工作。
本章能力在 Harness 中处于什么位置?
harness positioning
本章能力在 Harness 中处于什么位置?
上下文管理层坐在循环驱动层与模型内核之间:循环每转一圈,它决定「这一轮模型看到什么」。它与状态与恢复层紧密协作(上下文状态要能存档重载),与评估层互为镜像(压缩摘要的质量本身需要评估)。
本章概念清单 / 点击进入概念卡片
章末测验
chapter quiz
0/4 已答
Q1什么是 Context Rot(上下文腐烂)?
Q2Context Anxiety(上下文焦虑)的表现是?
Q3上下文压缩的两种最小可行策略是?
Q4openJiuwen ContextEngine 的 compress_context 返回 "busy" 表示什么?