baa-conductor

git clone 

im_wower  ·  2026-03-22

DESIGN.md

   1# BAA Conductor 实施设计说明
   2
   3## 0. 文档状态
   4
   5本文档是新 conductor 系统的实施基线。
   6
   7## 0.1 当前有效部署决策
   8
   9`2026-03-22` 起,当前有效目标已经简化为:
  10
  11- 只保留 `mini` 作为唯一中控
  12- 只保留 `mini` 的自启动、控制面、运行态检查与公网入口
  13- 不再继续推进 `mac` 备主、planned failover、emergency failover、switchback
  14
  15下面保留的主备/failover 内容,视为历史设计记录,不再是当前执行目标。
  16
  17它的细节深度是按“多个 Codex worker 读完就能并行开工”来写的。
  18
  19本文档定义了:
  20
  21- 系统目标
  22- 拓扑结构
  23- 域名与转发布局
  24- 历史上的 `mini` / `mac` 主备切换行为
  25- Cloudflare D1 控制平面设计
  26- task 与 step 模型
  27- `planner`、`conductor`、`worker` 的职责边界
  28- Codex 执行约定
  29- 日志、checkpoint 与恢复机制
  30- Firefox 插件集成方式
  31- 建议的仓库结构
  32- 建议的并行开发拆分
  33
  34如果代码与本文档不一致,要么更新代码以符合本文档,要么明确修订本文档。
  35
  36## 1. 总体目标
  37
  38构建一个稳定的 AI 执行系统,具备以下特征:
  39
  40- `mini` 是唯一长期运行的 conductor。
  41- `mini` 承担自启动、控制面、本地状态面和浏览器控制集成。
  42- 如需额外开发机或临时 worker,视为辅助资源,不再纳入主备设计目标。
  43- 人类只使用一个可见的 Claude `control` 对话。
  44- 自动化使用一个隐藏的 Claude `dispatch` 通道。
  45- task 真相存储在共享数据库中。
  46- 进度恢复依赖 checkpoint,而不是试图复活已死亡的进程。
  47
  48整个系统围绕一个简单原则展开:
  49
  50**恢复 task 进度,而不是恢复 Codex 进程内存。**
  51
  52## 2. 现有基础组件
  53
  54当前环境已经有三个关键基础组件。
  55
  56### 2.1 `baa-hand`
  57
  58当前职责:
  59
  60- AI 路由
  61- `POST /ask`
  62- `POST /chat`
  63- `POST /plan`
  64
  65当前优点:
  66
  67- 能调 Claude
  68- 能调 Codex
  69- Codex 和 Claude 网页版都能用它
  70
  71当前限制:
  72
  73- 现在的 Codex 集成是面向进程的,不是面向 task 的
  74- 它会等到进程退出后再返回
  75- 它还不是一个可持久恢复的 step worker runtime
  76
  77结论:
  78
  79- 继续用 `baa-hand` 走 planner 和 review 路径
  80- 不要把当前 `baa-hand -> codex exec -> 等进程退出` 这条路径当成最终的 durable worker 模型
  81
  82### 2.2 `baa-shell`
  83
  84当前职责:
  85
  86- shell 命令执行
  87- 文件读写
  88- task 报告
  89
  90结论:
  91
  92- 它仍然适合远程操作流和外部自动化
  93- conductor 在 HTTP 执行比直连 SSH 或本地 exec 更方便时,可以调用它
  94
  95### 2.3 Firefox 插件
  96
  97当前职责:
  98
  99- 拦截 Claude 网页流量
 100- 已有常驻 controller 概念
 101- 可以承载可见的自动化状态与控制按钮
 102
 103结论:
 104
 105- 用它接可见的 `control` 会话
 106- 增加全局 `pause`、`resume`,可选 `drain`
 107- 不要让浏览器状态成为真相来源
 108- 浏览器按钮必须写入 conductor 控制平面
 109
 110## 3. 设计原则
 111
 112### 3.1 Durable 真相必须在 AI 进程之外
 113
 114真相不能放在:
 115
 116- 浏览器标签页
 117- Codex 进程
 118- 临时 Claude 会话
 119- Node.js 进程内存中的 map
 120
 121真相必须放在:
 122
 123- Cloudflare D1
 124- 本地 run 目录
 125
 126### 3.2 Codex 是 Worker,不是 Conductor
 127
 128Codex 可以:
 129
 130- 在被要求时参与规划任务
 131- 执行某个 step
 132- 总结结果
 133
 134Codex 不可以:
 135
 136- 持有 lease
 137- 持有队列真相
 138- 持有调度真相
 139- 直接改全局状态
 140
 141### 3.3 Planner 是抽象角色
 142
 143`planner` 是角色,不是固定实现方。
 144
 145planner 可以是:
 146
 147- 确定性的模板
 148- Claude dispatch
 149- Codex
 150- 未来单独的 planner 模块
 151
 152是否使用 planner、是否接受 planner 输出,由 conductor 决定。
 153
 154### 3.4 Conductor 必须是确定性基础设施
 155
 156conductor 是 daemon,不是聊天会话。
 157
 158conductor 必须负责:
 159
 160- task 创建归一化
 161- step 拆分结果验收
 162- lease 逻辑
 163- worker 分配
 164- heartbeat 检查
 165- timeout 处理
 166- checkpoint 写入
 167- 恢复
 168
 169### 3.5 Step 边界就是恢复边界
 170
 171每个 task 都要拆成多个 step。
 172
 173每个 step 都必须:
 174
 175- 有边界
 176- 可观察
 177- 可 checkpoint
 178- 可重试或可终止
 179
 180## 4. 系统角色
 181
 182## 4.1 Human
 183
 184人只需要做这些事:
 185
 186- 使用一个可见的 Claude `control` 对话
 187- 查看当前状态
 188- 暂停、恢复或 drain 自动化
 189- 做高层决策
 190
 191人不应该需要盯着 Codex 终端界面。
 192
 193## 4.2 `control`
 194
 195这是人类交互使用的可见 Claude 对话。
 196
 197职责:
 198
 199- 讨论策略
 200- 创建 task
 201- 查看状态
 202- 请求暂停或恢复
 203- 审查进展
 204
 205规则:
 206
 207- `control` 是交互界面
 208- `control` 不是任务队列
 209- `control` 不是 durable 状态存储
 210
 211## 4.3 `dispatch`
 212
 213这是隐藏的 Claude 自动化通道。
 214
 215职责:
 216
 217- 提供 planning 支持
 218- 提供 review 支持
 219- 输出结构化结果
 220
 221规则:
 222
 223- `dispatch` 不面向用户
 224- `dispatch` 不能做 durable 真相来源
 225- `dispatch` 只能给 proposal,不能直接做最终状态迁移
 226
 227## 4.4 `planner`
 228
 229职责:
 230
 231- 把目标转换成一个建议的 step plan
 232
 233输入:
 234
 235- task 目标
 236- repo
 237- 约束
 238- 验收条件
 239- 可选的当前 repo 状态
 240
 241输出:
 242
 243- 结构化 plan JSON
 244- 拆分理由
 245- 风险标记
 246
 247规则:
 248
 249- planner 输出只是建议
 250- conductor 在持久化之前必须校验 planner 输出
 251
 252## 4.5 `conductor`
 253
 254职责:
 255
 256- leader lease 管理
 257- task 归一化
 258- plan 验收
 259- step 调度
 260- worker 监管
 261- timeout 执行
 262- 日志索引
 263- checkpoint
 264- 故障切换与恢复
 265
 266## 4.6 `worker`
 267
 268职责:
 269
 270- 执行且只执行一个 step
 271- 输出本地日志
 272- 返回结果
 273- 退出
 274
 275默认 AI worker 是 Codex。
 276
 277Shell 和 Git 类 step 可以由 shell runner 执行,而不是由 Codex 进程执行。
 278
 279## 5. 拓扑结构
 280
 281## 5.1 节点
 282
 283### `mini`
 284
 285角色:
 286
 287- 主 conductor
 288- 本地 worker 宿主机
 289
 290运行:
 291
 292- conductor daemon
 293- worker runner
 294- 可选本地 status API
 295- 可选 Firefox 自动化栈
 296
 297### `mac`
 298
 299角色:
 300
 301- 备用 conductor
 302- 本地 worker 宿主机
 303
 304运行:
 305
 306- standby conductor daemon
 307- worker runner
 308- 可选本地 status API
 309
 310### `vps`
 311
 312角色:
 313
 314- 入口与反向代理
 315
 316运行:
 317
 318- Nginx
 319- TLS 终止,或 Cloudflare origin 代理支持
 320
 321VPS 不持有 leader 真相。
 322
 323### `cloudflare`
 324
 325角色:
 326
 327- D1 数据库
 328- 可选 Worker 形式的 control API
 329- DNS 与自定义域名
 330
 331## 5.2 数据路径
 332
 333### 控制平面
 334
 335- human -> 可见 Claude `control`
 336- `control` -> conductor control API
 337- conductors -> D1
 338- workers -> conductors
 339
 340### 执行平面
 341
 342- conductor -> 本地 worker 进程
 343- conductor -> 本地 shell 与 git 命令
 344- conductor -> `baa-hand` 做 planning 或 review
 345- conductor -> `baa-shell` 在 HTTP 远程执行更方便时使用
 346
 347### 浏览器平面
 348
 349- Firefox 插件 -> control API 做 pause 与 resume
 350- Firefox 插件 -> 可见状态 badge
 351- Firefox 插件 -> 如有需要,访问隐藏 `dispatch`
 352
 353## 6. 域名、二级域名与内网地址布局
 354
 355本节定义推荐的双通道布局:
 356
 357- 公网流量走二级域名,经 VPS Nginx 转发
 358- 内网与节点间流量直接走 Tailscale `100.x` 地址
 359
 360这里明确不依赖 MagicDNS 名称。
 361
 362原因:
 363
 364- 当前环境里 ClashX 会和 MagicDNS 产生 DNS 接管冲突
 365- 即使 tailnet 已开启 MagicDNS,也不把 `*.ts.net` 作为生产配置依赖
 366- 内网配置统一写死到 Tailscale IPv4 地址,避免本机 DNS 状态影响运行
 367
 368## 6.1 必需的公网域名
 369
 370### `conductor.makefile.so`
 371
 372用途:
 373
 374- conductor API 的统一公网入口
 375- 指向 VPS Nginx
 376- Nginx 再转发到 `mini` 主、`mac` 备
 377
 378使用方:
 379
 380- 人类工具
 381- Firefox 插件控制动作
 382- 诊断请求
 383
 384### `mini-conductor.makefile.so`
 385
 386用途:
 387
 388- 经 VPS 直达 mini,便于调试和维护
 389
 390### `mac-conductor.makefile.so`
 391
 392用途:
 393
 394- 经 VPS 直达 mac,便于调试和维护
 395
 396### `control-api.makefile.so`
 397
 398用途:
 399
 400- 绑定到 D1 的 Cloudflare Worker 自定义域
 401- durable 控制状态的规范写入口
 402
 403名字可以不同,但设计上要求有一个稳定的、前置 D1 的 HTTP API 域名。
 404
 405## 6.2 必需的内网地址
 406
 407当前推荐直接使用这些 Tailscale 地址:
 408
 409- `mini` -> `100.71.210.78`
 410- `mac` -> `100.112.239.13`
 411- `racknerd-ff37952` -> `100.68.201.85`
 412
 413用途:
 414
 415- `mini <-> mac` 的节点间控制流量
 416- conductor 到 peer conductor 的探活、状态读取、受保护节点 API
 417- worker、status、运维脚本等内部调用
 418- VPS Nginx 到 mini/mac 的 upstream 回源
 419
 420规则:
 421
 422- 生产配置中不写 `mini.tail0125d.ts.net`
 423- 生产配置中不写 `mbp.tail0125d.ts.net`
 424- 不要求业务机器开启 `accept-dns`
 425- 只要求它们加入同一个 tailnet 且 `100.x` 地址可达
 426
 427如果未来 Tailscale 地址变化:
 428
 429- 先更新中央配置
 430- 再更新 VPS Nginx upstream
 431- 最后重载相关进程
 432
 433## 6.3 现有域名
 434
 435这些域名已经存在,继续沿用:
 436
 437- `led.makefile.so` -> `baa-hand`
 438- `s.makefile.so` -> `baa-shell`
 439
 440## 6.4 推荐 DNS 策略
 441
 442- `conductor.makefile.so` -> VPS 公网 IP
 443- `mini-conductor.makefile.so` -> VPS 公网 IP
 444- `mac-conductor.makefile.so` -> VPS 公网 IP
 445- `control-api.makefile.so` -> Cloudflare Worker 自定义域
 446
 447之后由 VPS 把节点专属域名再转发到 Tailscale `100.x` 地址。
 448
 449额外说明:
 450
 451- 公网 DNS 不承载 tailnet 内部寻址
 452- tailnet 内部寻址直接用 `100.x`
 453- 不使用 MagicDNS 名称做回源或服务发现
 454
 455## 6.5 建议的 DNS 记录
 456
 457推荐 DNS 记录如下:
 458
 459| Hostname | Type | Target | Purpose |
 460| --- | --- | --- | --- |
 461| `conductor.makefile.so` | `A``AAAA` | VPS 公网 IP | conductor 统一公网入口 |
 462| `mini-conductor.makefile.so` | `A``AAAA` | VPS 公网 IP | 经 VPS 直达 mini |
 463| `mac-conductor.makefile.so` | `A``AAAA` | VPS 公网 IP | 经 VPS 直达 mac |
 464| `control-api.makefile.so` | Worker 自定义域 | Cloudflare Worker | durable 控制平面 API |
 465| `led.makefile.so` | 现有 | 现有 origin | `baa-hand` |
 466| `s.makefile.so` | 现有 | 现有 origin | `baa-shell` |
 467
 468说明:
 469
 470- 如果 VPS 走 Cloudflare 代理,所有相关 host 的代理模式要保持一致
 471- `control-api.makefile.so` 最好留在 Cloudflare 内部,让 D1 访问保持本地化
 472- mini 和 mac 不应该直接暴露在公网,节点专属域名仍然应该经过 VPS
 473- tailnet 内部机器间调用不要再绕回这些公网域名
 474
 475## 6.6 TLS 策略
 476
 477推荐 TLS 模式:
 478
 479- 公网访问的域名尽量通过 Cloudflare 代理
 480- VPS 上给 `conductor.makefile.so`、`mini-conductor.makefile.so`、`mac-conductor.makefile.so` 申请 Let’s Encrypt 证书
 481- Worker 自定义域使用 Cloudflare 托管证书
 482
 483如果 conductor 域名启用了 Cloudflare 橙云:
 484
 485- VPS 上配置 origin certificate 或普通 Let’s Encrypt 证书
 486- 尽量限制对 origin 的直接访问
 487
 488如果 conductor 域名不走 Cloudflare 代理:
 489
 490- 保持 VPS 上 Let’s Encrypt 自动续期
 491- 对直连节点域名加鉴权保护
 492
 493## 7. VPS 与 Nginx 转发
 494
 495## 7.1 为什么需要 Nginx
 496
 497VPS 上的 Nginx 提供:
 498
 499- 稳定的公网入口
 500- TCP 与 HTTP 层的机器切换
 501- 到 mini 和 mac 的直连调试路由
 502
 503它不负责决定真实 leader。
 504
 505真实 leader 由 D1 lease 决定。
 506
 507它也不承载大部分内网控制流量。
 508
 509正确分工:
 510
 511- 浏览器、Claude 网页、管理页面 UI 走公网二级域名
 512- mini/mac 之间的内部控制流量直接走 Tailscale `100.x`
 513- VPS 只作为公网入口,不作为内网总线
 514
 515## 7.2 Nginx Upstream 设计
 516
 517推荐 upstream:
 518
 519```nginx
 520upstream conductor_primary {
 521    server 100.71.210.78:4317 max_fails=2 fail_timeout=5s;
 522    server 100.112.239.13:4317 backup;
 523    keepalive 32;
 524}
 525
 526upstream mini_conductor_direct {
 527    server 100.71.210.78:4317;
 528    keepalive 16;
 529}
 530
 531upstream mac_conductor_direct {
 532    server 100.112.239.13:4317;
 533    keepalive 16;
 534}
 535```
 536
 537本例中:
 538
 539- `100.71.210.78``mini`
 540- `100.112.239.13``mac`
 541- `4317` 是 conductor 本地 HTTP 端口
 542- upstream 使用 Tailscale IPv4 地址,不使用 `*.ts.net` 名称
 543- 这样可以绕开 ClashX 与 MagicDNS 的 DNS 接管冲突
 544
 545## 7.3 Nginx Server Block
 546
 547### 统一 conductor 入口
 548
 549```nginx
 550server {
 551    listen 443 ssl http2;
 552    server_name conductor.makefile.so;
 553
 554    ssl_certificate     /etc/letsencrypt/live/conductor.makefile.so/fullchain.pem;
 555    ssl_certificate_key /etc/letsencrypt/live/conductor.makefile.so/privkey.pem;
 556
 557    location / {
 558        proxy_pass http://conductor_primary;
 559        proxy_http_version 1.1;
 560        proxy_set_header Host $host;
 561        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 562        proxy_set_header X-Forwarded-Proto $scheme;
 563        proxy_set_header X-Request-Id $request_id;
 564        proxy_connect_timeout 3s;
 565        proxy_read_timeout 60s;
 566        proxy_send_timeout 60s;
 567    }
 568}
 569```
 570
 571### 直达 mini 的路由
 572
 573```nginx
 574server {
 575    listen 443 ssl http2;
 576    server_name mini-conductor.makefile.so;
 577
 578    ssl_certificate     /etc/letsencrypt/live/mini-conductor.makefile.so/fullchain.pem;
 579    ssl_certificate_key /etc/letsencrypt/live/mini-conductor.makefile.so/privkey.pem;
 580
 581    location / {
 582        proxy_pass http://mini_conductor_direct;
 583        proxy_http_version 1.1;
 584        proxy_set_header Host $host;
 585        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 586        proxy_set_header X-Forwarded-Proto $scheme;
 587        proxy_set_header X-Request-Id $request_id;
 588    }
 589}
 590```
 591
 592### 直达 mac 的路由
 593
 594```nginx
 595server {
 596    listen 443 ssl http2;
 597    server_name mac-conductor.makefile.so;
 598
 599    ssl_certificate     /etc/letsencrypt/live/mac-conductor.makefile.so/fullchain.pem;
 600    ssl_certificate_key /etc/letsencrypt/live/mac-conductor.makefile.so/privkey.pem;
 601
 602    location / {
 603        proxy_pass http://mac_conductor_direct;
 604        proxy_http_version 1.1;
 605        proxy_set_header Host $host;
 606        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 607        proxy_set_header X-Forwarded-Proto $scheme;
 608        proxy_set_header X-Request-Id $request_id;
 609    }
 610}
 611```
 612
 613## 7.4 健康检查与就绪检查
 614
 615每个 conductor 都应暴露:
 616
 617- `GET /healthz`
 618- `GET /readyz`
 619- `GET /rolez`
 620
 621推荐语义:
 622
 623- `/healthz`: 进程活着
 624- `/readyz`: 节点已准备好提供 conductor 服务
 625- `/rolez`: 返回 `leader``standby`
 626
 627重要说明:
 628
 629- standby 可以健康,但不是 leader
 630- 只要节点不是 leader,写 API 仍然必须拒绝调度类操作
 631
 632## 7.5 建议的端口暴露方式
 633
 634VPS 只应对公网暴露:
 635
 636- `80/tcp`
 637- `443/tcp`
 638
 639mini 和 mac 不应该直接对公网开放 conductor 端口。
 640
 641推荐访问模型:
 642
 643- 浏览器、人类运维、Claude 网页、管理 UI -> 公网二级域名 -> VPS -> Tailscale upstream
 644- mini 和 mac 的 daemon / worker / 脚本 -> 直接访问 Tailscale `100.x`
 645- 只有需要公网暴露的入口才经过 VPS
 646- 运维人员要访问 mini/mac 时,可通过 SSH、Tailscale `100.x`,或受保护的直连节点域名
 647
 648内网建议:
 649
 650- `mini` 本机调用本机 conductor:`http://127.0.0.1:4317`
 651- `mini``mac`:`http://100.112.239.13:4317`
 652- `mac``mini`:`http://100.71.210.78:4317`
 653- VPS Nginx upstream 也直接写这两个 `100.x`
 654
 655## 7.6 完整的 Nginx 模式
 656
 657推荐整体模式:
 658
 6591. `:80` 全部跳转到 HTTPS
 6602. `conductor.makefile.so` 走主加备 upstream
 6613. `mini-conductor.makefile.so` 直转 mini upstream
 6624. `mac-conductor.makefile.so` 直转 mac upstream
 6635. 直连节点域名额外加鉴权
 664
 665### HTTP 跳转
 666
 667```nginx
 668server {
 669    listen 80;
 670    listen [::]:80;
 671    server_name conductor.makefile.so mini-conductor.makefile.so mac-conductor.makefile.so;
 672    return 301 https://$host$request_uri;
 673}
 674```
 675
 676### 直连节点域名的 Basic Auth
 677
 678`mini-conductor.makefile.so``mac-conductor.makefile.so`,增加:
 679
 680```nginx
 681auth_basic "Restricted";
 682auth_basic_user_file /etc/nginx/.htpasswd-baa-conductor;
 683```
 684
 685除非你希望整个系统都放在认证后面,否则不要把这段加到统一入口域名上。
 686
 687## 7.7 Nginx 加固建议
 688
 689建议在每个 TLS server block 中增加:
 690
 691```nginx
 692proxy_set_header X-Real-IP $remote_addr;
 693proxy_set_header X-Forwarded-Host $host;
 694proxy_set_header X-Forwarded-Port $server_port;
 695proxy_buffering off;
 696client_max_body_size 10m;
 697```
 698
 699如果你需要日志串联请求:
 700
 701```nginx
 702add_header X-Request-Id $request_id always;
 703```
 704
 705如果后续要走状态流或长连接:
 706
 707```nginx
 708proxy_read_timeout 3600s;
 709proxy_send_timeout 3600s;
 710```
 711
 712## 7.8 Nginx Failover 的边界
 713
 714Nginx failover 只解决:
 715
 716- 网络路径故障
 717- TCP 连接失败
 718- 简单 upstream 不可达
 719
 720Nginx failover 不能解决:
 721
 722- split-brain
 723- 过期 leader 继续写
 724- task lease 冲突
 725- 安全恢复逻辑
 726
 727因此所有写接口仍然必须校验 D1 lease 状态。
 728
 729## 7.9 建议的 Nginx 文件布局
 730
 731建议在 VPS 上使用:
 732
 733```text
 734/etc/nginx/nginx.conf
 735/etc/nginx/sites-available/baa-conductor.conf
 736/etc/nginx/sites-enabled/baa-conductor.conf
 737/etc/nginx/.htpasswd-baa-conductor
 738```
 739
 740未来仓库中的文件应尽量镜像这个结构:
 741
 742```text
 743ops/nginx/baa-conductor.conf
 744ops/nginx/includes/common-proxy.conf
 745ops/nginx/includes/direct-node-auth.conf
 746```
 747
 748## 8. 主备与故障切换模型
 749
 750## 8.1 真相来源
 751
 752leader 真相在 D1,不在 Nginx。
 753
 754Nginx 只负责网络可达性和入口层 failover。
 755
 756## 8.2 推荐行为
 757
 758- `mini` 是首选 leader
 759- `mac` 是 standby
 760- 任意时刻只能有一个有效 leader lease
 761
 762推荐 lease 参数:
 763
 764- TTL: 30 秒
 765- 续租周期: 每 5 秒一次
 766- 连续 2 次续租失败后自我降级
 767
 768## 8.3 切换规则
 769
 770### 正常状态
 771
 772- mini 持有 lease
 773- mac 只监听,不调度
 774
 775### Mini 故障
 776
 777- mini heartbeat 停止
 778- lease 过期
 779- mac 抢到 lease
 780- mac 成为 active scheduler
 781
 782### Mini 恢复
 783
 784- mini 恢复上线
 785- mini 注册为 standby
 786- mini 不自动抢占
 787
 788### 手动切回
 789
 790管理员可以显式执行:
 791
 792- drain mac
 793- promote mini
 794- demote mac
 795
 796这样可以避免主备来回抖动。
 797
 798## 8.4 防止 Split-Brain
 799
 800规则:
 801
 802- 所有写动作都要求有效 lease
 803- claim task 与 claim step 前都要检查 lease
 804- 每条写路径都要校验当前 leader term
 805- standby 节点必须拒绝调度类写入
 806
 807## 9. Cloudflare D1 控制平面
 808
 809## 9.1 为什么使用 D1
 810
 811使用 D1 作为共享 durable 协调存储,是因为它适合承载:
 812
 813- mini 与 mac 之间的共享真相
 814- SQLite 事务语义
 815- 队列、lease、元数据与索引
 816
 817D1 不适合存储:
 818
 819- 长期保存的完整原始日志
 820- 大型二进制产物
 821- 高频逐 token 流式数据
 822
 823## 9.2 访问模式
 824
 825推荐模式:
 826
 827- 机器本身不要直接写 D1
 828- 全部通过统一的 HTTP control API
 829- control API 跑在 Cloudflare Worker 上,并绑定 D1
 830
 831这样可以集中管理:
 832
 833- 鉴权
 834- schema 访问
 835- 事务约束
 836- 版本演进
 837
 838## 9.3 读一致性
 839
 840控制路径上的读尽量使用主一致读。
 841
 842规则:
 843
 844- lease 获取与 task claim 必须保证主一致逻辑
 845- 状态查看类页面可以容忍轻微陈旧
 846
 847## 10. 数据库 Schema
 848
 849本节定义最小 D1 schema。
 850
 851## 10.1 `leader_lease`
 852
 853用途:
 854
 855- 全局唯一 active leadership lease
 856
 857```sql
 858CREATE TABLE IF NOT EXISTS leader_lease (
 859  lease_name TEXT PRIMARY KEY,
 860  holder_id TEXT NOT NULL,
 861  holder_host TEXT NOT NULL,
 862  term INTEGER NOT NULL,
 863  lease_expires_at INTEGER NOT NULL,
 864  renewed_at INTEGER NOT NULL,
 865  preferred_holder_id TEXT,
 866  metadata_json TEXT
 867);
 868```
 869
 870约定:
 871
 872- 只保留一行,`lease_name = 'global'`
 873
 874## 10.2 `controllers`
 875
 876用途:
 877
 878- 注册 conductors 及其 heartbeat
 879
 880```sql
 881CREATE TABLE IF NOT EXISTS controllers (
 882  controller_id TEXT PRIMARY KEY,
 883  host TEXT NOT NULL,
 884  role TEXT NOT NULL,
 885  priority INTEGER NOT NULL,
 886  status TEXT NOT NULL,
 887  version TEXT,
 888  last_heartbeat_at INTEGER NOT NULL,
 889  last_started_at INTEGER,
 890  metadata_json TEXT
 891);
 892```
 893
 894## 10.3 `workers`
 895
 896用途:
 897
 898- 注册 worker slot
 899
 900```sql
 901CREATE TABLE IF NOT EXISTS workers (
 902  worker_id TEXT PRIMARY KEY,
 903  controller_id TEXT NOT NULL,
 904  host TEXT NOT NULL,
 905  worker_type TEXT NOT NULL,
 906  status TEXT NOT NULL,
 907  max_parallelism INTEGER NOT NULL DEFAULT 1,
 908  current_load INTEGER NOT NULL DEFAULT 0,
 909  last_heartbeat_at INTEGER NOT NULL,
 910  capabilities_json TEXT,
 911  metadata_json TEXT
 912);
 913```
 914
 915## 10.4 `tasks`
 916
 917用途:
 918
 919- durable task 队列
 920
 921```sql
 922CREATE TABLE IF NOT EXISTS tasks (
 923  task_id TEXT PRIMARY KEY,
 924  repo TEXT NOT NULL,
 925  task_type TEXT NOT NULL,
 926  title TEXT NOT NULL,
 927  goal TEXT NOT NULL,
 928  source TEXT NOT NULL,
 929  priority INTEGER NOT NULL DEFAULT 100,
 930  status TEXT NOT NULL,
 931  planning_strategy TEXT,
 932  planner_provider TEXT,
 933  branch_name TEXT,
 934  base_ref TEXT,
 935  target_host TEXT,
 936  assigned_controller_id TEXT,
 937  current_step_index INTEGER NOT NULL DEFAULT -1,
 938  constraints_json TEXT,
 939  acceptance_json TEXT,
 940  metadata_json TEXT,
 941  result_summary TEXT,
 942  result_json TEXT,
 943  error_text TEXT,
 944  created_at INTEGER NOT NULL,
 945  updated_at INTEGER NOT NULL,
 946  started_at INTEGER,
 947  finished_at INTEGER
 948);
 949```
 950
 951索引:
 952
 953```sql
 954CREATE INDEX IF NOT EXISTS idx_tasks_status_priority
 955ON tasks(status, priority, created_at);
 956
 957CREATE INDEX IF NOT EXISTS idx_tasks_repo
 958ON tasks(repo, created_at);
 959```
 960
 961## 10.5 `task_steps`
 962
 963用途:
 964
 965- 持久化恢复边界
 966
 967```sql
 968CREATE TABLE IF NOT EXISTS task_steps (
 969  step_id TEXT PRIMARY KEY,
 970  task_id TEXT NOT NULL,
 971  step_index INTEGER NOT NULL,
 972  step_name TEXT NOT NULL,
 973  step_kind TEXT NOT NULL,
 974  status TEXT NOT NULL,
 975  assigned_worker_id TEXT,
 976  assigned_controller_id TEXT,
 977  timeout_sec INTEGER NOT NULL,
 978  retry_limit INTEGER NOT NULL DEFAULT 0,
 979  retry_count INTEGER NOT NULL DEFAULT 0,
 980  lease_expires_at INTEGER,
 981  input_json TEXT,
 982  output_json TEXT,
 983  summary TEXT,
 984  error_text TEXT,
 985  created_at INTEGER NOT NULL,
 986  updated_at INTEGER NOT NULL,
 987  started_at INTEGER,
 988  finished_at INTEGER,
 989  UNIQUE(task_id, step_index)
 990);
 991```
 992
 993索引:
 994
 995```sql
 996CREATE INDEX IF NOT EXISTS idx_task_steps_task_status
 997ON task_steps(task_id, status, step_index);
 998```
 999
1000## 10.6 `task_runs`
1001
1002用途:
1003
1004- 记录 step 的执行尝试
1005
1006```sql
1007CREATE TABLE IF NOT EXISTS task_runs (
1008  run_id TEXT PRIMARY KEY,
1009  task_id TEXT NOT NULL,
1010  step_id TEXT NOT NULL,
1011  worker_id TEXT NOT NULL,
1012  controller_id TEXT NOT NULL,
1013  host TEXT NOT NULL,
1014  pid INTEGER,
1015  status TEXT NOT NULL,
1016  lease_expires_at INTEGER,
1017  heartbeat_at INTEGER,
1018  log_dir TEXT NOT NULL,
1019  stdout_path TEXT,
1020  stderr_path TEXT,
1021  worker_log_path TEXT,
1022  checkpoint_seq INTEGER NOT NULL DEFAULT 0,
1023  exit_code INTEGER,
1024  result_json TEXT,
1025  error_text TEXT,
1026  created_at INTEGER NOT NULL,
1027  started_at INTEGER,
1028  finished_at INTEGER
1029);
1030```
1031
1032索引:
1033
1034```sql
1035CREATE INDEX IF NOT EXISTS idx_task_runs_task
1036ON task_runs(task_id, created_at);
1037
1038CREATE INDEX IF NOT EXISTS idx_task_runs_step
1039ON task_runs(step_id, created_at);
1040```
1041
1042## 10.7 `task_checkpoints`
1043
1044用途:
1045
1046- 恢复 step 中途进度的快照
1047
1048这一张表很关键,因为某些 Codex step 很长,在 step 完成前就可能已经产生了有价值的部分进度。
1049
1050```sql
1051CREATE TABLE IF NOT EXISTS task_checkpoints (
1052  checkpoint_id TEXT PRIMARY KEY,
1053  task_id TEXT NOT NULL,
1054  step_id TEXT NOT NULL,
1055  run_id TEXT NOT NULL,
1056  seq INTEGER NOT NULL,
1057  checkpoint_type TEXT NOT NULL,
1058  summary TEXT,
1059  content_text TEXT,
1060  content_json TEXT,
1061  created_at INTEGER NOT NULL,
1062  UNIQUE(run_id, seq)
1063);
1064```
1065
1066checkpoint 类型可以包括:
1067
1068- `heartbeat`
1069- `log_tail`
1070- `git_diff`
1071- `summary`
1072- `test_result`
1073
1074## 10.8 `task_logs`
1075
1076用途:
1077
1078- 存结构化日志尾部与生命周期事件
1079
1080```sql
1081CREATE TABLE IF NOT EXISTS task_logs (
1082  log_id INTEGER PRIMARY KEY AUTOINCREMENT,
1083  task_id TEXT NOT NULL,
1084  step_id TEXT,
1085  run_id TEXT NOT NULL,
1086  seq INTEGER NOT NULL,
1087  stream TEXT NOT NULL,
1088  level TEXT,
1089  message TEXT NOT NULL,
1090  created_at INTEGER NOT NULL
1091);
1092```
1093
1094## 10.9 `system_state`
1095
1096用途:
1097
1098- 全局自动化状态
1099
1100```sql
1101CREATE TABLE IF NOT EXISTS system_state (
1102  state_key TEXT PRIMARY KEY,
1103  value_json TEXT NOT NULL,
1104  updated_at INTEGER NOT NULL
1105);
1106```
1107
1108约定:
1109
1110- `state_key = 'automation'`
1111- value 中包含 `mode = running | draining | paused`
1112
1113## 10.10 `task_artifacts`
1114
1115可选但推荐。
1116
1117用途:
1118
1119- 保存 durable 输出的结构化引用
1120
1121```sql
1122CREATE TABLE IF NOT EXISTS task_artifacts (
1123  artifact_id TEXT PRIMARY KEY,
1124  task_id TEXT NOT NULL,
1125  step_id TEXT,
1126  run_id TEXT,
1127  artifact_type TEXT NOT NULL,
1128  path TEXT,
1129  uri TEXT,
1130  size_bytes INTEGER,
1131  sha256 TEXT,
1132  metadata_json TEXT,
1133  created_at INTEGER NOT NULL
1134);
1135```
1136
1137## 11. Control API
1138
1139推荐的生产设计是:
1140
1141- 一个 Cloudflare Worker
1142- 一个 D1 binding
1143- 所有 durable 状态迁移都经过这个 Worker
1144
1145## 11.1 Base URL
1146
1147推荐:
1148
1149- `https://control-api.makefile.so`
1150
1151## 11.2 必需接口
1152
1153### `POST /v1/controllers/heartbeat`
1154
1155由 mini 和 mac conductor 调用。
1156
1157Body:
1158
1159```json
1160{
1161  "controller_id": "mini-main",
1162  "host": "mini",
1163  "role": "primary",
1164  "priority": 100,
1165  "status": "alive",
1166  "version": "0.1.0"
1167}
1168```
1169
1170### `POST /v1/leader/acquire`
1171
1172由 conductors 用来获取或续租 lease。
1173
1174Body:
1175
1176```json
1177{
1178  "controller_id": "mini-main",
1179  "host": "mini",
1180  "preferred": true,
1181  "ttl_sec": 30
1182}
1183```
1184
1185Response:
1186
1187```json
1188{
1189  "ok": true,
1190  "holder_id": "mini-main",
1191  "term": 7,
1192  "lease_expires_at": 1760000000000,
1193  "is_leader": true
1194}
1195```
1196
1197### `POST /v1/tasks`
1198
1199创建 task。
1200
1201Body:
1202
1203```json
1204{
1205  "repo": "/Users/george/code/event-fabric",
1206  "task_type": "feature_impl",
1207  "title": "Add conductor D1 schema",
1208  "goal": "Implement the initial D1 schema and migration scripts.",
1209  "priority": 50,
1210  "constraints": {
1211    "target_host": "mini"
1212  },
1213  "acceptance": [
1214    "SQL schema committed",
1215    "migration runner stub added"
1216  ],
1217  "metadata": {
1218    "requested_by": "control",
1219    "branch_prefix": "feat"
1220  }
1221}
1222```
1223
1224### `POST /v1/tasks/:task_id/plan`
1225
1226持久化已通过校验的 step plan。
1227
1228该接口由 conductor 在完成 planner 输出校验后调用。
1229
1230### `POST /v1/tasks/claim`
1231
1232领取待规划 task 或下一个可运行 step。
1233
1234也可以拆成:
1235
1236- `POST /v1/tasks/claim-planning`
1237- `POST /v1/steps/claim`
1238
1239只要事务语义清晰,这两种形式都可以。
1240
1241### `POST /v1/steps/:step_id/heartbeat`
1242
1243Body 包含:
1244
1245- `run_id`
1246- `worker_id`
1247- `lease_expires_at`
1248- 可选 `checkpoint`
1249
1250### `POST /v1/steps/:step_id/checkpoint`
1251
1252Body:
1253
1254```json
1255{
1256  "run_id": "run_001",
1257  "seq": 3,
1258  "checkpoint_type": "git_diff",
1259  "summary": "Current worktree diff after route refactor",
1260  "content_text": "diff --git ..."
1261}
1262```
1263
1264### `POST /v1/steps/:step_id/complete`
1265
1266### `POST /v1/steps/:step_id/fail`
1267
1268### `POST /v1/system/pause`
1269
1270### `POST /v1/system/resume`
1271
1272### `POST /v1/system/drain`
1273
1274### `GET /v1/system/state`
1275
1276### `GET /v1/tasks/:task_id`
1277
1278### `GET /v1/tasks/:task_id/logs`
1279
1280### `GET /v1/runs/:run_id`
1281
1282## 11.3 鉴权
1283
1284最低要求:
1285
1286- conductor、worker 与 control API 之间使用 HMAC token 或共享密钥
1287- 修改系统状态的操作使用单独的 admin token
1288
1289推荐后续演进:
1290
1291- 签名 service token
1292- 按角色拆 scope
1293
1294## 11.4 Control API 授权模型
1295
1296推荐角色:
1297
1298- `controller`
1299- `worker`
1300- `browser_admin`
1301- `ops_admin`
1302- `readonly`
1303
1304推荐权限划分:
1305
1306| Role | Allowed Actions |
1307| --- | --- |
1308| `controller` | 获取与续租 lease、claim tasks、更新 run 状态 |
1309| `worker` | heartbeat、checkpoint、完成已分配 step |
1310| `browser_admin` | pause、resume、drain、查看队列 |
1311| `ops_admin` | promote、demote、维护操作 |
1312| `readonly` | 状态查看、日志、面板 |
1313
1314推荐实现:
1315
1316- `Authorization: Bearer <token>`
1317- token 元数据包含角色与可选主机身份
1318- 修改接口既校验角色,也校验资源归属
1319
1320## 11.5 Control API 运维约束
1321
1322Worker 应提供:
1323
1324- 尽量做到幂等写入
1325- 对陈旧 term 明确返回冲突
1326- 记录 request id
1327- 输出结构化错误体
1328
1329推荐错误格式:
1330
1331```json
1332{
1333  "ok": false,
1334  "error": "not_leader",
1335  "message": "This node does not hold the active leadership lease.",
1336  "request_id": "req_123"
1337}
1338```
1339
1340## 12. Task 模型
1341
1342## 12.1 Task 生命周期
1343
1344Task 状态:
1345
1346- `queued`
1347- `planning`
1348- `running`
1349- `paused`
1350- `done`
1351- `failed`
1352- `canceled`
1353
1354状态流转:
1355
1356- `queued -> planning`
1357- `planning -> running`
1358- `planning -> failed`
1359- `running -> paused`
1360- `paused -> running`
1361- `running -> done`
1362- `running -> failed`
1363- `running -> canceled`
1364
1365## 12.2 Task 输入 Schema
1366
1367推荐归一化格式:
1368
1369```json
1370{
1371  "task_id": "task_001",
1372  "repo": "/Users/george/code/event-fabric",
1373  "task_type": "feature_impl",
1374  "title": "Implement task scheduler",
1375  "goal": "Add the initial scheduler and step claim logic.",
1376  "priority": 50,
1377  "constraints": {
1378    "target_host": "mini",
1379    "write_scope": [
1380      "services/conductor-daemon/**",
1381      "packages/db/**"
1382    ]
1383  },
1384  "acceptance": [
1385    "claim endpoint exists",
1386    "scheduler handles one runnable step",
1387    "tests added"
1388  ],
1389  "metadata": {
1390    "branch_prefix": "feat",
1391    "requested_by": "control"
1392  }
1393}
1394```
1395
1396## 12.3 分支策略
1397
1398一个 task 对应一个分支。
1399
1400规则:
1401
1402- 分支由 conductor 创建
1403- worker 不自己发明分支名
1404- 分支命名应尽量确定性
1405
1406推荐命名:
1407
1408- `feat/T-001-d1-schema`
1409- `feat/T-002-step-claim`
1410- `fix/T-003-checkpoint-recovery`
1411
1412## 13. Step 模型
1413
1414## 13.1 Step 状态
1415
1416- `pending`
1417- `running`
1418- `done`
1419- `failed`
1420- `timeout`
1421
1422## 13.2 Step 类型
1423
1424推荐 step kind:
1425
1426- `planner`
1427- `codex`
1428- `shell`
1429- `git`
1430- `review`
1431- `finalize`
1432
1433## 13.3 Step 合约
1434
1435每个 step 都必须具备:
1436
1437- 明确的目标边界
1438- 输入 payload
1439- timeout
1440- retry 策略
1441- 完成后的 summary
1442
1443## 13.4 恢复边界
1444
1445step 完成是主要恢复边界。
1446
1447但因为 Codex step 可能很长,所以系统还必须维护 step 中途的 checkpoint。
1448
1449## 14. Planning 模型
1450
1451## 14.1 Planner 所有权
1452
1453task 拆 step 的所有权属于 conductor。
1454
1455含义是:
1456
1457- planner 只负责提案
1458- conductor 决定接受并持久化
1459- worker 只负责执行
1460
1461## 14.2 规划策略选择
1462
1463conductor 需要决定:
1464
1465- `template_first`
1466- `planner_assisted`
1467- `manual`
1468
1469例如:
1470
1471- 标准 bugfix -> `template_first`
1472- 模糊的大范围重构 -> `planner_assisted`
1473- 紧急运维变更 -> `manual`
1474
1475## 14.3 Planner 输出 Schema
1476
1477推荐 planner 输出:
1478
1479```json
1480{
1481  "task_type": "feature_impl",
1482  "strategy": "planner_assisted",
1483  "reasoning": "The task touches scheduler, persistence, and recovery logic. Split into deterministic steps.",
1484  "steps": [
1485    {
1486      "step_name": "prepare_branch",
1487      "step_kind": "git",
1488      "timeout_sec": 120,
1489      "retry_limit": 0,
1490      "input": {}
1491    },
1492    {
1493      "step_name": "inspect_context",
1494      "step_kind": "codex",
1495      "timeout_sec": 600,
1496      "retry_limit": 1,
1497      "input": {
1498        "goal": "Read the codebase and summarize where scheduler logic belongs."
1499      }
1500    },
1501    {
1502      "step_name": "implement_scheduler",
1503      "step_kind": "codex",
1504      "timeout_sec": 1800,
1505      "retry_limit": 1,
1506      "input": {
1507        "goal": "Implement scheduler claim loop and state transition logic."
1508      }
1509    },
1510    {
1511      "step_name": "run_tests",
1512      "step_kind": "shell",
1513      "timeout_sec": 1200,
1514      "retry_limit": 1,
1515      "input": {
1516        "command": "npm test"
1517      }
1518    },
1519    {
1520      "step_name": "commit_push",
1521      "step_kind": "git",
1522      "timeout_sec": 300,
1523      "retry_limit": 0,
1524      "input": {}
1525    }
1526  ]
1527}
1528```
1529
1530## 14.4 Planner 校验规则
1531
1532conductor 校验规则:
1533
1534- step 名称必须已知,或被明确允许
1535- step 顺序必须合法
1536- timeout 不能无上限
1537- step 输入不能为空缺
1538- 不允许 planner 指令直接做状态迁移
1539
1540## 15. 推荐的 Task 模板
1541
1542## 15.1 `feature_impl`
1543
1544推荐默认步骤:
1545
15461. `prepare_branch`
15472. `inspect_context`
15483. `implement`
15494. `run_tests`
15505. `review_fix`
15516. `commit_push`
15527. `finalize`
1553
1554## 15.2 `bugfix`
1555
1556推荐默认步骤:
1557
15581. `prepare_branch`
15592. `reproduce`
15603. `implement_fix`
15614. `run_targeted_tests`
15625. `commit_push`
15636. `finalize`
1564
1565## 15.3 `review_only`
1566
1567推荐默认步骤:
1568
15691. `prepare_context`
15702. `analyze_diff`
15713. `write_review`
15724. `finalize`
1573
1574## 15.4 `ops_change`
1575
1576推荐默认步骤:
1577
15781. `prepare_branch`
15792. `inspect_ops_context`
15803. `edit_ops_files`
15814. `validate_config`
15825. `commit_push`
15836. `finalize`
1584
1585## 15.5 `infra_bootstrap`
1586
1587推荐默认步骤:
1588
15891. `prepare_branch`
15902. `inspect_current_ops_state`
15913. `edit_dns_or_nginx_docs`
15924. `validate_nginx_config`
15935. `commit_push`
15946. `finalize`
1595
1596## 16. Conductor 详细职责
1597
1598conductor 必须承担以下职责。
1599
1600### 16.1 启动时
1601
1602-`controllers` 中注册自身
1603- 启动 heartbeat 循环
1604- 如果有资格则尝试获取 lease
1605- 加载本地未完成 runs
1606- 对本地过期 runs 做对账
1607
1608### 16.2 Leader Loop
1609
1610如果是 leader:
1611
1612- 规划 queued tasks
1613- claim runnable steps
1614- 分配 workers
1615- 监管 runs
1616- 持久化 checkpoints
1617
1618如果是 standby:
1619
1620- 只发 heartbeat
1621- 不调度新工作
1622- 随时准备在当前 leader 过期后接管 lease
1623
1624### 16.3 Worker 分配
1625
1626conductor 负责选择:
1627
1628- host
1629- worker slot
1630- worktree 路径
1631- run 目录
1632- timeout 预算
1633
1634### 16.4 Timeout 执行
1635
1636conductor 负责:
1637
1638- step timeout
1639- lease 过期
1640-`SIGTERM` 升级到 `SIGKILL`
1641
1642### 16.5 恢复
1643
1644重启后:
1645
1646- 扫描本地 run 目录
1647- 标记 orphaned runs
1648- 对比本地状态与 D1 状态
1649- 视情况重新入队或恢复
1650
1651## 17. Worker 模型
1652
1653## 17.1 为什么 Worker 要短生命周期
1654
1655短生命周期 worker 更容易:
1656
1657- 隔离故障
1658- 收集日志
1659- 管理资源
1660- 在崩溃后恢复
1661
1662## 17.2 Step 执行合约
1663
1664worker 合约是:
1665
16661. 接收一个 step
16672. 执行一个 step
16683. 输出本地日志
16694. 输出结构化结果
16705. 退出
1671
1672worker 不负责:
1673
1674- 管理队列
1675- 持有 lease
1676- 选择下一个 step
1677
1678## 17.3 Worker 类型
1679
1680### Codex Worker
1681
1682用于:
1683
1684- 阅读上下文
1685- 修改代码
1686- 输出总结
1687
1688### Shell Worker
1689
1690用于:
1691
1692- 测试
1693- lint
1694- 文件检查
1695- build 命令
1696
1697### Git Worker
1698
1699用于:
1700
1701- 建分支
1702- commit
1703- push
1704- 重置 per-task worktree
1705
1706## 18. Codex Worker 合约
1707
1708## 18.1 关键约束
1709
1710Codex 不以进程恢复为目标。
1711
1712系统恢复的是:
1713
1714- task
1715- step
1716- 最新 checkpoint
1717
1718系统不恢复:
1719
1720- 已死亡 Codex 进程内部的精确推理状态
1721
1722## 18.2 Codex Step 行为
1723
1724对于 Codex step:
1725
1726- conductor 准备 prompt 与上下文文件
1727- conductor 启动 Codex
1728- Codex 把输出写入本地日志
1729- conductor 周期性抓取 checkpoint
1730- step 完成后 Codex 退出
1731
1732## 18.3 为什么这比当前 `baa-hand` 的 Codex 模式更好
1733
1734当前 `baa-hand` 的 Codex 集成会一直等到进程退出,然后才返回输出。
1735
1736对于 durable worker 系统,这不够,因为:
1737
1738- 它不会把进度持续写成 durable 状态
1739- 它不支持 checkpoint 恢复
1740- 它没有把 task 状态和进程状态分离
1741
1742因此:
1743
1744- 当前 `baa-hand` 的 Codex 路径继续保留给临时用途
1745- 生产自动化要走 conductor 自己持有的 Codex worker 路径
1746
1747## 18.4 Codex 结果 Schema
1748
1749推荐最终 step 输出:
1750
1751```json
1752{
1753  "ok": true,
1754  "summary": "Implemented scheduler claim loop and added tests.",
1755  "needs_human": false,
1756  "blocked": false,
1757  "artifacts": [],
1758  "metrics": {
1759    "duration_sec": 812
1760  }
1761}
1762```
1763
1764可选 hint:
1765
1766```json
1767{
1768  "ok": false,
1769  "summary": "Need failing test output from package runtime.",
1770  "blocked": true,
1771  "suggested_followup": [
1772    {
1773      "step_name": "collect_runtime_test_output",
1774      "step_kind": "shell"
1775    }
1776  ]
1777}
1778```
1779
1780这些 hint 只是 proposal,不是直接状态修改。
1781
1782## 19. 日志模型
1783
1784## 19.1 本地完整日志
1785
1786每个 run 都有一个本地目录:
1787
1788```text
1789<repo>/.baa-conductor/runs/<task-id>/<run-id>/
1790  meta.json
1791  worker.log
1792  stdout.log
1793  stderr.log
1794  checkpoints/
1795    0001-summary.json
1796    0002-git-diff.patch
1797```
1798
1799本地完整日志是详细取证记录。
1800
1801## 19.2 D1 日志索引
1802
1803D1 中只存:
1804
1805- run 元数据
1806- 生命周期事件
1807- 周期性 tail chunk
1808- 最新 checkpoint 指针
1809
1810不应该把全部字节都长期塞进 D1。
1811
1812## 19.3 日志事件
1813
1814关键生命周期事件包括:
1815
1816- task created
1817- planner selected
1818- plan accepted
1819- step claimed
1820- worker started
1821- checkpoint persisted
1822- timeout
1823- step done
1824- task done
1825- failover occurred
1826
1827## 20. Checkpoint 模型
1828
1829这是稳定性的关键部分。
1830
1831## 20.1 为什么需要 Checkpoint
1832
1833如果 Codex 改了 20 分钟代码,机器突然死掉,只按 step 边界恢复,就会丢掉所有未提交进度。
1834
1835因此,长 AI step 需要 step 中途 checkpoint。
1836
1837## 20.2 Checkpoint 类型
1838
1839推荐类型:
1840
1841- `summary`
1842- `git_diff`
1843- `log_tail`
1844- `test_output`
1845
1846## 20.3 Git Diff Checkpoint
1847
1848对于 Codex 编辑类 step,conductor 应周期性快照:
1849
1850- `git status --short`
1851- `git diff --binary`
1852- 可选 `git diff --stat`
1853
1854推荐频率:
1855
1856- 每 30 到 60 秒一次
1857- 或在检测到有意义的文件变化后执行
1858
1859## 20.4 Checkpoint 存储策略
1860
1861MVP 期的策略:
1862
1863- 最新 checkpoint summary 存入 D1
1864- patch 文本大小合理时,一并写入 D1
1865- 完整 checkpoint 文件保存在本地
1866
1867如果 patch 太大:
1868
1869- D1 里只保留截断 summary
1870- 完整 patch 只保留在本地
1871- 后续可选接入 R2
1872
1873## 20.5 从 Checkpoint 恢复
1874
1875如果 mini 在执行 Codex step 时宕机:
1876
18771. mac 成为 leader
18782. mac 发现 run lease 已过期
18793. mac 创建新的 worktree
18804. mac checkout task branch 或 base ref
18815. 如果存在 checkpointed diff,则回放最新 diff
18826. 从最近一个可接受 checkpoint 继续,或直接重跑该 step
1883
1884这不是 bit-perfect 的进程恢复。
1885
1886这是 durable 的 task 恢复,并支持部分 patch 回放。
1887
1888## 21. Timeout 与 Retry 规则
1889
1890## 21.1 Timeout 所有权
1891
1892timeout 由 conductor 负责。
1893
1894worker 不对整个 task 自己做 timeout 判定。
1895
1896## 21.2 进程处理
1897
1898推荐升级顺序:
1899
19001. 发送 `SIGTERM`
19012. 等待 5 秒
19023. 发送 `SIGKILL`
19034. 标记 run 为 `timeout`
19045. 根据策略决定 retry 或 fail
1905
1906## 21.3 Retry 规则
1907
1908推荐默认值:
1909
1910- planner steps: 1 次重试
1911- codex inspect steps: 1 次重试
1912- codex implement steps: 1 次重试
1913- test steps: 基础设施失败时 1 次重试,确定性失败时 0 次
1914- git commit/push: 默认 0 次,除非 push 因瞬时网络原因失败
1915
1916## 21.4 失败分类
1917
1918需要明确区分:
1919
1920- `timeout`
1921- `worker_crash`
1922- `infra_unreachable`
1923- `deterministic_failure`
1924- `blocked`
1925
1926这个分类会直接影响 retry 策略。
1927
1928## 22. Pause、Drain 与 Resume
1929
1930## 22.1 全局模式
1931
1932全局自动化模式:
1933
1934- `running`
1935- `draining`
1936- `paused`
1937
1938## 22.2 语义
1939
1940### `running`
1941
1942- 正常运行
1943
1944### `draining`
1945
1946- 不再启动新的 step
1947- 已启动的 run 可以自然结束
1948- 适合计划内切换或维护
1949
1950### `paused`
1951
1952- 不再启动新的 step
1953- conductor 调度暂停
1954- 已运行任务是否继续或终止,由策略决定
1955
1956## 22.3 真相来源
1957
1958全局模式存储在 D1 的 `system_state` 中。
1959
1960浏览器 UI 只是控制面板,不是真相来源。
1961
1962## 23. Firefox 插件集成
1963
1964## 23.1 浏览器职责
1965
1966Firefox 插件应负责:
1967
1968- 展示当前全局自动化模式
1969- 提供 `pause`、`resume`,可选 `drain`
1970- 展示当前 leader host
1971- 如有需要,展示队列深度与 active task 数
1972
1973## 23.2 浏览器控制流
1974
1975可见的 `control` 对话仍然只用于人机交互。
1976
1977插件不应混用:
1978
1979- 你的交互式 `control` 对话
1980- 自动化 task dispatch
1981
1982推荐模型:
1983
1984- 一个可见 `control` 对话
1985- 一个隐藏 `dispatch` 通道
1986
1987## 23.3 浏览器按钮
1988
1989最少需要这些按钮:
1990
1991- `Pause`
1992- `Resume`
1993- 可选 `Drain`
1994
1995按钮应调用:
1996
1997- `POST /v1/system/pause`
1998- `POST /v1/system/resume`
1999- `POST /v1/system/drain`
2000
2001## 23.4 浏览器状态显示
2002
2003最低显示项:
2004
2005- 当前 mode
2006- 当前 leader
2007- active runs
2008- queued tasks
2009
2010## 24. `baa-hand` 与 `baa-shell` 集成
2011
2012## 24.1 `baa-hand`
2013
2014`baa-hand` 适用于:
2015
2016- planner 调用
2017- review 调用
2018- 非 durable 的临时 AI 工作
2019
2020推荐:
2021
2022- planner 与 review steps 调 `led.makefile.so`
2023- execution steps 不依赖当前 `baa-hand` 的 Codex 模式来保证 durability
2024
2025## 24.2 `baa-shell`
2026
2027`baa-shell` 适用于:
2028
2029- 当 HTTP 比 SSH 更方便时,执行远程 shell
2030- 让外部自动化读写文件
2031- 提供状态页面
2032
2033对于同机操作,本地 conductor 代码依然可以直接用 local exec。
2034
2035## 25. mini 与 mac 上的本地目录布局
2036
2037推荐根路径:
2038
2039```text
2040/Users/george/code/baa-conductor
2041```
2042
2043推荐 runtime 目录:
2044
2045```text
2046/Users/george/code/baa-conductor/state/
2047/Users/george/code/baa-conductor/runs/
2048/Users/george/code/baa-conductor/worktrees/
2049/Users/george/code/baa-conductor/logs/
2050/Users/george/code/baa-conductor/tmp/
2051```
2052
2053推荐的 per-run 目录:
2054
2055```text
2056/Users/george/code/baa-conductor/runs/<task-id>/<run-id>/
2057  meta.json
2058  state.json
2059  stdout.log
2060  stderr.log
2061  worker.log
2062  checkpoints/
2063  artifacts/
2064```
2065
2066推荐的 worktree 目录:
2067
2068```text
2069/Users/george/code/baa-conductor/worktrees/<task-id>/
2070```
2071
2072规则:
2073
2074- 一个 task 恰好一个 worktree
2075- worktree 路径应确定性
2076- 陈旧 worktree 只能由 conductor 清理,worker 不得自行清理
2077
2078## 25.1 本地文件约定
2079
2080建议由 conductor 写入的文件:
2081
2082- `meta.json`: 不变的 run 元数据
2083- `state.json`: 当前本地状态镜像
2084- `worker.log`: 当前 run 的 conductor 生命周期消息
2085- `stdout.log`: worker 原始 stdout
2086- `stderr.log`: worker 原始 stderr
2087
2088建议作为 checkpoint 写入的文件:
2089
2090- `checkpoints/0001-summary.json`
2091- `checkpoints/0002-git-diff.patch`
2092- `checkpoints/0003-test-output.txt`
2093
2094## 26. 建议的仓库结构
2095
2096此仓库最终建议长成这样:
2097
2098```text
2099apps/
2100  control-api-worker/
2101  conductor-daemon/
2102  status-api/
2103  worker-runner/
2104packages/
2105  db/
2106  planner/
2107  schemas/
2108  step-templates/
2109  logging/
2110  git-tools/
2111  checkpointing/
2112ops/
2113  nginx/
2114  sql/
2115  launchd/
2116  scripts/
2117docs/
2118  decisions/
2119```
2120
2121## 26.1 建议最先创建的文件
2122
2123为了方便并行开发,初始仓库至少应包含:
2124
2125```text
2126README.md
2127DESIGN.md
2128ops/sql/schema.sql
2129ops/sql/migrations/0001_init.sql
2130ops/nginx/baa-conductor.conf
2131ops/launchd/so.makefile.baa-conductor.plist
2132ops/launchd/so.makefile.baa-worker-runner.plist
2133apps/control-api-worker/src/index.ts
2134apps/conductor-daemon/src/index.ts
2135apps/worker-runner/src/index.ts
2136packages/schemas/src/index.ts
2137packages/db/src/index.ts
2138packages/planner/src/index.ts
2139packages/step-templates/src/index.ts
2140packages/logging/src/index.ts
2141packages/checkpointing/src/index.ts
2142```
2143
2144## 27. mini 与 mac 上的 launchd
2145
2146因为 mini 和 mac 都是 macOS,推荐用 `launchd` 做进程守护。
2147
2148建议服务:
2149
2150- `so.makefile.baa-conductor`
2151- `so.makefile.baa-worker-runner`
2152- 可选 `so.makefile.baa-status-api`
2153
2154建议行为:
2155
2156- 开机或登录自动启动
2157- 失败自动拉起
2158- 日志写文件
2159
2160conductor 的示例参数:
2161
2162```xml
2163<array>
2164  <string>/usr/bin/env</string>
2165  <string>node</string>
2166  <string>/Users/george/code/baa-conductor/apps/conductor-daemon/dist/index.js</string>
2167  <string>--host</string>
2168  <string>mini</string>
2169  <string>--role</string>
2170  <string>primary</string>
2171</array>
2172```
2173
2174## 27.1 建议的 launchd 路径
2175
2176推荐安装路径:
2177
2178- repo 中的 plist 源文件:`ops/launchd/*.plist`
2179- 用户级安装路径:`~/Library/LaunchAgents/`
2180- 如果需要开机登录前启动,可改用系统级 `/Library/LaunchDaemons/`
2181
2182推荐 label:
2183
2184- `so.makefile.baa-conductor`
2185- `so.makefile.baa-worker-runner`
2186- `so.makefile.baa-status-api`
2187
2188## 27.2 环境变量
2189
2190建议 conductor 与 worker 服务使用:
2191
2192```text
2193BAA_CONDUCTOR_HOST=mini
2194BAA_CONDUCTOR_ROLE=primary
2195BAA_CONTROL_API_BASE=https://control-api.makefile.so
2196BAA_CONDUCTOR_PUBLIC_BASE=https://conductor.makefile.so
2197BAA_CONDUCTOR_LOCAL_API=http://127.0.0.1:4317
2198BAA_CONDUCTOR_PRIVATE_BASE=http://100.71.210.78:4317
2199BAA_CONDUCTOR_PEER_BASE=http://100.112.239.13:4317
2200BAA_RUNS_DIR=/Users/george/code/baa-conductor/runs
2201BAA_WORKTREES_DIR=/Users/george/code/baa-conductor/worktrees
2202BAA_LOGS_DIR=/Users/george/code/baa-conductor/logs
2203BAA_TMP_DIR=/Users/george/code/baa-conductor/tmp
2204BAA_NODE_ID=mini-main
2205BAA_SHARED_TOKEN=replace-me
2206```
2207
2208mac 上至少需要不同的变量:
2209
2210- `BAA_CONDUCTOR_HOST`
2211- `BAA_CONDUCTOR_ROLE`
2212- `BAA_NODE_ID`
2213- `BAA_CONDUCTOR_PRIVATE_BASE`
2214- `BAA_CONDUCTOR_PEER_BASE`
2215
2216说明:
2217
2218- `BAA_CONTROL_API_BASE` 仍然指向 Cloudflare Worker,自身不走 Tailscale
2219- 节点间与内网调用统一使用 `BAA_CONDUCTOR_PRIVATE_BASE` / `BAA_CONDUCTOR_PEER_BASE`
2220- 不要把 `mini.tail0125d.ts.net``mbp.tail0125d.ts.net` 写进运行配置
2221
2222## 28. 安全模型
2223
2224## 28.1 Control API
2225
2226- conductor 和 worker 调用必须鉴权
2227- pause、resume、drain、promote 等操作需要更强的 admin 鉴权
2228
2229## 28.2 直连节点域名
2230
2231- `mini-conductor.makefile.so``mac-conductor.makefile.so` 必须受保护
2232- 使用 Basic Auth、IP allowlist 或两者同时使用
2233
2234## 28.3 日志
2235
2236日志中可能包含:
2237
2238- prompts
2239- diffs
2240- 文件路径
2241- 测试输出
2242
2243规则:
2244
2245- 尽可能做 secrets 脱敏
2246- 不公开暴露原始日志
2247
2248## 29. 可观测性
2249
2250每个 conductor 应暴露:
2251
2252- `/healthz`
2253- `/readyz`
2254- `/rolez`
2255- 如果后续需要,可加 `/metrics`
2256
2257有用的状态字段:
2258
2259- controller id
2260- role
2261- lease holder
2262- lease expiry
2263- active runs
2264- queue depth
2265- paused mode
2266
2267## 30. 故障场景
2268
2269## 30.1 Mini 在空闲状态下死亡
2270
2271- lease 过期
2272- mac 抢到 lease
2273- 队列继续运行
2274
2275## 30.2 Mini 在 Codex Step 中死亡
2276
2277- run lease 过期
2278- mac 成为 leader
2279- mac 读取最新 checkpoint
2280- mac 回放 checkpoint 或直接重跑该 step
2281
2282## 30.3 Mac 在 Standby 状态下死亡
2283
2284- 不影响服务
2285
2286## 30.4 D1 暂时不可用
2287
2288leader 应:
2289
2290- 停止 claim 新工作
2291- 在安全前提下继续监管本地正在运行的 worker
2292- 持续尝试重连
2293- 在控制面未确认前,不进行不安全写入
2294
2295## 30.5 浏览器 UI 失效
2296
2297- 自动化仍然继续
2298- 只是人类控制面临时不可用
2299
2300## 30.6 VPS 死亡
2301
2302如果 VPS 掉了:
2303
2304- 公网入口失效
2305- 公网域名无法再直接做运维操作
2306- 只要 mini 与 mac 还能访问 `control-api.makefile.so`,内部调度仍可继续
2307- 只要 tailnet 还通,运维仍可通过 Tailscale `100.x` 直达节点
2308
2309推荐处理:
2310
2311- 不要因为公网入口消失就主动停掉 worker 集群
2312- 单独修复 VPS
2313
2314## 30.7 Cloudflare Worker 或 D1 退化
2315
2316如果 control API 不可用:
2317
2318- leaders 停止 claim 新工作
2319- 已在本地运行的 steps 可以在安全前提下继续
2320- 不应盲目提交不可逆的调度状态
2321
2322恢复规则:
2323
2324- control API 恢复后,conductors 要把本地 run 状态与 D1 重新对账
2325
2326## 31. 建议的 API 返回码
2327
2328- `200` 正常读取
2329- `202` 接受异步创建
2330- `409` 节点健康但不是 leader,不能执行写操作
2331- `423` task 或 step 被 lease 锁住
2332- `503` 控制平面退化
2333
2334## 32. 建议的初始端口
2335
2336以下只是建议值:
2337
2338- conductor 本地 HTTP API: `4317`
2339- status API: `4318`
2340- 可选 metrics: `4319`
2341
2342## 33. 时序流程
2343
2344## 33.1 Task 创建流程
2345
23461. human 在可见 Claude `control` 中下指令
23472. `control` 通过 control API 创建 task
23483. leader conductor 取到 task
23494. leader 选择模板或 planner
23505. 持久化 plan
23516. steps 进入 runnable
2352
2353## 33.2 Step 执行流程
2354
23551. leader claim 下一个 runnable step
23562. leader 分配 worker 与 worktree
23573. worker 启动
23584. worker 把日志持续写到本地
23595. conductor 写 heartbeat 与 checkpoints
23606. worker 退出
23617. conductor 把 step 标记为 complete 或 failed
2362
2363## 33.3 Failover 流程
2364
23651. mini lease 过期
23662. mac 获取 leadership
23673. mac 扫描未完成 steps
23684. mac 基于 checkpoints 恢复 runnable 工作
2369
2370## 33.4 人工 Pause 与接管流程
2371
23721. 人在 Firefox 插件或状态页点击 `Pause`
23732. 浏览器调用 `POST /v1/system/pause`
23743. control API 更新 D1 中的 `system_state`
23754. 当前 leader 观察到 mode 变化
23765. leader 停止启动新的 steps
23776. 人开始和可见 Claude `control` 对话
23787. 人点击 `Resume`
23798. leader 恢复调度
2380
2381## 34. 建议的 MVP 范围
2382
2383第一阶段至少要包含:
2384
23851. 仓库骨架
23862. D1 schema 与 migration scripts
23873. control API Worker
23884. conductor lease loop
23895. task 与 step 持久化
23906. 一个带本地日志流的 Codex step runner
23917. 一个 shell step runner
23928. pause、resume、drain
23939. mini -> mac 的基础 failover
239410. Firefox 插件按钮接到 control API
2395
2396## 35. 建议的并行开发拆分
2397
2398这一节的目的就是让多个 Codex worker 立刻开始并行干活。
2399
2400每个 task 只能有一个分支和一个 owner。
2401
2402## 35.0 协作规则
2403
2404这些规则在多 Codex 并行时必须遵守。
2405
2406### 分支规则
2407
2408- 一个 task 等于一个 branch
2409- 一个 branch 同时只允许一个 owner
2410- branch 名由 conductor 或 task 创建方分配
2411
2412### Worktree 规则
2413
2414- 一个 task 等于一个 worktree
2415- 不允许两个 worker 共用一个活跃 worktree
2416
2417### Write Scope 规则
2418
2419每个 task 都必须声明 `write_scope`2420
2421如果需要修改超出 `write_scope` 的文件:
2422
2423- 要么新建 task
2424- 要么显式修订 scope
2425
2426### 全局真相规则
2427
2428- task 真相在 D1
2429- 本地真相在 run 目录
2430- 对话记录不是 task 真相
2431
2432### 热点文件规则
2433
2434以下内容应视为协调敏感文件:
2435
2436- 根级 lockfile
2437- 全局 CI 配置
2438- 共享 schema 文件
2439- 一旦开始实现后,这份 `DESIGN.md` 本身
2440
2441对热点文件的修改应串行化,或提前明确指派。
2442
2443## 35.0.1 Task Card 模板
2444
2445未来仓库中,每个并行 task 都应该有一个任务卡,例如:
2446
2447```md
2448---
2449task_id: T-004
2450title: Conductor lease and heartbeat loop
2451owner: codex-mini-01
2452branch: feat/T-004-conductor-lease
2453repo: /Users/george/code/baa-conductor
2454base_ref: main
2455write_scope:
2456  - apps/conductor-daemon/**
2457  - packages/db/**
2458depends_on:
2459  - T-002
2460  - T-003
2461acceptance:
2462  - leader lease acquisition works
2463  - standby nodes do not schedule
2464  - lease renewal tests added
2465status: in_progress
2466---
2467```
2468
2469## 35.0.2 完成报告模板
2470
2471task 完成后,worker 应至少回报:
2472
2473```md
2474task_id: T-004
2475branch: feat/T-004-conductor-lease
2476base_ref: <commit>
2477files_changed:
2478  - apps/conductor-daemon/src/index.ts
2479  - packages/db/src/lease.ts
2480commands_run:
2481  - pnpm test
2482result:
2483  - passed targeted tests
2484risks:
2485  - none
2486```
2487
2488## 35.1 Task A: 仓库骨架
2489
2490Branch:
2491
2492- `feat/T-001-repo-scaffold`
2493
2494Scope:
2495
2496- 仓库目录结构
2497- package manager 配置
2498- 根配置
2499- 初始 README
2500
2501不要改:
2502
2503- D1 SQL
2504- Nginx 配置
2505- Firefox 插件
2506
2507## 35.2 Task B: D1 Schema 与 Migrations
2508
2509Branch:
2510
2511- `feat/T-002-d1-schema`
2512
2513Scope:
2514
2515- `ops/sql/**`
2516- `packages/db/**`
2517
2518不要改:
2519
2520- conductor runtime
2521- worker runner
2522
2523## 35.3 Task C: Control API Worker
2524
2525Branch:
2526
2527- `feat/T-003-control-api`
2528
2529Scope:
2530
2531- `apps/control-api-worker/**`
2532
2533依赖:
2534
2535- Task B
2536
2537## 35.4 Task D: Conductor Lease 与 Heartbeat Loop
2538
2539Branch:
2540
2541- `feat/T-004-conductor-lease`
2542
2543Scope:
2544
2545- `apps/conductor-daemon/**`
2546- `packages/db/**`
2547
2548依赖:
2549
2550- Task B
2551- Task C
2552
2553## 35.5 Task E: Worker Runner 与本地日志流
2554
2555Branch:
2556
2557- `feat/T-005-worker-runner`
2558
2559Scope:
2560
2561- `apps/worker-runner/**`
2562- `packages/logging/**`
2563
2564不要改:
2565
2566- Nginx
2567- Firefox 插件
2568
2569## 35.6 Task F: Checkpoint 与 Git Diff Snapshots
2570
2571Branch:
2572
2573- `feat/T-006-checkpointing`
2574
2575Scope:
2576
2577- `packages/checkpointing/**`
2578- worker-runner 集成
2579
2580依赖:
2581
2582- Task E
2583
2584## 35.7 Task G: Planner 抽象与模板
2585
2586Branch:
2587
2588- `feat/T-007-planner`
2589
2590Scope:
2591
2592- `packages/planner/**`
2593- `packages/step-templates/**`
2594
2595## 35.8 Task H: Nginx 与 VPS 运维
2596
2597Branch:
2598
2599- `feat/T-008-ops-nginx`
2600
2601Scope:
2602
2603- `ops/nginx/**`
2604- 部署文档
2605
2606## 35.9 Task I: Firefox 插件 Pause 与 Resume
2607
2608Branch:
2609
2610- `feat/T-009-firefox-pause`
2611
2612Scope:
2613
2614- Firefox 插件集成文档
2615- 浏览器状态协议
2616
2617这部分现在统一收口在本仓库的 Firefox 插件子目录里,但协议仍然在这里定义清楚。
2618
2619## 35.10 Task J: Status API 与基础 UI
2620
2621Branch:
2622
2623- `feat/T-010-status-api`
2624
2625Scope:
2626
2627- `apps/status-api/**`
2628
2629## 35.11 Task K: launchd 与本地 Runtime 布局
2630
2631Branch:
2632
2633- `feat/T-011-launchd-runtime`
2634
2635Scope:
2636
2637- `ops/launchd/**`
2638- 本地 runtime 路径文档
2639
2640## 35.12 Task L: 鉴权与 Token 模型
2641
2642Branch:
2643
2644- `feat/T-012-auth-model`
2645
2646Scope:
2647
2648- worker 鉴权设计
2649- token 校验中间件
2650- 角色定义
2651
2652## 36. Runbook
2653
2654这一节故意写成运维操作说明,是真正出问题时要照着用的。
2655
2656## 36.1 初始部署 Runbook
2657
26581. 部署 Cloudflare Worker 与 D1
26592. 创建 D1 schema
26603. 部署 `control-api.makefile.so`
26614. 配置 DNS 记录
26625. 在 VPS 安装 Nginx 配置
26636. 验证 `mini-conductor.makefile.so``mac-conductor.makefile.so`
26647. 在 mini 安装 launchd 服务
26658. 在 mac 安装 launchd 服务
26669. 启动 mini conductor 并确认拿到 lease
266710. 启动 mac conductor 并确认处于 standby
266811. 验证浏览器 `Pause``Resume`
2669
2670## 36.2 计划内切换 Runbook
2671
2672从 mini 安全切到 mac:
2673
26741. 把全局模式设为 `draining`
26752. 等活跃 runs 正常结束或至少完成 checkpoint
26763. 确认 mini 没有未收口的可变工作
26774. demote mini
26785. promote mac,或让 mac 自然获取 lease
26796. 确认 mac 的 `GET /rolez` 返回 `leader`
26807. 把全局模式改回 `running`
2681
2682## 36.3 紧急故障切换 Runbook
2683
2684如果 mini 突然挂掉:
2685
26861. 确认 mini heartbeat 已过期
26872. 等 lease 超时
26883. 确认 mac 已获取 lease
26894. 查看未完成 runs
26905. 基于 checkpoints 恢复或重跑当前 steps
2691
2692## 36.4 切回 Mini 的 Runbook
2693
2694mini 恢复后:
2695
26961. 先让 mini 继续保持 standby
26972. 修复原始故障
26983. 选择一个低峰窗口
26994. 把系统设为 `draining`
27005. drain mac
27016. promote mini
27027. 确认 mini 成为 leader
27038. 把系统切回 `running`
2704
2705## 37. 接下来立刻要做的事
2706
2707下一波开发建议按这个顺序推进:
2708
27091. 创建代码骨架
27102. 实现 D1 schema
27113. 实现 control API
27124. 实现 conductor lease
27135. 实现一个 worker runner
27146. 实现 checkpoints
27157. 接上 Firefox 的 pause 与 resume
27168. 补上 VPS Nginx 配置
2717
2718做到这里,这套系统就能真正支撑多 Codex 并行执行。
2719
2720## 38. 总结
2721
2722这份设计刻意把以下东西拆开:
2723
2724- 人类对话
2725- 自动化对话
2726- 规划
2727- 编排
2728- 执行
2729
2730durable 核心是:
2731
2732- D1 存共享真相
2733- conductor 做确定性编排
2734- Codex 做短生命周期 step worker
2735- 本地日志加周期性 checkpoint
2736- mini 做首选 leader,mac 做 standby
2737
2738后续所有实现都应该以这个模型为准。