ByteNoteByteNote
DeepSeek Harness 工作区子系统设计解析
字

字节笔记本

2026年10月6日 · 约 7 分钟读完

DeepSeek Harness 工作区子系统设计解析

API中转
¥120

DeepSeek Harness(dsh)是 DeepSeek AI 在 GitHub 上开源的智能体框架,采用一切皆插件的架构,由 Cordis 插件框架驱动,仓库以 MIT 许可证发布,目前处于开发者预览阶段。它的设计文档按子系统逐一成文,本文继续拆解其中的工作区(workspace)子系统:它是用户工作目录的持久记录,由一个建立在规范路径之上的稳定 id、一个显示标题,以及归属于该目录的会话有序账本组成。

DeepSeek Harness 工作区数据模型与成员资格双重校验

对模型不可见的宿主侧能力

工作区由单个包 dsh-workspace 提供,暴露为 ctx.workspaceRegistry,是一项宿主侧可选能力。它不属于 agent loop 主干,对模型完全不可见:没有工具、没有提示词文本、也没有会话事件。换句话说,模型感知不到工作区的存在,它是纯粹为宿主与 GUI 组织会话服务的记账层。

它通过存储子系统的领域数据形式保存自己的记录,并对照会话持久化子系统的 SessionHeader 校验会话成员资格,因此 storageDomain 与 sessionPersistence 是必需的启动依赖。这样设计有一个直接好处:持久化依赖不可用时,插件保持 pending 状态,而不会把这种不可用误判成一个空历史。

标识:稳定 id 与规范路径分离

每个工作区记录由 WorkspaceId 标识,它是一个品牌化 id,取值为生成的 uuid,绝不使用路径本身。原因在于路径会被规范化改写,而引用锚点必须保持稳定。

路径标识与之分离:realpathNormalize 基于 fs.realpath 实现,把尾部斜杠、.. 与符号链接全部解析,是唯一性判断的唯一规范。工作区路径以规范化形式存储,两个工作区是否同一个,就看规范路径的字符串是否相等;指向已被占用目录的符号链接会与之冲突,attach 时的会话 cwd 检查也走同一套规范。

实体与成员资格的双重校验

消费方只能看到 Workspace 接口,实现保持在包内私有。一条记录包含:稳定的 uuid id;创建时给定的规范目录路径,此后即使目录消失也不改写;显示标题,默认取路径的 basename,允许重复;ISO-8601 的创建与更新时间戳;以及有序的 sessionIds。

所有权的真源是记录中有序的 sessionIds,绝不从会话 cwd 反推。但成员资格要求两个条件同时成立:账本上有该会话的 id,且会话 header 的规范 cwd 等于工作区路径。因此一个会话在结构上至多属于一个工作区。账本顺序由人工维护:新会话在 attach 时前插,显式重排走 insertSessionBefore,活动本身从不重排。读取时账本会被同步过滤:缺失 header、cwd 非法或规范 cwd 不匹配的候选项不会返回;每次被接受的变更还会把这些失效候选项持久修剪掉,并盖上 updatedAt。失败的写入会直接拒绝,其中账本操作非法以专门的移动错误拒绝,存储失败以普通错误抛出。

attachSession 的规则很严格:已记账的 id 直接返回,不产生写入;新 id 的实时或持久化 header cwd 必须解析为一个存在的目录,且等于工作区路径;未知 id、cwd 缺失、非法或不匹配一律拒绝且不写入。detachSession 是幂等的,移除不在账本上的 id 什么都不写,且从不触碰会话自己的存储日志。status() 做一次不缓存的实时检查,返回目录当前是否存在:目录缺失从不改写记录,因为它可能只是被临时移走。

注册表:注册、排序与删除

注册表拥有注册与解析。create(path, title?) 先规范化路径,不存在的路径原样传出 ENOENT,非目录同样拒绝;规范路径已被占用时原样返回既有实体;否则创建一条标题为 title 或 basename(path) 的记录,并前插到持久的注册表顺序中。get(id) 与有序的 list() 是同步缓存读取;resolveByPath(path) 应用同一套 realpath 规范但不创建。注册表还提供 insertBefore,按 DOM insertBefore 的语义移动工作区在展示顺序中的位置:有锚点就插到锚点前,无锚点就追加到末尾,返回完整提交后的顺序。另有 archiveSession,把一个会话持久归档,无论它是否归属某个工作区,已归档的 id 重复调用不产生写入。

delete(id) 只移除注册记录、顺序条目和会话账本:目录、用户文件、实时会话和已持久化的日志一概不动,这些会话随之变为未分组;未知 id 返回 false。

DeepSeek Harness 工作区的会话接入流程与崩溃安全设计

崩溃安全:待定标记与一次性引导

create 与 delete 各自涉及两次写入(记录加顺序条目),为了防止两次写入中途中断造成分叉,注册表会先持久写入一个待定变更标记。启动时恰好解决被标记的那一次变更,方式是删除被标记的表行:这会补完被中断的 delete,并回滚被中断的 create。回滚之所以是安全方向,是因为注册可以随时重建;而没有标记的顺序与表不一致,则作为损坏大声失败。

会话的 cwd 在创建时由创建者赋予,而不是由注册表赋予:API 网关从所选工作区的 path 解析新会话的 cwd,必要时回退到显式或默认值,先创建会话,使 cwd 落入其不可变的 SessionHeader,再调用 attachSession,由后者把已存储的 header cwd 与工作区路径重新校验一遍。

首次成功启动时,注册表仅凭已持久化的 header(id、cwd、createdAt,绝不读事件正文)引导历史:把规范 cwd 有效的会话按目录分组为工作区,最新的排在最前;已初始化标记最后写入,因此被中断的引导可以安全续跑。引导只发生这一次:没有 cwd 的历史遗留会话保持未分组,此后创建的会话只能通过 attachSession 加入工作区。

消费方:谁在真正使用它

dsh-host-apiproxy 是产品消费方:它经 ctx.workspaceRegistry 向 GUI 客户端提供工作区的增删改查,并执行上文先建会话再 attach 的流程。dsh-agent-instructions 尽管名字里带 agent,却不是消费方:它在 agent 自己的 cwd 下发现 AGENTS.md 风格的指令文件,从不触碰 ctx.workspaceRegistry。两个包共用的这个词指的是用户的工作目录,而非注册表里的实体,读代码时不要混淆。

小结

工作区子系统把目录、标题与会话账本这件小事做得很严谨:uuid 与规范路径分离保证引用稳定,账本与 header 的双重校验保证一个会话至多属于一个工作区,待定标记与最后写入的初始化标记把两类中断都变成可恢复操作,而模型全程无感。对想给自己的 agent 框架补上会话组织能力的开发者来说,这是一份可以直接借鉴的设计模板。

相关文章

分享: