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后续所有实现都应该以这个模型为准。