Skill、Preset 與自我修改
cordis_define 讓 Agent 在執行時改寫自己的執行時。核心目錄:packages/extensions/tool-cordis/ 與 apps/cli/config/agent-presets/。
cordis_define 和 cordis_run 在執行時給自己長出新工具,以及這套自我修改的信任邊界畫在哪;skill 分層註冊表裡 preset 層怎麼遮蔽全域層。
場景:創造模式(cordis preset)的會話裡,使用者說「給我做一個統計程式碼行數的工具」。左邊是執行時的即時狀態,右邊是 Agent 的動作,跟著字幕走一遍。
外掛登記簿(Plugin / Package)
工具目錄
Agent 的動作
packages/extensions/tool-cordis/src/index.ts 中 cordis_define 與 cordis_run 的工具描述原文(第 148 至 259 行)整理,核對日期 2026-08-13。先解發布文那個懸念:標準、程式碼、極簡、創造四種模式,在原始碼裡找不到一行模式分支。apps/cli/config/agent-presets/ 下就是四個目錄,每個目錄一份 agent.cordis.yml,一份檔案描述一種外掛組合,給一個會話掛載。極簡模式全文 62 行:persona 一句「You are a helpful software engineer assistant.」加 complete: true(拒絕任何後續拼裝往提示詞裡加料),工具只有持久 bash 和編輯器,連壓縮都沒有,這是拿來跑基準測試的配置。創造模式則是標準模式原封不動,多掛三樣:自指工具集 tool-cordis、一個教寫組合的 skill、一段教模型分清兩個平面的 persona。
兩個平面是這套體系的座標系。HOST 組合放跨會話共享的東西:持久化、沙箱與審批、模型路由、subagent 註冊表。AGENT PRESET 放一個會話貢獻給這些註冊表的東西:它的工具、persona、提示詞段落。有個細節能看出邊界畫得多細:preset 裡發布服務的行,要麼歸 host,要麼包進 isolate realm。極簡模式想用不帶沙箱的本地檔案系統,就把 fs-local 包在自己的 realm 裡,只遮蔽自己這個會話的 fs,別的會話照舊走沙箱(minimal/agent.cordis.yml 第 46 至 57 行)。
skill 也是分層的。全域層放部署級註冊的(倉庫外掛),preset 層放隨 preset 走的,讀取時近層同名直接贏,排序權重只在同一層內起作用。創造模式那個 editing-cordis-compositions skill 就住在 preset 目錄裡,跟著 preset 被複制和編輯,理由寫在 yml 註釋裡:它描述的是這個部署的兩個平面,preset 才是被複制的單位(cordis/agent.cordis.yml 第 248 至 254 行)。設計記錄裡還專門否決過跨層合併排序的方案,遮蔽必須乾脆,不然模型看到兩個同名 skill 會無所適從(.agents/notes/implemented/architecture/2026-08-09-layered-skill-registry.zh.md)。
define 不執行cordis_define 只校驗參數和語法、把原始碼記成一個不可變 Package,不申請審批、不執行、不動 currentPackageId。要讓它跑起來,得再調 cordis_run。定義與啟用分開,改壞了才有得回滾。
失敗不動指標cordis_run 只在完全成功後才切 currentPackageId;啟動失敗時舊 current 原地不動。改版本用 kind:"existing" 追加新 Package,舊版本永遠保留,回滾就是 run 一個舊 ID。
邊界是信任創造模式檔案頭原話:「Treat a session on this preset as shell access」。模型寫的 JS 貼著活執行時跑,沒有沙箱兜底,防線畫在誰能用這個 preset 上,程式碼本身不設圍欄。
第一段證據是創造模式 YAML 的檔案頭。前八行講它是什麼:標準模式加自指工具集。後四行是信任宣告,把「當 shell 權限對待」寫進了配置檔案的註釋裡:
# The `cordis` agent preset: the standard coding agent, plus the ability to
# read and write the runtime it is running in.
#
# It exists so a person can ask an agent to author another agent. Everything in
# `standard` is here unchanged; what is added is the self-referential Cordis
# toolset, a skill that teaches composition authoring, and a persona that says
# which of the two planes an edit belongs to.
#
# TRUST: `cordis_mount` evaluates model-written JavaScript against the live
# runtime, and a composition this agent writes becomes a preset other sessions
# mount. Treat a session on this preset as shell access — the toolset's own
# documentation makes the same statement.
apps/cli/config/agent-presets/cordis/agent.cordis.yml,核對日期 2026-08-13。程式碼塊保留原始碼原文。第二條證據是 skill 註冊表的合併讀取,邏輯不貼程式碼也說得清。collectFresh 把所有層排成一列:全域層排最前,preset 作用域鏈按遠祖先在前、本層最後依次跟上。然後按順序把每一層的條目灌進同一個 Map,鍵是 skill 名字。Map 的天性就是後寫的覆蓋先寫的,所以近層同名條目自動替換遠層,遮蔽就是一次 Map.set,沒有任何跨層的權重比較。函式頭上的註釋把這條規則和工具註冊表的遮蔽規則對齊了,還補了一句:排序權重只在同一層內對重名起作用。
出處:packages/skill/skill/src/index.ts 第 552 至 566 行的 collectFresh,核對日期 2026-08-13。
還有兩條機制值得記住,出處寫在這裡。其一,動態外掛讓工具集中途變了形狀時,會話日誌會記錄變更後的完整請求頭,維持「模型看到的 ⟺ 日誌裡的」不變數(.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.zh.md)。其二,persona 明令 Agent 絕不許編輯發行版 preset 目錄:升級會整個覆蓋它,而且改壞 cordis preset 等於親手關掉自己所在的模式,要改就複製出去改副本(cordis/agent.cordis.yml 第 27 行)。
Claude Code 定義一個角色,用的是一個帶 frontmatter 的 markdown 檔案:寫清 description、可用工具、model,AgentTool 的呼叫參數還能臨時覆蓋 frontmatter 裡的 model 欄位(書稿 study/chapters/05-multi-agent.md 第 32 至 38 行引 AgentTool.tsx 的 inputSchema)。Skills 同理,一個目錄一份 SKILL.md。這條路線是「配置描述一個角色」:角色能差異化的維度,取決於框架在 frontmatter 裡開放了哪些欄位。
DSH 的 preset 是「整包外掛組合一個角色」:persona 只是組合裡普通的一行外掛,和工具、壓縮策略、subagent 後端平起平坐。極簡模式甚至能換掉整個檔案系統實現、關掉壓縮,這種深度的差異化在 frontmatter 欄位裡表達不出來。代價也直白:寫一份 preset 要懂兩個平面和 isolate realm,門檻比寫一份 markdown 高得多,所以創造模式才要隨身帶一個教學 skill,再配一個能讀執行時的 cordis_inspect_query。一邊是低門檻的角色卡,一邊是全功率的組合語言,兩家對寫角色的人是誰,想得很不一樣。
推演兩個邊界場景
其一:創造模式的 Agent 一時糊塗,直接編輯了發行版的 cordis/agent.cordis.yml 並且改壞了,下一個想用創造模式的會話會發生什麼?persona 第 27 行為什麼把複製出去改副本寫成鐵律?其二:你自己組裝第五種模式時,把兩份 preset 都想發布的同名服務行都留在了各自檔案裡且都沒包 isolate realm,兩個會話同時掛載會怎樣?提示:回看極簡模式是怎麼用 realm 遮蔽 fs 的。