- Shell 51.2%
- JavaScript 38.6%
- Python 10.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
- 新增参考适配器 deepseek-dsh:经 dsh --profile headless --json 驱动 DeepSeek Harness; overlay 按环境变量选模型与 effort(off|low|high|max),锁定 sandbox/approval/permission, 只加载 ~/.dsh/skills 与 AGENTS.md;cancel.sh 杀整棵进程树;resume 拒绝改变权限(需 --fresh)。 - 新增宿主绑定 hosts/dsh:install.py --host dsh --role peer 把 model-wire-peer 装进 ~/.dsh/skills。 - route.mjs resolve 新增通用开关 --native-only 与第 12 个输出键 native_only。 - profile secaumene 接入 deepseek-flash:新 slot extract(TC14)与 review.of_deepseek(TC26), quick、implement.mechanical、implement.token_dense 按 gpt -> ds -> claude 插入 ds-flash, wire_defaults.ask 改为 ds-flash;policy.md D/E1、pact.md 措辞同步。 - 通用前端 resume.sh 的 --job 模式在包 write=true 时也传 --write。 评审:Opus 降级代评三轮(Codex 冻结),全部发现已修;真机冒烟通过(dsh 0.2.0-rc.2)。 Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
| examples/minimal | ||
| hosts | ||
| profiles | ||
| skills | ||
| tests | ||
| tools | ||
| .gitattributes | ||
| .gitignore | ||
| CHANGELOG.md | ||
| install.ps1 | ||
| install.py | ||
| install.sh | ||
| manifest.json | ||
| README.md | ||
| VERSION | ||
model-routing packet 3.1.0
三个通用 skill,让任意命令行主会话(Claude Code、Codex CLI 或其他)按一份机读路由表,把活分给任意厂商的模型,并用省 token 的线协议通信。核心 skill 不含任何厂商、模型、宿主名;差异全部放在数据里:宿主绑定(hosts/<id>/)、适配器(skills/model-wire/adapters/<id>/)、profile(profiles/<name>/,个人数据)。
Quick install (English). Requires a bash (Git Bash on Windows is fine), Node >= 20, Python >= 3.8.
cd <this packet directory>
python tools/make-manifest.py # regenerate manifest.json after editing any packet file
python install.py --host claude-code --dry-run # show what would change
python install.py --host claude-code --profile <name> # install; pick your own profile (without --profile it uses the one in the local config, else the fictional "example" demo profile, which has no usable channel)
python install.py --verify --host claude-code # hash-check the installation (--host is required, also with --verify)
node skills/model-routing/routing/route.mjs doctor --host claude-code # list vendors that have no channel on this host (exit 1 = gaps)
bash tests/run-tests.sh # offline wire tests; also: tests/install-test.sh, tests/negotiation-test.sh
node tests/route/route-test.mjs && node tests/lint-generic.mjs
1. 分层
| 层 | 位置(packet) | 内容 | 能否出现厂商 / 宿主名 |
|---|---|---|---|
| 核心 skill | skills/model-routing/、skills/model-wire/(不含 adapters/)、skills/model-wire-peer/ |
判定器 route.mjs、线协议前端、对端协议、通用文档、协商工具 |
不能(tests/lint-generic.mjs 强制) |
| 安装与构建 | install.py、tools/make-manifest.py |
按宿主数据安装 | 不能(同上) |
| 宿主绑定 | 参考实现 hosts/<host-id>/;本机生成物 <home>/hosts/<host-id>/ |
host.json、notes.md、assets/ |
能(只写本宿主) |
| 适配器 | 参考实现 skills/model-wire/adapters/<adapter-id>/(由同目录 REFERENCE.json 的哈希清单认定);本机生成物 <home>/adapters/<adapter-id>/ |
参考实现:adapter.json、六个脚本、README.md、PITFALLS.md、tests/;本机生成物只有哈希集里的文件、*.md 与 .registered.json,不含 tests/ 和点文件 |
能(只写本厂商与传输方式) |
| profile | profiles/<name>/ |
profile.json、policy.md、可选 pact.md、可选 history/ |
能(用户数据) |
| 本机目录 | $MODEL_ROUTING_HOME(默认 ~/.model-routing) |
config.json、profiles/、hosts/、adapters/(本机生成并经 register.sh 登记的适配器)、keys/registry.key(登记签名密钥)、state.json、backup/ |
安装与登记生成 |
三个 skill:
| skill | 装在主会话宿主(--role main)还是对端(--role peer) |
作用 |
|---|---|---|
model-routing |
main | 谁干什么:route.mjs resolve 按 profile 的 slot 候选链、本机 state 和宿主渠道选出模型、effort 与调用方式;resolve --native-only 对本次判定施加 side=dispatcher,输出多一个键 native_only(共 12 个);加原则、额度状态机、评审流程、契约检查与账本工具、协商工具 |
model-wire |
main | 怎么说、怎么收:线协议 MODEL-WIRE v2 的调度端 pack → dispatch → wait → unpack,续派 resume,速问 ask;前端不含厂商命令,厂商差异在适配器 |
model-wire-peer |
peer | 对端:识别首行 MODEL-WIRE v2,只读任务包,证据落盘,按固定标签加 REPORT_JSON 回复并自检;附同一份 contract-check.mjs |
适配器与宿主绑定是“标准 + 现场生成”:随包只带参考实现(三个适配器、四个宿主);遇到没有的厂商或宿主,主会话的 AI 按标准现场生成(model-wire/adapter-kit/、references/adapter-playbook.md、references/host-binding-playbook.md),适配器生成后必须真机冒烟通过才登记:唯一入口是 register.sh(route.mjs registry-commit),它亲自跑 conformance 与经 shim 核实真 CLI 的冒烟,再用本机密钥签名写记录;记录不能手写,也没有任何命令接受外部提供的验证结果。登记后才被路由采用。信任由固定位置加哈希清单决定(REFERENCE.json),不由 adapter.json 里的 origin 自称决定。流程概览见第 5 节。
渠道(chosen.channel):候选模型所属厂商在本宿主有原生派发机制(host.json 的 native.kinds 非空,且厂商声明了该宿主)→ native;否则有可在本宿主运行的适配器 → wire;都没有 → 跳过(no-channel)。
2. 目录
packet/
VERSION README.md CHANGELOG.md manifest.json install.py install.sh install.ps1
tools/make-manifest.py
skills/
model-routing/ SKILL.md routing/route.mjs references/{host.md,host-schema.md,host-binding-playbook.md,profile-schema.md,...} tools/{ledger.sh,contract-check.mjs} negotiation/
model-wire/ SKILL.md scripts/ references/{adapter-contract.md,adapter-playbook.md,...} adapter-kit/{template,conformance.sh,smoke.sh,register.sh} adapters/{REFERENCE.json,openai-codex,anthropic-claude-cli,deepseek-dsh}/
model-wire-peer/ SKILL.md instructions-block.md scripts/ references/
hosts/{claude-code,codex-cli,dsh,generic}/ host.json notes.md assets/
hosts/template/ host.json notes.md(带 TODO(host) 占位,给现场生成宿主绑定用)
profiles/{example,secaumene}/ profile.json policy.md [pact.md] [history/]
tests/ route/ run-tests.sh install-test.sh negotiation-test.sh lint-generic.mjs fixtures/
examples/minimal/ 最小任务包样例(仅测试夹具用:task_class 与 model 取自 tests/fixtures 的 profile,在 example / secaumene 下 pack 会失败;格式可参考,值要换成自己 profile 里的)
3. 安装
install.py --host <id>[,<id>…] [--profile <name>] [--role main|peer|both] [--home <dir>]
[--routing-home <dir>] [--dry-run] [--force] [--verify] [--uninstall] [--uninstall-pact]
[--no-instructions] [--reference-adapters all|none] [--quiet]
--host必填(包括--verify、--uninstall时),可逗号分隔多个,也可以是一个含host.json的目录;--home只在单个宿主时允许,覆盖host.json的 home;generic必须给--home。宿主 id 先在 packet 的hosts/找,再在<routing-home>/hosts/<id>/(本机生成的绑定)找;host.json的id必须等于它所在的目录名;两处同 id 时用随包的参考宿主,并把本机那份整目录移到<routing-home>/backup/<时间戳>/hosts/<id>/再写入参考绑定,打 WARN(所以生成的宿主别与参考宿主同名);两处都没有就退出 2,并打印四步:建目录 → 复制hosts/template/→route.mjs check-host <目录>/host.json→install.py --host <目录>(详见references/host-binding-playbook.md)。--profile缺省取本机config.json里的;本机也没有就用example(虚构厂商的演示 profile,装完多半没有可用渠道,真要用请自选 profile)。--role缺省both。--reference-adapters all|none(缺省all):none时不装随包的参考适配器(纯生成模式,.installed.json记reference_adapters,重装缺省沿用上次的值,--verify据此跳过model-wire/adapters/**)。本机<routing-home>/adapters/与hosts/里的生成物,重装不清(同 id 的宿主绑定见上,移入 backup)。- 安装末尾对每个宿主跑
route.mjs doctor --quiet,有厂商缺渠道时只打 NOTE(不算失败)并指向adapter-playbook.md;安装时的渲染与doctor都用刚写入的routing/host.json,与运行时一致。--routing-home不是缺省值($MODEL_ROUTING_HOME或~/.model-routing)时安装末尾打 NOTE:运行时要设同样的MODEL_ROUTING_HOME,--verify在不设该变量时发现不一致会报 FAIL。 - 路径限制:宿主绑定的
skills_dir、instructions_file、native.subagent.dir、legacy_cleanup.migrate_state_from展开后必须在展开后的{home}之内(check-host与安装器都拒绝跳出,安装器写文件、改名前再断言);legacy_cleanup.remove_skill_dirs不得含model-routing、model-wire、model-wire-peer。 - 每个宿主:备份将被覆盖的目录到
<routing-home>/backup/<时间戳>/<host>/→ 清空目标 skill 目录 → 复制核心 skill → 复制host.json到model-routing/routing/host.json→ 把notes.md装成model-routing/references/host.md与model-wire/references/host.md→ 把 profile 的policy.md渲染进SKILL.md的profile-policy标记块(frontmatter 的skill_description_append追加到 description 末尾)→ 按 profile 的subagent_types生成子代理文件(仅声明了native.subagent的宿主)→skill_extras→ 指令文件标记块(对端协议块;profile 带pact.md时再写策略块)→legacy_cleanup(旧 skill 目录先备份再删,旧块剥除,旧 state 迁移)→ 写.installed.json。 - 本机目录:复制 profile(不含
history/)与全部宿主绑定(整份:host.json、notes.md、assets/、子代理模板,本机那份也能单独通过check-host),写config.json,对每个安装的宿主生成models-and-task-classes.<host-id>.md,state.json不存在时迁移旧 state 或写空 state。本机 profile 受保护:<routing-home>/profiles/<name>/里与 packet 不同的文件,只有仍与安装器上次复制的内容逐字节一致(记在同目录的.profile-sync.json,值是 sha256)或给了--force才会被覆盖;否则保留本机版并打 NOTE。本机删除同样受保护:你删掉的文件(例如不要pact.md)重装时不会被恢复,只打 NOTE。所以改本机 profile(例如给厂商加适配器 id)不会被重装冲掉,packet 里的 profile 有更新时要自己合并,或--force(覆盖前备份)。 --verify --host <id>:按 manifest 校验 sha256,渲染块与 profile 的policy.md逐字一致,无多余文件,旧目录与旧块已清,对端协议首行为MODEL-WIRE v2。--uninstall只删.installed.json记录过、且属于本次--role的 skill 目录,以及子代理文件和指令块(策略块需--uninstall-pact),不动本机 state;不是本 packet 装的同名目录保留。- 改过 packet 里任何文件后先
python tools/make-manifest.py(--check只校验)。install.sh/install.ps1只是找到 Python 后转调install.py。
本机目录与环境变量
| 变量 | 含义 | 缺省 |
|---|---|---|
MODEL_ROUTING_HOME |
本机目录 | ~/.model-routing |
MODEL_ROUTING_PROFILE |
指定 profile(名字或路径) | config.json 的 profile |
MODEL_ROUTING_HOST |
指定宿主(id 或路径) | routing/host.json |
MODEL_ROUTING_STATE |
指定 state 文件 | <home>/state.json |
MODEL_WIRE_ADAPTERS |
适配器目录(只给测试用:设了就只用它;该目录若是可信参考位置,按 REFERENCE.json 认定,否则其中每个适配器都必须有效登记,没有“旁路目录免登记”;旁路下 doctor、adapter-dir、wire-target 都输出 bypass:true,每条命令向 stderr 打 WARN,--quiet 不静音;目录就是 <home>/adapters 时不算旁路) |
依次 <home>/adapters/、同级 model-wire/adapters |
MODEL_ROUTING_BASH |
前端使用的 bash | 宿主的 bash_env 变量,再自动探测 |
文档里的路径一律用 node <model-routing>/routing/route.mjs where 查(一行 JSON,含 adapters_dirs:[本机, 参考];适配器目录再用 route.mjs adapter-dir <id> 取)。测试时把 MODEL_ROUTING_HOME 设为临时目录并清掉前三个变量,不要读写真实目录。
4. 写一个 profile
profiles/<name>/profile.json(schema 4.0.0):
vendors:每个厂商{native_hosts, adapters, reserved}——哪些宿主能原生派它的模型、有哪些线协议适配器(按偏好排序)、是否保留不用。models:alias、id、vendor、tier、effort{default,critical,allowed}、allow_null、requires_authorization,可选native_name(宿主原生派发时用的名字)。slots:slot 名 → 候选链[{model, effort?, flags?}],顺序即优先级;main-session是保留 slot 名。task_classes:{id, name, role, slot, consult?, capability_fallback?, critical_effort?, effort_override?, binding?};binding只有requires(能力)、vendor、side(dispatcher|peer)。- profile
secaumene接入 deepseek:新增extract与review.of_deepseekslot、TC26,TC14 改走extract,quick、implement.mechanical、implement.token_dense插入 ds-flash,wire_defaults.ask为 ds-flash。 - 可选:
reviewer_by_vendor、hosts.<id>(覆盖能力、subagent_types)、adapters.<id>(覆盖能力)、wire_defaults.ask、negotiation.parties。 policy.md:策略正文(硬约束、纪律),渲染进 SKILL.md;frontmatter 可有skill_description_append。pact.md:对端行为准则正文(不含标记行,由安装器加)。- 字段逐项表与从零写 profile 的例子见
skills/model-routing/references/profile-schema.md(随 skill 安装)。先从profiles/example/抄起;写完跑route.mjs check --profile <名> --host <id>(宿主相关项只 WARN)。binding.side:"peer"的任务类只选 wire 渠道,"dispatcher"只选 native;字符集:适配器 id 与宿主 id 只允许[a-z0-9][a-z0-9-]*;profile 名与subagent_types[].name允许[A-Za-z0-9_.-]+(不能是.、..);check对 profile 里引用的适配器 id 与宿主 id 按前者校验。能力词(binding.requires、capabilities、spawn_limits)是 profile 与宿主 / 适配器约定的自由字符串。 - schema 在哪看:profile.json 见本节与
skills/model-routing/references/profile-schema.md;host.json 见skills/model-routing/references/host-schema.md(含占位符、spawn_limits);adapter.json(schema 1.1.0,必填test、origin;可选test.cli(真实 CLI 可执行名,origin:"generated"的适配器必填)、test.help_cmds、generated_by、generated_at、hash_files;touched取不到时fetch填 null)见skills/model-wire/references/adapter-contract.md第 1 节。这三处是包内自带的权威说明,不依赖作者仓库里的设计文档。
5. 加宿主、适配器、厂商(标准 + 现场生成)
主会话的 AI 在安装 skill 时或遇到缺口时,按标准自己生成,生成物放本机目录,不改包。
流程概览
route.mjs doctor [--host <id>]找缺口:输出{host,profile,gaps,unverified,ok},有 gap 退出 1。gaps是没有渠道的厂商,unverified是找到但不可用的适配器,reason取值:not-registered、not-reference、registered-invalid、bad-signature、not-committed、conformance-failed、smoke-missing、smoke-failed、smoke-fake、smoke-not-via-cli、bad-hash-files、hash-mismatch、undeclared-file、vendor-mismatch、shadowed、invalid-adapter-json(含义与处置见SKILL.md第 7 节;以route.mjs实际输出为准)。登记记录里的smoke.cli_bin与smoke.cli_version记的是冒烟时实际调用的真二进制与版本;真实 CLI 版本变了doctor会打 WARN,提示重跑冒烟。- 缺适配器(
model-wire的references/adapter-playbook.md):读目标 CLI 的--help→cp -r skills/model-wire/adapter-kit/template到暂存目录并按adapter-contract.md填写(模板的adapter.json、env.sh、common.sh留有TODO(adapter)占位,清单在模板README.md;check-adapter要求adapter.json里不再有占位;模板tests/test.sh依次从MODEL_WIRE_KIT、<适配器>/../../adapter-kit、<适配器>/..找conformance.sh)→ 写假 CLI →adapter-kit/conformance.sh <目录>直到通过 →adapter-kit/register.sh <暂存目录> [--model <id|alias>] [--effort e] [--force](即route.mjs registry-commit:先复制一份快照,自己跑 conformance 与冒烟并核对结果,都在快照上;冒烟用真 CLI 经 dispatch→wait→fetch 做一次随机 N 的“加 1”挑战,supports.ask为 true 时 ask 也问一次,花少量额度,先告诉用户;都通过才把去掉tests/的快照装到<home>/adapters/<id>/并写带签名的.registered.json,运行的目录就是冒烟的目录)→ 把 id 加进本机 profile 的vendors.<v>.adapters(改前先route.mjs check --host,给用户看 diff)→ 再跑doctor。 - 缺宿主绑定(
skills/model-routing/references/host-binding-playbook.md):install.py --host <id>退出 2 并给出四步 → 复制hosts/template/到<home>/hosts/<id>/并填写(消掉所有TODO(host),id等于目录名,路径字段都在{home}之内)→route.mjs check-host <host.json>→install.py --host <id>。 - 校验命令:
route.mjs check-host <host.json 或其目录>、route.mjs check-adapter <适配器目录>(退出 0 通过、1 不通过、2 用法错误);route.mjs adapter-dir <id>输出{id,dir,origin,verified,reason,bypass},找到(无论是否已验证)退出 0,id 未知退出 1,id 非法或用法错误退出 2,verified:false的目录不能用(线协议前端见到会拒绝并退出 2)。 - 登记不能造假,必须冒烟才登记,信任由固定位置加哈希清单决定:
- 免登记的只有一种:适配器在可信参考位置(
skills/model-wire/adapters/,以及仅在 packet 布局下才有的测试夹具目录tests/fixtures/adapters、tests/route/fixtures/adapters),被该位置的REFERENCE.json列出,且文件哈希全部相符。REFERENCE.json由python tools/make-manifest.py生成、安装器随适配器复制;哈希集 =adapter.json+ 六个脚本 +hash_files+tests/外的其余非*.md文件。install.py --verify另按 manifest 校验这些文件。adapter.json的origin只作说明,写什么都不改变信任结论。 - 其余适配器(本机目录、旁路目录,或可信位置里没列出的)都必须有效登记,否则
adapter-unverified(reason 见第 1 步)。适配器 id 不能与任何可信参考位置的 id 相同(register.sh拒绝)。 - 只有
register.sh能写.registered.json。没有registry-sign;registry-commit不接受外部提供的 conformance 或冒烟结果,一律自己跑:快照(不含点文件)→conformance.sh --out→smoke.sh --out→ 核对两份固定格式的结果 → 在快照上算哈希 → 本机密钥(<home>/keys/registry.key,route.mjs registry-key [--ensure]查或生成)签名 → 去掉tests/装到本机目录(先备份旧的,加锁)。--force只覆盖同 id 的本机生成物,不能跳过冒烟。 - 冒烟经 shim 核实真 CLI:临时目录放一个与
test.cli同名的 shim,记录调用并原样转调从剔除适配器目录后的 PATH 解析出的真二进制,test.bin_env指向 shim;dispatch 与(支持时)ask 都要经过 shim,且回复里的SMOKE-<N+1>必须出现在 shim 记下的真二进制输出里(回显问句原文判失败)。origin:"generated"的适配器必须填test.cli;test.bin_env指向适配器目录内的拒绝。 - 记录(
schema_version:"2")含committed_by:"registry-commit"、files(sha256,小写十六进制,无前缀)、conformance、smoke(ok、fake:false、via_shim:true、cli_bin、cli_version、nonces、steps)、sig。校验时除签名与各项结果外,目录里不得有哈希集、.registered.json、*.md之外的文件(reasonundeclared-file),所以运行树就是冒烟树:登记后不能再补文件,需要的运行时文件都要写进hash_files。 - 禁止手写、拷贝或改写
.registered.json,不要往已登记目录里补文件,不要用测试变量绕过冒烟。适配器运行时只写$WIRE_DIR,目录只读。威胁模型与残余风险见下面“适配器登记的安全边界”。
- 免登记的只有一种:适配器在可信参考位置(
手工方式(参考实现的写法)
- 加宿主:建
hosts/<id>/{host.json,notes.md}(字段见references/host-schema.md)。有原生派发就在native.kinds声明各 kind 的参数模板;没有就留空数组,所有非主会话任务走线协议。notes.md写怎么调invoke。需要子代理文件就加native.subagent与assets/agent-template.md。宿主沙箱会被子进程继承时设spawn_limits。 - 加适配器:建
skills/model-wire/adapters/<id>/,含adapter.json、dispatch.sh、wait.sh、fetch.sh、resume.sh、ask.sh、env.sh、README.md、PITFALLS.md、带假二进制的tests/test.sh。接口与步骤见skills/model-wire/references/adapter-contract.md。随包新增参考实现后要跑python tools/make-manifest.py,它会把该适配器写进skills/model-wire/adapters/REFERENCE.json;不在清单里的适配器不算参考实现,会被要求登记。 - 加厂商:在 profile 的
vendors写一项(native_hosts/adapters),在models里加模型,在slots与task_classes里引用;厂商若走线协议,需要有对应适配器。核心代码不用改。 - 通用性由
node tests/lint-generic.mjs把关:核心范围内出现任何厂商、模型、宿主名都会失败(含新增的skills/model-wire/adapter-kit/**)。
6. 验证
python install.py --verify --host claude-code # 安装文件 hash、宿主绑定、渲染块、指令块(--host 必填)
node tests/route/route-test.mjs # 路由用例(独立 oracle 写的 cases/*.json)
node tests/lint-generic.mjs # 核心范围零厂商词
bash tests/run-tests.sh # 线协议离线全链路(假适配器)+ 各参考适配器的 conformance + register 正反例
bash tests/install-test.sh # 临时 home 下装各个宿主、旧物清理、卸载
bash tests/negotiation-test.sh # 3 方合成数据的协商工具
附录 A 宿主:claude-code
- 安装:
python install.py --host claude-code --profile <name>;home 取CLAUDE_HOME,缺省~/.claude;skill 装到{home}/skills/,子代理文件装到{home}/agents/(由 profile 的hosts.claude-code.subagent_types生成,只写这些文件)。不写指令文件。 - 原生派发 kind:
workflow(agent({model, effort}))、agent_tool(Agent工具,只能靠子代理类型表达 effort)。用法见hosts/claude-code/notes.md。 - 旧物清理:删旧的
codex-offload、codex-wireskill 目录(先备份),从{home}/routing-state.json迁移 state。 - 派发线协议要在 Bash 工具里进行,
wait用run_in_background。
附录 B 宿主:codex-cli
- 安装:
python install.py --host codex-cli --profile <name> --role peer(作对端)或both;home 取CODEX_HOME,缺省~/.codex。指令文件{home}/AGENTS.md里写对端协议块与(profile 带pact.md时的)策略块;每个 skill 带agents/openai.yaml。 - 没有原生派发:全部走线协议,需要派发方身份的任务类退化为主会话自己做(
evidence:"no-native-kinds")。 - 旧物清理:删旧的
claude-wire目录,剥除旧的claude-wire与codex-offload:pact块。
附录 C 宿主:generic
- 安装:
python install.py --host generic --home <dir>(必须给--home),不写指令文件,无原生派发;对端协议块要手工放进宿主自己的指令机制。
附录 C2 宿主:dsh
- DeepSeek Harness CLI(
dsh)。安装:python install.py --host dsh --profile <name> --role peer;home 取DSH_HOME,缺省~/.dsh,skill 装到{home}/skills,不写指令文件,无原生派发(native.kinds为空)。用法见hosts/dsh/notes.md。
附录 D 适配器(随包参考实现;本机生成的适配器在 <home>/adapters/,不在此表)
| 适配器 | 厂商 | 可运行的宿主 | 对端宿主 | 说明 |
|---|---|---|---|---|
openai-codex |
openai | claude-code(依赖其 codex 插件的 companion) |
codex-cli |
用 companion 起后台作业;命令参考见其 README.md,坑见 PITFALLS.md;账本会话 id 由 env.sh 导出 |
anthropic-claude-cli |
anthropic | 任何有 bash 的宿主 | claude-code |
直接调 claude -p,作业记录在 $WIRE_DIR/jobs/<job>/ |
deepseek-dsh |
deepseek | 任何有 bash 的宿主 | dsh |
调 dsh --profile headless;overlay 选模型与 effort,锁定权限(sandbox-policy、approval、permission)与 skill 根;带 cancel.sh;resume 拒绝权限变更(要改用 --fresh)。细节见其 README.md,坑见 PITFALLS.md |
openai-codex-exec(直接调 codex exec,不依赖插件)已设计,推迟到 openai 订阅恢复后再做。
适配器登记的安全边界
威胁模型:登记闸门防的是宿主 AI 偷懒或犯错(手写记录、拿假 CLI 冒烟、事后改文件、把自己写的适配器放进参考目录冒充),不防有本机权限的蓄意者。原则:签名者必须亲自跑验证,任何命令都不接受外部提供的“证据”;可信与否由固定位置加哈希清单决定,不由文件自称。
已生效的防护
route.mjs registry-commit是.registered.json的唯一写入者,由它亲自跑 conformance 与冒烟,再签名安装;没有公开的签名命令。- 冒烟用 N+1 挑战加 shim:shim 记录真二进制的实际输出,
SMOKE-<N+1>必须出现在其中,才算经过真 CLI(via_shim:true,记录里留cli_bin、cli_version)。 - 登记记录带本机密钥的签名,并含文件哈希、conformance 与冒烟结果;校验时逐项核对。
- 可信参考位置只有固定目录,且各带
REFERENCE.json哈希清单;列在清单里且哈希相符才免登记,origin自称不算数。 - 运行树等于冒烟树:登记安装的目录不含
tests/与点文件,目录里出现清单外的文件即判undeclared-file。 - 前端(resume 等)拒绝 unverified 适配器,并报出 reason。
残余风险
没有工具能完全堵上,如实列出:
- PATH 上、适配器目录外有能正确计算 N+1 的替身程序,shim 分辨不出它是不是真 CLI。登记记录里的
cli_bin、cli_version可人工核对。 - 签名密钥
<home>/keys/registry.key可读,有本机权限者可自行计算 HMAC 签名。 - 运行时改
bin_env或 PATH,可绕开登记时核实过的那个二进制。 - 校验与执行之间存在 TOCTOU:目录只读、每进程缓存一次哈希能缩小窗口,不能消除。
- 适配器蓄意定位并改写 shim 日志。
- PATH 上被替换的 bash 或 node。
- 在 packet 布局里修改适配器后重跑
make-manifest。这属于对 packet 的代码改动,靠 git 审查。
另外:冒烟会花少量额度,登记前要先告诉用户;MODEL_WIRE_SMOKE_ALLOW_FAKE=1 与 MODEL_WIRE_ADAPTERS 只给测试用,它们产生的结果在真环境里会被判 smoke-fake 或带 bypass:true。
已知问题(3.0.0 未修,计划下一版;3.1.0 仍未修)
下面 6 项是 QA 负向验收发现的,规格 docs/routing-v4-design.md §22 已拟定修法,3.0.0 与 3.1.0 都没有实施。在它们修好之前,不要把 verified:true 当成对适配器的完整担保。
- A(严重度:高)冒烟没覆盖生产路径。 现象:smoke 总是把
test.bin_env改指 shim,适配器env.sh里bin_env未设时的默认分支从未被冒烟。影响:默认分支指向适配器目录内的替身,也能拿到verified:true。计划:只在调用方原本设了bin_env时才改指 shim,否则只靠 PATH 最前的同名 shim;默认值指向目录内替身时 shim 不被调用,判via_shim:false。 - B(严重度:中)输出可被“洗”。 现象:shim 把真二进制输出与适配器自己写的内容不加区分。影响:适配器可以自己拿到 N+1 并写进输出,伪装成真二进制产生。计划:只把真二进制在本次调用期间写出的 stdout/stderr 及新建或变化的输出文件计为其输出;argv 或 stdin 里已含
SMOKE-<N+1>则该步判失败。 - C(属蓄意行为)shim 日志对适配器可见。 现象:shim 日志目录通过环境变量暴露给适配器。影响:适配器可以直接改写日志。计划:不再导出日志目录,日志目录与 shim 目录分开且不在 PATH 上;蓄意定位并改写日志仍列为残余风险。
- D(属蓄意行为)kit 子进程环境未净化。 现象:registry-commit 调用 conformance/smoke 时继承原环境。影响:
BASH_ENV、BASH_FUNC_*等变量可注入代码;MODEL_ROUTING_BASH也不要求绝对路径。计划:去掉BASH_ENV、ENV、BASH_FUNC_*、SHELLOPTS、BASHOPTS、CDPATH、GLOBIGNORE,bash 用存在的绝对路径;PATH 上替换 bash/node 仍列为残余风险。 - E(严重度:中)
REFERENCE.json本身未防改。 现象:参考清单与它所列的适配器可以一起被改。影响:改一份清单就能让改过的适配器免登记。计划:安装器在.installed.json记reference_sha256,运行时比对,不符则整个参考目录不可信(reference-list-modified);安装布局下夹具目录永不可信;packet 布局要求manifest.json记录并匹配该清单的哈希。 - F(严重度:中低)哈希集不含
*.md与子目录。 现象:登记哈希与 undeclared 检查放过*.md。影响:登记后可往适配器目录补 md 文件,而不被发现。计划:生成物的哈希集与 undeclared 检查改为安装树内除.registered.json外的全部文件;参考适配器的REFERENCE.json改为除tests/**外的全部文件。
已知限制
-
3.1.0 新增已知问题(deepseek-dsh):假 CLI 接受
conf-前缀的未知会话(为配合 conformance);resume --thread在$WIRE_DIR/jobs里没有作业记录时不校验权限变更,调用方须自己确认;cancel.sh没有在真实 dsh 上验证过;目标 dsh 是 0.2.0-rc 版本,参数与配置键可能变动。 -
openai-codex的派发、轮询、取结果只能在 Claude Code 的 Bash 工具里、同一个项目目录下进行;会话结束时插件钩子会杀掉本会话的后台作业,跨会话只认checkpoint.json里的线程 id。 -
对端沙箱能否访问某执行环境因适配器和宿主而异(Codex 沙箱进不去 WSL;宿主
codex-cli的spawn_limits让从它拉起的 wire 子进程也进不去);packet.json里"deferred": true时对端不跑验收,只把命令写进peer/deferred.txt(报告字段deferred_commands,digest 行DEFERRED:),由派发方回到自己的环境执行。wsl现在只是 profile、宿主、适配器里的一个能力词,线协议里不再出现。 -
unpack的 digest 里touched: n/a表示适配器取不到对端实际改动的文件列表(adapter 的fetch输出touched:null),不等于“没改动”,不能判为与报告一致;对端写的证据路径必须是项目相对路径,绝对路径一律被report_check拒绝,验收只比对条数不比对命令原文。 -
真机冒烟(适配器登记)会花少量额度,每次登记前要先告诉用户;
MODEL_WIRE_SMOKE_ALLOW_FAKE=1只给测试用,用它登记出来的适配器会被判为adapter-unverified(smoke-fake)。防护对象是“宿主 AI 偷懒或犯错而绕过流程”,不是拿到本机权限的恶意者,详见“适配器登记的安全边界”。 -
对端会加载所有 skill 的描述,描述过长会被截断;
model-wire-peer的描述刻意写短。 -
packet.json和含正则的 JSON 要用编辑器 / 文件写入工具写,不用 shell heredoc。 -
本包 3.0.0 不兼容旧名:
codex-offload、codex-wire、claude-wire、.claude-codex/、CLAUDE-WIRE v1、MODEL-WIRE v1都不再被识别,旧的本机物只由安装器一次性清理或迁移。