qinghuan
← 让长任务能暂停和恢复 / 全部笔记

单元 31 / 让长任务能暂停和恢复

P1 · 重点提升L3 · 深入

让记忆可以纠错、过期并保持隔离

给长期记忆记录来源、版本和有效期,并处理修改与撤回,避免旧信息重新生效。

本篇目录 · 按需要跳转

读完这篇,你能做什么

能实现记忆管理方法,测试同时修改时的冲突、过期内容过滤,以及两个用户的摘要和其他派生数据是否隔离。

学习理由、准备要求与知识点

为什么学它

记住的内容可能过时、出错或带来隐私风险,所以要支持确认、纠正、到期停用,并同步清理相关摘要和缓存。

先读这些内容

知识点清单

  • 记忆来源与确认状态
  • 事实与推测区分
  • 临时要求和长期偏好
  • 修订及替代关系
  • 并发记忆更新
  • 过期与撤回
  • 向量缓存摘要清理
  • 跨用户记忆隔离

运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。

01先弄懂它是什么

长期记忆会变:用户可能改偏好、撤回信息,或只给这一次任务提要求。因此不能只把新内容不断追加,还要保存来源、确认状态、适用范围和有效期。例如模型猜测“用户喜欢复杂例子”,与用户明确说“本次请简短”,来源和生效时间都不同,不能合成一个永久不变的事实。

一条记忆可以分别记录用户、属性、版本、值、来源、有效期和撤回标记。版本帮助分清新旧,来源用于核对,有效期说明何时还能使用,各自解决不同问题。如果较新的记录被撤回,旧值也不应该自动重新生效;要不要恢复旧值,应由明确的业务流程决定。

“最后收到”不一定是“最新发生”。例如用户先把偏好改成版本二,网络较慢的版本一却晚到,不能再用旧内容覆盖。也可能两条事件都自称版本二,内容却不同,这需要报冲突。本篇先假定同一个用户属性的版本由可信程序递增分配,再按版本恢复状态;无论事件以什么顺序到达,都检查同版本冲突,选出最新状态后才判断它是否撤回或过期。

文中这几个词,先认一认

撤回墓碑
删除或撤回后仍保存的一条版本记录,用来挡住迟到的旧更新,避免已经删除的信息又被恢复。
派生记忆
根据原始事实生成的摘要、向量或缓存。原事实修改或删除时,这些派生内容也需要同步处理。
版本冲突
同一事实的同一个版本号对应了不同内容,此时需要核查,不能随意用后到的数据覆盖。

还有陌生词?到术语小抄里看一个具体例子 ↗

02看看它怎么工作

先用“用户是谁、改的是哪个属性”共同组成键,例如用户 A 的回答风格与用户 B 的回答风格就是两条记忆。每个键保留最大版本作为当前状态,较小版本不能覆盖它。对同一版本,值、来源、期限和撤回标记等字段完全相同算重复,有不同就报冲突;完整回放代码也会检查已经不是最新版的历史冲突。版本号必须由可信写入流程分配,不能让模型随便填一个巨大数字,借此抢成最新记录。

聚合完最新版本后,再检查它是否撤回或已过期。若先过滤过期事件再选最大版本,就可能回退到旧长期偏好;例如v2临时修正已经过期,v1仍被错误恢复。是否恢复旧值应是显式产品规则,本题选择不自动恢复。

读取记忆时,只返回当前用户仍然有效的事实,不能把不同用户的同名属性合并。原事实修改或撤回后,相关摘要、Embedding 索引和提示缓存也要按来源关系更新或失效。如果只删除用户能看到的那一行,旧摘要却还在送给模型,就会出现“明明删除了,它还记得”的不一致行为。

正在加载流程图…

图中状态只描述本篇可执行机制;业务调用仍需由外层应用负责。
查看流程图源码(Mermaid)
flowchart LR
  E["带来源事实事件"] --> K["用户与属性复合键"]
  K --> V["版本比较与冲突检查"]
  V --> L["最新值或撤回墓碑"]
  L --> F["期限及使用范围过滤"]
  F --> C["当前记忆与派生缓存同步"]

按顺序看,每一步做了什么

  1. 明确事实、推测、临时要求与长期偏好的确认政策。
  2. 用可信用户与属性键保存版本、来源和有效期。
  3. 回放或更新时拒绝同版本冲突,保留撤回版本。
  4. 先选择最新状态,再应用有效期和使用范围。
  5. 同步清理派生摘要、向量与缓存,并验证跨用户隔离。

03对照实现代码

OpenJDK 17 ConcurrentHashMap 源码 (新标签页)↗

OpenJDK 17 GA,jdk-17+35 · ConcurrentHashMap.compute

下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。

核心机制代码 · java
memories.compute(key, (k, old) -> {
    if (old != null && incoming.version() < old.version()) return old; // 旧事件不覆盖新状态。
    if (old != null && incoming.version() == old.version() && !incoming.equals(old))
        throw new IllegalStateException("version conflict");
    return incoming; // 撤回标记也保存为有版本的状态。
});
// 单键原子更新不等于跨键事务、跨进程一致性或派生缓存自动失效。

ConcurrentHashMap.compute对指定键的计算更新提供原子性,映射函数应简短,避免在其中执行慢网络调用或递归更新同一映射。

单键原子性适合比较旧版本并写入新状态,但多个属性同时更新不自动形成事务。数据库、向量索引和缓存属于不同系统时,需要额外版本和同步策略。

返回null会删除映射,因此撤回事实不能简单返回null而丢掉版本。保留墓碑后才能阻止迟到旧事件覆盖已撤回状态。

完整算法使用单线程回放构造快照,展示版本合并规则;生产并发实现可以用数据库条件更新或受控compute,但仍需可信版本来源与持久记录。

代码先调用谁,再调用谁

  1. 确认并生成可信事实版本
  2. 按用户和属性定位键
  3. compute比较版本及冲突
  4. 保存最新事实或撤回墓碑
  5. 读取时应用期限并同步派生数据

04动手写一个例子

版本化记忆回放与撤回

输入任意顺序事实事件、当前用户和非负时刻,输出该用户最新且未撤回、未过期的值;同版本不同内容拒绝。expires=0表示无期限,其他期限为不包含的上界。Java17 main测试回放。

输入 1
v2撤回后迟到v1
输出 1
无有效值
输入 2
用户A与B同属性
输出 2
只返回指定用户

复合键包含用户,避免不同用户同名属性互相覆盖。对每个事件先验证版本、来源与期限,再进行最大版本归并。

相同版本重复事件不改变结果,相同版本不同内容没有安全的自动赢家,本题报冲突。事件输入顺序因此不影响无冲突数据的最终快照。

最终过滤发生在版本归并之后,过期和撤回的最新状态都阻断旧值复活。输出只包含值,完整内部快照仍应保存来源与墓碑供后续更新核对。

为什么这样写是对的聚合表对每个用户属性键保留已处理事件的唯一最大版本,或在最大/同版本内容冲突时拒绝;过滤后不会退回任何较旧版本。

时间 平均O(n+C)额外空间 O(n+C)

n为事件数,C为相关字符串处理与保留量。程序保留各版本事件用于检查历史同版本冲突,另有最新键快照;散列表操作平均常数,内容比较需要读取字段。

保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。

Main.java · 完整可运行程序 · Java
import java.util.*;
public class Main {
    record Key(String user,String field){} record Revision(Key key,long version){}
    record Event(String user,String field,long version,String value,long expires,boolean revoked,String source){}
    static Map<String,String> replay(List<Event> events,String user,long now){
        if(now<0||user==null)throw new IllegalArgumentException("query");Map<Key,Event> latest=new HashMap<>();Map<Revision,Event> revisions=new HashMap<>();
        for(Event e:events){
            if(e.user()==null||e.field()==null||e.version()<1||e.value()==null||e.expires()<0||e.source()==null||e.source().isBlank())throw new IllegalArgumentException("event");
            Key key=new Key(e.user(),e.field());Event old=latest.get(key);
            Event duplicate=revisions.putIfAbsent(new Revision(key,e.version()),e);
            if(duplicate!=null&&!duplicate.equals(e))throw new IllegalArgumentException("version conflict"); // 历史同版本冲突也不忽略。
            if(old==null||e.version()>old.version())latest.put(key,e); // 撤回也保留最大版本。
        }
        Map<String,String> out=new TreeMap<>();
        for(Event e:latest.values())if(e.user().equals(user)&&!e.revoked()&&(e.expires()==0||now<e.expires()))out.put(e.field(),e.value()); // 先选新版本,再过滤期限。
        return out;
    }
    static Event e(String u,long v,String value,long expiry,boolean revoked){return new Event(u,"style",v,value,expiry,revoked,"message:"+v);}
    static void eq(Object x,Object y){if(!x.equals(y))throw new AssertionError(y);}
    public static void main(String[] args){
        eq(Map.of("style","short"),replay(List.of(e("A",2,"short",0,false),e("A",1,"long",0,false)),"A",10));
        eq(Map.of(),replay(List.of(e("A",2,"",0,true),e("A",1,"long",0,false)),"A",10));
        eq(Map.of(),replay(List.of(e("A",1,"long",0,false),e("A",2,"short",5,false)),"A",5));
        eq(Map.of("style","short"),replay(List.of(e("A",1,"short",0,false),e("B",9,"long",0,false)),"A",0));
        Event x=e("A",1,"short",0,false);eq(Map.of("style","short"),replay(List.of(x,x),"A",0));
        try{replay(List.of(x,e("A",1,"long",0,false)),"A",0);throw new AssertionError();}catch(IllegalArgumentException expected){}
        System.out.println("PASS 6 tests");
    }
}

这些特殊情况也要试一试

  • 期限边界now==expires视为过期。
  • 撤回和过期不自动回退旧值。
  • 版本相同但来源或内容不同也属于冲突。
进阶选读 · 后续再读:多源事实合并与因果关系

场景多个设备离线修改偏好,单一全局版本难以立即分配。

做法记录来源与因果关系,使用向量版本或明确合并政策识别并发;不能把墙钟更晚直接视为更可信。重要冲突交由用户或规则核对。

复杂度向量版本比较O(p),p为参与者数,元数据随参与者增长;一般语义冲突没有通用自动正确合并算法。

好处与代价自动合并可减少交互,但可能抹掉用户意图;保留冲突比悄悄覆盖更利于长期一致性。

05项目里怎么用

记忆写入需要明确触发规则,敏感信息和推测不应默认永久保存。来源包含用户原话位置与确认时间,读取时按当前任务范围筛选。临时要求到期后是否回到默认偏好,应通过单独规则决定,而不是偶然由数据库查询顺序决定。

撤回一条事实时,要追踪哪些东西是根据它生成的:摘要、向量、缓存,以及政策范围内的导出副本。派生内容可以安排后台逐步清理,但在清理完成前,读取入口就要认出这个版本已经撤回,不能继续把旧内容交给模型。另一个问题是哪些记录因审计需要保留;“仍存作审计证据”和“还能作为用户记忆使用”应有分别明确的规则,不能混在一起。

并发验收至少包含旧事件晚到、重复事件、同版本冲突、撤回后重放和跨用户属性同名。使用固定事件集置换顺序,检查最终结果是否符合合并规则;这比只测一次写入后读取更能发现生命周期错误。

容易踩的坑

  • 模型推测直接写成用户已确认的长期偏好。
  • 先过滤过期新记录,导致旧事实复活。
  • 删除值时一起丢掉撤回版本,迟到事件重新写回。
  • 把单键compute原子性当作跨系统事务。

几种做法,怎么选

表格有四列,窄屏可左右滑动查看代价和适用条件。

方案优势代价与限制适用条件
显式版本与墓碑可处理旧事件和撤回增加存储与清理政策长期记忆及异步更新
只保留最新值实现简单,读取快难追溯冲突与迟到写入没有恢复要求的临时数据
保留冲突待确认减少错误覆盖需要额外用户或规则处理多来源重要事实

06检验是否读懂了

先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。

先用自己的话解释

01来源与版本分别解决什么?

展开参考答案与推导

来源帮助核对事实依据,版本决定同一属性的更新次序;高版本不能自动证明内容可信。

02墓碑为什么不能立即删除?

展开参考答案与推导

它记录撤回后的最新版本,防止迟到旧事件再次成为当前值;清理需结合重放窗口和同步状态。

03过期后为何不自动恢复旧值?

展开参考答案与推导

旧值是否重新适用是业务语义,不能由过滤顺序隐式决定;本题明确不自动复活。

换个情况,还会处理吗

01用户删除偏好后模型仍提到它,查哪里?

展开参考答案与推导

检查摘要、向量和提示缓存是否仍引用旧版本,以及读取路径是否识别墓碑;不只检查主表值。

02两设备同时修改同属性怎么办?

展开参考答案与推导

识别并发或版本冲突,按明确来源与确认政策处理;不能仅凭服务器接收顺序决定用户真实意图。

03模型给出很大version能覆盖旧值吗?

展开参考答案与推导

不应允许。版本由可信应用或存储分配,模型只能提出待核验内容,不能控制更新优先级。

写代码试一试

01过期v2与永久v1如何输出?

输入 / 条件
v1=long无期限,v2=short到5,now5
输出 / 预期
无有效值

提示 先归并最大版本。

展开参考答案与推导

最新v2过期后被过滤,v1仍旧,不会自动回退;若产品要恢复需额外显式事件。

02同版本重复是否增加版本?

输入 / 条件
同一事件两次
输出 / 预期
结果不变

提示 重复与冲突分开。

展开参考答案与推导

完全相同事件可幂等重放,不生成新版本;内容不同才拒绝并调查。

03如何测试顺序无关性?

输入 / 条件
无冲突事件列表不同排列
输出 / 预期
相同最终快照

提示 最大版本归并。

展开参考答案与推导

对小列表穷举排列或固定打乱,比较结果;若变化说明合并中依赖了到达顺序或提前过滤。

读完之后,独立完成

课后作业:自己做一遍

本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。

这次练什么

能实现记忆管理方法,测试同时修改时的冲突、过期内容过滤,以及两个用户的摘要和其他派生数据是否隔离。

本篇重点加练

记忆修订需同步传播到派生视图,重点检查旧事实是否会通过缓存或摘要重新进入上下文。

  • 修订及替代关系
  • 向量缓存摘要清理

重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。

先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。

基础必做

  1. 作业 01原理问答

    给三条候选记忆标注来源、确认状态与事实或推测类别,说明来源缺失时可以做哪些用途及不能做哪些用途。

    提交内容
    一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
    怎样算完成
    • 逐项回应题目要求,区分事实、假设与个人判断。
    • 能用自己的例子解释适用条件,记录尚未弄懂的点。

    练到的知识点记忆来源与确认状态 · 事实与推测区分

  2. 作业 02代码推演

    按时间排列“平时用Java、今天改用Python、撤回刚才要求”三条消息,设计长期偏好与临时要求的修订关系。

    提交内容
    手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
    怎样算完成
    • 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
    • 主动检查至少一个边界或异常条件,能解释手算与运行差异。

    练到的知识点临时要求和长期偏好 · 修订及替代关系

  3. 作业 03原理问答

    为事实修订建立来源、替代与派生视图的依赖图,声明哪些派生结果可以局部更新、哪些必须重新生成以及判断依据。

    提交内容
    概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
    怎样算完成
    • 关键概念有明确边界,能用案例检验声明。
    • 未获证据支持的假设单独标注。

    练到的知识点修订及替代关系 · 向量缓存摘要清理

综合应用

  1. 作业 04面试场景

    两台设备同时修改同一偏好,随后用户要求过期删除。画并发事件时间线,声明冲突与撤回规则并设计可核验状态。

    提交内容
    场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
    怎样算完成
    • 方案回应给定约束,列出至少一种替代方案及代价。
    • 每项关键结论说明所需证据,不能核实的部分明确标注。

    练到的知识点并发记忆更新 · 过期与撤回

  2. 作业 05编程实践

    实现本地记忆存储及两个派生视图的模拟,删除用户A一条记忆后检查原表、摘要、向量缓存与用户B读取结果。

    提交内容
    可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
    怎样算完成
    • 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
    • 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。

    练到的知识点向量缓存摘要清理 · 跨用户记忆隔离

  3. 作业 06排错实验

    故意让摘要仍引用被替代的事实,记录它在哪个处理环节重新进入上下文,提交修复前后的来源链检查。

    提交内容
    最小故障输入、复现步骤、观测记录、定位依据及回归用例。
    怎样算完成
    • 其他人可按记录复现故障,并区分现象与已验证原因。
    • 定位过程有观测依据,回归用例可以再次暴露原问题。

    练到的知识点记忆来源与确认状态 · 修订及替代关系 · 向量缓存摘要清理

  4. 作业 07编程实践

    实现一个内存中的事实与两个派生视图模拟器,连续修订同一事实两次,故意打乱视图更新顺序;检验每次读取能追溯到哪一版来源。

    提交内容
    可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
    怎样算完成
    • 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
    • 验证目标由合同确定,模拟结果不冒充真实服务证据。

    练到的知识点修订及替代关系 · 向量缓存摘要清理

  5. 作业 08排错实验

    模拟清理成功后一个迟到的旧摘要写回,记录版本检查与依赖校验能否阻止旧事实复活,并提交最小事件序列。

    提交内容
    最小故障序列、观测记录、原因核对与可再次触发原问题的测试。
    怎样算完成
    • 故障可以稳定重现,现象与已验证原因分开。
    • 观测记录足以判断声明的合同是否被违反。

    练到的知识点修订及替代关系 · 向量缓存摘要清理

进阶挑战 · 掌握前置后选做

  1. 作业 09设计评审

    在掌握并发控制后,为批量记忆过期设计补偿和重试协议,说明执行中断、部分清理和迟到更新的处理范围。

    提交内容
    设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
    怎样算完成
    • 比较使用一致条件与事先声明的标准,记录失败与未知结果。
    • 说明方案的适用前提、未覆盖风险及后续验证方法。

    练到的知识点临时要求和长期偏好 · 并发记忆更新 · 过期与撤回

思维拓展

先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。

  1. 作业 10思维拓展 · 跨域迁移

    把记忆修订的传播合同迁移到商品价格缓存或搜索索引,比较替代关系、历史审计与旧视图失效条件的异同。

    提交内容
    原场景与目标场景的对应表、可迁移的机制、不能迁移的前提及验证设计。
    怎样算完成
    • 逐项核对两种场景的前提,不只比较相似术语。
    • 至少一个不能直接迁移的边界有具体检验方法。

    练到的知识点修订及替代关系 · 向量缓存摘要清理

完成后,再检验一次

给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。

继续阅读

先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。

打开资料库,找下一份学习材料 ↗