- 人工智能
- AI Agent
- 多智能体
- Agent 编排
- 代码智能体
- CLI
【免费下载链接】openrig
Multi-agent harness that runs Claude Code and Codex together as one system
导读
本文基于 openrig 仓库docker/testbed/runbooks/L3-daemon-in-container.md(测试床三级验证阶梯的第三级运行手册),完整拆解一条在容器内验证 openrig daemon 的核心链路:守护进程如何在容器本地 sqlite 上启动、如何通过发布端口(published port)暴露/healthz健康检查与受 bearer 令牌保护的管理路由,以及rig up如何在零 LLM token 消耗(runtime: stub)的前提下落定一个容器本地的桩拓扑。读者将获得一份可直接照抄执行的 Docker/Apple 双运行时 A/B 验证流程,并理解其背后由源码支撑的四项耦合机制:显式绑定地址、强制 bearer 令牌、显式端口分配与字节级一致的探针设计。所有论点均可在当前仓库源码中逐行印证,文末给出证据路径索引。
一、L3 在测试床阶梯中的定位与核心命题
docker/testbed/runbooks/目录按 L0~L6 划分了测试床验证阶梯,L3 是其中承上启下的一环:
- 前置条件:L3 在宿主侧运行,依赖 L2(tmux server 生命周期)完成后启动的容器环境;
- 本轮职责:消化(exercise)桩负载(stub payload)——即
docker/testbed/stub-assets.list清单中列出的桩rig.yaml、agent fixture 与culture.md,验证容器内的 daemon 能够启动并提供可探测的 HTTP 服务面; - 零 token 的构造保证:桩拓扑使用
runtime: stub(确定性 node 脚本 harness,见 桩 rig.yaml),因此整轮验证不调用任何 LLM,从构造上保证零 token 消耗; - Census 范围:
rig up落定的那组桩资产(rig.yaml+ agent fixture +culture.md)正是构建清单(census)的哈希范围,构建脚本会将其哈希进字节级可复现的 census receipt(见scripts/build-testbed-image.sh:101)。
L3 运行手册的写作背景是一个真实发生的 A/B 对照组事故:旧版 runbook 启动了一个回环绑定(loopback-bound)的 daemon,却通过发布端口去探测它——由构造决定不可达,导致 Docker 与 Apple 两个运行时分支的探测全部失败(操作者 A/B 收据Q2-AB-d121568ad-20260807-host/AB-RESULT.md:Docker curl 52 / Apple curl 56)。一个连控制组都无法通过的实验无法评判实验组。因此本 runbook 的核心是四项"耦合机制"修复,四项全部在源码层落地,且对两个运行时分支原样适用:
| 机制 | 内容 | 源码锚点 |
|---|---|---|
| (a) 绑定地址 | 显式指定绑定地址,绝不依赖默认值 | packages/daemon/src/index.ts:148读取OPENRIG_HOST;绑定计划解析见packages/daemon/src/domain/bind-plan.ts |
| (b) Bearer 令牌 | daemon 在该绑定下启动的强制前提,且被单独证明 | assertBindAuthInvariant,见packages/daemon/src/middleware/auth-bearer-token.ts:240-264 |
| (c) 端口分配 | 显式宿主端口,绝不用0(Apple container 1.2.0 拒绝临时端口发布形式) | runbook 约定HOSTPORT=19433 |
| (d) 探针 | 两个运行时分支字节级一致的探针 | Probe 1 打/healthz(无鉴权);Probe 2 打受守卫的路由(带 bearer) |
二、机制 (a):绑定地址 —— 为什么容器内必须显式OPENRIG_HOST=0.0.0.0
2.1 绑定决策的源码路径
packages/daemon/src/index.ts的startServer()是守护进程的启动入口。绑定决策链如下:
:236读取端口(OPENRIG_PORT/RIGGED_PORT,默认7433);:239通过resolveDaemonDbPath将数据库锚定在OPENRIG_HOME下(避免裸 CWD 相对文件名误开共享 fleet 库);:265-269调用resolveBindPlan生成绑定计划;:284-295依据计划进入显式绑定分支(执行assertBindAuthInvariant强制 bearer 校验)或默认分支(回环 + 探测到的 tailscale 接口多绑定);:317-365对每个绑定 host 各启动一个serve()实例(Hono 的@hono/node-server),共享同一个 Hono app。
2.2 一个必须注意的演进:OPENRIG_HOST已被降级为 ROUTING 环境变量
runbook 写作时引用packages/daemon/src/index.ts:148说明"OPENRIG_HOST设置时绑定该 host、未设置时默认127.0.0.1"。当前源码在此基础上做了关键演进(OPR.0.5.5.20,见 bind-plan.ts 的注释):
- 绑定意图现在只通过专用通道
OPENRIG_BIND_HOST声明;OPENRIG_HOST/RIGGED_HOST被降级为"路由环境变量"(client 端点,任何受管环境都可能注入),永远不参与绑定分支选择; - 若路由环境变量被传入,daemon 会打印一条 bind-provenance 日志(
index.ts:270-275)说明它被忽略,绝不静默; - 演进动因:此前父 daemon 从某个受管环境继承了
OPENRIG_HOST=127.0.0.1,导致其 Tailscale 监听器悄然丢失(operator batonqitem-20260827070400)。
因此,在容器场景下正确且与当前源码一致的绑定声明是:
-e OPENRIG_BIND_HOST=0.0.0.0 # 当前源码:专用绑定意图通道(推荐) -e OPENRIG_HOST=0.0.0.0 # runbook 原文写法:路由变量,绑定决策已不读取无论走哪条通道,其效果等价:让 daemon 绑定到0.0.0.0,使容器发布端口可达。绝不要使用默认绑定——默认分支只绑定127.0.0.1(外加检测到的 tailscale 接口),容器内的回环对宿主发布的端口探针不可达,这正是旧 runbook 事故的根源。
三、机制 (b):Bearer 令牌 —— 启动即强制,且在真正受守卫的表面上单独证明
3.1 启动期硬门禁:assertBindAuthInvariant
packages/daemon/src/middleware/auth-bearer-token.ts:240-264的assertBindAuthInvariant是启动期的硬门禁(HARD-GATE audit row 8)。逻辑:
- 绑定
127.x.x.x/::1/localhost(回环)→ 短路放行; - 绑定 tailscale 网段(IPv4 CGNAT
100.64.0.0/10,IPv6 ULAfd7a:115c:a1e0::/48)→ 短路放行(tailnet 自身即鉴权边界); - 主机名绑定 → 先 DNS 解析再按上述两条重判;
- 以上均不满足(即真正的公网/LAN 绑定,
0.0.0.0正在此列)且OPENRIG_AUTH_BEARER_TOKEN为空 → 抛出AuthBearerTokenStartupError,daemon 拒绝启动。
对容器场景,0.0.0.0属于非回环、非 tailscale 绑定,因此必须设置非空的OPENRIG_AUTH_BEARER_TOKEN,否则 daemon 启动即失败。这个"拒绝"正是产品诚实的体现——runbook 满足该前提,而不是绕过它。
3.2 已核实的更正:/healthz本身是未鉴权的
runbook 特别强调一个"已在源码核实的更正"(不要为了凑预期而修改探针):
/healthz直接注册在 Hono app 上(packages/daemon/src/server.ts:633),返回健康 JSON(含事件循环楔检测、bind 计划、stuck-sweep 心跳等附加字段);app.use("*")(server.ts:486)只注入依赖到上下文,不是全局鉴权中间件;authBearerTokenMiddleware只挂载在六个路由模块内:compaction、hosts、mission-control、rig-policy、sessions、transport。
所以健康探针不需要Authorization 头,也绝不能被用 Authorization 头来评分。
3.3 Bearer 的单独证明面:/api/transport/*
runbook 将 bearer 的证明放在一个真正受守卫的表面上:packages/daemon/src/routes/transport.ts:26的router.use("*", authBearerTokenMiddleware(...))守卫整个 transport 路由器,包括POST /send。于是两个探针对应两个独立事实:
- Probe 1(
/healthz,无鉴权):证明绑定/发布路径可达 daemon; - Probe 2(受守卫路由 + bearer):证明鉴权路径生效;
- 负控制(同一调用但不带 bearer):必须返回 401,证明守卫确实在守卫。
只记录 Probe 1 的 runbook 根本没有测到 bearer——本 runbook 二者都记录。
四、机制 (c):端口分配 —— 显式宿主端口,绝不用临时端口
Apple container 1.2.0 拒绝临时端口发布形式(Error: invalid publish host port range: 0),而 Docker 接受——这是被捕获的运行时差异(见上文操作者收据)。因此 runbook 固定使用一个显式端口HOSTPORT=19433,让两个运行时分支以完全相同的发布形式运行;临时端口分配不是本次 A/B 要测量的差异。
Apple 臂另有已捕获的注意点(非 workaround):回环限定的发布形式127.0.0.1:PORT:7433在 Apple 1.2.0 上会重置,而不加限定的PORT:7433可用。因此 runbook 在两个分支上统一使用不加限定的发布形式——完全相同的输入,而非 Apple 专属让步。
五、完整 Setup:双运行时分支的容器启动
以下脚本是 L3 的容器启动部分(两个分支仅RUNTIME二进制不同,其余完全一致):
GIT_SHA="$(git rev-parse HEAD)"; IMAGE="openrig-testbed:${GIT_SHA}"; EVID="dist/testbed-image/evidence/${GIT_SHA}"; mkdir -p "${EVID}" NAME="orig-l3-${GIT_SHA:0:8}" HOSTPORT=19433 # (c) 显式端口——绝不用 0(Apple 1.2.0 拒绝临时端口) TESTBEARER="l3-testbed-$(date +%s)" # (b) 一次性、容器作用域,绝不是真实凭据 RUNTIME=docker # Apple 臂在此替换其 CLI;其余全部一致 "${RUNTIME}" run -d -t --name "${NAME}" \ -p "${HOSTPORT}:7433" \ -e OPENRIG_HOST=0.0.0.0 \ -e OPENRIG_AUTH_BEARER_TOKEN="${TESTBEARER}" \ -e OPENRIG_SELF_HOST_ID=testbed-l3 \ "${IMAGE}"关键参数语义:
-p "${HOSTPORT}:7433":宿主端口19433映射到容器内 daemon 默认端口7433(index.ts:236的默认值);不加127.0.0.1:前缀以兼容 Apple 1.2.0;OPENRIG_HOST=0.0.0.0:让 daemon 绑定到所有接口,发布端口才可达(见机制 (a);当前源码推荐改用OPENRIG_BIND_HOST);OPENRIG_AUTH_BEARER_TOKEN:非空才能通过assertBindAuthInvariant启动门禁(见机制 (b));OPENRIG_SELF_HOST_ID=testbed-l3:容器内 daemon 的自标识,用于身份溯源。
镜像本身由 scripts/build-testbed-image.sh 在宿主侧构建:digest 钉死的 base、钉死的 Node(默认22.22.1)、从本地 pack tarball(npm pack)安装的 openrig——绝不从 npm registry 拉取(0.5.1 未发布)。构建末尾还内置了 effect-proof:在容器内rig daemon start --no-kernel+curl /healthz,证明 better-sqlite3 原生模块在无工具链的镜像内真实可用(scripts/build-testbed-image.sh:95)。
六、L3.1 —— daemon 在容器本地 sqlite 上启动 + 双探针应答
"${RUNTIME}" exec "${NAME}" bash -lc 'rig daemon start && sleep 2 && rig status || true' | tee "${EVID}/L3-start.txt" # PROBE 1 — 绑定,按设计无需鉴权: curl -fsS "http://127.0.0.1:${HOSTPORT}/healthz" | tee "${EVID}/L3-healthz.txt"; echo # PROBE 2 — 鉴权路径,在真正受守卫的路由器上: curl -sS -o "${EVID}/L3-auth-probe.txt" -w '%{http_code}\n' \ -X POST "http://127.0.0.1:${HOSTPORT}/api/transport/send" \ -H "Authorization: Bearer ${TESTBEARER}" -H 'Content-Type: application/json' \ -d '{"session":"nonexistent@testbed","text":"auth-path probe"}' | tee "${EVID}/L3-auth-code.txt" # 负控制——同样调用但不带 bearer,必须被拒绝: curl -sS -o /dev/null -w '%{http_code}\n' \ -X POST "http://127.0.0.1:${HOSTPORT}/api/transport/send" \ -H 'Content-Type: application/json' -d '{"session":"nonexistent@testbed","text":"x"}' \ | tee "${EVID}/L3-auth-negative.txt" "${RUNTIME}" exec "${NAME}" bash -lc 'ls -l "${OPENRIG_HOME:-$HOME/.openrig}"/*.sqlite* 2>/dev/null || find "$HOME" -name "*.sqlite*" 2>/dev/null' | tee "${EVID}/L3-db.txt"6.1 判定标准
PASS的全部条件:
- Probe 1 在发布端口上返回健康的 JSON;
- Probe 2不是 401(bearer 被接受——未知会话返回任意应用级 4xx 仍证明鉴权路径通过);
- 负控制是 401;
- 存在容器本地的 sqlite 文件。
FAIL的判定(逐条对应机制失效):
- 发布端口上无 healthz → 绑定地址错(机制 a 失效);
- Probe 2 返回 401 → bearer 路径错(机制 b 失效);
- 负控制不是 401 → 守卫根本没在守卫;
- sqlite 落在容器外 / 挂载了真实 HOME → 栅栏(fence)被突破。
6.2 探针背后的源码事实
POST /api/transport/send要求session与text(transport.ts:47-49),未知 session 会走应用级错误分支——所以 runbook 中的nonexistent@testbed会得到"非 401 的应用级状态码",恰好满足 Probe 2 的 PASS 判据;- 负控制与 Probe 2 只差一个
Authorization头,authBearerTokenMiddleware在缺头时立即返回带三段式错误体的 401(auth-bearer-token.ts:104-105)——这就是负控制必须 401 的源码依据; - token 比较使用
crypto.timingSafeEqual常量时间比较,长度不同时也会跑一次同长度零缓冲比较以抹平时序差异(auth-bearer-token.ts:36-51)。
七、L3.2 ——rig up落定零 token 桩拓扑
7.1 桩负载的嵌套路径:这是承重设计,不是意外
构建动词把每条 census 条目以其完整仓库相对路径复制进构建上下文(scripts/build-testbed-image.sh:51-52:cp "${REPO_ROOT}/${rel}" "${CONTEXT}/stub-assets/${rel}"),Dockerfile 再把整个stub-assets/目录拷入镜像(Dockerfile:52COPY stub-assets/ /opt/openrig-testbed/stub-assets/)。
因此容器内三件套落在:
/opt/openrig-testbed/stub-assets/docker/testbed/stub-assets/而不是 stub-assets 根目录。这个嵌套是承重的:同一组相对路径正是 manifest 哈希进字节级可复现 census receipt 的对象(scripts/build-testbed-image.sh:101,经 scripts/testbed-emit-manifest.mjs 生成)。若扁平化暂存路径,receipt 字节就会改变,导致此前 r1 验证过的收据失效。runbook 适配暂存现实,暂存保持原样。(这一确切故障此前已被预测并划定范围到本 runbook——51-04-STUB-ASSET-TRIO-REVIEW-VERDICT-review-r1.md,标记为 "Honest forward-flag",只是备注一直没进正文,直到现在。)
7.2 显式 source 参数:绝不能裸跑rig up
rig up的 source 是必选位置参数(packages/cli/src/commands/up.ts:79:.argument("<source>", "Path to a .yaml rig spec or .rigbundle, or a library name such as secrets-manager"))。裸调用会报missing required argument 'source'退出——这正是操作者当初踩到的错误。合法形式包括rig up secrets-manager、rig up ./rig.yaml、rig up ./demo.rigbundle --target ~/work(见up.ts:70-75的帮助文本)。
7.3 执行脚本
"${RUNTIME}" exec "${NAME}" bash -lc ' set -e STAGED=/opt/openrig-testbed/stub-assets/docker/testbed/stub-assets # 预检栅栏:在构建真正暂存的位置断言负载。暂存变更必须在此 LOUD 失败并点名路径, # 而不是在下游表现为 "missing required argument"。 [ -f "${STAGED}/rig.yaml" ] || { echo "L3.2 FAIL: no rig.yaml at ${STAGED} — staged payload moved; reconcile the runbook against scripts/build-testbed-image.sh"; ls -R /opt/openrig-testbed/stub-assets | head -40; exit 1; } mkdir -p ~/work && cp -r "${STAGED}/." ~/work/ cd ~/work && rig up rig.yaml && sleep 3 && rig ps --json' | tee "${EVID}/L3-topology.txt"执行要点:
- 预检栅栏:
rig up之前先断言rig.yaml存在于暂存路径,暂存布局一旦变更,此处在脚本内立即失败并点名路径(而不是让下游误报参数缺失),实现了"响亮失败"; - 显式 source:
rig up rig.yaml带显式文件参数,不依赖任何库名解析; - 容器本地工作区:负载复制进
~/work(容器内非 root 用户openrig的 home),绝不触达宿主; - 判定:
rig ps --json输出中桩 seat 达到 settled/ready 状态即为 PASS——零 LLM token 消耗(runtime: stub);拓扑无法落定或调用了非 stub runtime 即为 FAIL。
7.4 桩拓扑的三件套内容
镜像内 staged 的桩资产(census 范围)与 runbook 论断完全一致:
- rig.yaml:
version: "0.2",一个 poddev、一个成员worker,runtime: stub,agent_ref: "local:agents/worker",无边(edges 为空); - agents/worker/agent.yaml:无 skills 的最小 agent 定义;
- culture.md:工作文化提示。
runbook 还提示:确切的rig ps形态 / ready 判定在运行时刻对着随镜像分发的 stub adapter 落地——不要断言某个记住的字段名,要读真实的--json输出。
八、Teardown 与证据汇总
"${RUNTIME}" exec "${NAME}" bash -lc 'rig down || true' ; "${RUNTIME}" rm -f "${NAME}" >/dev/null { grep -qi 'health' "${EVID}/L3-healthz.txt" \ && [ "$(cat "${EVID}/L3-auth-code.txt")" != "401" ] \ && [ "$(cat "${EVID}/L3-auth-negative.txt")" = "401" ] \ && echo "VERDICT: PASS — published bind reachable, bearer path proven, negative control refused, stub topology settles" \ || echo "VERDICT: FAIL — see L3-*.txt"; } | tee "${EVID}/L3-verdict.txt"证据文件全部落在dist/testbed-image/evidence/${GIT_SHA}/下,与镜像同 sha 关联,形成字节级可追溯的证据链:
| 文件 | 内容 | 判定角色 |
|---|---|---|
L3-start.txt | rig daemon start+rig status输出 | 启动路径证据 |
L3-healthz.txt | Probe 1 健康 JSON | 绑定/发布路径 |
L3-auth-code.txt | Probe 2 HTTP 状态码(应非 401) | 鉴权路径 |
L3-auth-negative.txt | 负控制状态码(应恰为 401) | 守卫有效性 |
L3-db.txt | 容器本地 sqlite 列表 | 数据隔离栅栏 |
L3-topology.txt | rig up+rig ps --json输出 | 零 token 拓扑落定 |
L3-verdict.txt | 自动判定的 PASS/FAIL | 汇总裁决 |
九、验收门(ACCEPTANCE GATE):先让 Docker 臂单独全绿
验收门是不可协商的:先只在Docker臂上跑完整流程并证明 GREEN——一个过不了的对照组不是对照组,A/B 在 Docker 基线按本手册原文全绿之前保持阻塞;只有在那之后,Apple 臂才以替换 CLI 的方式跑完全相同的流程。
这也是本 runbook 最值得借鉴的工程纪律:控制组必须能通过,才能评判实验组。任何"修复探针去匹配错误预期"的做法都被明确禁止——探测对象是源码事实(/healthz未鉴权、transport 守卫全路由),runbook 顺应事实,而不是让事实迁就脚本。
十、证据路径索引(便于继续深入源码)
- 守护进程启动与绑定:
packages/daemon/src/index.ts(端口默认值:236、绑定计划解析:265-269、显式/默认绑定分支:284-295、多绑定 serve:317-365) - 绑定计划解析与
OPENRIG_HOST降级说明:packages/daemon/src/domain/bind-plan.ts - Bearer 启动门禁与中间件:
packages/daemon/src/middleware/auth-bearer-token.ts(assertBindAuthInvariant:240-264、常量时间比较:36-51、401 三段式错误体:57-73) /healthz注册与全局中间件注入:packages/daemon/src/server.ts(app.use("*")依赖注入:486、app.get("/healthz"):633)- transport 全路由守卫:
packages/daemon/src/routes/transport.ts:26 rig upsource 必选参数:packages/cli/src/commands/up.ts:79- 测试床镜像构建与 stub 暂存:
scripts/build-testbed-image.sh(暂存:51-52、manifest 哈希:101、容器内 effect-proof:95) - 镜像层定义与 stub 拷贝:
docker/testbed/Dockerfile(COPY stub-assets/:52) - 桩资产清单:
docker/testbed/stub-assets.list、docker/testbed/stub-assets/rig.yaml、docker/testbed/stub-assets/agents/worker/agent.yaml - 同阶梯其他 runbook:
docker/testbed/runbooks/README.md与L0-resolve-inputs.md、L1-pty-allocation.md、L2-tmux-server-lifecycle.md、L4-hermetic-fail-closed.md、L5-multi-host-and-51-09.md、L6-container-runner-e2e.md
- 人工智能
- AI Agent
- 多智能体
- Agent 编排
- 代码智能体
- CLI
【免费下载链接】openrig
Multi-agent harness that runs Claude Code and Codex together as one system
相关推荐
openrig-testbed 验证 Runbooks 全解析:L0–L6 主机侧证据驱动测试体系与容器内 daemon 验证实践
openrig testbed 验证 Runbooks 全解析:L0–L6 主机侧证据驱动测试体系与容器内 daemon 验证实践 本篇技术指南以 openri
人工智能AI Agent多智能体Agent 编排代码智能体CLITurbo 后台守护进程(turborepo-daemon)架构与实现深度解析
Turbo 后台守护进程(turborepo daemon)架构与实现深度解析 导读 Turborepo(Rust 编写的 JavaScript / TypeS
构建工具开发工具CLIOpenRIG 51-02 Hermetic Fail-Closed 验证:容器内拒绝外来 Daemon 目标(L4 运行手册深度解析)
OpenRIG 51 02 Hermetic Fail Closed 验证:容器内拒绝外来 Daemon 目标(L4 运行手册深度解析) 导读 本文以 Open
人工智能AI Agent多智能体Agent 编排代码智能体CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考