baa-conductor

git clone 

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