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 的。