Files
deepseek-harness/docs/rfc/implemented/architecture/2026-07-05-reconstructable-requests.zh.md
T
Ziya 2565133af3 docs(i18n): RFC tree batch — 146 bilingual pairs via the committed pipeline
implemented(除 4 篇超长文档随后补)、proposed、rejected 全树配对;
同一流水线 + 二遍校验(paraphrase-back + 仓库上下文一致性)产出。
docs/rfc/implemented/AGENTS.md 与其 CLAUDE.md 符号链接列入排除
(agent 指令文件,与根 AGENTS.md 同策略)。
2026-07-15 23:25:06 -07:00

9.1 KiB
Raw Blame History

RFC:每个 LLM 请求都可从会话日志重建

Status: implemented

English | 中文

问题

请求流水线此前不保证前缀稳定性以利用提供方缓存,会话日志也无法重建模型实际看到的内容。日志遗漏了 model、系统提示词和工具 schema,同时允许逐次调用的请求改写。因此缓存行为和回放等价性取决于碰巧加载了哪些插件。

快乐路径的参考形态是 MiniCode 的 LLMClient:一个有状态的对话客户端,随对话推进只追加、从不重建,仅在系统提示词、工具集或压缩(compaction)真正改变了模型必须看到的内容时才重置。本 RFC 回答的设计问题是:如何在不放弃事件溯源的前提下获得这种纪律。

决策

原则

模型可见 ⟺ 已记录。 凡到达模型请求的内容,都必须记录在会话日志中。可检查的推论:循环发出的每个对话请求都是会话日志的纯函数——任何持有日志的人都能逐字节重建它。精确的范围说明:保证覆盖循环构建的 GenerateOptions;提供方协议格式(wire format)字节由它推导而来,因为两个适配器的序列化在固定代码版本下都是逐消息的纯函数;直接的一次性调用(压缩的 summarize 调用)记录其信封标量(compact/summary.{model, maxTokens}),其输入是对已记录区域的确定性代码运算——可从日志加代码重建,通过 unfrozen-request 标记排除在不变式之外。

前缀缓存稳定性是推论 #1,而非标题:一个仅追加的日志经逐节点纯函数投影,在 header 不变时自然产出前一请求的追加扩展——稳定性是涌现的,不是管理出来的。逐字节精确的审计/回放是推论 #2;带可归因漂移的恢复与 fork 是推论 #3。

机制

消息。 Session.deriveMessages() 带缓存:每个 surface 节点在首次出现时通过公开的逐节点函数 deriveEventMessage(event) 精确投影一次;surface 改写(压缩的 replace——SurfaceManager.replaceGeneration)触发重建。调用方每次获得一个新数组,其中的消息是共享的、深度冻结的:通过投影修改已记录的历史是不可表达的(会抛异常),取代了旧的每次调用克隆隔离。外部重建器对日志前缀折叠同一个公开函数,因此不可能有两条路径产生分歧。

EpochHeader 记录请求的非历史状态:调用配置、渲染后的系统提示词、工具 schema 和会话前缀,空值规范化为缺失。request/header 写入完整的初始、恢复或回退快照。request/header-delta 通过公共前缀/后缀行裁剪编码系统提示词变更,通过按名称键控的增/删/改编码工具变更,通过完整替换编码配置或前缀变更。foldRequestHeader、diffHeader 和 applyHeaderDelta 是纯编解码器。每个循环实例在其首次请求时写入一个快照,以锚定进程边界。Delta 仅是优化:写入方验证往返等价性,对不可表达的变更(如纯工具重排序)回退到完整快照。

每一步重建 prompt 组装。实例的第一步中,agent/session-prefix 用仅限请求的开场消息扩展一个冻结的空种子;结果被冻结并缓存于该循环实例。agent/pre-step 随后在消息快照紧接 step/start 之前接收组合后的前缀。首次调用配置从显式的 AgentOptions 出发,保留 fork 覆盖和恢复重配置;后续调用从折叠后的 header 出发。agent/request 只能替换那个冻结的配置种子,而模型可见的内容通过已记录的通道进入。循环记录欠写的 header 事件(前缀唯一的持久化归属),从前缀、快照和 header 构建 GenerateOptions,并深度冻结它,同时保持 AbortSignal 活跃。每实例状态仅有缓存的前缀和其锚定快照是否已写入。

step/start 是重建边界。 一步从该序列之前的事件派生消息。快照之后的注入加入下一次请求,事件发布期间的重入追加被拒绝。agent/pre-step 是当前请求所需内容的 seam。Header 重建折叠该步骤自身的 request/header* 事件,或在无新 header 写入时沿用前一次折叠结果。

强制执行。 在开发环境中,dsh-invariants 通过一个全新的 Session 独立重建每个循环请求,使活跃缓存无法为自身背书,然后在 llm/stream 处比较消息和折叠后的 header 字段。循环请求通过其冻结形态和 session id 识别;直接的一次性调用被排除。正确性依赖于序列有界的重建而非监听器顺序。带密钥的 e2e 要求首次请求之后出现正数的 cache-read token;逐步 usage 是生产信号,header 变更或压缩表现为下一步 cache-read 的下降。

MiniCode 形态:采纳,但溯源箭头反转

与 MiniCode 一样,对话仅追加推进,仅在模型可见状态变更时重置。与 MiniCode 不同的是,事件日志仍是真源,因为它还拥有持久化、恢复、边界、工具配对和溯源。Session 缓存从日志派生的消息和 header 折叠结果,使每个请求都可独立检查。

曾考虑的替代方案

  • 客户端作为真源(照搬 MiniCode):在日志之外出现第二个生效的真相——两者漂移而无人察觉;见上节。
  • 镜像日志的有状态传输客户端:重复对话状态,需要围绕监听器做回滚,留下未记录的编辑面,且仍无法重建请求 header。Session 拥有的缓存加已记录的 header 避免了这些分裂的真相。
  • 逐次调用的请求标量(每次 agent/request 分发时传入一个可自由修改的配置):监听器可以零记账地逐次切换 model,悄然放弃本设计旨在保护的提供方缓存。配置是逐对话的已记录状态;waterfall(瀑布式事件)提议,日志记录。
  • 检测并报告(比较连续请求,发现分歧时警告):事后捕获违规;违规请求仍可构造并发出。因接口层面的不可表达性而否决。
  • 事件驱动组装(仅在变更信号时重新渲染):存在信号遗漏的 bug 类别——会话中途注册的工具发出 tools/change 而非 system-prompt/change,第三方提供方可能什么都不发。逐步渲染加值比较在零信号纪律下仍然健壮。
  • Header 事件上的叙事字段(delta 上的 reason/changed 列表):可通过 diff 连续事件派生——每个事实只有一个归属;快照携带 reason 是因为锚点的成因无法从数据本身派生。

后果

  • 一个无法由日志解释的请求不可能被意外构造——无论是循环还是监听器;修改已构建的请求会抛异常;每次 header 变更都是一个持久的、可 diff 的日志事件。
  • 在建议通道之间做选择是变更频率决策,而本设计让稳定的那个成为结构性的:agent/session-prefix 的贡献在每个循环实例中只组合一次并逐字复用,因此它以零边际成本扩展可缓存前缀,且不可能在会话中途击穿提供方缓存;会话中途变化的内容通过仅追加的历史通道流入——agent.inject()、tools/post-execute 决策的 additionalContext、prompt-submit 的 additionalContext——每个都是持久的 context/message,付出一次代价后即享受前缀缓存,代价是在历史和日志中累积。将会话冻结的开场内容路由到前缀,将变更通知路由到历史通道;逐步的仅限请求尾部槽位被有意放弃(无消费方,且持久追加覆盖了所有当前更新模式)。
  • 在提供方处仍需全价的内容是固有的且已记录的:压缩(其 compact/* 事件和 replace 节点)、真正的 prompt/工具变更(request/header-delta)、配置切换(同上)、带漂移的进程边界('resume' 快照与前一个不同)。提供方自身的 reasoning-content 排除由服务端管理。
  • step/start 监听器行为变更(见上文)是对插件唯一可观察的语义变更;agent/pre-step 是当前请求的 seam。
  • 工具结果裁剪(计划中)无需新机制:一个已记录的单节点 surface replace(start === end),携带同一 callId 下裁剪后的 tool/result——属于压缩家族,回放正确,缓存击穿由相同的压力逻辑批量处理。
  • 会话日志每个对话增长一个 request/header 快照(系统提示词 + 工具 schema:主导项),加上真正变更时的 delta——相对于 assistant/chunk 的体量很小;SESSION_FORMAT_VERSION 保持 0(预发布期间的变动被吸收,后端拒绝而非迁移)。
  • 快照 golden 文件变更一次(每份 transcript 增加其 header 事件);写文件系统的 fixture 以规范化的撰写形式存储,工具参数使用 cwd 相对路径,因为回放只往返 cwd 无关的参数路径。
  • FIXME(call-config-shape):重新审视 LlmCallConfig 的确切字段集——哪些字段对缓存而言真正属于 epoch 级别(model 毫无疑问;采样标量出于谨慎放在那里),以及当适配器需要时,提供方特有的额外项(reasoning 选项、额外 body 参数)应归属何处。