2026-09-23·by Sijie Wang#software

external-means-fully-external

外置就要彻底外置

上级:structure · 邻居:owner-facade-from-registry · mcp-capability-plugins · convergence-inbound-and-outbound

一个被外置的能力,必须连它自己的存储一起带走。 如果内核还攒着那张表,那这个能力只是换了个地址,不是被外置了 —— 而"更整洁的地址"能通过每一道闸门,同时什么都没改变。

它命名的那个失效模式

把一个能力搬出内核,很容易只搬一半,而这个一半的状态还很舒服:代码住进了 capabilities/<id>/,import 图是绿的,arch-lint 过了,diff 看起来像进展。留在原地的是数据 —— 共享 schema 里的一张表、共享数据模块里的一个 repo、共享配置结构体上的一个字段。内核仍然知道这个能力存在,而加下一个能力仍然要改中间那一层。

诊断的问题不是"代码住哪儿",而是 "把这个能力删掉,内核里还剩下什么?" 如果答案是一张表、一个列、一个结构体字段、或者一个 switch 分支,那它就不是外置的。

彻底外置长什么样

  • 自己的存储。 每个能力有一份隔离存储,按它自己的声明 provision —— 绝不是一个"每个能力都伸手进去"的共享数据模块。隔离存储正是让出站收口(码上的那个字段)、入站够回(沙箱的读写)、用量闸三条路,对每个能力都从同一处取的原因。
  • 自己的声明,形式是数据。 身份、它要用哪些 host op、它占邀请码上的哪个字段、它的配置默认值 —— 写在 backend/capabilities/<id>/manifest.yaml,而不是组装根里的 Go 字面量。写在这个能力被描述的地方,不是程序被接线的地方(convergence-inbound-and-outbound)。
  • 自己的说明书。 策略和措辞跟着能力自己的 MCP instructions 走,于是一个没被授予它的会话根本读不到。内核陈述事实,能力下达指示。
  • 它的边一起搬走。 让一次搬家烂掉的不是代码 —— 是错误表、lint 豁免、testid、'use client' 边界。这些都不会自动跟着走,而其中任何一个没跟上时不会报错;系统只是比原来少做了一件事。

为什么内核必须忘得掉

外置是否成立,检验在 check-core-agnostic 那条棘轮上:内核包里根本不允许出现任何一个具体能力的名字。 不是"不许 import",是不许被提到。import 图的检查看不见"一个内核文件把某个能力的逻辑写在了自己包里",因为每根箭头都是绿的。所以那道守卫读的是字符串,而且跑在空基线上:一条豁免都没有。

被它抓到的那个范例不是一张表,是一句话:内核那段常驻的日期时间上下文提到了 owner 的日程,于是把排期建议送给了手上根本没有订会工具的访客。能力早就外置了;关于它的一句话没有。

read next
about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →