2026-09-23·by Sijie Wang#standmeet#architecture#design

convergence-inbound-and-outbound

两个收口——出站与入站

上级:structure

后端最深的那个结构性想法,而它一直没有笔记。一个能力能做什么、以及它能反过来向宿主要什么,各自只从一个地方过——而且两者都由闸门守着,不靠评审。

出站:dispatcher

每个域把自己会做的事声明成数据fp.Op —— id、说明、入参 schema、种类、可达面)。dispatcher 把这些声明汇成唯一一份注册表;每一个对外的面 —— admin HTTP、owner MCP、API-key 面、访客 agent 的工具 —— 都是这一份注册表的投影,而不是它旁边手写的另一个网关。

check-routes-via-dispatcher 守着这条线:一个面直接够到域门面就是红。它以棘轮方式运行——还没迁完的文件有一份基线,只许变少。

为什么要紧:四个手写网关,正是一个产品最后长出"这个 op 在 admin 面板上有、在 MCP 上没有"的方式。parity 于是不再是一件要去审计的事,而是一件做不到的事facade-parityui-mcp-parity)。

入站:desk

沙箱里的能力会反过来够回宿主(读语料、写自己的存储、查配额)。这条反向的路收口在 internal/routes/hostdesk,两条结构规矩把它钉在那儿:

  1. 只有 desk 能开能力的 socket。 capsocket.ListenWith 只允许出现在 hostdesk(生产)和 agentcore(eval 的小宿主,用 stdio 提供同一批域声明的 op)。出现在别处——尤其是组装根——意味着某个能力拿到了一个 desk 的清单里没有的 socket,于是"沙箱能向宿主要什么"这个问题不再有答案
  2. 只有域能声明一个 host op。 hostop.Op{ 字面量住在 internal/<域>/{ops,usecase}/,或者两根轴自己的路由包里。组装根负责接线,它不铸造动词

两者互为镜像:dispatcher 回答"宿主能替外面做什么",desk 回答"沙箱能向宿主要什么"。对称本身就是要点——一条绕过 desk 的入站够回,跟一个绕过 dispatcher 的出站面,隐形程度完全一样

内核不持有任何能力

check-core-agnostic 是一条字符串棘轮,它存在是因为 go-arch-lint 有个盲区:它只看包与包之间的 import 箭头,所以看不见"一个内核文件把订会逻辑写在了自己包里"——那种文件的每一根箭头都是绿的。

基线已排干,闸门跑在纯红模式,一条豁免都没有。最后被清掉的那一条值得当范例留着:agent_instruction.go 里出现了 calendar 这个词,因为那段常驻的日期时间上下文对每一个访客说"owner 的日程跑在这个时区"、并让他在提议时间前先确认自己的时区——一条排期指示,发给了手上根本没有订会工具的访客。现在内核只陈述事实(现在几点、哪个时区、访客时区已知时是哪个);怎么换算、什么时候反问、要不要两边都报,是订会那个能力自己的事,写在它自己的 MCP 说明书里,授了才出现。这条规矩压成一句:内核陈述事实,能力下达指示。

向内那一侧只封,不解封

一份凭据只走到半路:向内这一侧,owner 填完表,它被封存起来,而这一侧完全没有打开它的办法。打开发生在出站那一侧,由它把明文交给要花掉它的人。封/开这一对不是某种连接器——它是两者共同站着的那个底层机制

check-core-seals-only 用两个范围守它,因为这条不变量有两个逃逸口,而只有一个是看得见的。import 级的检查抓得住"向内的包 import 了解封器",但它抓不住一个类型是 func([]byte) ([]byte, error) 的结构体字段从外面递进来——那玩意儿一个非法包都没 import,go-arch-lint 看到的是一张全绿的图。所以内核被禁掉的是解封的词汇,不只是那条 import。

一个调度器,以及"轴用数据声明"

  • check-periodic-via-scheduler —— 任何按时跑的东西,都走那唯一一个调度器。语料 reconcile 那个循环的手写版本从来没在任何地方登记过:它一直在跑,而 Monitor 的后台任务面板上看不见它。一个没人数得出来的定时器,是一个没人能被告知的进程。
  • check-axes-declare-in-data —— 两根插件轴把内建件声明成数据,放在各自的顶层目录(backend/capabilities/<id>/manifest.yamlbackend/connectors/<id>/manifest.yaml),组装根只负责拼装。它们以前是 cmd/server 里的 Go 字面量:一个能力的身份、它要用哪些 host op、它占邀请码上的哪个字段、它的配置默认值——全写在程序被接线的地方,而不是这个能力被描述的地方。加一个能力意味着改组装根,而组装根身上长出了每个能力的形状的一份拷贝。

共同的形状

这五条规矩是同一个动作:事实只放在一个地方,其余一切都是它的投影,再由一道机械闸门让第二份拷贝不可能存在,而不是"不建议存在"(mechanical-guardrails)。它们防的不是 bug,是同一件事的两份描述之间的漂移——而评审对这种漂移一贯抓不住,因为每一份拷贝单独看都是对的。

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 →