☰
openrig 测试床 runbook 深度解析:L3 daemon-in-container 容器化守护进程的零令牌拓扑验证
2026/10/1 2:42:29 网站建设 项目流程
  • 人工智能
  • AI Agent
  • 多智能体
  • Agent 编排
  • 代码智能体
  • CLI

【免费下载链接】openrig

Multi-agent harness that runs Claude Code and Codex together as one system

项目地址:https://gitcode.com/GitHub_Trending/op/openrig
点击查看免费下载

导读

本文基于 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()是守护进程的启动入口。绑定决策链如下:

  1. :236读取端口(OPENRIG_PORT/RIGGED_PORT,默认7433);
  2. :239通过resolveDaemonDbPath将数据库锚定在OPENRIG_HOME下(避免裸 CWD 相对文件名误开共享 fleet 库);
  3. :265-269调用resolveBindPlan生成绑定计划;
  4. :284-295依据计划进入显式绑定分支(执行assertBindAuthInvariant强制 bearer 校验)或默认分支(回环 + 探测到的 tailscale 接口多绑定);
  5. :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)。逻辑:

  1. 绑定127.x.x.x/::1/localhost(回环)→ 短路放行;
  2. 绑定 tailscale 网段(IPv4 CGNAT100.64.0.0/10,IPv6 ULAfd7a:115c:a1e0::/48)→ 短路放行(tailnet 自身即鉴权边界);
  3. 主机名绑定 → 先 DNS 解析再按上述两条重判;
  4. 以上均不满足(即真正的公网/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的全部条件:

  1. Probe 1 在发布端口上返回健康的 JSON;
  2. Probe 2不是 401(bearer 被接受——未知会话返回任意应用级 4xx 仍证明鉴权路径通过);
  3. 负控制是 401;
  4. 存在容器本地的 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.txtrig daemon start+rig status输出启动路径证据
L3-healthz.txtProbe 1 健康 JSON绑定/发布路径
L3-auth-code.txtProbe 2 HTTP 状态码(应非 401)鉴权路径
L3-auth-negative.txt负控制状态码(应恰为 401)守卫有效性
L3-db.txt容器本地 sqlite 列表数据隔离栅栏
L3-topology.txtrig 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

项目地址:https://gitcode.com/GitHub_Trending/op/openrig
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询