Delta压缩连续失败降级:整批转存archive低置信度记录(issue #165) #170

Merged
Yushu merged 1 commit from feat/165-delta-compression-archive-fallback into main 2026-08-20 04:45:24 +00:00
Member

Closes #165

What

从 issue #156 拆出的"archive 内容产出方"部分:run_delta_compression_cycle 压缩 LLM 调用连续失败达到阈值时,触发整批当前 delta 原样转存为低置信度归档记录,调用 issue #156 落地的 archive 分条化写入 API。

  • 新增跨重启存活的连续失败计数(DeltaCompressionFailureStoragePortrecord_delta_compression_failure/clear_delta_compression_failures)——per-chat_id 一行,结构仿 UnansweredEscalationModel 先例,成功压缩即清零
  • 新增静态阈值配置 arise_delta_compression_archive_threshold(默认 3,ADR-0011 阈值三层分类,同 arise_agent_loop_breaker_threshold 归属)
  • run_delta_compression_cycle 新增分支:连续失败达阈值时不再原样写回 Delta 缓冲,改为调用 _archive_failed_delta 整批转存(逐条 DeltaEntry 对应一个 ArchiveCandidate,不合并成 blob);转存成功后清零计数,不计入返回的 processed(延续既有写回重试分支的约定)
  • 熔断(PoolExhausted)不计入连续失败计数——无论发生在压缩阶段还是转存阶段自己的 embedding 调用(design-verify 核实到的真实场景:_pool_is_broken 前置闸只查 reflection 池,不保护转存要用到的 embedding 池,两者相互独立),都走"写回整批 delta + 停整个 cycle",不当作"这个 chat 内容有问题"
  • ArchiveStoragePort/DeltaCompressionFailureStoragePort 正式组进 StoragePort(32 切片)与 CompressionStoragePort(10 切片)——issue #156 落地时刻意没接入,本票是真正需要经由 host 契约调用它的那一票

Design-Verify

两轮独立方案 + 对抗式交叉核实 + 合并,核实过程中发现两份原方案都遗漏的两个真实问题并已采纳进最终方案:

  • 转存阶段的 embedding 调用同样可能撞见 PoolExhausted(前置闸不保护 embedding 池),两份原方案的转存代码都用一个 blanket except Exception 把它吞掉了,会把系统级熔断误判成"这批转存失败"
  • PersistentStorage 的计数器实现如果在 commit() 之后才读返回值,会在真实 SQLAlchemy 引擎下炸出 MissingGreenletexpire_on_commit=True 默认值陷阱,同 write_knowledge 既有教训)

Review 修复

两轴 review + 对抗式 Verify 确认 2 处问题并已修复:

  • _archive_failed_delta 逐条串行 await embedding_client.embed(...)——这批内容已经被 drain_delta 从 Postgres 删除,在全部 embedding 调用完成前只存在于函数局部变量里,串行等待让"进程被硬杀导致整批原文永久丢失"的窗口随批量线性变长。已改为 asyncio.gather 并发发起,把窗口收窄到约一条网络请求的时长(收窄而非消灭——这个风险与整条 run_delta_compression_cycle 共享同一个"硬杀不经过 Python 异常处理"的既有敞口,issue #94 起就存在,不是本票引入的新问题,已在代码注释里如实说明)
  • :CONTEXT.md/docs/design.md 仍写着"产出方尚未实现,见 issue #165"——本票正是这个产出方,已同步更新;docs/adr/0009-memory-engineering.md 补一节确认落地,并明确"归档接三因子召回"这半仍未做、留给单独评估

Test plan

  • uv run pytest -q:2027 passed
  • uv run ruff check src/ tests/:全通过
  • uv run lint-imports:2 kept, 0 broken
  • ty check:47 diagnostics(与 main 基线一致,无新增)
  • mutation testing:阈值比较、熔断不计入计数(压缩阶段+转存阶段两处)、成功归零、转存阶段异常收集与重新抛出逐一变异验证测试能捕获(过程中发现并修复了两处测试断言本身不够精确的问题)
Closes #165 ## What 从 issue #156 拆出的"archive 内容产出方"部分:`run_delta_compression_cycle` 压缩 LLM 调用连续失败达到阈值时,触发整批当前 delta 原样转存为低置信度归档记录,调用 issue #156 落地的 archive 分条化写入 API。 - 新增跨重启存活的连续失败计数(`DeltaCompressionFailureStoragePort`,`record_delta_compression_failure`/`clear_delta_compression_failures`)——per-chat_id 一行,结构仿 `UnansweredEscalationModel` 先例,成功压缩即清零 - 新增静态阈值配置 `arise_delta_compression_archive_threshold`(默认 3,ADR-0011 阈值三层分类,同 `arise_agent_loop_breaker_threshold` 归属) - `run_delta_compression_cycle` 新增分支:连续失败达阈值时不再原样写回 Delta 缓冲,改为调用 `_archive_failed_delta` 整批转存(逐条 `DeltaEntry` 对应一个 `ArchiveCandidate`,不合并成 blob);转存成功后清零计数,不计入返回的 `processed`(延续既有写回重试分支的约定) - **熔断(`PoolExhausted`)不计入连续失败计数**——无论发生在压缩阶段还是转存阶段自己的 embedding 调用(design-verify 核实到的真实场景:`_pool_is_broken` 前置闸只查 `reflection` 池,不保护转存要用到的 `embedding` 池,两者相互独立),都走"写回整批 delta + 停整个 cycle",不当作"这个 chat 内容有问题" - `ArchiveStoragePort`/`DeltaCompressionFailureStoragePort` 正式组进 `StoragePort`(32 切片)与 `CompressionStoragePort`(10 切片)——issue #156 落地时刻意没接入,本票是真正需要经由 host 契约调用它的那一票 ## Design-Verify 两轮独立方案 + 对抗式交叉核实 + 合并,核实过程中发现两份原方案都遗漏的两个真实问题并已采纳进最终方案: - 转存阶段的 embedding 调用同样可能撞见 `PoolExhausted`(前置闸不保护 embedding 池),两份原方案的转存代码都用一个 blanket `except Exception` 把它吞掉了,会把系统级熔断误判成"这批转存失败" - `PersistentStorage` 的计数器实现如果在 `commit()` 之后才读返回值,会在真实 SQLAlchemy 引擎下炸出 `MissingGreenlet`(`expire_on_commit=True` 默认值陷阱,同 `write_knowledge` 既有教训) ## Review 修复 两轴 review + 对抗式 Verify 确认 2 处问题并已修复: - **中**:`_archive_failed_delta` 逐条串行 `await embedding_client.embed(...)`——这批内容已经被 `drain_delta` 从 Postgres 删除,在全部 embedding 调用完成前只存在于函数局部变量里,串行等待让"进程被硬杀导致整批原文永久丢失"的窗口随批量线性变长。已改为 `asyncio.gather` 并发发起,把窗口收窄到约一条网络请求的时长(收窄而非消灭——这个风险与整条 `run_delta_compression_cycle` 共享同一个"硬杀不经过 Python 异常处理"的既有敞口,issue #94 起就存在,不是本票引入的新问题,已在代码注释里如实说明) - **中**:CONTEXT.md/docs/design.md 仍写着"产出方尚未实现,见 issue #165"——本票正是这个产出方,已同步更新;docs/adr/0009-memory-engineering.md 补一节确认落地,并明确"归档接三因子召回"这半仍未做、留给单独评估 ## Test plan - [x] `uv run pytest -q`:2027 passed - [x] `uv run ruff check src/ tests/`:全通过 - [x] `uv run lint-imports`:2 kept, 0 broken - [x] `ty check`:47 diagnostics(与 main 基线一致,无新增) - [x] mutation testing:阈值比较、熔断不计入计数(压缩阶段+转存阶段两处)、成功归零、转存阶段异常收集与重新抛出逐一变异验证测试能捕获(过程中发现并修复了两处测试断言本身不够精确的问题)
新增跨重启存活的连续失败计数(DeltaCompressionFailureStoragePort,
per-chat 计数,成功压缩即清零),达到静态阈值
arise_delta_compression_archive_threshold(默认3)时不再原样写回Delta
缓冲,改为调用issue #156落地的archive分条化写入API整批转存(逐条
DeltaEntry对应一个ArchiveEntry,不合并成blob)。熔断(PoolExhausted)
不计入连续失败计数——无论发生在压缩阶段还是转存阶段自己的embedding
调用,都走写回+停整个cycle,不当作chat内容问题。embedding调用并发
发起收窄"已从Delta缓冲删除但还未确认写入archive"的持久性窗口。
Yushu merged commit 9f8de41176 into main 2026-08-20 04:45:24 +00:00
Yushu deleted branch feat/165-delta-compression-archive-fallback 2026-08-20 04:45:25 +00:00
Sign in to join this conversation.
No description provided.