Gajae-Code Compaction压缩机制详解:长会话如何不丢上下文?分支摘要原理剖析
【免费下载链接】gajae-codeGajae Code MVP项目地址: https://gitcode.com/gh_mirrors/ga/gajae-code
Gajae-Code 是一个开源的 AI 编程助手,长会话中上下文窗口会被对话与工具输出不断填满,一旦超出模型上下文上限,之前的工作进度就会"失忆"。Gajae-Code 通过Compaction 压缩与分支摘要(Branch Summary)两大机制解决这个问题:Compaction 把当前分支的旧历史改写为一份结构化摘要,分支摘要则在/tree导航时归档被放弃分支的上下文,两者都会持久化为会话条目,并在重建 LLM 输入时还原为上下文消息,让长会话永不丢上下文。
为什么长会话会"记不住"?
LLM 的每一轮请求都要携带完整历史:你让 AI 读文件、跑命令、改代码,read、search、bash的工具输出会持续累积。真实的会话统计曲线能直观看到这种增长——下图是各工具调用累计消耗的 token 量,read与search是绝对大头:
更麻烦的是,真实工作流往往横跨多个阶段。下图的"需求澄清 → 实施计划 → 执行验证"多阶段流程,每一步都会产生大量轮次:
当上下文超出窗口,模型直接报错;粗暴地删掉最旧消息又会打断因果链(比如孤立的工具结果)。Gajae-Code 的解法不是"删",而是"压缩 + 归档"。
Compaction 压缩:把旧历史改写成语义摘要
核心实现位于 compaction.ts,整体流程在 docs/compaction.md 中有完整描述。
压缩何时触发:4 种入口
- 手动压缩:输入
/compact [指令],立即对当前分支执行压缩,你还可以附带定制指令; - 溢出自动恢复:模型返回上下文溢出错误时,先尝试"上下文晋升"(切换到更大窗口的模型重试),不可用时才执行压缩,压缩成功后自动继续;
- 阈值维护:正常回合结束后,若上下文 token 数超过阈值(默认约为
min(上下文窗口 - max(15%, 保留token), 300000)),自动压缩; - 空闲维护:可选开启,空闲时用
reason: "idle"重写历史,且不会自动续跑。
压缩长什么样:切割点的"硬规则"
压缩不是截断,而是在历史中选一个切割点(cut point),把切割点之前的消息交给 LLM 生成摘要,切割点之后的"近期消息"原样保留(默认保留约 20000 token,配置项compaction.keepRecentTokens):
- 边界记为
firstKeptEntryId,之后的消息照常发给模型,之前的旧消息不再随请求发送; - 硬规则:永远不在工具结果(toolResult)处切割,避免产生孤立的因果链碎片;
- 切割点若落在某个回合中间(split turn),会生成两份摘要——历史摘要 + 当前回合前缀摘要,用分隔线合并存储;
- 第二次压缩时不再从零总结,而是基于上一份摘要增量更新(对应模板 compaction-update-summary.md)。
压缩完成后,模型实际看到的是:系统提示 + 一份摘要消息 + 切割点之后的近期消息——篇幅小得多,语义完整度几乎不降。
摘要里都有什么:不止"几句话"
摘要由专门的结构化提示词(compaction-summary.md)驱动,强制包含:目标、约束与偏好、进度(已完成/进行中/被阻塞)、关键决策、下一步行动、关键上下文,并要求精确保留文件路径、函数名、报错信息。除此之外,系统还会自动附加两类"确定性信息",不依赖总结模型自己猜:
- 文件活动标签:全程统计
read/write/edit工具调用,摘要尾部追加<read-files>与<modified-files>清单; - 在途状态注入:当前活跃目标、至多 5 个进行中的工作流技能(含阶段)、至多 10 条未完成 todo,被写进摘要请求的附加上下文。
这正是"长会话不丢上下文"的关键——工作进行到一半时压缩,模型能精确知道"做到哪了、还差什么"。
先修剪后压缩:工具输出裁剪(Pruning)
在压缩判断之前,系统会先做一轮工具输出裁剪(pruning.ts),很多场景下光靠它就不必动用摘要。默认策略非常保守:
- 保护最近 40000 token 的工具输出,最近 2 轮真实用户对话内的输出完全不动;
- 预期节省不足 20000 token 时直接放弃裁剪;
skill与read的结果默认受保护,仅当后续读取被证明覆盖了旧结果时才允许裁剪;- 被裁剪的输出替换为一行高信号摘要(退出码、报错行、路径提示);若会话 artifact 管理可用,完整输出会外溢为 artifact 文件——裁剪是可逆的,模型可以用
artifact://地址随时读回原文,而无需重跑工具。
下图是每次调用平均 token 的趋势(7 日滑动平均),可以看到单次read常在 1000+ token 量级,裁剪这类重复输出收益显著:
分支摘要:给"被放弃的分支"写归档备忘
Compaction 解决的是"时间轴太长",分支摘要解决的是"空间轴分叉"。Gajae-Code 的会话是一棵树:用/tree(见 docs/tree.md)回退到历史节点、换一条路线继续时,原来的分支就被"放弃"了。
当branchSummary.enabled开启时,导航流程会:
- 找出旧叶子到新叶子的公共祖先,收集被放弃路径上的全部条目(如 B、C、D 三条消息);
- 按预算
模型上下文窗口 - branchSummary.reserveTokens,从新到旧挑选要总结的消息(优先保留近期上下文); - 用 branch-summary.md 提示词生成摘要,同样附上读/改文件清单;
- 把摘要以
BranchSummaryEntry持久化到导航目标位置(实现见 branch-summarization.ts)。
之后无论从哪个分支重建上下文,被放弃分支的探索过程都会以一条摘要消息的形式回归——你回退重做时,AI 依然记得"方案 A 为什么失败",不用把走过的弯路再走一遍。整个过程支持按 Escape 取消。
快速上手:关键配置一览
相关默认值定义在 settings-schema.ts 中,概念与调优指南见 docs/concepts/context-compaction.md:
| 配置项 | 默认值 | 说明 |
|---|---|---|
compaction.enabled | true | 压缩总开关 |
compaction.strategy | context-full | 可选handoff(新开会话交接,见 docs/handoff-generation-pipeline.md)或off |
compaction.thresholdPercent | -1(哨兵值) | 自动压缩阈值百分比,gjc config set compaction.thresholdPercent 70即可提前压缩 |
compaction.keepRecentTokens | 20000 | 切割点之后原样保留的近期 token 数 |
compaction.autoContinue | true | 压缩后若检测到未完成工作才自动续跑,已完成则跳过 |
compaction.adaptive.enabled | false | 自适应压缩:长会话高频调用时自动下调阈值 |
branchSummary.enabled | false | 开启/tree导航时的分支摘要 |
对工具调用密集、会话动辄上十万 token 的长任务,可参考文档推荐的自适应起步配置(baseThresholdPercent: 75、aggression: 0.2),避免大上下文被反复整包重发。
扩展点:用 Hooks 定制压缩行为
压缩管线对扩展完全开放(hooks/types.ts):
session_before_compact:压缩前介入,可取消压缩或提供自定义摘要;session.compacting:覆盖摘要提示词、注入额外上下文、附加保留数据;session_compact:压缩完成后的通知事件;session_before_tree/session_tree:分支摘要的前置与后置钩子。
总结
Gajae-Code 用三层防线保证长会话"不丢上下文":工具输出裁剪先回收可逆的冗余输出,Compaction 压缩把旧历史固化为带进度、决策与文件清单的结构化摘要,分支摘要则在会话树分叉时给放弃的路线留下归档备忘。三者都以一等会话条目持久化,重建上下文时自动还原——这就是长会话可以无限延续、随时回退而不失忆的底层原理。更多实现细节可查阅 docs/compaction.md。
【免费下载链接】gajae-codeGajae Code MVP项目地址: https://gitcode.com/gh_mirrors/ga/gajae-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考