DeepSeek-Reasonix 文件读取证据链重构:read_id、续读游标与写入证据门禁的设计与实践
【免费下载链接】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 是面向终端的 DeepSeek 原生 AI 编码代理,其工程化重点之一是 prefix-cache 稳定性——因此"模型读了多少、读的是哪个版本、能否据此修改文件"必须由主机精确记账。本文围绕 docs/plans/read-evidence-redesign.md 这一核心方案,系统讲解其文件读取语义(inspect / range / full)、稳定读取身份与主机签发游标、按操作检查读取证据的统一门禁,以及有界续读的预算控制与前端进度展示。读完本文,你将理解 DeepSeek-Reasonix 如何做到"局部查阅不欠债、全文任务不虚报、读不到不影响独立工作、改之前先验证"这四条默认行为,并能定位到对应的源码实现与测试用例。
1. 改造目标:四条默认行为,零配置切换
方案的第一原则是普通用户无需配置开关、无需选择技术模式、无需学习新操作,即可获得以下默认行为:
- 普通局部查阅不产生全文读取债务,不要求模型提交机械回执;
- 明确的全文任务可正确分页,无法完成时说明缺口,不虚报成功;
- 文件读取受阻只影响依赖它的操作,独立工作仍可继续;
- 修改前检查实际目标范围及内容是否仍有效;
- 重复读取有界退出,正常分页只更新一条进度状态。
方案同时明确列出不包含的内容:不增大默认上下文、不放宽修改权限、不移除截断标识、不重做会话日志、不通过"读取完成标记"来证明模型已正确理解代码。也就是说,这套改造解决的是"证据真实性"问题,而不是"上下文大小"问题。
从 internal/agent/agent_config.go 可以看出,新行为即为默认值,回退路径只以ReadPipelineOptions形式保留在主机内部:
type ReadPipelineOptions struct { LegacyCoordinator bool // 恢复旧的 incomplete-read 执行所有者 LegacyEvidenceGates bool // 关闭 writer 声明的证据检查 LegacyImplicitFullReads bool // 恢复"无窗口读取即承诺全文"的旧规则 }这三个开关"按 turn 固定"(agentConfig在 Agent 生命周期内不可变),仅用于诊断与紧急回退,不暴露为普通用户设置项,也不参与 prefix-cache 稳定前缀。
2. 基础契约:来源身份、读取快照身份、窗口摘要三者必须分离
方案反复强调三个概念必须分开,不能互相替代:
| 概念 | 含义 | 约束 |
|---|---|---|
| 来源身份(source identity) | 工作区、规范路径、实际数据来源(磁盘文件或编辑器 overlay),以及实际生成输出的原始字节/缓冲区的 SHA-256 | 不能在执行完成后重新探测来源、再给旧输出补发身份 |
| 读取快照身份(snapshot identity) | 同一次逻辑读取所使用的稳定内容版本,跨页保持不变;源内容变化时改变 | 局部读取不为此强制扫描全文 |
| 窗口摘要(window digest) | 该页实际交付内容的摘要,只证明这一页 | 不能替代整文件版本 |
协议版本为v2;v1 字段仅用于历史诊断,不授予修改许可。
关键机制如下:
- 逻辑 read_id:第一次读取创建;后续分页沿用它,但每次工具调用仍保留独立调用 ID 与
result_ref。 - 主机签发游标:游标绑定会话、运行代次(generation)、读取任务、规范路径、来源快照、请求范围和准确下一位置;校验发生在执行入口,"能解码不等于被接受"。拒绝跨会话、跨文件、过期、伪造及不匹配位置的引用;恢复运行时旧游标失效。
- 模型无需理解游标字段,可以直接使用续读引用。
- EOF 语义:
EOF必须携带可信的源结束位置;不能仅因某页出现 EOF 就判定整个 range 完成;空文件必须有明确的空文件事实。 - 证据裁剪:依据最终模型可见输出裁剪证据——被截断的半行、未交付原文、摘要和搜索结果不计作完整原文覆盖。
- 批次隔离:同一 provider batch 的新读取不能为该批次的修改提供证据。
- 深拷贝:协调器返回深拷贝快照;取消、版本变化及新一代任务使用generation fencing。
- 压缩后的历史:上下文压缩后的历史证据保留审计价值,但修改所需的当前可见片段仍须重新获取。
在源码层面,读取信封(envelope)由 internal/agent/read_result_envelope.go 负责构造与裁剪(clipDeliveredRead保证只有模型真正可见的字节才计入交付),快照版本变化时协调器会丢弃已累计的覆盖并递增 generation,见 internal/readcoord/coordinator.go:
// Fragments of two content versions must never be stitched into one // coverage claim, so a version change discards what was accumulated. if (ob.Version != "" && env.Source.Snapshot != ob.Version) || (env.Source.Snapshot == "" && ob.Pages > 0) { ob.Covered = nil ob.SawEOF = false ob.SourceEnd = nil ob.Generation++ tr.Stale = true }3. 读取语义:inspect / range / full 三级调度
统一协调器(Coordinator)接入真正的默认执行路径,统一负责意图、预算、进展、完成与暂停;旧状态机不再同时 enforce。读取语义表如下:
| 调用 | 默认行为 |
|---|---|
| 无范围、无 intent | inspect:有界预览,文件有剩余内容不产生全文债务 |
| 有 offset 或 limit、无 intent | range:只要求指定范围,按可信源结束位置截止 |
intent=full | 建立不可由模型自行降级的全文义务,用续读游标推进 |
| 冲突参数 | 返回明确参数错误和合法调用方式 |
全文义务的来源包括:明确用户要求、适用项目规则和已确认任务要求;任务契约保留其来源,不能因后续工具改用inspect而解除。方案同时诚实声明:自然语言任务理解仍属模型能力,不宣称主机能形式化识别所有全文审查意图。
执行入口在 internal/agent/read_pipeline.go:
readPipelineActive()判定新协调器是否接管(readShadow.enabled && !legacyImplicitFullReads);issueReadContinuation()依据协调器返回的 Transition 计算下一位置(取Missing[0].Start或最后覆盖区间的End),连同ReadID、规范路径、快照、会话 ID、运行代次、请求终点与绑定信息编码为ReadCursor注入信封;readContinuation()拥有执行决策:硬停止(hard stop)优先于其他文件的续读;策略武装(strategy-armed)的读取走显式定向恢复路径;模型请求的 full intent 是既有显式完整性契约,不从未经确认的用户散文或 preview 推断新义务;- 无进展时前缀切换为策略提示:"The last two pages added no content. Change strategy: use the host's exact next window instead of repeating the previous page.",并附后缀提醒 "Independent work may continue... do not claim a complete review until full coverage is verified."
协调器本体位于 internal/readcoord/coordinator.go,它管理全部义务(obligation),按 provider 顺序由单一 finalizer 喂入观察结果:
Begin在首次调用前注册义务,重新注册刷新需求但保留同一内容版本上的累计覆盖;Observe将一份交付信封折叠进义务:无身份或已终态的信封会被拒绝,已满足或已取消的读取不会被迟到的交付复活;enforcePolicy应用硬预算与无进展阶梯,内容变化不重置预算,只有满足或取消才结束预算。
4. 按操作检查证据:CheckOperationEvidence 与写入证据门禁
主机内部通过CheckOperationEvidence完成"按操作检查证据":真实 writer 声明目标和所需证据,复用其路径、编码、overlay、唯一匹配及区间解析,不信任模型自报的写范围。证据检查只判定"证据是否满足",永不授予许可——授权、沙箱、唯一性和原子替换保持独立。
各操作的证据规则:
| 操作 | 处理规则 |
|---|---|
| 搜索、列目录及可信只读工具 | 保持可执行,不因其他文件读取未完成而阻塞 |
| 创建新文件 | 不要求读取不存在的内容;执行时仍检查目标不存在条件及授权 |
| 精确编辑 | 要求目标旧文本及必要上下文已交付,执行前重新验证 |
| 范围或符号删除 | 要求完整目标、两端锚点及中间内容;替换旧的"必须无参数重读全文"fallback |
| 覆盖已有文件 | 默认要求完整当前内容证据;只有明确授权完全重建时例外 |
| notebook、移动等其他内置写工具 | 按真实影响声明证据,不静默视为安全 |
| shell/MCP 等未知写范围 | 保持原有授权边界;存在可能相关的未满足修改证据时保守阻止 |
| 最终回答 | 允许报告局部结果或明确阻塞;未满足的明确全文任务不得标记为整体完成 |
multi_edit仍是单文件原子编辑,本方案不将其改造为多文件事务工具;多目标操作与已声明写范围的工具批次执行统一证据预检,实际写入继续遵守原有原子性语义。
核心实现在 internal/agent/operation_evidence.go,几个值得注意的工程细节:
- writer 声明驱动:只有实现了
tool.EvidenceDeclarer的写工具才参与证据检查;不能声明自身影响范围的 writer 在存在未解除义务时会被blockUndeclaredWriter保守阻止(bash/shell会先尝试shellsafe.StaticWritePaths静态分析缩小义务范围,无法分析的命令保留全部义务)。 - 行哈希拼接:观察记录以"起始行 + 行 SHA-256 哈希"形式保存(
hashLine),stitchBySnapshot只把共享同一内容快照的窗口拼接成最大连续窗口;无快照的观察独立成窗——主机无法担保它们描述同一内容版本。evidenceCoversTarget逐 range 比较行哈希,跨快照绝不拼接。 - 全文件哈希上限:
maxEvidenceReadPages = 16、readEvidencePageLines = 2000,主机为证明全文件覆盖而自行读取最多 16 页,超出即报告whole_file_unverifiable,避免每次写入都扫描大文件。 - 批次级预检:
preflightEvidenceBatch在批次执行前评估每个 writer 的声明证据,被阻塞的调用根本不会开始执行——混有 read 与 write 的批次中,read 不可能为 write 买单。 - 重建授权归属用户:
recordRebuildAuthorization只记录用户指令中显式命名的重建路径(通过runtimepolicy.ParseRebuildPaths解析),rebuildAuthorized做集合成员测试;模型永远无法通过提示词文本自我授权,且拒绝否定语句、同名不同目录及文件名子串匹配。 - 同批隔离:
observationBoundary冻结批次边界,eligibleObservations只取"最后一次写入之后、冻结批次边界之前"的模型可见窗口,实现"同批 read 不能为同批 write 作证"。
5. 有界续读与成本控制:64 页、120 秒与无进展阶梯
needs_scope接入真实上下文预算:复用现有有效上下文、压缩阈值及任务预算,不扩大默认上下文,不自动压缩以强行塞入全文。初始内部上限如下:
- 每个逻辑读取最多自动推进64 页、累计主动读取执行时间120 秒;
- 同时受动态 token 预算与任务总预算约束,最先达到者生效;
- 等待模型响应不计入主动读取执行时间。
策略定义在 internal/readcoord/coordinator.go:
type Policy struct { MaxPages int MaxActiveTime time.Duration PivotAfter int // 连续无进展多少次后切换一次策略 PauseAfter int // 再停滞多少次后暂停 } func DefaultPolicy() Policy { return Policy{MaxPages: 64, MaxActiveTime: 120 * time.Second, PivotAfter: 2, PauseAfter: 2} }补充规则:
- 初始 inspect 页沿用现有行数与输出上限;正常局部读取不自动扫完文件;
- 连续两次无新增有效内容:切换一次有依据的策略;切换后再连续两次无进展则暂停该依赖链;
- 重复页、重复搜索、换调用 ID不算进展;文件变化不重置硬预算;
- 显式全文任务预算不足时保留未完成范围,继续独立事项,不自行降级;
- 旧策略回执保留为兼容适配器,依据主机证据验证;新流程不要求调用;
- 由现有通用进度保护统一决定暂停,避免一次行为被多个计数器重复处罚;
- 这些上限是内部默认值,不增加用户设置项;调整必须依据资源数据并记录。
身份捕获同样有界:inspect/range只对不超过 256 KiB的磁盘文件捕获全文身份;更大的局部读取仍为有界流式读取。显式full及其主机续读的磁盘快照上限为64 MiB,避免无界内存;超出时仍可局部读取,但不能拼接成未经证明的全文覆盖,未完成的 full 转为needs_scope。这也是内部资源边界,无新增用户配置。
6. 状态展示:每次读取任务一条状态,分页不抖动
带版本的读取状态事件以会话、回合、read_id、generation 和 sequence定位,前端按读取任务upsert 一条状态;正常分页使用中性进度,不逐页追加警告。细节规则:
- 局部页完成只在工具卡展示返回范围及是否还有内容;
- 全文任务显示已覆盖范围;总量未知时不显示虚假百分比;
- 暂停时显示原因、缺失范围及可执行恢复动作,默认折叠诊断细节;
- 活动状态由稳定宿主管理,避免分页更新改变历史消息几何;
- 覆盖取消、并行读取、会话切换、断线重连、乱序事件和历史恢复;
- CLI、serve、ACP 使用同一语义;旧客户端只收到有界起止及阻塞 Notice;
- 完成 en、zh、zh-TW 文案与无障碍通知节流。
前端测试位于 desktop/frontend/src/tests/read-status-upsert.test.ts,覆盖"连续 100 次更新仍只有一条活动状态""乱序事件不回退""新回合清空上一回合状态"三个关键验收点。
另外,本轮新增可选read_pauseLocalOnly 记录(internal/agent/read_pause.go)及incomplete_read回合 outcome,补充持久化边界:无存储迁移,旧会话缺失字段保持原行为,旧客户端通过既有 LocalOnly 工具标识忽略记录。摘要最多包含 32 个文件、每个区间字段 64 段,不保存正文或可执行游标;实时展示与历史回放按同一记录 ID 去重。该状态是未完成暂停,不是成功或网络重试;用户通过现有输入框补充要求继续。该记录不恢复旧游标,也不授权写入。
7. 验收矩阵:协议、任务、界面与跨平台
7.1 协议与证据(对应测试均可运行)
| 验收点 | 覆盖测试 |
|---|---|
| 多页共用稳定读取身份并累计覆盖 | TestRangeObligationPagesUntilCovered、TestOutOfOrderPagesStillSatisfyAWholeFileRead、TestReadContinuationCursorJoinsTheLogicalRead |
| 不同窗口摘要不被误判为文件变化 | TestReadEnvelopeNamesTheServingStore、TestReadEnvelopeSeparatesSourceIdentityFromWindowDigest |
| CRLF/UTF-16/编码差异不错误复用证据 | TestReadEnvelopeKeepsUnicodeWindowsIntact、TestReadEnvelopeSeparatesSourceIdentityFromWindowDigest |
| 半行截断不计作已读 | TestClipToNarrowsDeliveredRangeToVisibleBytes、TestReadShadowRecordsTheCoordinatorVerdict |
| 单独 EOF 不能伪造完成 | TestRangeCompletionNeedsATrustworthySourceEnd、TestWholeFileRequiresContiguousCoverageFromLineZero |
| 旧游标、跨会话、跨文件、过期引用被拒绝 | TestReadContinuationCursorRejections、TestReadCursorRoundTripAndMatching |
| 同批 read 不能为同批修改作证 | TestEvidenceGateIgnoresSameBatchReads |
| 跨快照分页不拼接 | TestEvidenceGateNeverStitchesAcrossSnapshots、TestEvidenceGateStitchesPagesOfOneSnapshot |
| host-only 元数据被剥离 | TestReadResultEnvelopeDoesNotAffectProviderVisibleBytes、TestModelInputMessagesStripsReadResult |
| 协调器状态不可被外部修改 | TestReturnedObligationsAreDeepCopies |
7.2 实际任务(执行链路回归)
| 验收点 | 覆盖测试 |
|---|---|
| 大文件局部查询不强制全文 | TestImplicitReadIsABoundedPreview |
| 明确全文任务分页到 EOF | TestExplicitFullReadContinuesSourcePagesToEOF |
| 覆盖/删除/精确编辑的证据规则 | TestEvidenceGateBlocksAnUnreadOverwrite、TestEvidenceGateAllowsAfterTheModelSawTheContent、TestEvidenceGateRejectsStaleContent、TestWriteFileDeclaresWholeFileEvidenceOnlyForOverwrites |
| 未知写范围不绕过证据阻塞 | TestEvidenceGateBlocksUnknownScopeWriterAfterABlock、TestEvidenceGateLeavesUndeclaredWritersAlone |
| 批次级预检先于执行 | TestEvidencePreflightBlocksBeforeTheBatchRuns |
| 用户显式重建授权(模型不能自授) | TestEvidenceGateHonorsAnExplicitRebuildInstruction、TestParseConstraintsRecognizesAnExplicitRebuild |
| 重复页与无进展有界退出 | TestRepeatedPageIsNotProgress、TestStalledPagesPivotOnceThenPause |
| 预算耗尽与内容变化不重置 | TestPageBudgetStopsContinuation、TestActiveTimeBudgetStopsContinuation、TestContentChangeDoesNotResetTheBudget |
| 未知上下文窗口不猜测 | TestReadShadowNarrowsAnUnboundedFullRead |
| 固定任务集的新旧策略对比 | TestFixedTaskSetComparesReadPolicies |
执行链路补测以 internal/agent/read_pipeline_regression_test.go 为准:新协调器实际控制续读、暂停和最终回答;旧状态机仅在内部回退模式运行。例如TestReadPipelineDefaultFullReadCompletesAndKeepsOneTask验证一次 full 任务分两页完成、协调器状态为StateSatisfied、无旧版警告、且两次交付窗口都被记账;TestReadPipelineBudgetActuallyStopsDispatch分别以 1 页与 1 纳秒为预算,断言续读被阻塞并返回结构化的有界暂停回执。
7.3 界面与跨平台
- 界面验收:
read-status-upsert.test.ts(单状态、乱序不回退、新回合清空); - 跨平台:
go test在 CI 的 ubuntu-latest、macos-latest、windows-latest 三平台矩阵运行,因此路径、换行与文件替换的确定性用例由 CI 覆盖;本机只验证了 macOS; - 仍需真实环境执行:Windows 原生 WebView2 与 macOS WKWebView 的界面与滚动验收(需要真实桌面与交互);真实 provider 下的任务集成功率与 token 对比(本仓库提供确定性 harness 覆盖轮数、读取次数、主机续读指令与放行写入数)。ACP 消费同一结构化事件,终端侧由 CLI 渲染。
禁止交付:错误放行、虚假全文完成、无限续读、跨任务证据串用。
8. 实施路径与当前状态
| 步骤 | 内容 | 状态 |
|---|---|---|
| 1 | 来源身份、稳定读取 ID、游标及覆盖判定 | 已完成(19d91b570) |
| 2 | 真实 writer 证据要求与统一预检 | 已完成:writer 声明、统一预检、批次级预检先于执行、未知写范围保守阻止、用户显式重建授权 |
| 3 | 预算、有界续读及旧回执兼容 | 已完成:预算与阶梯、旧策略回执作为兼容适配器保留(仍按主机证据校验,5 个测试覆盖),新流程不要求调用 |
| 4 | 结构化事件、单卡 UI、跨端与本地化 | 已完成:事件、桌面状态行、CLI 状态行与 en/zh/zh-TW 文案 |
| 5 | 默认启用新协调器,移除冲突的旧执行路径 | 已完成:新行为即默认,Options.ReadPipeline只保留主机内部回退开关 |
| 6 | 方案文档、工具说明、兼容说明与验收记录 | 已完成 |
默认行为已切换:无范围、无 intent 的读取是有界预览,不再产生全文债务;只有intent=full会分页到结尾。跨平台界面验收(Windows WebView2、macOS WKWebView)与固定任务集对比需要在具备真实桌面的环境执行,不能由本仓库的单元测试替代。旧活跃状态不能在半个工具批次中转换;恢复时重新验证,回退不撤销用户文件中已完成的修改。
9. 收尾修复与契约兼容细节
9.1 身份与记账职责划分
- 历史完成事实、当前请求可见原文、写入操作依据分别由协调器、请求可见索引和 writer 预检拥有;
- 读取结果在有序 finalizer 内按 workspace、规范路径、来源类型、原始身份和快照关联任务;同版本复读保持已满足状态,扩大范围保留覆盖与预算,只补缺失区间;新回合使用新的注册表和游标绑定。
9.2 引用语义收紧
结构化 reader 不再使用通用字符串去重。仅当本轮实际请求仍包含字节一致的原始结果、且区间已经覆盖时才能返回引用;引用不计入新增交付,不允许引用链或同批结果充当模型已见内容。原文被投影移除或被扩展改写后重新交付必要窗口,但不撤销过去的完成事实;扩展将正文替换成不可解析文字时也必须清除来源身份。
9.3 写入工具的派生规则
edit_file、multi_edit、delete_symbol、notebook_edit从真实 Preview 的最终修改推导原始所需范围;write_file覆盖要求当前原始版本的全文证据;delete_range继续由锚点审计拥有;move_file不替换源内容且拒绝覆盖已有目标,不要求全文阅读。
同批读取不提供同批写入证据;依赖前一步新文本的一组修改使用multi_edit以最初版本统一预检;历史分立调用链的兼容测试显式启用回退模式。写入执行再次检查预检来源,防止预检之后新增的用户内容被覆盖。未知范围写工具只受真实未解除义务约束;补齐前一轮证据后重新判断并解除历史阻塞;只读和不修改工作区的记账操作保持可用。
9.4 契约兼容
现有read_file的 intent/cursor 已属于本方案的模型可见 schema;更新 golden 固定该有意变化,升级首次请求可能重新建立工具前缀缓存(这正好与项目 prefix-cache 稳定性的核心关切呼应)。内部读取信封、原始来源摘要和写入预检数据不进入模型请求;不改变会话存储格式;旧游标在新运行中失效后须重新读取。真实 provider 成功率、token 费用及原生 WebView 验收仍须单独记录,不能由单元测试代替。
9.5 测试规模与 HTTP 上限
保留原始 64 组真实测试,增加 32 组定向场景及 12 组精确写入测试。HTTP 层限制全部上游请求(含重试)为 600 次,每次预留 128,000 token,未知用量保留预留额,总计最多 300 万 token 或 4 小时;代理不存储凭据和请求正文;模型执行任务失败与宿主不变量失败分别统计。
10. 结语:从"读没读"到"读的是哪个版本、能否据此改"
DeepSeek-Reasonix 的读取证据改造,本质上是把"文件读取"从模型的一个自由动作升级为主机可验证的契约对象:来源身份回答"内容从哪来",快照身份回答"读的是哪个版本",窗口摘要回答"这一页到底交付了什么",游标回答"下一步该从哪继续"。三者分离配合写入证据门禁,最终让模型既不会因局部查阅背上全文债务,也无法用残缺、过期或同批读取来伪造一次完整审查。对于关心 LLM 代理可靠性与防幻觉写入的开发者而言,docs/plans/read-evidence-redesign.md及其在internal/agent/、internal/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),仅供参考