本页讲的是上下文压缩——
HarnessAgent 用来把对话控制在模型 token 预算内的几种策略。它建立在上下文与 AgentState 所描述的无状态引擎和 AgentState 持久化基础之上。如果还没有读过那一页,建议先看——压缩操作的对象正是持久化链路保存和恢复的同一份 AgentState。两条链路如何配合:压缩在内存里更新 AgentState.contextMutable(),状态存储在 call 结束时把更新后的 AgentState 整体持久化存储。两条路径独立但按顺序执行——状态存储拿到的永远是压缩后的版本。HarnessAgent 内置了一整套压缩链路,默认是关的,按需 .compaction(...) 或 .toolResultEviction(...) 开启。
HarnessAgent 内置的几种策略
四套策略正交,可以任意组合,默认全部不开。
1. 对话摘要压缩 (CompactionMiddleware)
按消息条数或估算 token 触发,把对话前缀用一次 LLM 调用压成结构化摘要,保留尾部 N 条最近消息原文,然后把 [summary] + [recent tail] 写回 AgentState.contextMutable()。
SESSION INTENT / SUMMARY / ARTIFACTS / NEXT STEPS 四个小节,适合工程/编排类 agent。CompactionConfig 还支持 .model(...) 为压缩摘要指定独立模型(不设则用 agent 主模型)。完整字段表(triggerTokens、keepTokens、flushBeforeCompact、offloadBeforeCompact、model、TruncateArgsConfig)与摘要 prompt 模板在记忆文档里有详细列表,这里不重复。
2. 大工具结果卸载 (ToolResultEvictionMiddleware)
跟摘要压缩独立。当某条工具结果文本超过阈值(默认 80K 字符 ≈ 20K tokens),把全文写到工作区某个目录,上下文里只保留首尾各约 2K 字符 + 一个 read_file 路径提示符。agent 想看全文就自己 read_file。
read_file / write_file / edit_file / list_files / memory_* / session_search——这些工具要么自带分页、要么返回值很小。grep_files 和 glob_files 会强制限制结果条数,但仍可触发大结果卸载,作为单条结果异常大时的第二道保护。Shell execute 默认不排除,因为命令输出可能非常大。
详情见记忆 - 大工具结果卸载。
3. 上下文溢出兜底
如果模型直接返回context_length_exceeded / maximum context / token limit 等错误,HarnessAgent.recoverFromOverflow() 会强制走一次 triggerMessages=1 的极端压缩,然后自动重试一次。前提是构造 agent 时配了 .compaction(...),否则错误原样抛回上层。
这条兜底链路无需额外配置:只要 compaction 开了,溢出恢复就自动开。
4. 预压缩参数截断 (可选)
在 LLM 摘要前,先做一遍不走 LLM 的字符串截断——write_file、edit_file 这类工具的入参体量大但事后没人再看:
压缩与 Memory 的联动
CompactionConfig.flushBeforeCompact(默认 true)决定摘要发生前是否先把对话前缀里的事实抽取到长期记忆(Memory)中——这一步由 MemoryFlushMiddleware + MemoryFlushManager 完成,会读 <workspace>/MEMORY.md 与 memory/*.md,把新事实增量写进去。等会儿摘要丢掉前缀消息时,信息不会随之消失——agent 仍可以通过 memory_search / memory_get 工具回头查。
类似地,offloadBeforeCompact(默认 true)在摘要前把原始消息整段写到永不压缩的 *.log.jsonl,供 session_search 检索。
Memory 子系统的完整工作机制——双层结构、后台维护任务(归档、合并)、记忆工具——见 记忆 文档。压缩与 memory 是一对常常一起用的组件,但有各自独立的开关。
压缩不会触碰的内容
ConversationCompactor 只处理 AgentState.contextMutable() 里的对话消息列表。下面这些活在 AgentState 其他字段里,完全不会被摘要压缩波及:
- Plan Mode 状态(
AgentState.getPlanModeContext()):是否在 plan 阶段、当前计划文件路径。计划文件本身在工作区plans/下,生命周期由 Plan Mode 自己管理。详见 Plan Mode。 - 子 agent 后台任务(
task_id、状态、结果):住在<workspace>/agents/<parentAgentId>/tasks/<sessionId>.json里,由TaskRepository单独维护;主 agent 下一轮推理前通过 system reminder 反向注入完成结果,不进入对话消息流,所以摘要也无从压缩。详见 子 Agent - 异步任务的存储位置。 todo_write任务清单(AgentState.getTasksContext()):独立字段,跟着AgentState一起持久化,但不参与对话压缩。详见 Plan Mode - 与todo_write的协作。- 权限规则(
getPermissionContext()):独立字段,自带持久化。
.compaction(...) 而不用担心丢 plan / 丢未完成的后台 task。
用 agent 自己查历史会话
启用会话能力时(默认开),三个查询工具会自动注册,agent 自己就能调:session_list agentId="..."—— 列出某个 agent 的历史会话。session_history agentId="..." sessionId="..." lastN=20—— 看某次会话最近 N 条消息。session_search query="..." agentId="..."—— 在历史会话里关键词搜索。
<workspace>/agents/<agentId>/sessions/<sessionId>.log.jsonl),所以即使上下文已经被压缩成摘要,agent 也能查到原始消息。