DeepSeek-Reasonix 读取证据生命周期(Read Evidence Lifecycle)深度解析:分页、源版本与写授权的三元分离
【免费下载链接】DeepSeek-ReasonixDeepSeek-native AI coding agent for your terminal. Engineered around prefix-cache stability — leave it running.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSeek-Reasonix
本篇技术指南围绕 DeepSeek-Reasonix 的读取证据生命周期机制展开,系统讲解分页读取、观察到的源文件版本、写入授权三者为何必须作为相互独立的事实分别管理,以及宿主(host)如何用「操作台账 + 观察台账 + 收据 ID + 源令牌」构建一套可验证、可恢复、防循环重试的读写一致性保障。读者读完后将掌握该机制的状态机、收据与源令牌的引用规则、写边界约束、机器可执行的恢复协议以及配套的可观测与验证体系,可直接对照源码定位每一条规则的落点。
背景:为什么读证据需要独立的生命周期
在 DeepSeek-Reasonix 的终端 Agent 工作流里,模型先读取文件、再执行编辑是常态。历史问题报告(对应本仓库演进中的 #9994/#9995 反复编辑问题,以及 #9966/#9992 的读取证据工作)暴露出一个根本矛盾:分页(pagination)、观测到的源版本(observed source version)、写授权(write authorization)被混为一谈时,会出现“模型声称读过、实际只读了部分”“基于过期快照覆盖新内容”“同一失败被无限重试”等错误。
该机制的设计原则很明确:三个事实各自独立记录、各自验证,且不放松任何权限或沙箱边界——它不扩大模型能做什么,只收紧“宿主能确认什么”。从源码看,这套机制分别落在四个运行时代理上:
| 拥有者(Owner) | 契约(Contract) | 源码位置 |
|---|---|---|
readcoord(读取协调器) | 追踪读取区间(ranges)、源快照(source snapshot)、EOF 与续读预算(continuation budget)。普通inspect/range缺口不阻塞最终化;已挂载的 Stop 仍会阻塞 | internal/readcoord/coordinator.go |
| 观察台账(Observation ledger) | 记录已交付的行哈希与顺序。当前 provider 批次内的读取不能授权该批次自身的写入;当旧观察早于最近一次写入时,去重会重新交付文本 | internal/evidence/text_observation.go |
| 操作守卫(Operation guard) | 冻结每个被拒绝操作的目标、版本、区间/哈希及 provider 边界;绝不以重放旧替换参数的方式清除一次拒绝 | internal/agent/operation_lifecycle.go |
| 操作台账(Operation ledger) | 端到端地拥有一次预期变更:佐证它的读取、应用它的变更、裁定它的验证,以及为其失败准备的有限恢复预算 | internal/evidence/operation.go |
操作生命周期:以“意图”为身份的状态机
一个操作的身份来自调用意图做的事情本身——真实目标与参数,而不是 provider 每轮分配的 call ID。这意味着同一编辑在换了一个新 ID 后重新提交,宿主仍能识别出它是同一个操作——这正是重复失败检测的前提。
在 internal/evidence/operation.go 中,状态集合被定义为一组显式常量:
prepared → applied → verification_pending → settled(终态) ↘ failed →(重试预算耗尽)→ needs_user(终态) ↘ unknown(宿主无法确认效果,只能由用户裁决)关键规则:
settled与needs_user是终态,永远不会二次转移(对应Terminal()方法);- 普通工作在真实工具结果上落定;只有「交付底线」(delivery floor)会持有一项变更,直到覆盖其路径的验证通过;
- 状态机完全由宿主维护:源码注释明确写道“模型从不写它:每次转移都是真实的工具结果,因此一次完成声明永远无法移动宿主未曾观察到的操作”。
重复失败即循环:预算与恢复纪元
同一个操作以相同方式失败两次,是循环而不是自我修正:宿主停止提供自动恢复,将其移入needs_user,并拒绝再次运行它(applyOperationGate会在模型以新 call ID 重发同一被拒调用时直接拦截,参见 internal/agent/operation_lifecycle.go)。恢复预算常量operationRecoveryBudget = 2位于 internal/evidence/operation.go,Fail()以(operation, failure_code)为键计数——不同的失败码是新信息,各自拥有独立预算;而同一拒绝出现两次就交给用户。
只有真实的新信息才能开启新的恢复纪元(NewEpoch):
- 源版本发生变化;
- 出现全新证据;
- 一次成功的写入取代了源;
- 用户开启了新的一轮(new user turn)。
值得注意的是,只有宿主拒绝才消耗该预算;一个真正运行并报告了实际失败的工具,其结果是信息,不消耗预算,仍走既有的重复失败与循环守卫。
操作 ID 的稳定推导
OperationID(toolName, args)对工具名与规范化后的参数做 SHA-256,取前 8 字节生成op_前缀 ID。参数规范化(canonicalArguments)会抹平键序、空白等装饰性 JSON 差异,并剥离source_token、expected_digest、receipt_ids、call_id、cursor等“单次执行句柄”——目的是让新源令牌开启的是同一操作的新恢复纪元,而不是绕过其预算。实现见 internal/evidence/operation.go。
收据与源令牌:引用必须精确
收据与令牌的形态
每个成功工具结果都携带宿主自己的收据 ID([receipt r_1a2b3c4d]),read_file结果则携带源令牌([source_token r_…])——两者是同一个 ID,可直接作为该读取所展示版本的句柄。接口层面的变化:
complete_step接受receipt_ids;edit_file、write_file、multi_edit接受可选的source_token。
在 internal/agent/source_token.go 中,citedSourceToken解析写入方传入的令牌,checkCitedSourceToken则对照宿主自己签发的读取记录解析它。关键校验链:
- 令牌必须在本轮内对应同一个规范化路径(
filepath.Clean)的读取,否则返回source_token_unknown,并给出具体恢复指令“重新读取该文件并引用其打印的 source_token”; - 若宿主记录中有快照且与当前快照不一致,返回
stale_or_partial_evidence,恢复指令为“重新读取并引用新令牌”; - 若令牌覆盖的窗口只覆盖写入替换内容的一部分,同样判定为部分证据,恢复指令为“重新读取缺失行并引用新令牌”;
- 只有覆盖完全满足时,才在操作台账上绑定该源令牌(
NoteSourceToken)。
引用 ID 是精确的,而匹配模型重打的命令文本不是:丢失的cd前缀、不同的引号风格、重排的 flag、不同的工作目录,都曾让确实运行过的验证被错误拒绝。因此命令文本仅保留为“未命名 ID 的引用”的兼容路径,其余情况只用于展示。
令牌能证明什么、不能证明什么
被引用的源令牌只能对照宿主的签发记录解析。以下情况证明不了任何事,会被拒绝一次并附带具体恢复方案:
- 令牌指向另一个文件;
- 令牌指向更早的快照;
- 令牌覆盖的窗口只包含写入所替换内容的一部分;
- 令牌根本不在本轮宿主签发记录中。
引用是可选的:不带令牌时,既有的快照匹配仍具权威性,因此普通的“先读后改”路径完全不变(internal/evidence/text_observation.go 的SourceToken()对未知句柄返回空——令牌是宿主签发的事实,绝不是模型可以拼装的字符串)。
整文件最终化与intent=full
只有显式工具参数intent=full才会产生整文件最终化要求。自然语言声明不会被机械验证或解析成新义务:一次普通的成功最终答复不构成完整审阅的证据,intent=full要求必须到达已验证的 EOF,或在既有预算内暂停。策略性读取的收据同样不证明完整读取——这正是分页证据与整读声明解耦的体现。
被拒操作的退役与备忘录
一次更早被拒绝的操作在以下情况退役:证据得到满足、一次新观察确立了不同版本或确认缺失、或一次完成的写入取代了它。未来的写入会独立检查它们各自的目标,互不背书。备忘录(memo)键包含 call ID、工具名、参数与观察边界,随批次过期;清理只移除完全相同的需求键。
写入边界:每类写操作的安全声明范围
- 编辑(edit):使用其真实预览(preview)的受影响区间与源身份,执行期间再次检查该身份。无版本约束的有界窗口能凭当前哈希证明单个区间,但不能跨版本拼接,也不能确立整文件覆盖。
- 整文件替换:要求完整的当前证据,或宿主已记录的既有重建授权。创建绑定“确认缺失”(confirmed absence);preflight 与执行之间若文件出现,不会被覆盖(internal/evidence/text_observation.go 中
Absent是确认的读取结果,绝不是从错误消息或目录列举中推断的)。 - 锚定删除:保留既有锚定审计;不新增
delete_file工具。 - 移动(move):保留字节,包括二进制文件——检查宿主观察到的源身份,不要求文本覆盖;目标与平台级移动检查保持原生。该机制并不承诺在最后一次身份检查后,对任意外部写入者提供文件系统级的原子 CAS。
- 纯元数据的
git commit -m:不欠文件内容证据;改变内容的提交形式保持保守。共享分类器识别git --no-pager diff/status/log;重定向、外部 diff 与任意-c覆盖不获得只读豁免。 - Shell 写入:只有字面
echo/printf输出重定向在本机制下具有可证明的 shell 写入范围。不相交目标不继承另一文件的阻塞;脚本、动态展开、glob 目标、命令链、钩子与未知范围保持不透明。 - 缺证据的 preflight 失败:允许同批次内已获批准的不相交单文件写入者放行;已执行的失败、钩子、歧义范围与依赖验证仍维持常规依赖屏障。
恢复与兼容:机器可执行的拒绝,而不是散文
诊断码全集
诊断使用内容无关的错误码(定义于 internal/tool/operation_diagnostic.go):
READ_PARTIAL、READ_CURSOR_INVALID、READ_SOURCE_CHANGED、READ_HARD_STOP、WRITE_EVIDENCE_MISSING、WRITE_EVIDENCE_STALE、WRITE_TARGET_ABSENT、WRITE_TARGET_AMBIGUOUS、VERIFICATION_RECEIPT_MISSING、VERIFICATION_RECEIPT_MISMATCH、OPERATION_NEEDS_USER。
它们携带可用的路径、操作、版本/区间与恢复信息,绝不携带文件内容。
封闭的恢复动作集合
一次拒绝是机器可执行的而非散文:它列出已存在的收据 ID、宿主接受的封闭动作集合,以及剩余重试预算(internal/tool/operation_diagnostic.go):
use_receipt:<id>— 引用某个已存在收据;reread_target— 重新读取目标;run_verifier— 运行验证器;mark_manual— 标记为人工处理;abandon_edit— 放弃编辑。
模型从集合中选择动作,永远不必猜测宿主会接受哪种措辞。诊断经ModelFacing()渲染为紧凑 JSON(recovery: {...}),单次拒绝最多列出maxCitableReceipts = 6个收据(见 internal/agent/operation_lifecycle.go)。预算耗尽后,操作带着下一步动作(continue_verification,暂停中的则是resolve_with_user)报告给用户,不再送回模型。
交付底线之外与验证分类
在交付底线之外,complete_step只是备注:证据可选,宿主无法确认的内容会与签注并列报告而非拒绝;参数形状仍会被校验。宿主无法识别为标准验证器的成功命令,在两种模式下都报告为“未分类”(unclassified)——因为项目常通过 Makefile、包装脚本和私有脚本验证——而交付门控仍独立要求在变更工作最终化前有被识别的验证。
兼容性矩阵
| 数据 | 新读取方读旧数据 | 旧读取方读新数据 |
|---|---|---|
| Read envelope v2 | 既有含义保留 | 协议不变 |
| 分页文本 | 接受旧 trailer 与PARTIAL view | 仅展示文本 |
可选tool_diagnostic | 缺失安全 | 未知可选字段忽略 |
LocalOnlyread_completion | 缺失安全;仅诊断 | 既有孤儿哨兵防止 provider 重放 |
| 读状态判定、暂停码/快照 | 既有 State/Reason 仍可用 | 可选字段忽略 |
| 被拒操作与 preflight 备忘录 | 新 Run 从空开始 | 不持久化;无需迁移 |
| 操作台账、收据 ID、源令牌 | 回合作用域;新回合从空开始 | 不持久化;无需迁移 |
complete_step.receipt_ids、写入方source_token | 可选;省略保持旧行为 | 未知可选属性忽略 |
| 诊断恢复字段 | 缺失安全 | 未知可选字段忽略 |
partial_read_sufficient意味着允许最终化,而不是宿主验证了模型的理解。规范覆盖收据仅作诊断,永不授权恢复写入;模型与压缩投影会剥离这些字段(涉及 internal/agent/projection.go、internal/agent/compact_projection.go 与 internal/agent/context_receipt.go 等投影实现)。完整短读保留其字节;部分结果与追加提示文本有变化。complete_step不再要求evidence并新增receipt_ids;文件写入工具新增可选source_token。这些模式变更会在升级时一次性改变稳定系统前缀;会话内前缀保持字节稳定。
可观测性:无内容计数器
状态转移向任意需要它们的 sink 发布内容无关计数器(实现于 internal/agent/operation_lifecycle.go 的auditOperation):
operation_settled_totaloperation_needs_user_totaloperation_recovery_attempt_totaloperation_duplicate_block_totalverification_auto_attached_totalverification_unclassified_totalread_source_changed_totalcomplete_step_optional_call_total
它们只携带宿主标识符——绝无路径、参数、命令或工具输出。真正回答“这套机制有没有生效”的三个指标是:每操作平均恢复次数(average recoveries per operation)、needs_user占比、未分类命令占比。
验证与回归覆盖
回归测试覆盖(internal/agent/operation_lifecycle_test.go、internal/agent/read_delivery_test.go、internal/readcoord/coordinator_test.go 等):
- 三轮重复的编辑/读取/重试循环;
- 变更锚点(changed anchors);
- 确认删除(confirmed deletion);
- 源/存在性竞争(source/presence races);
- 冻结批次边界(frozen batch boundaries);
- 不相交写入(disjoint writes);
- 不透明 shell 阻塞(opaque shell blocking);
- 部分/完整最终化(partial/full finals);
- Stop 优先级;
- 元数据投影(metadata projection)。
其中从 #9992 改编的真实 Build 回归会暂存一个一次性文件、读取大文件、提交并验证真实的 Git 提交,确保机制对真实工作流有效。本地测试、竞态检查、lint 与交叉编译是与远程 CI、原生 Windows 交互、真实 provider 资格认证相互独立的证据面。
小结:把“读过”变成宿主可验证的事实
DeepSeek-Reasonix 的读取证据生命周期用一套清晰的职责切分解决了终端 Agent 最棘手的信任问题:readcoord管“读到哪了”,观察台账管“宿主交付过什么”,操作台账管“一次变更从佐证到验证的全过程”,收据与源令牌则让模型可以精确引用而不是模糊复述。对使用者而言,这意味着更少的幽灵性文件覆盖、更少的死循环重试,以及每当自动恢复走到尽头时,一个带着明确下一步动作的、交给用户的终态。
【免费下载链接】DeepSeek-ReasonixDeepSeek-native AI coding agent for your terminal. Engineered around prefix-cache stability — leave it running.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSeek-Reasonix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考