Runtime 升级
通过 A/B 升级让本机 runtime 更新不打断远程开发。
herdr-mcp 把公网入口和本机 runtime 拆成两个独立的发布平面。
对 ChatGPT 来说,公网 Edge、OAuth issuer 和 MCP URL 应该长期稳定;对工作站来说,herdr-mcp runtime 可以升级、验证、切换和回滚,而不必让 ChatGPT Connector 跟着重连。
ChatGPT
│ stable MCP/OAuth origin
▼
Cloudflare Edge
│ persistent workstation WSS
▼
herdr-link
│ active generation pointer
├───────────────┐
▼ ▼
runtime A runtime B
127.0.0.1:8772 127.0.0.1:8773
这就是 Runtime A/B。
它解决什么问题#
如果公网入口直接指向某一个本机 runtime 进程,那么一次普通升级可能同时影响:
- HTTP/MCP 连接;
- OAuth identity;
- 正在进行的工具调用;
- ChatGPT Connector;
- 回滚路径。
A/B 把这些问题分开。
一个 candidate runtime 先在新的 loopback endpoint 启动并验证,通过 gate 后才成为 active。旧 generation 在需要时继续 drain 已经进入它的请求,并保留为 rollback 目标。
Runtime generation 是什么#
一个 generation 是本机某一份可独立访问的 herdr-mcp runtime 实例,例如:
generation A
endpoint: http://127.0.0.1:8772/mcp
version: current stable
generation B
endpoint: http://127.0.0.1:8773/mcp
version: candidate
herdr-link 持有 active-generation pointer。Edge 并不需要知道端口切换细节;它只和同一个 workstation link 通信。
因此:
切换 runtime ≠ 切换 Connector
这是整个机制最重要的边界。
DEV / PROD 是 generation 之上的 provenance 平面#
当前 runtime 在同一套 generation 机制之上提供明确的源码开发平面,但不会因此多出第三套长期 runtime 环境:
PROD
published / verified installed binary
固定 recovery source
DEV
带 source provenance 的 repo/worktree build
作为 managed generation 激活
日常源码 dogfood 使用:
herdr-mcp dev status
herdr-mcp dev sync
herdr-mcp dev rollback
dev sync 在激活 repo-built DEV generation 前先固定当前 PROD binary/checksum,而且只有 server/Link generation reconcile 完成后才接受切换。dev rollback 回到这个固定 PROD source,不重新编译源码,也不猜“哪个 previous generation 才是稳定版”。底层 bin/herdr-runtime-generation ... 仍可用于实现/UAT,但不再是维护者日常源码 dogfood 的主入口。
Runtime DEV/PROD 与浏览器扩展的 DEV/STANDALONE/STORE 身份模型无关。
A/B 不是进程管理器#
Generation manager 不负责“随便启动任何命令”。
推荐流程是:
- 构建 candidate;
- 独立启动 candidate runtime;
- 注册 generation;
- 运行 health / contract gate;
- activate;
- 观察;
- 成功后再回收旧 generation。
把 process creation 和 traffic activation 分开,可以让“新程序能不能启动”和“是否应该接生产请求”成为两个不同问题。
CLI#
bin/herdr-runtime-generation status
bin/herdr-runtime-generation register \
--generation candidate-<id> \
--endpoint http://127.0.0.1:8773/mcp \
--runtime-version <version>
bin/herdr-runtime-generation activate \
--generation candidate-<id>
bin/herdr-runtime-generation rollback
bin/herdr-runtime-generation remove \
--generation candidate-<id>
生命周期 mutation 禁止使用 launchctl submit。launchd 的 inferred job 可能在命令退出后继续 replay,导致 rollback/update 等破坏性操作重复执行。需要独立进程时使用受管 lifecycle 命令;确需 launchd one-shot job 时,必须使用显式 plist,并设置 RunAtLoad=true、KeepAlive=false。
status 是第一入口。任何升级/回滚前都应该先知道:
- desired active;
- actual active;
- previous generation;
- last known good;
- candidate health。
不要根据上一次发布日志猜当前 active generation。
Activation gate 检查什么#
Candidate 只有在真实 loopback endpoint 上通过验证后才允许切流。
核心 gate 包括:
- endpoint 可达;
- runtime health / discovery 可用;
- 真实
tools/list可用; - public tool contract 与当前 contract epoch 匹配;
- 如果声明 expected runtime version,则版本一致;
- 必要时连续多个 observation window 保持健康。
当前 first-party DEV/PROD 的公共 Edge contract 是 epoch 8 / 19 actions,workstation Runtime Execution Contract 是 epoch 5 / 18 tools。紧邻上一代冻结的 epoch 4 与冻结的 epoch 2 catalog 仅作为有界的 rollback/compatibility 基线保留,epoch 3 仅属历史,公共 Edge epoch 3 身份仍作为非 DEV/PROD 的有界基线保留。具体 canonical hash 属于构建/发布事实,不应该作为一篇长期教程里的常量;实际激活以当前运行配置和冻结 contract 定义为准。
为什么 contract epoch 和 Runtime A/B 必须分开#
A/B 适用于:
同一公开契约下,更换实现。
例如:
- 修一个 fs bug;
- 改 snapshot fallback;
- 提升 exec 稳定性;
- 修 OAuth relay 内部实现但 public tool surface 不变。
如果 tools/list 本身发生不兼容变化,就是 contract migration:
runtime implementation upgrade
≠
public MCP contract migration
后者会影响 ChatGPT tool snapshot、Edge identity、Link contract 以及新会话验收,需要单独的 epoch 迁移流程。
因此 herdr-self-update 不应该被用来偷偷跨 contract epoch。
跨一个 epoch(N → N+1)#
选择一个发布包含两个独立步骤:发现(discovery)与接纳(admission)。
发现只回答“这台工作站应该看哪个发布”。它读取 GitHub releases 索引,选出唯一一个同时满足以下条件的最高版本:非 draft 的语义化版本、该 tag 发布了 release manifest、并且当前 update 通道接纳该版本。发现流程不读取 contract identity,所以它既不会退而选择更旧的发布,也不会把选择改写到某个中间 bridge;通道内最高的合格发布就是被选中的发布。
接纳随后在下载任何东西之前校验被选中的 manifest。所有已发布的 updater 都只实现“完全相等”这一种接纳:manifest 的 contract identity {epoch, hash, tool_count} 必须等于本机 Runtime 的 contract identity,否则以 release manifest contract identity mismatch fail closed。因此任何已发布的 v1.0.x(epoch 4)都无法在任何通道上直接 update 到 epoch 5 的发布,而且结果是拒绝,不是回退到另一个发布。
向前跨恰好一个 epoch 是计划能力,任何已发布的 updater 都尚未实现。它将来会写在 runtime-manifest.json 里:
"contract": { "epoch": 5, "hash": "sha256:<epoch-5>", "tool_count": 18 },
"contract_migration": {
"from": { "epoch": 4, "hash": "sha256:<epoch-4>", "tool_count": 18 },
"state_schema_from": 15
}
这段声明属于 manifest 字节本身,因此会(在实现后)被现有的 manifest digest GitHub artifact attestation 覆盖。
计划中的接纳规则(L 为本机 contract identity,M 为清单中的 contract):
M == L:普通同 contract 更新,行为不变。M != L:仅当以下全部成立时接纳:M.epoch == L.epoch + 1;contract_migration存在且格式合法;contract_migration.from与L完全相等(epoch、hash、tool_count);contract_migration.state_schema_from等于本机 state schema;manifest.state_schema等于本机 state schema。- 其他情况在安装任何东西之前就拒绝,并报出本机与目标 epoch:跨多个 epoch、epoch 更低、
fromhash/tool_count 不匹配、声明缺失或格式错误、contract_migration内出现未知字段。
其余关卡全部保留并照常执行:manifest attestation、tag/source_ref identity、asset sha256、candidate probe、健康门控激活、update fence 与通道规则。在跨 epoch 更新中,下载后的 candidate 会按目标 contract M(而不是 L)验证。
update auto 与交互命令使用同一套发现和同一套接纳检查,因此它可以在完全相同的规则下跨一个(计划中的)epoch,且仍仅限 PROD + Stable。本协议不允许 state schema 迁移;schema 变更仍走显式的 major migration 路径。
v1.0.4 bridge 是计划中、尚未发布的版本#
常规的 v1.0.x → v1.1 路径是两步:先 herdr-mcp update 到 v1.0.4 epoch-4 bridge(contract identity 相同,属于普通同 contract 更新),再 herdr-mcp update 到第一个在 manifest 中声明 contract_migration.from = epoch 4 的 v1.1 发布(计划 v1.1.0-rc.2)。
v1.0.4 是计划中、尚未发布的 bridge。在它发布之前没有可安装的 bridge,v1.1.0-rc.1 也无法从 v1.0.x 到达。
v1.0.4 一旦发布,已发布的 v1.0.0–v1.0.3 可以正常更新到它。它的 version 输出还会打印一行包含 contract epoch 2 / 18 tools 的旧版兼容文本,因此这些已发布 updater 已有的 candidate probe 会接纳该 bridge。前台 herdr-mcp update 和 macOS 后台 stable scheduler(update auto)都会安装它。不需要手工安装。
第二步仍取决于计划中的 contract_migration 接纳规则,任何已发布的 updater 都尚未实现。v1.1.0-rc.1 没有声明 contract_migration,所以只有第一个作出该声明的 v1.1 发布(计划 v1.1.0-rc.2)才能从 v1.0.4 bridge 被接纳。
跨 epoch 之后的回滚#
herdr-mcp rollback 重新激活上一个已安装的 generation(例如 1.1 → v1.0.4)。它属于 service 回滚,因此平台边界很重要:
- 在 macOS 上,一次成功的 install/activation 会记录一份 ready service rollback(上一份 service 定义和上一个 generation),
herdr-mcp rollback会重新校验并重新激活它; - Linux 不写入成功之后的 service rollback 记录。
herdr-mcp rollback仅限 macOS。
两侧都是 state schema 15,所以不需要数据库恢复,但更新之后写入的持久状态不会被回退。在依赖它之前,先检查真实 service/state。
browser_dispatches.message_canonical_digest是一个可空的前向新增列,不是编号迁移。schema 15 的 epoch 4 binary 会打开同一个数据库并忽略该列;更早写入的行保持NULL并只走 raw 比对,属 fail-closed,而不是伪造 canonical 匹配。- PR #605 合并后,包含该修复的发布版本将在 1.1 重新注册时保留显式的 webchat-control 拒绝,因此回滚到 epoch 4 将不会放宽权限。
激活发生时会怎样#
可以把 active pointer 理解成一个本机路由开关:
切换前
new request → A
activate B
切换后
new request → B
existing request on A → A 上完成/drain
理想情况下:
- persistent WSS 不断;
- Edge URL 不变;
- OAuth issuer 不变;
- ChatGPT Connector 不变;
- 已经投递到旧 generation 的请求不会被重复投递到新 generation。
这也是为什么 activation 和 drain 要有明确语义。
Rollback#
如果 B 激活后暴露问题,不需要重新部署公网入口。
bin/herdr-runtime-generation status
bin/herdr-runtime-generation rollback
Rollback 让新请求回到 previous / last-good generation。
回滚前同样要观察真实状态:如果某个 mutation 已经在 B 上发生,回滚 runtime 不会自动回滚 Git、文件、远端服务或 Agent 行为。
Runtime rollback 是执行环境回滚,不是业务副作用回滚。
本机控制状态#
Generation manager 使用本机 control/status state 保存:
- generation specs;
- desired active;
- revision;
- observed active;
- previous / last-good;
- activation observations。
这些文件属于 workstation 控制状态,不是仓库源码,也不应该保存 bearer secret。
Candidate endpoint 限制在 loopback,是为了保持“generation 是本机 runtime”的安全边界,不把 A/B 变成任意远端转发器。
Heartbeat 如何让 Edge 知道版本变化#
workstation link 在 heartbeat 中报告当前 active runtime identity,例如 generation / version 等运行信息。
因此 Edge 可以观察:
workstation online
active generation changed
runtime version changed
不需要因为一次本机 A/B 切换重建 WSS。
公网状态可能在下一次 heartbeat 才收敛,所以刚 activate 后短时间内看到旧 version 并不等于切换失败;以本机 generation status + 后续 heartbeat 为准。
推荐升级流程#
Inspect current state
↓
Build candidate
↓
Start candidate on second loopback endpoint
↓
Register generation
↓
Validate health + tools contract
↓
Activate
↓
Observe real tool calls / heartbeat
↓
Keep old generation during observation
↓
Remove old generation only after confidence
如果任一步骤失败:
- candidate 尚未 activate:修 candidate,不影响 production;
- candidate 已 activate:先判断是否 rollback;
- mutation delivery 不确定:先重新观察,不重复执行发布动作。
从现有 v0.4.8 一键升级到 1.0#
普通已 enrollment 用户只需要执行一条命令:
herdr-mcp update
用户不需要手动下载 1.0 binary、不需要自己运行 update major-apply、不需要删除或重建 Worker、不需要重新 pair 设备,也不需要重新添加 ChatGPT Connector。已经发布且不可修改的 v0.4.8 updater 会先发现仍保持 schema 5 / Runtime epoch 2 身份的 v0.4.9 migration bridge。bridge 只作为原 update job 的 detached worker 运行,成功路径不会把 v0.4.9 安装成 production service。它会下载并验签兼容的 schema 15 / Runtime Contract epoch 4 最终 Runtime,在精确 v0.4.8 service 仍然是 source 时调用已经验收过的 major migration,然后由新的 Runtime 对已有 Cloudflare Worker 做原地 reconcile。
Worker name 与公网 origin 保持不变;Durable Objects、secrets、已知可选 binding、已 enrollment 设备、OAuth issuer 和 Connector records 都保留。同一次 update job 中可能自动打开 Cloudflare 授权页面。若多个可访问 Cloudflare Account 都存在同名 Worker,则 fail-closed,用户可用非秘密的 CLOUDFLARE_ACCOUNT_ID 消歧。如果 v0.4.8 前台 updater 的有界等待先结束,而浏览器授权仍在进行,detached migration 会继续执行;不要求用户再输入第二条 update 命令。
迁移前会保存精确 v0.4.8 binary 与 schema-5 数据库 snapshot 作为 N-1 rollback material。如必须回退,显式使用 herdr-mcp update major-rollback。update major-apply 继续作为 bridge 内部及维护者恢复/UAT 的底层 primitive,不再是普通 v0.4.8 用户的升级步骤。
这条单命令路径只覆盖 schema-5 的 v0.4.8 → 1.0 边界。计划中的 v1.0.4 epoch-4 bridge 是另一条边界:该尚未发布的版本一旦存在,已发布的 v1.0.0–v1.0.3 会用前台 herdr-mcp update 和 macOS 后台 stable scheduler(update auto)安装它。见上一节。
herdr-self-update 适合做什么#
bin/herdr-self-update 是受监督升级入口,它复用 generation 机制,而不是原地覆盖当前 active runtime。
适合:
- 同 contract epoch 的代码更新;
- 可以构建 candidate、跑 gate、切换和回滚的升级。
不适合:
- public tools contract 变化;
- Edge/OAuth identity 迁移;
- Custom Domain/DNS cutover;
- Herdr daemon 本体的任意跨边界升级。
这些是不同 release plane,应单独操作。
与 Edge 发布的关系#
系统有两个主要 release plane:
Public Edge plane
Worker / Durable Object / OAuth / public MCP relay
Local runtime plane
herdr-link / active runtime generation
它们应该尽量独立发布。
例如本机 herdr_fs_* bugfix 通常无需改 Worker;OAuth relay bugfix 通常也无需切本机 runtime generation。
分离 release plane 可以减少一次发布的爆炸半径。
安全规则#
- candidate 必须使用 loopback endpoint;
- 当前 contract epoch 内,contract 不匹配的 candidate 不激活;
- 已投递请求不因切代而重复执行;
- 旧 generation drain 前不强杀;
- credential 不写进 generation control/status;
- 回滚 runtime 前后都重新观察 Git / Agent / 服务真实状态;
- contract migration、Edge deployment、Domain/DNS mutation 不混成一次普通 self-update。
验收标准#
一次成功 A/B 升级至少证明:
- candidate 独立健康;
- 当前 public contract gate 通过;
- active generation 已改变;
- Edge heartbeat 收敛到新 runtime identity;
- 新请求进入新 generation;
- Connector/OAuth URL 没有变化;
- 至少一次真实 MCP tool call 通过;
- rollback target 仍然存在于观察窗口内。
这时才算“本机 runtime 升级完成”,而不是仅仅“新进程启动成功”。
相关内容: