资讯动态

LLM之Agent(九十六)|DeepSeek-Harness(六)Monorepo 结构与包职责地图

发布时间:2026/9/23 8:56:54 来源:尧图企业网站定制
DeepSeek Harness 的源码仓库不是一个大项目里塞了很多文件夹,而是一个刻意设计成291 个可独立发布叶子包的能力矩阵——packages/下 54 个一级目录只是能力领域的分类标签,真正的构建单元、发布单元、依赖单元永远是二级目录里的那个叶子包。理解这套两级目录的设计意图,是读懂整个仓库其余部分(构建体系、测试体系、供应链治理)的前提。本文对照的是0.1.6-alpha.2版本;仓库这几周迭代很快,包数量、分类目录、apps 列表都在持续变化,如果你本地find packages -mindepth 2 -maxdepth 2 -type d | wc -l数出来的数字和这里不一样,以你本地实际数出来的为准,不用纠结于具体数字是否精确匹配。学习目标通过pnpm-workspace.yaml的packages:字段,理解仓库把哪些顶层目录纳入了 workspace,以及每个顶层目录各自的角色。搞清楚packages/下一级分类目录 二级可发布叶子包两级结构为什么这样设计,而不是把几百个包直接摊平在packages/下。建立一张从apps/cli(以及apps/desktop)入口,经过packages/bundle/*组合层,一路下钻到具体能力叶子包的整体地图。认识vendor/、native/、python/、website、benchmarks这几个特殊顶层目录各自承担的非常规角色。能够看着任意一个deepseek-ai/dsh-*包名,推断出它大致属于哪个能力领域、扮演什么角色(抽象座 / 本地实现 / 模型工具 / UI 插件)。背景与设计动机如果把 DeepSeek Harness 的全部代码塞进几十个大包——比如一个core、一个tools、一个client——会发生什么?任何一次修改都会牵连一个体积巨大的包的重新构建、重新发布、重新做兼容性判断;想单独替换会话持久化用 SQLite 还是 JSONL这种实现细节,也没有清晰的边界可以替换。反过来,如果把 219 个包全部摊平在packages/下,人在ls packages/的时候会看到一堵无从下手的名字墙——dsh-fs、dsh-fs-local、dsh-fs-sandbox、dsh-tool-fs、dsh-tool-fs-search……这些名字之间的关系只能靠字符串前缀去猜。这个仓库选择的答案是两级目录:一级目录(如fs/、shell/、subagent/)是纯粹的能力领域分组,不参与构建、不对应发布单元,只是让人和工具能够按领域浏览;二级目录(如fs/fs、fs/fs-local、fs/tool-fs)才是真正的 npm 包,拥有自己的package.json、tsconfig.json、独立的版本号和发布边界。这背后是一条贯穿全仓库的架构原则——能力座(abstract seam)与实现(provider)分离:几乎每个领域都会看到一个不带后缀的抽象包(dsh-fs、dsh-shell、dsh-sandbox)定义ctx.fs、ctx.shell这类 Cordis 服务契约,再由若干个-local、-sandbox、-e2b后缀的包提供具体实现,外加tool-*前缀的包把能力座包装成模型可调用的工具。二级目录的颗粒度,直接决定了这条座 / 实现 / 工具三层结构能不能被讲清楚。核心机制详解顶层 workspace 边界:pnpm-workspace.yaml仓库的 workspace 成员由根pnpm-workspace.yaml的packages:字段精确定义:# pnpm-workspace.yaml当前版本,已实测核对 packages: - vendor/* - packages/*/* # The Landlock launcher is developed with its harness consumers but keeps # its native build and publication scripts under native/system. - native/system - native/system/packages/* # Product assemblies over the package tier; apps/cli owns the dsh bin. - apps/* # Private package owning repository-level benchmark dependencies. - benchmarks - website # Deploy root of the single-exe build: a pure dependency manifest whose # closure is what the exe bundles and what the Python runtime distributes. - python/sdk-runtime逐行拆解(注意:native/landlock-run已经重命名为native/system,原来的examplesworkspace 成员已经被移除,新增了benchmarks成员——这几处和课程写作时的版本相比都变了):vendor/*:vendored 进仓库的 Cordis 框架及其生态库(第 04 篇详细展开),每个子目录是一个独立包。packages/*/*:这才是本篇的主角——两级 glob,一级目录本身不是 workspace 成员,只有二级目录才是。native/system及其packages/*:进程沙箱相关的原生启动器所在目录(原名native/landlock-run,现已改名为更通用的native/system,因为它现在承载的不只是 Linux Landlock 一种后端),单独维护自己的原生构建和发布脚本,但作为 harness 的消费者之一被纳入同一个 workspace 以共享依赖解析。apps/*:产品层——真正被人安装、运行的东西,组装在包这一层之上。现在这个目录下有四个成员:cli(dsh命令行入口)、web(浏览器前端构建壳)、desktop和desktop-host(新增的 Electron 桌面应用及其宿主进程,下一节详细展开)。benchmarks:仓库级别基准测试的私有依赖宿主包,不对外发布。website:文档站点(VitePress),独立成员。python/sdk-runtime:单文件可执行构建的部署根——它本身只是一份纯依赖清单,pnpm deploy据此产出的依赖闭包既是可执行文件打包的内容,也是 Python SDK 运行时分发的内容。原来的examples成员(一个专门用来做依赖解析而非构建目标的可运行 demo 集合)在当前版本里已经不存在了——仓库里也找不到独立的顶层examples/目录或packages/examples/分类目录,原来归在这个分类下的 ACP/agent-spine/JSON-RPC SDK 演示已经被移走或整合进其他地方,不再是这套两级目录设计里的一员。linkWorkspacePackages: true配合overrides把 vendored 的deepseek-ai/cosmokit、deepseek-ai/schemastery强制链接回vendor/*下的本地源码,这一点第 04 篇会专门讲。两级目录:一级分类,二级发布单元用find packages -mindepth 2 -maxdepth 2 -type d | wc -l数出来的真实数字(截至0.1.6-alpha.2)是291个叶子包,分布在54个一级分类目录下——课程写作时是 219/49,一个多月里新增了 8 个分类目录、70 个叶子包,同时也有分类目录被整体移除,说明这套两级目录结构本身很稳定,但里面装的能力矩阵扩张得很快。下表是当前完整的分类目录 → 一句话职责地图(职责描述综合自各分类目录下叶子包package.json的description字段):分类目录一句话职责acpAgent Client Protocol 自动化服务器,驱动 harness agent 走 JSON-RPC stdioapiTypert 生成的 Remote 网关分发器与 Client API 端点、Remote BFF 组装attachment附件的抽象存储座与本地内容寻址实现boot启动期公共胶水:.env 加载、Loader 引导序列、命令行参数交接browser-use新增独占式命名的 browser-use 供应商注册座bundle三种可发布的整机组合:base(核心插件层)、headless(无 Host 一次性)、web-app(浏览器面)clientWeb 前端的 55 个叶子包:运行时、UI 插件、Slot 系统、连接层、主题compaction会话压缩策略、LLM 摘要后端、工具结果裁剪、/compact 命令computer-use新增独占式命名的 computer-use 供应商注册座contextAGENTS.md/CLAUDE.md 加载、时间上下文、tmux 上下文、跨会话引用coreAgent 接口与注册表、Session 事件溯源存储、Tools 执行管线、System Prompt 组装credentials凭证抽象座与本地 .env 实现deliverables新增显式工作区文件交付声明,以及基于 git 工作树快照的逐轮文件变更记录document新增Office 转 PDF 的共享能力,带限流队列和缓存experimental新增,16 个包Agent Teams(多智能体协作)、实验性浏览器/computer-use 供应商(Cua Driver、Stagehand、Playwright MCP)、跨域 CDP 调试桥、浏览器运行时 VFS 打包器等一批还在孵化的能力extensions模型可动态挂载/卸载的 Cordis 插件运行时(code mode沙箱)feedback会话/消息反馈的记录服务与斜杠命令fs文件系统能力座、观察策略、沙箱围栏、glob/grep 与 read/write/edit 模型工具goal同会话目标的事件溯源状态、执行时权限校验、斜杠命令guard重复工具调用提醒、工具调用超时策略hooksClaude Code / Codex hook 配置在 harness 拦截点上的桥接执行hostWeb GUI Host 侧:API 网关、静态资源服务、目录选择器、插件清单identity匿名用户 ID(遥测与反馈关联用)interaction用户提问、用户审批、权限预设、人类命令注册表jobs后台任务注册表(进程内实现)及模型侧 job 控制工具llm供应商中立的 LLM 服务接口、DeepSeek 适配器、请求重试、Token 计量lsp语言服务器能力座、stdio LSP 提供者、模型侧只读 LSP 工具mcpMCP 客户端桥接:连接 MCP 服务器并把其工具注册进 ctx.toolsplanPlan Mode:带部署指引的计划模式与用户复核退出preset会话级 Agent 组合预设(cordis.yml)与 persona 章节ptc-runtime新增,取代了原来的 code-runtime抽象的 PTC(进程化代码执行)能力座,及沙箱化 Node 进程、CPython 子进程两种具体实现runtime-diagnostics包自持有的运行时不变量注册表sandbox进程沙箱抽象座及本地后端:bwrap、Landlock(经 native/system 启动器)、macOS Seatbelt、Windows ACL 限制令牌——原来的 E2B 云沙箱后端(e2b 分类目录)已经从仓库里彻底移除,现在只保留同机进程级沙箱这一条路线scheduleAgent 范围内的持久化定时提醒(after/at/fixed-rate)sdk对外 stdio JSON-RPC SDK:协议、Server 插件、TypeScript 客户端session-query会话历史检索(SQLite FTS5 全文搜索)、模型侧查询工具session事件溯源持久化后端(JSONL/SQLite)、投影缓存、标题生成、遥测(现已扩展到 19 个叶子包)settings用户设置能力座与文件后端(settings.yaml)shellbash/pwsh 执行器座、本地/沙箱实现、持久 Bash 工具skillAgent Skill 提供者注册表及内置 skill(badge、filesystem)spill超大工具结果的溢出存储座与裁剪策略ssh新增,4 个包共享的 OpenSSH 连接与远程 POSIX helper,以及基于它实现的远程文件系统/沙箱/子进程/终端 provider——这是本课程第 06 章写作时还不存在的一整条远程执行能力线storageKV 存储中枢及 schema 校验的领域数据表单subagent子代理能力座及若干后端(fork/spawn 进程内、ACP、Claude Code、Codex、dsh SDK 跨进程,共 10 个包)subprocess托管子进程能力座与本地实现(输出限流、进程组升级击杀)terminal持久 PTY 会话座及 bash 后端test-support测试基建:LLM mock server、回放插件、ACP 快照套件、Loader smoke 工具todotodo_write 模型工具(基于事件溯源会话日志)typertTS 项目分析器、模型驱动的产物生成器/Loader 集成/运行时注册表(Typert)util零依赖工具原语:原子写入、品牌类型、超时、路径、输出保留webWeb 访问能力座及 search/fetch 各供应商实现(Exa、Perplexity、DeepSeek)webhook新增签名校验的 GitHub HTTP webhook 适配器,以及由 webhook 规则驱动、自动创建 Workspace 绑定 Session 的运行时workflow模型编排脚本引擎(worker_thread 执行、桥接回子代理调用)workspace工作区实体注册表(会话绑定的持久工作区记录)和课程写作时相比,code-runtime被ptc-runtime取代(概念更明确地叫PTC 执行而不是笼统的代码执行),e2b云沙箱整体被移除,packages/examples这个分类目录也不再存在;新增的八个分类目录里,experimental一个就占了 16 个包,基本对应Agent Teams 多智能体协作和浏览器/computer-use 自动化这两个还在快速迭代中的方向——如果你在别的章节看到多智能体浏览器自动化相关的新概念,大概率源码就在packages/experimental/*下。这张表里能看出一个反复出现的命名规律,以fs分类为例展开成真实的叶子包列表就是最好的示范:packages/fs/ ├── fs # deepseek-ai/dsh-fs —— 抽象能力座:ctx.fs 服务契约 ├── fs-local # deepseek-ai/dsh-fs-local —— 本地文件系统实现 ├── fs-observation-policy # deepseek-ai/dsh-fs-observation-policy —— 读前写策略 ├── fs-sandbox # deepseek-ai/dsh-fs-sandbox —— 沙箱围栏实现 ├── tool-fs # deepseek-ai/dsh-tool-fs —— 模型侧 read/write/edit 工具 ├── tool-fs-search # deepseek-ai/dsh-tool-fs-search —— 模型侧 glob/grep 工具 └── tool-str-replace-editor # deepseek-ai/dsh-tool-str-replace-editor —— 另一种编辑工具变体一级目录fs把这七个包聚在一起,是因为它们共享同一个业务语境;但它们各自的版本号、package.json、构建产物完全独立——dsh-tool-fs可以只依赖抽象的dsh-fs,完全不知道背后跑的是dsh-fs-local还是dsh-fs-sandbox。这正是两级目录设计要保护的边界:一级目录是给人看的地图,二级目录才是给构建工具、给依赖解析、给发布流程看的真实单元。从入口到框架层:整体地图把apps/、packages/、vendor/、native/、python/串起来,可以画出一条从用户敲下dsh到最底层的 Cordis Context的完整链路:用户终端 / 浏览器 / 桌面应用 │ ▼ apps/cli (deepseek-ai/dsh, bin: dsh → lib/bin.js) apps/desktop apps/desktop-host (Electron 桌面壳 私有 Node 宿主进程) │ apps/cli 是唯一的命令行可执行入口,也承担 dsh web 的浏览器 UI 别名; │ apps/desktop 是新增的 Electron 桌面应用,把打包好的 dsh 运行时和外部插件 │ 一起塞进一个桌面壳,apps/desktop-host 是它私有的 Node 模式宿主进程 ▼ packages/bundle/* (base / headless / web-app) │ 以 cordis.yml 补丁层的形式组合具体能力包—— │ base 是每个 profile 的第一层补丁,headless 在其上叠加 │ 无 Host、无 HTTP、无浏览器的一次性运行器,web-app 叠加 │ 浏览器面补丁 前端 dist 服务 web 专属系统提示词 ▼ packages/{core,fs,shell,llm,session,...}/* (291 个能力叶子包) │ 能力座(seam)与实现(provider)分离,以 Cordis 插件形式 │ 相互 inject/provide 服务 ▼ vendor/{cordis,loader,include,...} (source-vendored 框架层) │ Context / Fiber / Loader / Include 等 Cordis 核心机制 ▼ Node.js runtimeapps/web(deepseek-ai/dsh-web-frontend)是这条链路上的一个侍从分支:它是vite build over thedeepseek-ai/dsh-client-webshell library——也就是说浏览器前端的真正实现代码全部在packages/client/*里,apps/web只是把它 vite 构建成静态资源,交给apps/cli的dsh web子命令通过packages/host/frontend-static提供服务。这也解释了为什么packages/client/*会有单独的一套 tsconfig 和 tsdown 构建管线——这是第 02 篇的主题。apps/desktop(deepseek-ai/dsh-desktop,Electron desktop shell for a bundled dsh runtime and external plugins)和apps/desktop-host(deepseek-ai/dsh-desktop-host,Private Node-mode host process for the Electron desktop application)是课程写作之后新加进来的一对成员——桌面应用本身跑在 Electron 壳里,真正驱动 harness 运行时的是一个私有的 Node 模式宿主进程,和apps/webpackages/client的前端壳 / 真实实现分离是同一种设计思路的桌面版本。native/system(原名native/landlock-run)和python/sdk-runtime则是两个依附但独立的顶层目录:前者是本地进程沙箱相关原生启动器的构建产物(有自己的docs/、scripts/、tsconfig.base.json),被packages/sandbox/sandbox-local作为其中一种探测到即用的后端;后者不是代码,而是单文件可执行构建的纯依赖清单,pnpm deploy依据它算出的依赖闭包同时喂给可执行文件打包器和 Python SDK 的运行时分发。291 个包,一个版本号:统一发布策略两级目录带来了独立的发布边界,但 DeepSeek Harness 并没有让这些叶子包各自演化出互不相同的版本号——抽查几个分布在不同分类目录下的包会发现它们此刻共享同一个版本:deepseek-ai/dsh-agent 0.1.6-alpha.2 deepseek-ai/dsh-fs-local 0.1.6-alpha.2 deepseek-ai/dsh-brand 0.1.6-alpha.2这不是偶然的巧合,而是scripts/release/bump.ts里明确写死的发布策略,脚本顶部的注释直接给出了定义(措辞比课程写作时略有调整,但核心策略没变):// scripts/release/bump.ts /** * Bump one release familys version and commit it, so the published version is * ... * The dsh family shares one version across its publishable members, private * package manifests, and the workspace root: major, minor, patch, or an * explicit x.y.z (including a prerelease such as 0.0.1-rc.1). The vendored * family has one version line per package, but every release advances and * publishes the complete family so the next release never reuses an unchanged * members existing version from a different family. */也就是说,仓库里其实活跃着两条独立的版本线:根package.json(deepseek-ai/dsh-root)和所有deepseek-ai/dsh-*包属于dsh family,统一共享一个版本号,pnpm run release:dsh -- major|minor|patch|x.y.z一次性把这条族群的每个成员(现在连不对外发布的私有包也算在内)和 workspace 根一起推进;vendor/*下的九个包属于vendored family,各自保留自己的版本号语义(与上游同步时甚至会倒退到比当前版本更低的上游版本),但每次发布依然要求这个族群的全部成员一起推进,不允许只发布其中一部分。这个设计和两级目录、独立发布边界并不矛盾,反而是对它的补充:边界独立保证的是某个包可以被单独替换、单独依赖、单独测试覆盖,版本统一保证的是消费者不需要去猜哪个dsh-fs版本能兼容哪个dsh-tool-fs版本——这些包在任意一个发布时间点上,永远处在同一条时间线上,兼容性问题被从版本号排列组合降级成了这一次发布是否整体可用。命名空间小结:从包名反推物理位置结合前面的deepseek-ai/dsh-*通配路径映射(第 02 篇会深入讲tsconfig.base.json里的这套映射),包名到物理目录基本遵循这样的推断顺序:先看是否命中某个专属映射(比如dsh-sdk-client实际在sdk/client,dsh-host-*系列在host/*,dsh-client-*系列在client/*)——这类前缀映射通常对应仓库里体量较大的分类目录,为了避免deepseek-ai/dsh-client-ui-cordis这种命名被误判到client/ui-cordis(实际物理路径是extensions/ui-cordis),专属映射表要逐一列出例外。命中不到专属映射时,落到通配规则——去掉dsh-前缀剩下的名字,在core/*、llm/*、shell/*等几十个候选一级目录下按第一个匹配上的目录名获胜解析,这也是为什么二级目录名在全仓库范围内必须保证唯一,不能在两个不同分类目录下出现同名叶子包。常见问题/易踩坑不要把一级分类目录当成包:packages/fs/package.json是不存在的——试图import或pnpm add一级目录名会失败,必须精确到叶子包,比如deepseek-ai/dsh-fs。不要照搬课程文字里examples是依赖解析成员这类具体结论去核对当前仓库:这是版本迭代很快的仓库,examplesworkspace 成员本身现在已经不存在了(见前文);更通用的教训是,遇到任何仓库里有 XX 个包YY 目录是干嘛的这类具体断言,先用find/grep在自己本地的仓库里核实一遍,再当作事实使用。包名前缀不总能唯一确定所属分类目录:比如dsh-client-ui-cordis实际物理路径是packages/extensions/ui-cordis,而不是packages/client/ui-cordis——命名空间(dsh-client-*)是给消费者看的产品分组,不等价于物理目录分组;具体实现可参考tsconfig.base.json里deepseek-ai/dsh-client-*一类的路径映射,本文未逐一穷举所有例外。小结DeepSeek Harness 用54 个一级分类 291 个二级叶子包(数字仍在快速变化)的两级目录,把给人浏览的能力地图和给工具消费的发布单元彻底分离;apps/(现在包含 cli/web/desktop/desktop-host 四个成员)、vendor/、native/system、python/、website、benchmarks这几个顶层目录则分别承担产品入口(含桌面壳)、框架层、原生沙箱后端、单文件分发闭包、文档站点、基准测试六种不同但边界清晰的角色。理解这张地图,是继续往下读构建体系、测试体系、供应链治理三篇的基础——它们都是在这张地图上叠加不同维度的工程约束。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价