跳到主要内容

创建日期:2026-09-09 | 最近更新:2026-09-09 这是一篇设计架构文:承接篇3:把 Agent 的时序看穿的结论,把「智能体 = 消息序列的编排器」推进一步,回答「长期任务里上下文膨胀怎么办」。文中的数据结构/流程为规格草案,尚未在 ioi/mini-agent 落地实现;所有机制都从会计里借,都有可验收的标准。

结算式长期 Agent:把会计带进上下文设计

一句话:长期 Agent 的难点不是“让模型记住”,而是“让越滚越大的上下文有界、可信、可审计”。 会计早就把「如何在长期记录里不丢真、可追查、能结转」这套纪律固化成了流程。本文把它翻译成 Agent 架构:原始凭证 → 登账 → 结账前对账 → 结转期初余额,得到一种「外部只增账本 + 滚动有界窗口」的长期 Agent 形态。

0. 为什么需要它(问题定位)

篇3 得出过一个精确说法:Agent 的记忆 = 每次把历史再发一遍,上下文窗口是一张越来越长的“黑板”。于是任何想做长期任务(跨多天、几十个子任务、产物要可验证可复用)的 Agent,都会撞上同一个墙:

任务推进 → 上下文变长
→ token 成本线性涨
→ 模型对“埋在深处的关键事实”注意力稀释
→ 上下文触顶,被迫截断,早期结论丢失

传统的两个逃避姿势都有硬伤:

  • 整段截断:丢关键事实,任务“失忆”;
  • 无差别摘要:摘要由模型生成 = 又引入一份“可能”,还丢了原始证据,无法复核。

这篇的答案是把「结算/记账」的纪律引入:不是删上下文,而是把上下文“结算”掉——已验证的结论转成余额结转,原始凭证留在外部账本可随时对账。

1. 三层账本:凭证 → 账 → 表

会计的底层是三个分离的层次,Agent 照搬:

会计Agent 对应放哪特性
原始凭证发票/回单宿主工具的真实产物:写盘的文件、查询返回、测试/编译结果外部只增存储(文件/DB)不可改、可审计
账(Ledger)明细账事实表:每条已验证事实 + 指向凭证的引用外部结构文件追加式,可版本化
表(Statement)总账/报表上下文余额:本期要用的结论摘要 + 指针模型上下文每期有界、可结转

核心纪律:只有宿主能“登账”。 模型输出的任何话都只是“待审凭证”(claim);被宿主用确定性手段验证(文件真实存在、read_file 读回一致、测试通过)后才升级为“已入账事实”(fact)。这正是篇3「truth 只能由宿主盖章」的落地。

2. 整体架构

一个 Agent 实例的推进方式:要么在窗口内小步前进(普通多轮问答),要么把当前子任务“做账做平”后结账、开新窗口。上下文里永远只放两样:期初余额(已验证事实的摘要 + 指针)+ 最近若干轮的滚动工作窗口。

3. 数据规格(草案)

给关键实体定个形,方便之后敲实现(字段都是最小集):

// 凭证库:宿主工具产出的原件,只增不改,带哈希防篡改可对账
Artifact = { id, at, kind: 'file'|'query'|'test'|'tool_result',
ref: 'workspace/report.md', // 实际位置
hash: 'sha256:…', // 对账用:可重算比对
contentMeta: { size, lines } }

// 事实表:每一条入账事实,必须带 source 指针 → 可审计
Fact = { id, at, kind: 'verified'|'claim',
statement: '上海人口 2487 万(2026 快照)',
source: ['artifact:report.md#L12'], // ★ 必须指向凭证,可空=未入账
supersededBy: null, // 红字冲销:不物理删,追加标记
status: 'current'|'superseded' }

// 期/余额:一次结账的产物,就是下一期的“期初余额”
Period = { id, closedAt, windowFrom, windowTo,
openingBalanceRef: 'period:3', // 上一期结转而来
balance: [ Fact.id,], // 本期结转的事实(摘要+指针)
lossy: true } // 明示:这是有损摘要,证据在凭证库

三个不可违反的不变量(Invariant),对应 §5 的验收:

  1. 可审计:每条 balance 的 fact 都能沿 source 一路追到凭证库里的原件;
  2. 只增不回写:凭证与事实都追加式写,纠正 = 新增 supersededBy,不删旧记录;
  3. 有界:任何一个时刻,模型上下文里的余额 + 工作窗口大小都受预算约束,与总任务时长无关。

4. 生命周期:做账四步

以「跨三天的数据分析任务」为例,把一天的“账期”走一遍:

翻译成宿主该提供的最小接口(规格,非实现):

// settle API —— 规格草案
const ledger = await openLedger('workspace/ledger'); // 账期上下文
const art = await vault.store({ ref: 'avg.json', content }); // 1 留凭证
const ok = await verify(art, expected); // 2 宿主验证(读回/测试)
if (ok) await ledger.post({ statement, source: [art.id] }); // 3 登账
await ledger.reconcile(); // 4 对账:逐条复核凭证
const period = await ledger.close(); // 5 结账 → 生成期初余额
const ctx = period.opening(ctxBudget); // 6 开新窗口,只带余额+指针

5. 验收标准(写代码前先定“怎样算赢”)

#验收项判定方式
1有界性连续结算 N 个子任务后,ctx 的 token 数 ≤ 预算(与 N 无关);对话原稿不再整体进窗口
2可推进只给「期初余额 + 指针」开新任务,模型能续上(通过 read 工具读凭证取细节)
3可审计随机抽一条 balance 的 fact,沿 source 追到凭证并重算比对一致
4抗漂移故意篡改凭证库某原件 → 下一轮 reconcile 必须报对不上
5容错/冲销一条事实被新结果推翻 → 走 supersededBy,旧记录仍在、可回滚

没有 3/4,这套就退化成“高级摘要”——对账和审计,才是会计带来的、普通记忆方案给不了的东西。

6. 已知边界与折衷(别神话它)

  • 语义事实没有“借贷必相等”:会计有数值恒等式自动爆错账;文本事实没有天然勾稽,所以对账不能省、且要挑对象——关键事实强对账(读回+比对),次要事实弱对账(指针即可),否则对账本身会吃掉成本;
  • 摘要是有损的lossy: true 写在明处,凡要“精确”的都回到凭证,别指望摘要保真;
  • 结转时机 = 里程碑而非时间:一个子任务被验证完成才是天然账期边界(呼应篇3「以落盘/结果为准」);硬按时间结转容易在任务做到一半时把现场打碎;
  • 多 Agent / 并行共享账本:追加式 + 指针天然友好;写同一账本需乐观锁或“先落凭证再登账”的顺序约定;
  • 余额放 system 还是开头:期初余额属高优先级事实,放 system / 窗口开头,降低模型“背对结论”的概率。

7. 结语:它是不是对的,取决于对账

回到篇3那个坐标——模型是无状态单步函数,Agent 是宿主编排的问答序列。这篇的贡献只有一句:当这个序列长到装不下时,别裁、别盲压,而是像会计一样“结账”——已由宿主验证的事实登账、结转成有界的期初余额,原始凭证留在外部账本里随时可对账。智能体的长期记忆,不该是一段越来越长的自言自语,而是一套能复核、能结转、越滚越精的账

动手(把这套做成真的)

  1. mini-agent 里先做最小闭环:runtimeledger.json,让 complex 每次 write_file 后自动登一条 fact + 读回对账;
  2. 实现 reconcile:随机挑一条 fact,调 read_file 重读凭证核对哈希/数值;
  3. 用验收表 1/3/4 跑:结算 5 个子任务后量窗口 token、抽 3 条审计、篡改一个文件看是否报对不上。

自测

  1. 为什么「摘要」必须有指针指向凭证?没有会退化成什么?
  2. 「结账前对账」解决的是哪一类漂移?会计里叫什么?
  3. 上下文里永远放哪两样东西?为什么它是「有界」的?
  4. supersededBy 为什么不物理删除旧事实?
  5. 这套设计里,模型的“记忆”到底存不存在?如果不存在,那“长期”是谁在长期?

关联:篇3 时序模型与本质篇1 简单 Agent篇2 复杂 Agent;配套实验库 ioi/mini-agent