第四章 上下文外无物

抽象边界把复杂性收束在工具内部,让当前步骤不必从零展开。但被收束的中间结果如果只活在这一轮里,下一步就接不上:计划带不到下一步,失败记录影响不到后续选择,已确认的事实回不到当前工作面。边界于是只能局部成立,撑不住一个持续推进的 Agent。

真正要回答的不是“模型能不能记得更多”,而是“系统怎样把先前形成的中间结构外置为状态,并在下一轮重新装配成当前上下文”。一个最小运行时骨架就能把这件事说清楚:

context = build(memory, task, environment)
output = llm(context)
memory = update(memory, output, environment)

只看模型这一层,更短:

context -> llm -> output

剩下的全部都在这个箭头之外。LLM 不保存跨轮状态,它只消费 context,吐出 output。连续运行靠的不是模型脑中持续存在的记忆,而是系统在模型之外保留 memory,每一轮从中提取、裁剪、拼接出新的 contextoutput 结束之后,再决定哪些结果值得写回 memoryLLM 是无状态的,Agent 的连续性来自外置状态的读写循环。

更锋利地说:对当前这一轮模型来说,文本之外无物。 环境存在,状态存在,工具结果存在;但只要它们没有进入当前上下文,对这一轮模型就等于不存在。

这个判断会推出一个反直觉结论:模型可以被替换。前几十轮看似一直在和模型 A 对话,但只要状态保存完整,下一轮的 context 可以原样喂给模型 B。对人来说这几乎不可思议,因为人的连续性和主体强绑定;对 Agent 系统而言,可迁移的从来不是模型脑中的记忆,而是模型之外那套可重建的上下文与状态。

但状态外置只降低对单一模型的绑定成本,不自动消灭迁移成本。真正难迁移的是模型外那层运行时契约:状态格式是否通用,摘要有没有失真,恢复边界稳不稳定,拼接逻辑能不能在另一家模型上继续成立。同一份 context 喂给不同模型,推理习惯、遵循程度和 tool use 风格仍然可能明显不同。

怎么构建这层运行时

接受这个最小骨架,很多误解会消失。系统真正要解决的不是“怎样让模型一直记得过去”,而是“怎样让下一轮还能在正确的位置继续”。前者容易把人带向“记忆越多 Agent 越强”的愿望;后者逼着系统回答更具体的问题:上一轮留下了什么,哪些必须保留,哪些可以压缩,哪些只是噪音,下一轮看见的最小条件是什么。memory 的价值不在于存得多,而在于能不能支撑下一轮重建出正确的 context

所以 memory 更像运行时状态仓,而不是泛泛的长期记忆。它里面可以有历史消息、摘要、计划、待办、失败记录、环境观测、分支结论和用户偏好,但这些东西不会自动变成推理能力。只有当系统从中选出一部分,按顺序和角色重新组织进 context,它们才真正进入这一轮计算。memory 是库存,context 才是这一轮真正开上生产线的材料。

这也解释了为什么“上下文窗口变大”不能直接解决连续性问题。窗口变大,只是让系统有机会一次塞进更多内容;放什么、先放什么、哪些原样保留、哪些先摘要、哪些只在事件后注入,这些判断一个都没有消失。系统如果不会取舍,更大的窗口只会带来更大的噪音。很多失败并不是模型不懂,而是关键状态没有进入 context,或被不相关信息稀释了。

“工作内存”就是那一小层被选中、并真正进入 context 的结构。它服务的不是回忆,而是当前行动:当前计划要保留,因为它决定下一步做什么;最近一次失败要保留,因为它决定哪些路径不要再试;恢复提示要保留,因为它决定从哪里继续。工作内存不是历史副本,而是下一步动作成立的最小现场。

要把这层做出来,至少要回答四个问题:memory 里放什么、build 怎么选、update 怎么写、边界画在哪里。

memory 里至少要分清三类状态。稳定事实 是规则、偏好、长期约束,变化慢,适合在多轮中重复使用;中间结果 是刚读过的文件、最近的工具结果、当前计划、失败记录,变化快但和眼前任务强相关;压缩经验 是摘要、分支总结、恢复点,不是原始事实,而是为了以后还能继续而做的二次表达。三类不分开,后面的拼接策略很快会乱。

build 不是一个简单检索函数,而是一套选择机制:哪些状态常驻,哪些按需拉取,哪些只在事件触发时注入,哪些必须先压缩才能进入 context。它决定的不是“系统知道多少”,而是“模型这一轮能看见什么”。Agent 最核心的工程能力之一,就是把外置状态收束成一份足够短、又足够正确的 context

update 同样关键。许多系统只设计了读,没有认真设计写,连续性很快退化。output 不天然等于新状态:模型说“我已经完成”,不代表任务真的完成;模型提出一个计划,不代表它值得长期保留;模型刚犯过一次错,如果系统没有写回合适位置,下一轮很可能重犯。真正的 update 要同时处理三件事:写回已确认的事实,保留仍要继续用的中间结构,及时丢掉不再需要的局部状态。

边界决定写回的去向。不是所有状态都该进入下一轮,也不是所有状态都该永久保留。边界不清楚,系统容易走向两个坏结果:要么把全部历史背在身上,context 越来越脏;要么过度依赖摘要和检索,关键局部状态被提前丢掉。一个可运行的 Agent 必须区分当前轮、当前任务、整个 session 和跨 session 长期状态。边界清楚,恢复、压缩、分叉和迁移才有稳定基础。

真实系统中的两条路线

落到真实系统里,差异很快显现。所有系统都承认 memory -> context -> llm -> output -> memory 这条闭环,但复杂度落点不同:有的在解决线性会话如何可恢复地续跑,有的在解决非线性探索如何保住分支经验。把项目放回这个问题里,才能看清它们真正设计的是什么。

Claude Code 是前一种代表。它的重点不是做一个“会记忆”的聊天框,而是把当前会话做成一种可恢复的运行时。最值得注意的不是单独的 /resume 命令,而是围绕它长出来的状态组织方式:会话不是一次性重写的大对象,而是持续追加的事实流;关键元数据周期性回写到尾部,恢复时不必全量扫描;真正恢复时也不是把旧消息生硬重放,而是先修复中断状态,再补上 continuation 这类续跑信息。

进入当前轮的,也不是“恢复后的 transcript”本身,而是几层不同来源的消息重新装配后的结果:受保护的规则记忆放在前面,恢复后的主消息链和尾部元数据提供会话事实,子任务通知、提醒、工具结果和压缩后的历史切片再按时机插入当前轮。当前 context 不是把历史原样喂一次模型,而是在一个已恢复、已修补、已减载的会话状态上重新装配出来的工作面。

这一支实现画成图,大致是这样:

当前工作面

减载与修复链

持久化层

Transcript

Tail Metadata

Recovery

Repaired Chain

Compact Chain

Instruction

Notifications

Tool Results

Current Context

LLM

关键不在“有很多模块”,而在 context 不直接来自历史,而是来自一条恢复和减载链。Claude Code 在压缩这件事上的特点也不是“会压缩”,而是把压缩做成由轻到重的减载链:先削局部肥厚段,再压旧工具结果,再折叠已完成子链,最后才进入更重的 compact,必要时还要做反应式恢复压缩。这背后的判断是:当前轮最怕的不是历史不完整,而是噪音过多、结构过肥、恢复点不清。 工程复杂度被投在“怎样让当前轮继续可用”上,而不是“怎样把所有过去都记住”上。

pi-mono 走的是另一条路。它真正有意思的地方不是 /fork/tree 这些交互,而是直接否定了一个更深的假设:memory 不一定是线性的。线性 transcript 里,回退通常意味着删除、覆盖,或把失败历史继续拖在主线上;在 pi-mono 里,会话文件本身就是一棵 append-only 状态树。每个节点通过 idparentId 连到上一状态,新的探索不是覆盖旧历史,而是在旧节点上长出新分支;当前会话“活”在哪个状态上,由当前 leaf 决定。回退因此不再是擦掉过去,而是把指针切回旧节点,从那里继续生长。

这棵树不只是展示结构。真正进入模型当前轮的 context,是运行时沿当前 leaf 一路回溯,再由 buildSessionContext 这类过程重建出来的有效路径。树是真正的工作内存骨架:切分支、切会话、恢复续跑、压缩减载,最后都回到同一个问题——当前叶子路径怎样被装配成这一轮上下文。

只有树还不够。回到旧节点,系统仍然可能失忆,不知道另一条分支为什么失败。pi-mono 的关键一步,是把失败经验也做成节点。BranchSummaryEntry 的价值不在于“多了一条总结消息”,而在于把另一条时间线里的失败教训变成当前分支可消费的状态。系统保留的不是完整旧历史,而是对下一步仍然起作用的那条结论。这正是 memory 的价值:不保存一切,而是为下一轮构造有用的 context

这条路线画成图,大致是这样:

探索分支

主状态树

提取教训

Root

Old State

Branch Point

Branch Summary

Compaction

Current Leaf

Failed Path 1

Failed Path 2

Failed Path 3

Current Context

LLM

第二个关键点,是压缩也发生在树里,不是外挂到别处。CompactionEntry 的意义不是“生成了一个摘要”,而是把摘要和压缩边界直接内联进主状态树,并显式保留“从哪里开始仍然算当前历史”的切点。系统回溯上下文时,不是模糊地知道“前面有段历史被压了”,而是明确知道从哪个保留节点继续接上。压缩第一次不再只是 token 优化,而成为可恢复边界的定义。

由此,pi-mono 给出了一个更完整的判断:记忆不只有“原样保留”和“彻底删除”两种命运。除了完整历史,还可以把离开的支路蒸馏成 branch summary,把过长的主路径折叠成 compaction 边界,再把这两类压缩结果重新放回状态树。对运行时来说,这些不是注释,而是下一轮重建工作面的正式输入。代价是树结构和节点协议会更复杂;收益也很直接:压缩、分叉、回退、经验传递发生在同一套状态结构里,不需要再维护一套平行记忆系统。

回头看,项目差异就清楚了。Claude Code 这类系统强调可恢复会话运行时,复杂度落在恢复、续跑、压缩和受保护规则记忆的装配上;pi-mono 这类系统强调非线性探索,复杂度落在状态形状、当前叶子路径重建、分支经验传递和内联压缩上。表面上都在做 memory/context management,真正的区别是:前者努力让线性会话持续可运行,后者努力让非线性探索持续可演化。

成熟系统几乎都会把状态分层,差别只在分层位置和主矛盾。有的强调规则记忆、会话事实、压缩摘要和当前工作面的分层组装;有的强调热状态、冷状态和检索记忆的切分;还有的把工作记忆做成受保护上下文,强行固定在每轮请求前面。项目名不是关键,关键是系统把哪一层当成了 memory,又把哪一层真正送进了这一轮 context。这个问题回答不清,所谓“长期记忆”“恢复能力”“分叉能力”就只能停留在功能名词上。

Context over Control

把连续性外置,并不意味着问题被解决了,只是被放到了一个更可工程化的位置。收益很清楚:模型可替换,会话可恢复,历史可压缩,探索可分叉,系统能在多轮之间维持基本连续性。代价也很直接:系统最脆弱的地方不再只是模型不够聪明,而是 context 的组装可能出错。

关键状态没被放进去,或顺序不对,模型就会在错误现场里做看似合理的推理。为了让历史变短,系统不得不压缩,但压缩天然丢信息;丢掉的若恰好是后面需要的约束,恢复出来的就不再是原来的任务。状态边界也可能污染,一个分支里的失败教训被错误带到另一个分支,系统就会出现不必要的保守。

系统复杂度由此从“怎么写控制流”转移到“怎么管理状态”。这本质上是一场 Context over Control 的范式转移。

传统软件工程习惯的是 Control over Context:系统走向由严格的 if-else、循环和状态机决定,数据只是被动在管道里流动。面对具备推理能力的 LLM,这套硬编码控制流既框不住模型的判断,也跟不上任务的变化。现代管理学里有个对应的说法,“Context, not Control”:不要微观管理聪明人,给他清晰的目标和现场,让他自己决定怎么做。Agent 把这个类比推到了更极端的位置——你面对的可能不是普通聪明人,而是在某些维度上已经超过你的执行体。对这样的对象,微观控制不只是低效,而是根本无从下手:你没有能力替它写好每一步,唯一能做的就是把最准确的 memory、最清晰的约束和最干净的现场组装成 context,再把判断和路由交还给它。

于是系统对 Agent 的“控制”,实质上都通过“提供什么上下文”来实现。Agent 走偏了,解决办法通常不是再加一段 if-else,而是修复 context 里缺失的约束,或清理多余的噪音。

context 本身也会劣化。Agent 会随上下文污染、历史拖累或局部失败的积累而变钝;编排者不必带着这个已经劣化的工作面继续前进,可以借助 session 的回退、裁剪、分叉与重组,把系统重新拉回一个更好的起点。这恰恰说明:Agent 本身并不是一个持续存在、不断累积的主体,而更像被反复唤起、反复结束的无状态执行体;真正持续存在的是 session 那条可恢复、可改写、可重新进入的状态时间线。上一轮不是消失的历史,而是可以被恢复、裁剪、分叉、压缩、回放和重组的状态切片。上层编排器管理的也不只是“这个 Agent 现在要做什么”,而是“它从哪段过去继续”“哪段失败要带到当前轮”“哪段历史要折叠成摘要”“同一个 Agent 是否要从另一个时间分支重新开始”。session 不只是状态存储,更像 Agent 的时间接口;orchestration 编排的不只是步骤,还包括时间。

本章真正想表达的是一句话:Agent 的连续性不是模型能力的自然延伸,而是外置状态系统的工程产物。 一个 Agent 做得深不深,往往不取决于它接了多少模型、多少工具,而取决于它是否真的建立了 memory -> context -> llm -> output -> memory 这条闭环,以及边界是否清楚、写回是否可靠、压缩是否可控。

连续性还不是终点。状态系统回答的是“这次发生了什么,以及下一轮怎样接着做”,解决的是不失忆、不掉线、不散架。但同类任务反复出现时,系统会撞上另一个问题:既然某些中间结构(特定的搜索路径、分析框架、验证流程)在多次会话里被证明有效,它们是不是应该被固定下来,变成一种不必每次从零重建的可复用协议?

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐