
DeepSeek Harness 配置與 Patch像打補丁一樣改 Agent 行為上一篇結(jié)尾說「改行為而不碰源碼」。這一篇就講這個——Harness 的配置與 Patch 體系。它是整個框架「可組裝、可替換」承諾的落地機制你想改默認模型、換存儲后端、加一個工具、關(guān)掉某個 provider都不需要 fork 源碼改一個 YAML 文件就夠了。但要真正用好得先分清它其實有兩套配置體系很多人一開始就栽在這里。一、兩套配置體系別混為一談Harness 里叫「配置」的東西有兩套職責(zé)完全不同組合配置cordis.patch.yml決定運行時組裝成什么樣——加載哪些插件、每個 seam 選哪個 provider。它管的是「結(jié)構(gòu)」。用戶設(shè)置Settings seam決定用戶可編輯的那部分行為——某個 namespace 下的具體值。它管的是「數(shù)值」而且是面向「用戶在界面或配置頁里能改」的那一子集。官方對二者的邊界說得很清楚組合配置留在cordis.yml一個 namespace 只攜帶用戶可編輯的子集。換句話說——「換哪個存儲后端」是組合配置的事「這個后端的連接串」才是 Settings 的事。二、cordis.patch.yml兩種操作一個鐵律cordis.patch.yml里能做的操作就兩種insert往樹里加新行新插件、新服務(wù)id 尋址覆蓋用穩(wěn)定的id找到某一行替換它的配置。每一行都有兩個字段穩(wěn)定的id跨層尋址用和name要加載的 npm 包。id 是「誰被覆蓋」的錨點name 是「加載什么」。# 概念示意patch 的兩種操作rows:-id:system-prompt# insert 或 override 都靠 id 定位name:deepseek-ai/dsh-system-promptconfig:persona:You are a coding agent…三、last-write-wins整行替換不深合并這是 Patch 體系里最重要的一條鐵律記不住會踩大坑一個后層 patch 用 id 命中某一行時替換的是那一行的整個config而不是深合并。也就是說如果你想覆蓋某個行必須把那一行「自己擁有的每個 key」都重述一遍。少寫一個 key它不會「保留之前的默認值」而是直接沒了。官方反復(fù)強調(diào)patch 替換目標行的整個 config沒有 deep-merge。這條規(guī)則的代價是「覆蓋要寫全」但好處也明顯每一行最多屬于一個 bundle 層 用戶覆蓋層。于是「這個值到底是誰定的」永遠可查、可審計——不會出現(xiàn)「六個插件各自往同一個對象里 merge 一點點最后誰也不知道完整值從哪來」的亂象。四、bundle layer ordering誰后疊誰贏上一篇已經(jīng)埋過伏筆這里展開。層疊順序決定了「誰覆蓋誰」dsh.profile.bundles里每個 bundle 的 patch按聲明順序profile 自己的cordis.patch.ymlhome 級的$DSH_HOME/cordis.patch.yml--patch命令行覆蓋層。順序的意義在于你的 bundle 的 patch 應(yīng)用在它之前所有 bundle 之后所以它能覆蓋前面任何一個 bundle 配過的行。而后面的 profile / home /--patch又能覆蓋你的 bundle。這是一條清晰的「誰后誰贏」鏈。一個典型的應(yīng)用base 層把一個「按 mode 不同而不同」的行留白由各 mode bundle 去填各自完整 config——這樣「不同形態(tài)不同默認值」的差異就落在了各自的 mode bundle 里而不是污染 base 層。五、用戶 Settingsnamespace schema 三層解析Settings 這邊更「應(yīng)用層」。每個插件可以用ctx.settings.register(ns, schema, options)注冊一個namespace得到一個SettingsScopeconstscopectx.settings.register(my-plugin,schema,{base:{timeout:3000},// 組合層默認值applies:live,// live 即時生效 / restart 重啟生效validate:(v){/* schema 表達不了的交差校驗 */},})awaitscope.update({timeout:5000})// 合并稀疏 patch 到用戶層awaitscope.replace({timeout:5000})// 整段替換reset 路徑scope.watch((next,prev){/* 觀察變更 */})解析值的順序永遠是schema 默認值 → 組合base→ 用戶層。三個寫接口語義不同別用錯update(patch)把稀疏 patch合并進用戶層只改你給的 keyreplace(section)整段替換用戶層缺的 key 回落到base和 schema 默認——這是「刪除/重置」路徑replace({})等于全重置mutate(ops)路徑尋址編輯{op:set|unset, path}給「只拿到脫敏視圖、無法重建整段」的調(diào)用方用避免誤刪沒見過的 secret 字段。每次提交還會發(fā)settings/updated (ns, next, prev, source)事件source區(qū)分是進程內(nèi)update還是外部provider編輯。validate是 schema 表達不了的交差校驗比如「這個 provider profile 我服務(wù)不了」在寫的時候就拒絕而不是存一個會靜默禁用主人的值。六、避坑清單整行替換不是深合并。覆蓋一行要把它擁有的 key 寫全別指望少寫的 key 能「繼承默認」。兩套配置別搞混。「換哪個后端」寫 patch「后端連哪」寫 Settings放錯位置要么不生效要么語義錯亂。replace會重置沒寫的 key。只想改一個字段用update或mutate別圖省事replace只傳一個 key否則其余字段全回落默認。secret 字段靠mutate保護。任何 wire 界面必須redactSecrets: true拿到脫敏視圖的調(diào)用方只能用mutate按路徑改用replace會靜默刪掉它從沒見過的 secret。層疊順序決定覆蓋力。要臨時改用--patch要持久改寫 home 級 patch別去改內(nèi)置 bundle 的源文件。小結(jié)Patch 體系是 Harness「去鎖死、可替換、可組合」這一價值觀的機械實現(xiàn)。它用「id 尋址 整行替換 last-write-wins」換來一個極其可貴的性質(zhì)最終配置永遠可被精確歸因。你想知道「這個模型是從哪來的」順著層疊鏈一查就知道而不是對著一個被 merge 了 N 次的運行時對象干瞪眼。下一篇我們往「運行起來之后」看——配置改好了、Agent 跑起來了怎么知道它到底干得怎么樣這就是可觀測性與 OpenTelemetry 遙測。參考deepseek-ai/deepseek-harness 官方倉庫docs/subsystems/settings.md、apps/cli/README.md。