Agent Engineering 101|10|Compaction
Compaction 不是把历史随便删短,而是在长会话中用 summary entry 替代旧上下文,同时保留最近工作状态。
核心结论
配套代码仓库: github.com/llm-101/mini-agent-harness
读完本文,你应该能回答
- Compaction 为什么不是简单删除旧消息?
- summary entry 如何接入 session tree?
- keepLast 和 threshold 分别控制什么?
- 压缩后下一轮模型如何继续当前任务?
本篇在系列中的位置
- 上一篇:09 Context Builder 负责构造当前工作集。
- 本篇:本文处理长会话里的预算压力:如何压缩而不丢任务连续性。
- 下一篇:11 Skills 会把可复用任务知识作为运行时资源注入上下文。
贯穿例子
本系列会反复使用同一个任务来连接各章:
用户说:“帮我修复这个 repo 里的 failing tests。”
在这个任务里,Compaction 在会话变长后保留“目标、已读文件、失败原因、已尝试修改、还没验证的点”,把旧过程折成 summary,再接上最近几轮。读者要关注的是:压缩不是删短历史,而是让任务还能继续。
- Compaction 是把旧消息折叠成 summary,并把 summary 作为 session tree 的边界节点。未来上下文从 summary 开始,再接上最近消息。
- 上下文窗口有限,Agent 的会话却可能很长。没有 compaction,系统要么超出 token 预算,要么粗暴截断并丢失任务连续性。
- 在
mini-agent-harness中,Compaction 被接到 session tree 上,是为了让压缩后的会话仍然能沿着同一条任务线继续。
定义
Compaction 是把旧消息折叠成 summary,并把 summary 作为 session tree 的边界节点。未来上下文从 summary 开始,再接上最近消息。
为什么要单独看这一层?
长会话不会因为上下文窗口有限而自动变短。Compaction 的价值,是把旧过程压成可继续工作的状态,而不是让系统在截断和爆窗之间二选一。

边界
这一层的职责可以拆成几个稳定部分:
- Token Estimate:用 JSON 字符串长度估算 token
- Threshold:超过 compactionThreshold 才触发
- keepLast:保留最近若干消息
- SummaryEntry:旧消息摘要成为树节点
- Re-parent:first kept message 指向 summary
- Event:向产品层发出 compaction
这一层的边界可以用一个问题检验:如果它的内部实现变化,模型适配、工具执行、状态存储和产品外壳是否都不需要跟着重写?如果答案是否定的,说明这个边界还没有真正收束变化。
代码锚点
本篇主要对应这些模块:
src/compaction/compaction.tssrc/session/jsonl-session-store.tssrc/session/session-tree.tssrc/ai/tokenizer.ts
阅读代码时建议先看类型,再看运行路径。
类型定义告诉你这一层暴露什么 contract;运行路径告诉你这个 contract 在 Agent 执行中何时被消费、何时被写回、何时被产品层看见。
运行流程

一次典型执行可以概括为:
- Estimate:shouldCompact
- Summarize:compactMessages
- Insert Summary:createSummaryEntry
- Re-parent:firstKept.parentId
- Continue:messagesForActiveBranch
- Continuity:保留任务上下文
- Budget:控制窗口压力
- Recoverability:summary 可持久化
这里最容易被忽略的是“中间态”。生产级 Agent 不是只关心最终答案;它还要在运行过程中展示进度、捕获错误、记录 usage、允许取消,并把可恢复状态写回 session。
读者抓手:压缩前后发生了什么
| 阶段 | 历史形态 | 风险 | Harness 处理 |
|---|---|---|---|
| 压缩前 | 很长的 message branch | 超出 token 预算 | 估算 token,判断 threshold |
| 选择保留 | 最近几轮仍在工作 | 丢掉会破坏连续性 | keepLast 保留最新上下文 |
| 生成摘要 | 较旧历史太长 | 粗暴截断会丢决策 | summary 记录目标、已做、未做、关键发现 |
| 接回树上 | 新旧上下文断开 | resume 时找不到来源 | summary entry 成为 first kept message 的 parent |
| 继续执行 | 上下文变短 | 模型忘记任务状态 | summary + recent messages 组成新工作集 |
好的 compaction 不是“更短”,而是“短到能继续工作”。
可迁移伪实现:会话压缩
下面的伪代码是机制抽象,不对应真实 API 或文件结构。它只用来说明这一层的控制点:
if (shouldCompact(messages, threshold)) {
const { summary } = compactMessages(messages, keepLast);
const summaryEntry = createSummaryEntry(summary, firstKept.id, firstKept.parentId);
firstKept.parentId = summaryEntry.id;
yield { type: "compaction", summary };
}
这个草图的价值在于说明控制点,而不是提供可复制的库代码。
真正的工程实现还要处理错误、取消、并发、token 预算、日志、权限、序列化和 provider 差异。
工程原则
将这一层从 Agent 系统中拆出来,通常带来四个直接收益。
第一,可替换。
外部系统、模型 provider、工具集合或产品外壳变化时,核心运行时不必整体重写。
第二,可观测。
边界清晰后,事件、trace、usage、错误和状态迁移都有稳定落点。
第三,可恢复。
只要状态写入 session,运行时就可以在进程重启、工具失败或长任务中断后继续推理。
第四,可治理。
权限、脱敏、审批、路径保护和执行策略可以放在稳定 hook 或 runtime boundary 上,而不是只靠 prompt 约束。
和 Agent Harness 的关系
Agent = Model + Harness 这个公式的重点,不是把模型之外的所有东西都称为“工程杂活”。
相反,它提示我们:模型之外存在一套必须被设计的运行时系统。
Compaction 就是这套系统中的一个切面。它不替代模型能力,也不替代产品体验;它让模型能力可以被组织成可执行、可观察、可恢复、可治理的任务流程。
小结
Compaction 的核心价值,是把一类容易扩散的复杂性收束到明确边界中。
在 mini-agent-harness 中,这个边界被刻意写得较小,方便阅读和教学。但它对应的问题并不小:只要一个 Agent 要长期运行、调用工具、管理上下文、支持 UI、保存状态并处理失败,这个边界就会出现。
下一步可以继续沿着系列计划,把这些边界组合成完整 Agent Harness:模型边界、工具边界、状态边界、上下文边界、扩展边界和产品外壳边界。