mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
4.1 KiB
4.1 KiB
Agent Note:投影缓存改为每会话文件
Status: implemented
English | 中文
Problem
持久投影缓存曾是单个全局 session_projcache.json——存储根目录下一个文件里的 sessions 表。每次节流检查点都会重写包含所有会话行的整个文件,写放大随会话数量增长;且一个畸形文件会让整个缓存一起失效。
Decision
缓存打开采用新增 per-record 布局的 session_projcache 存储域:每个会话一个带版本戳的文档,位于 <root>/session_projcache/sessions/<id>.json,介质归存储栈所有——storage / storage-json / storage-domain 与缓存一起落在共享 base 装配里,缓存重新变回纯粹的域消费方。所有随附且基于 base 的 profile 都保持启用缓存,因此会话生产方会记录检查点,不取决于当前应用是否提供列表接口;不使用 base 组合包的 sdk-minimal 不在此装配范围内。缓存绝不咨询持久化层:没有 locate、不依赖挂载的是哪个后端。
读写共享同一份一致状态:每次读取(cachedSnapshot)都是对域内存表的同步查找(零 I/O);每次写入排进该域的单条写链,先落盘成功才改内存——不再有落后于节流写入的直读磁盘。缓存保留其余全部职责:检查点折叠、写策略(turn/end + dispose 强制点、count/interval 节流)、fail-soft 持久化与列表读。cachedSnapshot(meta) 是同步的。缓存不运行冷重折叠阶梯(那需要读取会话日志,属于持久化层的职责);需要保证冷快照的消费方自行从日志重折叠。json 后端以仅属主权限(0o700)创建自己的目录树。
Consequences
- 每会话写入隔离:每次节流写入只替换该会话的小文档,消除全局写放大。域写链将写入串行化,新切面绝不会先于旧切面落盘;域关闭时会排空在途写入。
- 列表读取是同步内存读;没有记录文档的会话只是缺少投影列。
- ACP、headless、SDK 与 Web 会话都会发布缓存行,供后续消费方使用。确保日志领先的持久性屏障可能按缓存节奏 flush 已覆盖的前缀,并拆分原本会合并的物理 JSONL 行;各 profile 的录制快照会重新 pack 逻辑事件流,因此缓存时序不会决定 fixture 布局。
- per-record 契约把故障范围缩小到单记录:畸形或过期版本的文档在打开时读作"无此记录",单个坏文件不会拖垮整个缓存;检查点 schema 升级按会话丢弃过期行,而不是拒绝整个域。
- json 后端仅在枚举时没有发现任何新布局文档路径,才从旧整单元缓存引导 per-record 目录树。只要存在任意新文档路径,即使文件不可读或版本陈旧,也会对整个单元禁用引导;缺失的会话行从日志重折叠。旧文件保持不变。
- 缓存记录仍绑定同一日志生命周期:存储的
{createdAt, cwd}身份防止被重建的 id 误导。
Alternatives considered
- 保留全局 sessions 表。 保留一次加载式列表,但保留了促成此改动的全局写放大与单文件爆炸半径。
- 缓存自持的每会话文件(
<root>/<session-id>/projection_cache.json,本改动的第一版)。试过并在评审中回退:缓存手搓了介质——路径、按路径的写链、在途跟踪、仅属主文件权限,以及 sqlite 无路径特判——而且它的列表读每次调用都直读磁盘、写却在节流,读写永不一致。 - 经
sessionPersistence.locate(meta)解析路径(文件放在会话日志旁)。未采用:缓存得从日志 artifact 路径"猜"日志旁边(dirname+ 固定文件名),把缓存耦合到持久化服务与后端的布局。 - 把
per-record做成既有单元的一种模式而非独立单元类。 未采用:两种布局的状态模型本质不同——single内存权威、整文件发布;per-record无状态(目录即状态,loadAll重扫目录树)——所以它们是同一后端下的两个小型独立类,记录键做路径安全校验而非编码。