一个会话是DAG,不是一条直线。每条分支上说过的话都该进记忆,共享前缀上说过的话不该进两次。
这一页画的就是决定这两件事的那一个事实。正文在
docs/reference/design/memory/written-marker.md,中文对照版是
written-marker.zh.md;它外面的记忆子系统在
overview.md,机制图在
memory-architecture.html。
位置游标:一个会话一个整数,存在runtime.json里。分叉之后整条分支漏写,
或者只写下一段从对话中间开始的残片,并且返回成功。
在节点上打「已写」标记,从分支尖端往回走到第一个有标记的为止。 七个文件、改约200行新增约130行,外加一次性迁移。
不存这本账,欠账从记忆自己的内容里查出来。挡路的是索引与失效规则, 比整个第三层都大。
写到哪了,记的是一个整数,键是会话id。一个会话有六条分支,六条分支共用这一个整数。
_records里就是
for index, message in enumerate(messages):链被展开成列表的那一刻才有这个号,
列表没了它也就没了。同一条消息沿两条线走到会拿到两个号,两条线上深度相同的两条不同消息会拿到同一个号。_records的序号是"这一行在整条分支里的位置,跳过的行也算",
工具调用行和运行时自己调度的轮次都占编号。所以越过游标的是几行、对应几轮,不是一次简单的减法,
上面两个数字是实测出来的,不是算出来的。advance_cursor在新序号低于已存序号时抛
cursor cannot move backwards,而往回走恰恰是分叉需要的。单调计数器和会分叉的对话是两个形状,
其中一个修不成另一个。
references/下九个目录,claude-code/只有某个工具的五个文件,
所以真正有答案的是八个框架。八个里四个真的有长期记忆。
边界只能落在能标识一条消息的东西上,而标识一条消息的就是它这个节点本身。 所以"记忆写过这一轮"这件事,记在这一轮身上。
metadata是自由字典,消息适配器把不认识的字段全放进去,
读取方再把每个键摊回顶层,所以键名不能和消息字段撞(id role
content predecessor caller timestamp
status 这些都被占了)。往已存节点上写也不是新事:rewind盖
rewound、revert盖reverted、一轮的收尾流程就地重写节点文件盖检查点戳记;
而SessionNodeWriter.update已经在做标记需要的那个动作,只是它先经过
SessionStore._open(会把过期索引重建掉),再用atomic_write_text直接重写,
绕开了上面两条。git add -A一次,所以标记只打在这一批上,永远不做全量扫一遍,
否则每轮加上整个会话的重量,一生下来是平方级。
| 在节点上打标记 | 记忆自己存一份 ID 集合 | |
|---|---|---|
| 怎么算出还没写的 | 从尖端往回走,停在第一个带标记的 | 走完整条分支,减去集合 |
| 分叉时 | 走到共享前缀,发现有标记,停 | 前缀的 ID 在集合里,于是被跳过 |
| 要存什么 | 一个本来就存在的节点上多一个字段 | 每个 thread 一个文件,装下写过的每一个 ID |
| 怎么增长 | 不增长 | 每条消息一项,会话在多久就留多久 |
| 一次读取的代价 | 从尖端往回走几步 | 这个 thread 的整份清单 |
| 迁移 | 从可信归档候选中只标DAG连续前缀 | 用同一份安全前缀播种集合 |
| 标记或条目丢了 | 走行提前停下,比它更老的全部搁浅 | 那一轮会被重新拿出来 |
| 耦合 | 记忆往会话存储里写一个字段 | 记忆自己记账 |
rewind有没有把它退掉;
多一个"记忆写过这一轮"的字段,和那些是同一类东西。第三层回答"下一步动手做什么",这一层回答"如果重新设计这块该是什么样"。 下面三条不是标记的分阶段版本,它们是同一个问题的另一种摆法。
metadata.memory_written_scriptorium = <workspace-id>记录写入状态,当前分支按节点标记计算待写后缀,
成功且实际修改记忆的批次才打标记;会话结束时先处理当前head,再处理其他活分支。
RuntimeState.cursors和advance_cursor已经移除。旧安装首次写入时同时校验legacy header和
sources/openprogram/_v2/*.md的严格合法前缀:legacy只接受标题后第一组合法header,v2遇到非法frame停止。
候选ID在真实DAG路径上经过与在线写入相同的记录过滤,只标记第一个缺口之前的连续前缀;缺口后的尾部允许重写。
所有session标记成功后才删除旧cursors,失败时保留供重试。
结构化speaker字段、可信v2 frame和speaker过滤也已经实现。历史memory backfill有意忽略节点marker,
只选择未被Topic引用的trusted Source并排除pending;它不增加或删除session marker。节点marker记录在线writer处理状态,
Topic引用记录历史Source是否已经进入可召回内容,两项定义互不替代。正式工作区尚未执行历史backfill。第四层仍未实现。