27. DeepSeek Harness 源码全景与知识体系渗透

Harness Engineering

27. DeepSeek Harness 源码全景与知识体系渗透

本章不重复前 20 个知识维度的通用原理,而是回答“DeepSeek Harness 怎样把这些原理做成一个可组装、可恢复、可对外发布的产品”。阅读时不按包名孤立记忆,而要始终追三条链:组装链决定当前运行了哪些插件,执行链决定一次模型提议如何变成受控副作用,事实链决定界面、恢复、检索和遥测如何共用同一份可证明状态。

阅读本章前:先建立“一切皆插件”的心智模型

DeepSeek Harness 一切皆插件架构

可以把 DeepSeek Harness 想成一台由配置装机的计算机:Profile 选择产品形态,Bundle 给出基础配件,Patch 做部署级替换,三者生成实际加载的 Cordis 插件树;Service 是能力插槽,Event 是消息与拦截总线,Effect 管理热插拔和资源回收,Scope 则让 Root、Preset、Agent 与 Child Agent 获得分层能力视图。图中的 Session、Agent Loop、LLM、工具、安全、Web 和遥测不是“核心之外的可选外挂”,它们本身都由插件贡献。

因此,“仓库里存在某个包”不等于“当前产品拥有该能力”。确认一项能力必须依次回答:它的 Definition 在哪里、哪个 Provider 实施真实副作用、哪个 Consumer 提供模型或用户入口、当前 Profile/Bundle 是否真正把它组装进插件树。插件产生的 Turn、Message 与 Tool 事实最终写入 Session Event Log,再被恢复、投影、查询、界面和遥测复用。

27.1 证据基线、成熟度与阅读规则

项目本章基线
本地路径/deepseek-harness
Git 提交47f943859bef60e4160492346772ded9b24f765a
包版本0.1.0-rc.5
运行时Node `^22.19.0
产品状态开发者预览,尚未承诺磁盘格式和公共 API 长期兼容
主要事实来源packages/*/*/src、当前 cordis.patch.yml、生成目录和中文架构文档

本章将“存在包”、“存在可加载插件”、“当前 Bundle 已组装”和“默认配置真正启用”分开。例如 session-query-sqlite 已在 base bundle 挂载,但默认 openAt: never,因此精确读取与血缘追踪可用,全文搜索仍是显式选配;session-telemetry-otel 已挂载,但默认 mode 为 DISABLED。

建议每验证一项能力都保留四份证据:Service Definition 的类型与事件契约、Provider 的真实副作用、Consumer 的模型或用户入口、Bundle 中的实际组装行。少任何一份,都不应声称产品闭环已成立。

27.2 底层编程模型:Cordis 中的 Service、Event、Effect 与 Scope

DeepSeek Harness 的“一切皆插件”不是口号,而是四个组合原语:

  1. Service 为 ctx.llm、ctx.tools、ctx.sessions、ctx.fs、ctx.sandbox 等能力提供唯一或注册表式入口,消费者依赖定义而不是具体提供方。
  2. Event 提供 emit、parallel、serial、waterfall 四种分发语义;waterfall 监听器必须调用 next() 才会委派到下一层。
  3. Effect 将注册和 disposer 绑定,插件卸载、HMR 或 Scope 结束时撤销工具、提示段、监听器、连接和缓存。
  4. Scope 把全局、Preset 和 Agent 层的贡献叠加为查找链,同名贡献按近端优先;子 Agent 的工具限制、Persona 和策略只对子 Scope 可见。

图表加载中…

实战阅读时,先在 docs/capability-seams.zh.md 查 ctx 键的角色,再进入相应包的 src/index.ts,最后搜索 ctx.<service>、ctx.on('<event>') 和 Bundle id。这比从文件名猜测调用关系更可靠。

27.3 组装链:Profile、Bundle、Patch 与启动入口

一个运行中的 dsh 由空条目列表按顺序叠加:Profile 宣告的多个 Bundle → Profile 自己的 cordis.patch.yml → Harness home 级 patch → 命令行 --patch overlay。Patch 通过 id 定位行,替换整个 config 而非深层合并,因此后层必须重述所有应保留字段。

组装层当前主要职责
bundle/baseLLM、Session、Agent Loop、Tool Runtime、JSONL 持久化、文件/Shell/Skill/Web Search、Plan/Goal/Todo、Subagent/Workflow、Approval/Sandbox、Telemetry
bundle/web-appHost WebServer、API Gateway、Web transport、Storage/Workspace、Projection Cache、Browser Client plugin roster
bundle/headless一次性任务解析、Agent 创建、结果输出,不挂载 HTTP/Browser
Profile/Home/User overlay选模型、增删插件、替换 Provider、更改策略和产品默认

packages/boot/app-boot/src/profile.ts 是 Profile 发现和 Bundle 解析的核心路径,dsh --profile web --dump-config 则是检查最终插件树的一手证据。对一项“为什么我的 Tool/Provider 没有生效”问题,应先看 dump-config 和服务注入,再看具体执行代码。

27.4 核心主干:Session、System Prompt、Tools、Agent 与 Agent Loop

主干ctx 键所有的核心事实
core/sessionctx.sessions仅追加 Session、Event seq、Surface、deriveMessages()
core/system-promptctx.systemPrompt有序 section、runtime context、工具 schema 组装
core/toolsctx.tools工具注册、Scope 限制、策略流水线、Code Mode、呈现意图
core/agentctx.agentsAgent 工厂、活跃句柄、Inbox、agent/* 事件
core/agent-loopctx.agentLoop默认 React Loop、Turn/Step、模型请求、工具调度、终止原因

它们之间的关系是“主干最小化、行为从扩展点注入”。Agent Loop 没有直接导入 Skill、Compaction、Approval、Subagent、Plan 或 Telemetry;这些包通过 agent/pre-step、agent/request-error、agent/turn-stopping、tools/*、session/event 或 System Prompt 贡献接入。新功能若可以通过已有事件或 seam 表达,就不应修改 agent-loop。

27.5 一次 Turn 的完整时序

图表加载中…

这条时序中最容易忽略的是:agent/pre-step 可以拒绝或改写已领取消息,但首次领取被拒绝或改为空仍会产生一个持久 Turn 边界;Tool Result 先作为 Session Event 保存,再进入下一 Step;Turn 的最终原因从 completed/max-tokens/blocked/aborted/error 中明确选择,不用“驱动器停了”代替业务终态。

27.6 工具执行:从模型 ToolCall 到受控副作用

图表加载中…

并发调度保留两种顺序:Provider 可并发运行,但策略、结果持久化和模型下一次看到的 Context 按原 ToolCall 顺序提交。每个待启动调用都重新读取当前注册表与 concurrency mode,因此在前一个工具运行期间卸载插件或改变策略,尚未启动的后续调用会看到新状态。

Code Mode 通过 run_code 让模型写程序调用多个工具,但 sub-call 带 parent execution token 重新进入同一 Pipeline,仍受 Tool Restriction、Timeout、Spill 和每个能力的安全策略约束。“组合多调用”不等于“获得绕过治理的内部 API”。

27.7 能力 seam:Definition、Provider、Consumer 的完整闭环

能力Service DefinitionProvider 例子Consumer 例子替换时保持的契约
LLMllm/llmllm-deepseek、llm-pi-ai、llm-replayagent-loop、compaction-basicMessage/Chunk/Error 词汇、路由与取消
Filesystemfs/fsfs-local、fs-sandbox、fs-e2btool-fs读写编辑语义、观测事件、工作区限制
Shellshell/shellbash-local、bash-sandbox、pwsh-*tool-bash、tool-pwsh、Hooks请求/规格分离、输出、超时与取消
Sandboxsandbox/sandboxsandbox-localbash-sandbox、terminal-bash精确 argv 包装、enforcement 说明、拒绝分类
Skillskill/skillskill-filesystem、skill-badgetool-skill、Host 显式调用目录、优先级、调用权限、按需正文
Webweb/webDeepSeek/Exa/Perplexity Search、HTTP Fetchtool-web稳定 Tool Schema、Provider 选择、超时和结果
Subagentsubagent/subagentspawn/fork-in-process、ACP、Codex、Claude Code、DSH SDKtool-subagent*调用身份、血缘、终止原因、续跑语义
Persistencesession-persistenceJSONL、SQLiteAgent Loop、Hooks、Session Query连续 seq、durability、恢复与 revision
Telemetrysession-telemetryOTel记录后端/反馈流程Ledger/Ops 记录、脱敏点、shutdown drain

判断 seam 是否完整的方法是反向替换 Provider:把 fs-local 换成 fs-e2b 时,tool-fs 和 Agent Loop 不应改变;把 JSONL 换成 SQLite 时,Session Event 词汇与 Resume 语义不应改变。若替换必须改 Consumer 的分支或请求类型,说明 seam 泄漏了提供方细节。

27.8 Context、Skill、MCP 与 Compaction 怎样进入主链

图表加载中…

Skill 和 MCP 的共同点是“不把外部能力绕过 Tool Runtime”:Skill 正文通过 ToolResult 或显式 Message Source 进入模型,MCP Tool 被转换为普通 ToolDefinition 后进入统一策略和 Session Event 链。区别在于 Skill 提供可执行指导文本,MCP 提供跨进程/网络动作入口;前者的主要风险是指令信任和版本,后者还要处理连接代际、名称空间、协议输入和远端失败。

27.9 Plan、Goal、Todo、Jobs、Workflow 与 Subagent 的责任边界

概念状态所有者持久化解决的问题
Plan Modectx.planModeplan/mode + Projection当前是否只许探索和提交计划
Goalctx.goalsgoal/change 折叠跨 Turn 的用户目标、revision 和续跑边界
Todotool-todo/Sessiontodo/write当前 Agent 可见的执行清单
Jobsctx.jobs运行中任务 + Session 完成通知统一管理后台 Shell、Terminal 和 Subagent
Workflowctx.workflowEngine工作流与子 Session 事件用 worker-thread 执行可控脚本化 Agent 调度
Subagentctx.subagents子 Session header/log + descriptor/projection把任务委派给可替换的子 Agent Provider

深度限制使用父 Session header 中的持久 delegationDepth 作为单调下界,恢复后不会被当作根 Agent。Spawn 子 Agent 从空白历史开始,Fork 子 Agent 以父日志的稳定边界为 seed;两者都写 parentSession、Preset 和权限覆盖。Continuable 子 Agent 可在冷恢复后继续,其每次激活的终止原因只从该 epoch 的日志后缀推导,避免把上一次回答当成本次产物。

27.10 Session 事实、持久化、表层与投影

图表加载中…

三个一致性边界必须分开:

  1. Session.append() 返回,只证明事件已进入当前进程的 canonical log。
  2. session/event 已发布,只证明持久化和投影消费者已获得提交后通知。
  3. session/flush 完成,才证明当前所有 durability listener 已达到静默点。Fork、对外导出、跨进程 Hook 和关键调度交付应在这个边界后声称可恢复。

冷恢复的修复也是追加语义:最后一条物理记录若被撕裂可丢弃,但已完整持久的开放 Turn 不能删除;系统为已记录但无结果的 ToolCall 追加 outcome-unknown/not-started 结果,再补 Step/Turn 终止,使模型回放仍满足工具配对不变量。

27.11 Approval、Permission Preset 与 Sandbox 的安全链

图表加载中…

Permission Preset 只是用户可理解的联动层:read-only 与 workspace-write 默认配 approval: ask,danger-full-access 默认配 approval: never。Approval 决定是否允许本次动作,Sandbox 决定执行进程或文件提供方实际能访问什么;即使 Approval allow,也不应自动取消 Sandbox。

当前 local sandbox 主要管理文件效果边界,其 full | partial 是对已声明承诺的表达程度,不是“对所有 OS 风险都完全隔离”。安全评审还应单独检查环境变量、网络、进程树、设备、硬链/符号链接和远程 Provider 的信任边界。

27.12 Web、API Gateway、SDK 与 ACP 的进程外边界

Web 产品不是一个绕过主干的独立 Runtime。Host 侧 api-gateway 将 Typert 生成的 Remote 描述符与实时 Cordis Service 绑定,host/apiproxy 实现 Agent、Session、Settings、Workspace、Approval、Question、Skill、Goal 和 Job 等边界,client-connection 在 Browser 侧处理 RPC 与下行 frame。界面插件通过 client modules 和 UI slots 组装,不把所有功能写进一个巨型 App 组件。

边界用途核心证据
Web Host/Client人类对话、设置、审批、进度和历史bundle/web-app/cordis.patch.yml、host/apiproxy、client/*
JSON-RPC SDK进程外 TypeScript 客户端和 Serverpackages/sdk/{protocol,server,client}
ACP自动化导向 Agent Client Protocol Serverpackages/acp/acp
HooksClaude Code/Codex 线协议桥接packages/hooks/{hook-protocol,hooks-*}
Typert从 TypeScript 类型图生成/加载运行时 RPC 描述符packages/typert/*、packages/api/gateway

这些边界都需要把类型可信的同进程值与需要运行时校验的 Wire/File/Queue 输入区分。例如 Browser 上传图像会验证 canonical base64、数量、总字节和内容类型,保存为内容寻址 Attachment 后 Session 只记持久引用;Settings 对客户端返回脱敏描述符,Credential 只允许写入引用和在真正调用时解析值。

27.13 可观测、运行时不变量与测试金字塔

DeepSeek Harness 把“不变量是运行时产品能力”与“测试是作者证据”分开。packages/runtime-diagnostics/invariants 让每个包注册自己所有的关系检查,例如组装中必须有唯一 Provider、模型可见输入必须可从日志重建、配套 Consumer 与 Service 必须同时存在。它们检查真实事件流或可变数据,不通过“某方法存在”证明关系正确。

验证层命令/基础设施能证明什么
Unitpnpm run test / Vitest局部类型、状态机、失败与 disposer 语义
Coverage gatepnpm run test:coveragepackages/*/*/src 每文件覆盖约束,不等于产品组装正确
Snapshotpnpm run test:snapshot无密钥回放与真实可运行示例的用户/模型可见输出
E2Epnpm run test:e2e有密钥时的真实 Provider 行为;无密钥自跳过不是通过证据
Static/buildtypecheck、lint、build、hygiene类型、静态规则、发布产物和消费者兼容性
Docsdoc-sync、website:build生成目录新鲜度、双语配对、链接与站点构建
Platform sandboxCI matrix/有针对性的真实后端测试操作系统实际拒绝越界动作,而非只验证 Policy 编译

Telemetry 记录与 Session Log 共用 event type/seq 可实现回放对齐,但 Telemetry 是可失败、可脱敏、可关闭的外发副本,不是恢复的事实源。反过来,Session Log 可以重建业务状态,但不能代替 OTel 处理批量、重试、传输和跨实例查询。

27.14 二十个知识维度的 DeepSeek Harness 必读路径

维度第一站第二站完成阅读后必须能回答
Harnessdocs/architecture.zh.mdbundle/base/cordis.patch.yml抽象架构怎样变成实际插件树?
Runtimepackages/core/agent-loop/src/agent.tspackages/core/agent/src/{inbox,dispatch}.ts输入、取消、唤醒与终态归谁?
Multi-Agentpackages/subagent/README.zh.mdpackages/subagent/subagent/src/{child-agent,lifecycle}.ts血缘、深度、权限与续跑怎样持久?
Agent Loopdocs/agent-lifecycle.zh.mdpackages/core/agent-loop/src/{agent,tool-calls}.ts一个 Turn 为什么包含多个 Step?
Skillpackages/skill/skill/src/index.tspackages/skill/tool-skill/src/index.ts目录、优先级、正文和调用权限如何分开?
MCPpackages/mcp/mcp-client/src/index.tspackages/mcp/mcp-client/src/{connection,tools}.ts如何在掉线和目录更新时避免半代工具?
长期记忆packages/session-query/README.mdpackages/session-query/*/src当前实现到哪一层,真正记忆抽取还缺什么?
短期记忆packages/core/session/src/surface.tspackages/session/session-projection/src/index.ts事实、模型表层和查询状态如何分层?
压缩packages/compaction/compaction-basic/src/index.tspackages/compaction/compaction-basic/src/{region,summarizer}.ts、packages/compaction/compaction/src/tool-pairing.ts什么证明压缩已持久进展?
窗口packages/llm/token-meter/src/*packages/llm/llm/src/types.ts预算为什么要绑定实际 provider/model 请求?
Promptpackages/core/system-prompt/src/index.tspackages/preset/persona、packages/plan/plan-mode、packages/context/*段落的顺序、Scope 与卸载怎样保证?
Contextpackages/context/*/srcpackages/core/agent-loop/src/runtime-context.ts每项模型可见输入能否从 Session 日志重建?
UXpackages/bundle/web-app/cordis.patch.ymlpackages/host/apiproxy、packages/client/ui-*UI 怎样只从事件和投影构建状态?
Tool Usepackages/core/tools/src/index.tspackages/core/agent-loop/src/tool-calls.ts并发执行与确定提交怎样同时成立?
Memorypackages/core/session、packages/session/session-projectionpackages/session-query、packages/attachment、packages/spill不同存储类型的写入权和失效语义是什么?
Planningpackages/plan/plan-modepackages/goal/*、packages/todo/*、packages/workflow/*Plan、Goal、Todo、Workflow 为什么不能合并?
Sessionpackages/core/sessionpackages/session/session-persistence、packages/session/session-projection-cacheappend、feed、flush 三个边界各证明什么?
可观测packages/session/session-telemetrypackages/session/session-telemetry-otelLedger 与 Ops 如何区分,上传何时发生?
权限packages/interaction/user-approvalpackages/interaction/permission-presets、packages/subagent/subagent/src/child-agent.ts为什么没有回答方必须拒绝,子 Agent 为什么不能升级?
沙箱packages/sandbox/sandbox-policypackages/sandbox/sandbox-local、packages/fs/fs-sandbox、packages/shell/*-sandbox同一 cwd/mode 如何同时限制文件与进程?

27.15 四条源码实战追踪

27.15.1 追一次文件编辑

packages/bundle/base/cordis.patch.yml 中 tool-fs + fs-observation-policy + fs-sandbox → packages/fs/tool-fs/src/edit.ts 构造编辑请求 → ctx.fs 提供方 → 观测策略验证是否先读 → sandboxPolicy.resolve() 固定 cwd/mode → Provider 写入 → Tool Runtime 投影 diff → tool/result 按原调用顺序持久。需要回答:“用户审批”、“必须先读”和“OS/Provider 路径限制”分别在哪一层强制?

27.15.2 追一次 MCP 工具目录更新

mcp-client.apply() 保留 serverName → Connection Supervisor 建立 transport/client 代际 → syncTools() 分页获取全量新定义 → 名称规范化与 schema 支持性检查 → 撤销旧代并注册新代 → 冲突时撤销已注册的新工具 → 调用时使用 raw name、timeout 和 AbortSignal。需要回答:拉取失败为什么保留旧代,注册冲突为什么收敛为零工具?

27.15.3 追一次崩溃后恢复

session/event 进入 write-behind → session/flush 等待 durability → 进程在开放 Step 中终止 → JSONL/SQLite load() 读取连续前缀 → 丢弃物理 torn tail,保留完整事件 → Repair 对未完 ToolCall 写 outcome-unknown/not-started 并关闭 Step/Turn → prepare() 创建未发布 Session → Agent Factory 恢复 Preset、路由、Plan、Goal、Sandbox/Approval 等日志折叠状态。需要回答:为什么不能删除已完整写入的开放 Turn?

27.15.4 追一次子 Agent 委派

tool-subagent 选择 provider/mode → resolveChildDepth() 以持久父深度为下界 → 捕获父 Session 的明确 Sandbox override 和 approval: never → 创建子 Session header(parent/origin/depth/seed/preset)→ 组合父 Preset、子 Persona 与 Tool Filter → 写入 delegation policy events → Provider 启动子 Agent → subagent/start/end 观测 epoch → 从子日志后缀推导终止原因 → 前台结果或后台通知进入父 Inbox。需要回答:子 Agent 为什么可继承工作区 Sandbox mode,却不能继承父级交互式升级能力?

27.16 与 Claude Code、Codex 的对照结论

观察点Claude CodeCodexDeepSeek Harness
主要扩展方式Tool/Hook/Skill/MCP 和产品内部状态Rust trait/crate、Tool Handler、Protocol EventCordis Service/Event/Effect/Scope,所有产品部件均为插件
模型历史与恢复事实Transcript 与恢复投影History 与 Rollout 明确分离Session Surface 与 append-only Session Log 明确分离
能力替换多为工具或服务层扩展Trait/Handler/Provider 边界强调 Definition/Provider/Consumer 三角 seam
Multi-AgentAgentTool、Task/Mailbox、Worktree内建 Agent Control 与 Thread多 Provider seam,支持本地、ACP、Codex、Claude Code 和 DSH SDK 子 Agent
持久化Session JSONL 与顺序写队列Rollout + Resume/ForkJSONL/SQLite Provider + flush barrier + Projection Cache
安全Permission Decision + 外部 sandbox runtimePermissionProfile + 多平台 sandboxApproval Event + Sandbox Policy + 文件/Shell Provider 组合
长期记忆存在产品实现存在后台抽取与合并当前主要是 Session Query 检索基础,未见同等自动抽取闭环

对照的意义不是评判“谁更先进”,而是识别同一不变量的不同实现形式。例如三者都需要将 ToolCall 与 ToolResult 稳定配对,但 Claude Code 主要从 ToolUse/Transcript 观察,Codex 从 Response Item/Rollout 观察,DeepSeek Harness 从 tool/call/tool/result Session Event 与恢复修复观察。工程分析中应先回答这个不变量,再用三套源码证明你理解不同取舍。

27.17 完整理解的自测与工程检验题

  1. 画出 profile → bundle → patch → Cordis tree 链路,解释为什么只看 packages/ 不能确认功能已启用。
  2. 从 followup() 开始,一直追到 turn/end,标出每个持久事件与实时 waterfall。
  3. 说明为什么工具可以并发执行,但 ToolResult 必须按模型原顺序提交。
  4. 解释 agent.inject() 与直接 Session.append('user/message') 的语义差异,以及注入何时真正变成模型可见事实。
  5. 用 Definition/Provider/Consumer 三角检查 Filesystem 或 Subagent seam,指出替换 Provider 时哪些契约不能变。
  6. 对比 Skill Catalog 和 MCP Tool Catalog:两者的缓存、版本、信任与失效边界有何不同?
  7. 说明 Session Log、Surface、Projection、Projection Cache 和 Session Query 五者的事实所有权。
  8. 构造“ToolCall 已持久,副作用可能已发生,ToolResult 尚未写入”崩溃,解释为什么恢复不能直接重放工具。
  9. 解释 Approval allow 为什么不代表关闭 Sandbox,以及没有 Approval answerer 时为什么必须 fail closed。
  10. 为 Linux/macOS/Windows 各设计一个真实 Sandbox conformance 用例,区分“Policy 返回拒绝”与“OS 确实阻断”证据。
  11. 说明 Plan Mode、Goal、Todo、Jobs、Workflow 和 Subagent 为什么各自需要独立状态与事件。
  12. 指出 DeepSeek Harness 当前长期记忆闭环的缺口,设计不修改 Agent Loop 的 Memory seam。

能独立完成这 12 题,并对每个回答给出“定义 → 组装 → 正常路径 → 失败路径 → 持久化/强制边界”五段证据,才算完成对 DeepSeek Harness 的渗透式理解;只能罗列包名、Tool 名或 UI 功能,仍停留在功能清单层。

最后更新 8/18/2026, 10:55:01 AM