第 15 讲:文件系统、策略与 Workspace

DeepSeek Harness 源码解析 · 第 15 讲

DeepSeek Harness 源码解析系列 第 15 讲:文件系统、策略与 Workspace 基于 DeepSeek Harness 源码 · 2026-08-30 💡 本讲一句话: 模型改文件最怕"基于过期内容盲写"。这一套文件系统能力用三个零件防住它:每个文件带一个版本令牌、每次写入前校验版本、没读过的文件根本不让写——而且全部靠事件解耦,后端可换(本地/沙箱/远程)而工具层一行不改。 一、问题:盲覆盖是怎么发生的 上一讲我们看了子进程怎么被安全地 spawn 和终止。这一讲看另一个高频危险动作: 写文件 。 想象这个场景:模型读了 config.json ,思考了 30 秒,期间用户手动改了它。如果模型这时直接"整文件覆盖写回",用户的修改就没了——而且模型完全不知道。 更隐蔽的是:模型 根本没读过 这个文件就直接 write,等于对着一份它一无所知的内容做覆盖。DeepSeek Harness 的解法不是"祈祷模型别这么干",而是在文件系统能力层把这两种情况都变成 显式报错 。 先看这套能力的包结构——四个包各管一层: 包 职责 dsh-fs Service Definition:抽象接口 + 类型词汇表(FsTarget/FsVersion/错误码) dsh-fs-local 本地后端:realpath 身份、版本令牌、原子写 dsh-fs-observation-policy 观察策略插件:记录"谁读过什么文件",派生写/编辑守卫 dsh-tool-fs 模型可见工具:read / write / edit 的 schema、校验、输出格式 注意一个关键设计: 策略是插件,不是接口的一部分 。不装 observation-policy 插件时,工具退化为"无条件原子写"——能力层永远提供原子性,守卫由组合决定。 二、Service Definition:不透明的目标与版本令牌 所有操作都不直接吃路径字符串,而是先 resolve() 成一个 FsTarget 。这是整套设计的地基: 📄 packages/fs/fs/src/types.ts (第 60-83 行) export interface FsTarget {   targetKey: FsTargetKey  // 不透明键:本地后端是 realpath,远程后端可以是 URI/文件 ID   displayPath: string    // 给模型/UI 看的路径——两个字段刻意分开 } export interface FsInfo {   version: FsVersion      // 目标此刻的新鲜度令牌,是写守卫的比对基准   type: 'file' | 'directory' | 'other'  // 让策略层在读之前就能拒绝目录/特殊文件   size?: number          // 字节数:据此选 readText 还是 streamText,不用靠失败探测 } 为什么 targetKey 必须不透明? 因为本地后端和远程后端的"身份"完全不同:本地是 realpath(符号链接解析后的真实路径),远程可能是一个 revision id。把键做成 branded string、禁止消费方解析,工具层和策略层就永远不需要知道文件到底在哪——换后端时上层零改动。 再看写操作的"意图"类型,这是防盲覆盖的核心词汇: 📄 packages/fs/fs/src/types.ts (第 123-125 行) export type FsWriteIntent =   | { kind: 'createIfAbsent' }             // 只允许"创建":目标已存在 → FS_NOT_OBSERVED   | { kind: 'replaceIfVersion'; version: FsVersion }  // 只允许"替换":版本不符/文件消失 → FS_STALE_VERSION 注意注释里那句"省略 intent 表示无条件创建或覆盖, 不是第三种 union 分支 "——类型系统把"无守卫"表达为 expected?: FsWriteIntent 的可选参数,而不是一个 { kind: 'unconditi…

阅读全文:DeepSeek Harness 源码解析 第 15 讲