A
返回 發現
發現2026/08/16 Bryan Chan33 分鐘閱讀

DeepSeek Harness 源碼級架構解剖:一切皆插件的 Agent 框架是怎麼蓋出來的

從 219 個 workspace 包、45 萬行 TypeScript 裡拆出 DeepSeek Harness(dsh)的完整架構:Cordis 插件底座、Event Sourcing 會話日誌、能力 Seam 三角色、策略單一家 + fail-closed 審批、上下文工程四層防線、前端同等插件化。附對自建 Agent 基建的移植評估。

一句話版本

DeepSeek Harness(dsh)不是一個 agent,而是運行 agent 的「骨架」。 模型適配、工具系統、會話管理、沙箱、審批、Web UI、插件機制全部內建,且每一部分都可從配置替換。它的架構哲學只有一句話:Everything is a plugin(一切皆插件),沒有一個需要打補丁的特權內核——連 agent loop 本身都是插件。

這篇是對 github.com/deepseek-ai/deepseek-harness(v0.1.0-rc.5,developer preview)的源碼級拆解。不是功能介紹,是看它怎麼把「可替換性」做到骨子裡


項目規模

項目 數值
組織方式 pnpm monorepo
Workspace 包 219 個 @deepseek-ai/dsh-*
TypeScript 文件 ~2,046 個 / ~45 萬行
Native 代碼 ~300 行 C11(Linux 沙箱啟動器 landlock-run)
子系統文檔 40+ 篇雙語(en/zh 配對校驗)
底座框架 vendored Cordis 4.0.0-rc.7
License MIT

一條命令即可跑起來:

npx @deepseek-ai/dsh web   # 啟動 Web GUI,默認 http://127.0.0.1:3080

一、底座:Cordis 插件框架

DSH 沒有引入外部插件框架,而是把 Cordis 以 vendor 方式整個吃進來(重命名進 @deepseek-ai scope),完全擁有自己的框架層——可審計、可打補丁、可鎖定版本。Cordis 的五個核心概念是理解一切的鑰匙:

概念 含義
插件即 Service 插件是帶 inject + apply(ctx) 的函數或 Service 子類,生命週期掛到上下文
上下文是服務容器 服務佔據穩定的 ctx.<key>ctx.toolsctx.llmctx.sessions),按 key 查找而非 import 實現
inject 聲明依賴 插件等待依賴服務就緒才啟動,加載順序由依賴圖驅動,無需手動編排
類型化事件 用 TS declaration merging 註冊事件名;四種分發模式:emit(觀察)、waterfall(環繞中間件,next() 可委託/短路)、parallel(並行)、serial(有序)
可逆副作用 一切註冊經 ctx.effect() / ctx.on() 安裝,插件卸載時自動撤銷

因為連 agent loop 本身都是插件,模型適配器、工具註冊表、會話日誌、CLI 旗標解析無一例外,所以產品的任何部分都可被替換。這是整個設計的地基。


二、Monorepo 分層

vendor/      vendored Cordis 框架層(cordis/loader/hmr/include/timer/schemastery/cosmokit)
packages/    219@deepseek-ai/dsh-* 包,按 <group>/<pkg>40+ 組
  core/        產品 API 脊柱:sessionsystem-prompttoolsagentagent-loopscope
  llm/         LLM 詞彙 + 適配器(deepseekpi-ai)+ retry + token-meter
  api/ typert/ 遠程 API 網關 + 類型圖生成器
  fs/ shell/ subprocess/ sandbox/ terminal/ lsp/ jobs/ web/ skill/ ...   能力 seam 家族
  subagent/ workflow/ goal/ plan/ compaction/ spill/                    編排與上下文工程
  session/ storage/ session-query/                                      持久化數據面
  interaction/ guard/ preset/ hooks/                                    人機協作與護欄
  bundle/      可安裝的 profile patch 層(base / web-app / headless)
  host/ client/ Web GUI 的後端半與瀏覽器半
  boot/        app-bin 啟動膠水
apps/        cli(dsh 命令)+ web(Vite 前端入口)
native/      landlock-run(Linux 沙箱啟動器)
python/      Python SDK 與單 exe 分發運行時
examples/    可運行的 cordis.yml 示例葉子

依賴紀律是關鍵:擴展插件只依賴 Service Definition,絕不依賴具體 Provider。dsh-agent-loop 可換,UI/工具只依賴 dsh-agent。依賴圖由 peerDependencies 生成(docs/module-graph.md),CI 守衛新鮮度。


三、組裝機制:Profile / Bundle / Patch

運行中的 dsh 是一棵由啟動時按序疊加的各層組成的插件樹

  1. Bundle(組合包):npm 分發的 Cordis 配置+掛載代碼。dsh-base 是每個 profile 的第一層(模型適配器、工具、持久化、沙箱與審批策略、設置、憑據、遙測);dsh-web-app 加瀏覽器應用;dsh-headless 加一次性運行器。
  2. Profile:Harness home 中的具名組裝,列出疊放的 bundle + 用戶自己的 cordis.patch.yml
  3. Patch:按 id 定位條目並整體替換其 config,或插入新條目。疊加順序:bundle(按 profile 順序)→ profile patch → home 級 patch → --patch overlay,後寫覆蓋先寫。

幾個值得品的優雅設計:

  • 旗標即服務--host/--port 由普通的 web-startup 插件解析成 webStartup 服務,webserver 條目用 !!js ctx.webStartup.port ?? 3080 惰性插值。commander 只認 --profile/--patch 兩個啟動器旗標,其餘參數原樣交給插件樹。
  • 符號鏈接場composeProfile$DSH_HOME/profiles/node_modules 維護符號鏈接場,保證依賴閉包共享單一 cordis 實例,插件圖不會出現重複框架實例。
  • 熱重載:啟動後 watchUserPatches 經 HMR 熱重載用戶 patch 層。
  • 可 dumpdsh --profile web --dump-config 打印實際配置樹,其中任何條目都可被用戶 patch 替換。

dsh-base 的約 60 行 insert 涵蓋:模型(llm、llm-deepseek、llm-pi-ai、llm-retry)、持久化(session、session-persistence-jsonl、session-projection、session-query-sqlite)、沙箱/審批(sandbox-local、sandbox-policy 默認 workspace-write、bash/pwsh-sandbox 平台互斥、approval、permission-presets)、設置/憑據/遙測(otel 默認 DISABLED)、以及全部模型面工具(bash/fs/web/skill/todo/goal/subagent/workflow/ralph/jobs)。


四、核心運行時

Agent 與 Agent Loop:接口與實現分離

  • Agent 句柄core/agent):公開接口,send/followup/steer/inject 統一投遞;ctx.agents 註冊表通過 setFactory() 把創建委託給 agent-loop——接口與實現分離,loop 可換
  • ReactLoopAgentcore/agent-loop):idle | maintenance | running 單相狀態機,wakeDriver → kick → while(turn())
  • Inbox:雙隊列(next-turn / next-step),且是持久投影——每次變更先以 agent/inbox/spliced 事件落日誌再改內存,resume 時從種子邊界重放。

Turn / Step 輪次流程

一個步驟(step) = 一次模型請求 + 它調用的工具;一個輪次(turn) = 0..n 個步驟:

turn/start
  claim 下一批輸入
  → agent/pre-step (waterfall:可拒絕/改寫)
    step/start → user/message 落日誌
    system-prompt/assemble (waterfall)
    → agent/request → llm/stream → assistant/chunk* → assistant/message
    → tool/call* → tools/pre-execute → tools/execute → tools/post-execute → tool/result*
    step/end →(還欠工作?claim 下一批 → 下一 step)
  → agent/turn-stopping (serial)
turn/end

工具調度有 exclusive 屏障 + parallel 有界滾動池,結果按模型順序提交;請求失敗走 agent/request-error(可決定 retry)。

Session 日誌:Event Sourcing 一切

append-only SessionEvent 日誌是唯一事實源。 deriveMessages() 從日誌投影出模型歷史;assistant/chunk 原始事件保證回放與 UI 保真。fork、resume、transcript、遙測、持久化全部派生自這條事件流。

核心不變量:「模型可見即已記錄」——抵達模型請求的一切都必須能從日誌重建(由運行時不變量斷言)。request/header(EpochHeader 快照)使每次請求是日誌的純函數。追加時做 lossless-JSON 校驗 + 深凍結,seq 連續。

工具系統與執行流水線

  • ToolDefinition = schema + 強制 output 聲明 + execute + finalizeContent + present*;defineTool DSL 做類型推導。
  • 註冊表按 scope 分層 + ToolRestriction(allow/deny),schemas() 白名單投影進提示詞。
  • 流水線:tools/pre-execute(allow/deny/ask)→ ask 經 ctx.approval 一次性把關(不可答即拒)→ 單調 ToolGuard(只有 deny/abstain,順序不可翻案)→ tools/execute(超時/重試環繞)→ 工具體 → tools/post-execute → 歸一化 → tool/result 落日誌。

三個事件域

事件域 性質 用途
Session 事件turn/*assistant/* 持久事實,追加落盤 必須扛住 reload 的事實
Agent 事件agent/* 實時,攜帶活體 Agent 觀察/攔截進行中的工作
能力事件fs/*tools/*llm/stream seam 上的掛點 無需 import 循環即可附加策略與適配器

五、能力 Seam 模式:執行層的組織方式

每項可替換能力由三角色組成:

  • Service Definitiondeclare modulectx.<name> 合併進 Context + 抽象 Service 子類,包本身零實現;
  • Service Provider:實現並註冊(單實現 seam 載入第二個會 throw;註冊表型如 subagent/llm 按名共存);
  • Consumer:只 inject ctx key,不 import 任何 provider。

切換 provider = 在 cordis.yml 換一個插件,模型面工具與 agent loop 完全不動。

最精彩的應用是執行世界(execution world)ctx.fsctx.subprocess 的契約綁定「同一個世界」。bash 執行器、PTY 後端、LSP host、外部 CLI subagent 全部只依賴 ctx.subprocess + ctx.fs。因此把這兩個 provider 換成 e2b 實現(fs-e2b + subprocess-e2b 共享一個 E2B SDK 句柄)後,bash/PTY/LSP/claude-code 等整體搬進遠程 Linux 沙箱,無一處消費者代碼改動。遷移單位是「世界」,不是單個工具——這是 seam 模式的收益極值。

沙箱與審批

  • ctx.sandbox:同世界進程圍欄。消費者交出即將 spawn 的精確 argv,provider 按 per-call 策略返回 ConfinedArgv + enforcement: full|partial。後端:Linux bwrap / landlock(原生 C 啟動器,fail-closed)/ macOS seatbelt / Windows 私有 SID+ACL。
  • ctx.sandboxPolicy 是策略的唯一之家:bash 與 fs 兩個強制家族讀同一份解析結果與 writableRoots從結構上消滅圍欄漂移;解析結果寫進 session 日誌,重放時重建同一策略。
  • 升級 choreographyescalation.ts 統一更寬模式階梯、sandbox_permissionsjustification 配對校驗,approveEscalation 在任何執行之前ctx.approval 走 fail-closed 審批,通過後以一次性更寬策略重試。
  • 審批 seamapproval/request waterfall 分發一次性決策,無回答者時 fail closed 為 unavailableapproval/asked|decided 落日誌審計。
  • 誠實定位:fs-sandbox、workflow vm 都明確文檔化自己是 containment(遏制)而非安全邊界;真正的內核級隔離交給 sandbox runner,並如實上報 enforcement 等級。

六、多輪編排:subagent / workflow / ralph / goal

機制 本質 適用
subagent ctx.subagents 命名 provider 註冊表:in-process spawn/fork、外部 CLI(claude-code/codex)、ACP 協議、dsh-sdk。能力用靜態描述符聲明,不支持即 UNSUPPORTED_CAPABILITY 明確拒絕,絕不靜默降級 委派獨立子任務
workflow ctx.workflowEngine 在 worker-thread 的 vm context 執行模型自寫的 JS 編排腳本,腳本內 agent() 橋接 subagents 做 fan-out,phase()/log() 報進度 大規模扇出編排
ralph workflow 引擎上的凍結用法:每輪起全新子代理,輪間只帶不可變 objective + 有界 handoff(16KB),共享 workspace 當長期記憶 新鮮上下文迭代(隔離污染)
goal 同 session 機制:goal 狀態從 session log fold 出(帶 revision),round driver 在同一 Agent 的 inbox 排入後續 round,不開子代理 同會話自治續跑(保持上下文)

另有 ctx.jobs(通用後台作業運行時 + job_* 工具)、ctx.schedule(會話內定時跟進)、plan-mode(計劃協作狀態)。


七、上下文工程:四層防線

完整的上下文窗口防線,由四個無縫銜接的機制組成:

  1. token-meter:per-session replay fold 的 token 計量,判斷壓力;
  2. compaction-tool-result-pruner:先把超大當前 tool result 改寫成可重放的替換節點(無需模型);
  3. compaction:LLM 摘要。compaction/start|summary|end 三事件僅寫日誌(鎖-摘要-釋放,中途崩潰表現為可檢測的遺留鎖),摘要通過 surfaceOp: {op:'replace'}user/message 做唯一的 surface 變更;
  4. spilltools/post-execute 變換器,結果超閾值即存全文到 spill store,只給模型有界 head/tail 預覽 + locator;失敗絕不把成功調用變 error

八、持久化與 Projection

  • PersistenceSessionEvent 事件源日誌是唯一權威;JSONL(每 session 一文件)與 SQLite(node:sqlite)兩後端共享 PersistenceCoordinator(批寫、prepared-session 緩存)。
  • Projection:域插件貢獻純同步 ProjectionDefinition(init/apply/view),框架對已提交事件做 fold 維護讀模型。整值事件規則(事件必須攜帶完整後態,不許裸 delta)保證 fold 廉價、單位轉移自描述。
  • Cache:projection 檢查點持久化,冷讀走「cache 行 + persistence 尾部重放」的梯子——列表頁永不載入全日誌。
  • 關係:persistence 是權威日誌,projection 是 fold 出的讀模型,cache 是捷徑而非權威。

九、Web GUI:前端與後端同等插件化

技術棧:React 18 + TypeScript + Vite(apps/web 僅為入口,rejectStandaloneServe 禁止裸跑——只有 host 能注入 window.__DSH_BOOT__);CSS Modules + --dsw-* 設計 token,無 Tailwind/組件庫。

通信協議:單工 RPC(POST /api/<namespace>/<method>)+ 兩條只下行 WebSocket(/api/events.mux session 事件復用流、/api/events.host)。信任邊界:/api 前統一檢查 Host/Origin/sec-fetch-site(防 DNS rebinding);特權方法(目錄選擇、設置、憑據)限 loopback。

插件化 UI 的三個關鍵設計

  • window.__DSH_BOOT__:host 以 index-tap 注入的插件入口圖。shell 兩階段啟動:loading 頁 → cordis Loader 全部 ACTIVE → 一次性切換真實 UI。主 bundle 只含 shell 與模塊系統,全部功能 UI 都是運行時加載的插件 bundle
  • Slots 機制:「聲明 = 渲染授權 = 運行時規約」的單表 register();chain 槽反轉選擇權(條目自薦,首個非 null 當選)——approval/question 等搶佔式 UI 無需中央 switch。
  • ConversationNodeDefinition:聊天消息是開放註冊制——插件 declaration-merge ChatNodeDataMap 的 key,註冊 match/start/update/buildViewNode 狀態機 + keyed 渲染器;事件按 seq 可重放折疊。

Typert:構建期嚴格契約。業務服務用 @Remote 裝飾器標記;構建期從 host 的 ts.Program 生成類型圖/Zod schema/Remote 描述符,客戶端只掛載產物。複雜 host 對象(如 Agent)經 lookup 換成 wire id;冷會話自動 resume。


十、LLM 抽象層與 MCP

ctx.llm 是消息與流式詞彙(Message/ContentBlock/StreamChunk)+ 適配器註冊表。Provider:llm-deepseek(官方)、llm-pi-ai(多提供商目錄);llm-retry 以插件形式提供重試策略。添加模型供應商 = 在 ctx.llm 註冊適配器,一行配置的事。 MCP 集成:mcp-client 每實例連一個 MCP server,工具以 mcp__<server>__<name> 註冊進 ctx.tools


十一、工程實踐與質量體系

  • 文檔即代碼:40+ 篇子系統文檔雙語維護(en/zh 配對校驗),module-graph.mdcapability-seams.md、配置目錄等由腳本從源碼生成並有 CI 新鮮度守衛;type-equiv 代碼塊與源碼漂移檢查。
  • 測試矩陣:vitest 單測 + e2e + snapshot(錄製/回放 LLM 交互)+ web 壓力/性能測試 + Windows(Wine)門禁;llm-mock-server/llm-replay 讓測試不依賴真實 API。
  • 不變量註冊表ctx.invariants 讓包註冊自己的運行時不變量(如「模型可見即已記錄」),由框架統一斷言。
  • Pre-release 姿態:明確「基礎優先於兼容」——可自由重命名/重打包,SQLite 用單調 SCHEMA_VERSION,不背兼容包袱。
  • Agent 友好:倉庫自帶 AGENTS.md/CLAUDE.md.agents/notes/(每個設計決策一篇 Agent Note)、cookbook 分步指南——這個項目本身就是為「由 agent 維護」設計的

十二、關鍵設計決策總表

# 決策 收益
1 一切皆插件,無特權內核(連 agent loop 都是插件) 任何部分可從配置替換;註冊隨卸載撤銷
2 Event sourcing 一切(歷史/inbox/請求頭皆派生自 SessionEvent) fork/resume/回放/審計同源;「模型可見即已記錄」可斷言
3 能力 seam 三角色 + 執行世界邊界 換 2 個 provider 即把整個執行世界遷到遠程沙箱
4 策略與執行分離的單一家(sandboxPolicy)+ fail-closed 審批 結構上消滅圍欄漂移;審批缺席 = 拒絕
5 三事件域 + 四種分發模式(waterfall 攔截點 + 單調 guard) 策略可堆疊且順序安全
6 聲明合併擴展核心 sum type 插件不改核心即可擴展核心類型,全為編譯期檢查
7 Shell 自足 + 運行時 UI 插件圖 + Typert 構建期契約 前端與後端同等插件化;跨進程 RPC 全類型安全
8 上下文工程分層防線(token-meter→pruner→compaction→spill) 長會話不爆窗口,每層可重放、可審計
9 框架層 vendor 自有化 可審計、可打補丁、版本鎖定
10 整值事件規則 + projection/cache 派生 fold 廉價、冷讀走梯子;權威只有一份
11 Patch 整體替換 config + 旗標即服務 + 符號鏈接場 每行至多一個 bundle 層 + 用戶層擁有,可預測、可 dump、可熱重載

十三、對自建 Agent 基建的移植評估

讀完這套架構,最值得偷師的不是某個具體功能,而是四個結構性原則。以我們自己的多 Agent 基建(UltraClaw / OpenClaw 生態)為參照:

① 「模型可見即已記錄」不變量 → 根治子代理數據匯報不一致

我們踩過的坑:AK-SDD 子代理把 00928 的數據混進 00653 的報告、手動匯報時丟三落四。根因是「子代理說它做了什麼」和「它實際做了什麼」之間沒有可斷言的橋。dsh 的解法是把一切抵達模型的內容都強制從 append-only 日誌重建。可移植做法:給每個子代理的產出強制寫一條結構化事件(輸入+工具調用+結果 hash),主代理驗收時比對事件流而非子代理的自述。這正好和我們已有的 external supervisor 思路合流。

② 策略單一家 → 消滅規則圍欄漂移

我們現狀:行為規則散落在 PERMANENT-RULES / RULES / AGENTS / SOUL 多處,同一約束可能有多份表述,改一處漏一處。dsh 的 sandboxPolicy 是策略的唯一之家,bash 和 fs 都讀同一份解析結果。可移植做法:把「哪些目錄可寫、哪些命令需審批、哪些來源禁用」收斂到一份機器可讀的策略文件(YAML/JSON),各 skill 與 cron 讀同一份,而非各自 hardcode。

③ fail-closed 審批 → 升級我們的 Gate 機制

dsh 的審批 seam 在無回答者時 fail closed 為拒絕,且審批決策落日誌可審計。我們的 Gate/verify.py 已有雛形,但可以補上兩點:審批缺席時默認拒絕(而非默認放行或卡住)、以及把每次 Gate 決策寫進可追溯的事件流。

④ 上下文工程四層防線 → 印證 headroom 的方向,補上 spill

我們已經部署了 headroom 做上下文壓縮。dsh 的額外啟發是 spill:工具結果超閾值時只給模型 head/tail 預覽 + locator,全文存到 store 按需取回,且「失敗絕不把成功調用變 error」。這比單純壓縮更適合我們動輒幾百 KB 的年報/PDF 提取場景——可以做成 AK-OCR pipeline 的標準後處理。

暫不建議直接搬的:Cordis 整套插件底座和 Typert 構建期契約。這些是框架級重構,收益大但遷移成本極高,更適合作為長期參照而非短期移植。我們現階段把①②③④四個原則落到現有基建,性價比最高。


結語

DSH 最打動我的一點是它的誠實:fs-sandbox 明確寫自己是「遏制而非安全邊界」,pre-release 明確寫「將有破壞性變更」,能力不支持時明確返回 UNSUPPORTED_CAPABILITY 而非靜默降級。一個框架敢把自己的邊界寫進文檔,比任何營銷話術都更有說服力。

而「一切皆插件」不是口號——當連 agent loop、CLI 旗標、前端 UI 都被做成可卸載的插件時,它是一種可以被源碼驗證的架構紀律。


分析方法:克隆源碼,由多個並行子代理分別深讀核心運行時、啟動/插件系統、Web GUI、執行層,結合官方架構文檔交叉驗證。本文為 2026-08-14《DeepSeek Harness 深度研究:首日實測》的源碼級續篇。