Delta压缩周期的事务分段:events/Knowledge循环逐条切段 #199
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
#151(ADR-0013 ①层单一事务边界落地——请求级事务,issue #108 Q1)。事务边界方向已确认为分段:
事务只包住连续无外部 I/O 打断的一段纯 DB 读写,下一步要发起任何外部调用(LLM 补全、embedding 调用、
host 工具调用)前,当前段必须先提交/关闭(见 #151 comments"拆片前的事务边界设计方向已与用户确认")。
本片负责 Delta 压缩周期:
delta.py::_compress_one_chat(run_delta_compression_cycle逐 chat驱动)。
What to build
design-verify 阶段点名的具体实例是"
write_tentative_facts/write_event/write_knowledge之后,循环里先
get_relationship_edges(读) 再判断写——会把前面已 flush 的写入打断"。本片起草时核实现状发现,
_compress_one_chat里真实存在的外部 I/O 打断点比这条描述的更多、更细:compress(...)(主 LLM 结构化抽取调用)——函数体最前面,之后才是全部写入embedding_client.embed(event.content)——每条事件各自调用一次,与storage.write_event逐条交替(
for event in events: vector = await embed(...); await write_event(...)),不是"先全部 embed 完再统一写"
judge_and_maybe_veto(...)(Knowledge 候选的人设漂移守护判断,drift_veto.py::judge_and_maybe_veto内部调
judge_persona_drift)——每条 Knowledge 候选各自调用一次,同样与storage.write_knowledge逐条交替也就是说,
_compress_one_chat内部实际有三类外部 I/O,其中两类(embed/judge)是逐条循环内交替发生的,不是"函数前段读、LLM 一次调用、函数后段全部写"这种三段式。落地时不能把"函数体除
compress()调用外的全部写入"当成一个大段处理——那正是本片存在理由要防的坏模式,会重新做出一个被 #151 comments"拆片前的事务边界设计方向已与用户确认"一节明确否决的形状。
Acceptance criteria
一、pre-
compress()段(纯读,通常不需要事务,但若有并发一致性诉求可归为一段)gather_profile_facts(画像)+storage.get_knowledge(已有 Knowledge,供预筛)合并进compress()调用前的读取段二、
compress()之后、events 循环之前的写入段get_fast_affective_state(读,valence 更新前置读取,若尚未读过)+_apply_interaction_polarity内部写入(
update_fast_affective_state、条件性update_slow_affective_state)+record_affinity_interaction(循环,issue #126)+record_tension_conflict(循环,issue #126)+write_tentative_facts+record_trust_disclosure(画像候选敏感分支循环)合并进同一段——这些操作之间没有任何外部 I/O 调用,可以安全合并
embed()调用前提交/关闭三、events 循环:每条事件各自一段
fast_record is None时的get_fast_affective_state),这次读取须并入"二"节那个段(不是循环内新增一次跨越 embed() 的读)——按现状代码,这次读取在循环
开始之前(
if events: ... fast_record = await storage.get_fast_affective_state(...)),本来就在"二"节范围内,不需要额外处理,本条只是确认这一点不要在改造时挪位置
embed(event.content)(外部 I/O)→ 新开一段 →write_event(event, vector=vector)+ 条件性record_trust_disclosure(敏感事件参与者循环)→提交/关闭这一段 → 下一轮
embed()前必须已经关闭embed()调用之后、write_event提交前崩溃,断言第 1 条事件已落库、第 2 条事件未落库(防空转正例——不能只测"全部成功"或"全部
失败")
四、Knowledge 循环:每条候选各自一段
get_persona is not None)judge_and_maybe_veto(...)(外部 LLM 调用,内部可能含
record_veto_if_needed的留痕写入——见"六"节的独立提交裁定)→ 若通过,find_matching_entry/merge_knowledge(纯函数,不碰存储)→ 新开一段 →write_knowledge→提交/关闭
knowledge_snapshot本地快照的更新(written并回本地列表)是纯内存操作,不影响分段五、relations 循环:整个循环可合并为一段(循环内无外部 I/O)
get_relationship_edges(读)+ 判重 + 条件性write_relationship_edge(写)——这个循环内部没有任何外部调用,可以把整个 relations 循环合并成一段,不需要像 events/Knowledge 循环那样
逐条切段
实现都满足"无外部 I/O 打断"要求,具体怎么分交给实现期决定,不强制一种
原文(design-verify HIGH 发现,issue #151 comments):
六、
judge_and_maybe_veto内部的留痕写入(record_veto_if_needed)如何提交,需实现期显式裁定record_veto_if_needed(否决/失败时的留痕写入,issue #116)发生在judge_persona_drift这次外部 LLM 调用返回之后、调用方(本片 Knowledge 循环)真正落库候选之前——需要裁定它
算"这次判断的收尾"(在
judge_and_maybe_veto内部独立提交,不依赖调用方传入的段)还是"下一次写入段的一部分"(并入"四"节新开的那一段)。建议前者:
judge_and_maybe_veto是delta.py(本片)与
reflection.py(姊妹片)共用的函数,若它内部独立管理自己的提交,两个调用方都不需要各自处理这个细节,且"记录一次判断确实发生过"与"这次判断产出的候选是否最终落库"是两件语义上
独立的事,类比"三"节 AC 已经确立的决策记账类写入独立提交先例。若实现期选择这个方案,需要
与「反思闭环家族」姊妹片协调(
judge_and_maybe_veto/drift_veto.py不属于任何一片单独拥有,是共享模块,谁先落地这部分改造,另一片直接复用,不要重复设计)
七、测试
Not in scope
judge_and_maybe_veto/drift_veto.py本身的实现改造——若"六"节裁定它需要独立提交能力,具体代码改动与「反思闭环家族」片协调后由先动手的一片落地,本片不预先垄断
run_delta_compression_cycle(调用_compress_one_chat的调度驱动层)本身的 session 隔离——这是否属于「APScheduler 定时作业」片的范围需要那一片确认(
_compress_pending_delta是否是它调用
run_delta_compression_cycle的唯一入口)Blocked by