Delta压缩周期的事务分段:events/Knowledge循环逐条切段 #199

Open
opened 2026-08-21 06:13:30 +00:00 by KumaAgent · 0 comments
Member

Parent

#151(ADR-0013 ①层单一事务边界落地——请求级事务,issue #108 Q1)。事务边界方向已确认为分段
事务只包住连续无外部 I/O 打断的一段纯 DB 读写,下一步要发起任何外部调用(LLM 补全、embedding 调用、
host 工具调用)前,当前段必须先提交/关闭(见 #151 comments"拆片前的事务边界设计方向已与用户确认")。

本片负责 Delta 压缩周期delta.py::_compress_one_chatrun_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 打断点比这条描述的更多、更细

  1. compress(...)(主 LLM 结构化抽取调用)——函数体最前面,之后才是全部写入
  2. embedding_client.embed(event.content)——每条事件各自调用一次,与 storage.write_event
    逐条交替(for event in events: vector = await embed(...); await write_event(...)),不是
    "先全部 embed 完再统一写"
  3. 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 调用,可以安全合并
  • 这一段须在 events 循环的第一条embed() 调用前提交/关闭

三、events 循环:每条事件各自一段

  • 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() 前必须已经关闭
  • 补一条测试:events 列表含 2+ 条事件,模拟第 2 条事件的 embed() 调用之后、write_event
    提交前崩溃,断言第 1 条事件已落库、第 2 条事件未落库(防空转正例——不能只测"全部成功"或"全部
    失败")

四、Knowledge 循环:每条候选各自一段

  • 循环体每一轮:(若 get_persona is not Nonejudge_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 循环那样
    逐条切段
  • 这一段紧接在 Knowledge 循环最后一段之后开始(两者之间没有外部 I/O),或作为独立一段——两种
    实现都满足"无外部 I/O 打断"要求,具体怎么分交给实现期决定,不强制一种

原文(design-verify HIGH 发现,issue #151 comments):

write_tentative_facts/write_event/write_knowledge 之后,循环里先 get_relationship_edges
(读) 再判断写——会把前面已 flush 的写入打断,且不触发方案自己设计的"失败回退到 Delta 缓冲"兜底。

六、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_vetodelta.py
    (本片)与 reflection.py(姊妹片)共用的函数,若它内部独立管理自己的提交,两个调用方都不需要
    各自处理这个细节,且"记录一次判断确实发生过"与"这次判断产出的候选是否最终落库"是两件语义上
    独立的事,类比"三"节 AC 已经确立的决策记账类写入独立提交先例。若实现期选择这个方案,需要
    与「反思闭环家族」姊妹片协调
    judge_and_maybe_veto/drift_veto.py 不属于任何一片单独拥有,
    是共享模块,谁先落地这部分改造,另一片直接复用,不要重复设计)

七、测试

  • events/Knowledge 两个循环各自补"防空转"崩溃注入测试(见"三")
  • relations 循环补一条测试验证这个段与前一段独立(该段崩溃不影响 Knowledge 循环已提交的写入)
  • 全量测试通过

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

  • #197(存储层分段事务基建:session 传递契约与 SAVEPOINT 修复)——需要它交付的 session/事务传递 API
## 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 打断点比这条描述的更多、更细**: 1. `compress(...)`(主 LLM 结构化抽取调用)——函数体最前面,之后才是全部写入 2. `embedding_client.embed(event.content)`——**每条事件各自调用一次**,与 `storage.write_event` 逐条交替(`for event in events: vector = await embed(...); await write_event(...)`),不是 "先全部 embed 完再统一写" 3. `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 调用,可以安全合并 - [ ] 这一段须在 events 循环的**第一条**`embed()` 调用前提交/关闭 **三、events 循环:每条事件各自一段** - [ ] 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()` 前必须已经关闭 - [ ] 补一条测试:events 列表含 2+ 条事件,模拟第 2 条事件的 `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 循环那样 逐条切段 - [ ] 这一段紧接在 Knowledge 循环最后一段之后开始(两者之间没有外部 I/O),或作为独立一段——两种 实现都满足"无外部 I/O 打断"要求,具体怎么分交给实现期决定,不强制一种 原文(design-verify HIGH 发现,issue #151 comments): > `write_tentative_facts`/`write_event`/`write_knowledge` 之后,循环里先 `get_relationship_edges` > (读) 再判断写——会把前面已 flush 的写入打断,且不触发方案自己设计的"失败回退到 Delta 缓冲"兜底。 **六、`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` 不属于任何一片单独拥有, 是共享模块,谁先落地这部分改造,另一片直接复用,不要重复设计) **七、测试** - [ ] events/Knowledge 两个循环各自补"防空转"崩溃注入测试(见"三") - [ ] relations 循环补一条测试验证这个段与前一段独立(该段崩溃不影响 Knowledge 循环已提交的写入) - [ ] 全量测试通过 ## 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 - #197(存储层分段事务基建:session 传递契约与 SAVEPOINT 修复)——需要它交付的 session/事务传递 API
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ProjectKuma/arise#199
No description provided.