- commit
- e8fba02
- parent
- 2dd5941
- author
- im_wower
- date
- 2026-04-01 23:42:23 +0800 CST
docs: add unified automation task queue mvp tasks
2 files changed,
+371,
-0
Raw patch view.
1diff --git a/plans/UNIFIED_AUTOMATION_TASK_QUEUE_MVP.md b/plans/UNIFIED_AUTOMATION_TASK_QUEUE_MVP.md
2index 199d70fce4eb5127e98811001301e1ce7dd99699..43ed09523c0010cbba959bc7b74fcb3468da843c 100644
3--- a/plans/UNIFIED_AUTOMATION_TASK_QUEUE_MVP.md
4+++ b/plans/UNIFIED_AUTOMATION_TASK_QUEUE_MVP.md
5@@ -3,6 +3,8 @@
6 日期:`2026-04-01`
7 状态:`proposed`
8
9+配套任务拆解见:[UNIFIED_AUTOMATION_TASK_QUEUE_MVP_TASKS.md](/Users/george/code/baa-conductor/plans/UNIFIED_AUTOMATION_TASK_QUEUE_MVP_TASKS.md)
10+
11 ## 目标
12
13 把当前分散的 `指令即时执行`、`renewal 独立 job`、`proxy_delivery / DOM delivery` 收敛成一条最小主线:
14diff --git a/plans/UNIFIED_AUTOMATION_TASK_QUEUE_MVP_TASKS.md b/plans/UNIFIED_AUTOMATION_TASK_QUEUE_MVP_TASKS.md
15new file mode 100644
16index 0000000000000000000000000000000000000000..7105a0e9c95e1358a9d5d8e5f2ccb8daa40dd7aa
17--- /dev/null
18+++ b/plans/UNIFIED_AUTOMATION_TASK_QUEUE_MVP_TASKS.md
19@@ -0,0 +1,369 @@
20+# 统一自动化任务队列 MVP 任务拆解
21+
22+日期:`2026-04-01`
23+状态:`proposed`
24+关联文档:[UNIFIED_AUTOMATION_TASK_QUEUE_MVP.md](/Users/george/code/baa-conductor/plans/UNIFIED_AUTOMATION_TASK_QUEUE_MVP.md)
25+
26+## 目标
27+
28+把现有的:
29+
30+- instruction ingest 现场执行
31+- renewal 独立 `renewal_jobs + dispatcher`
32+- delivery 的多条执行路径
33+
34+收敛成:
35+
36+- 一张 `automation_tasks`
37+- 一个 timed `dispatcher`
38+- 一个主执行链 `HTTP proxy executor`
39+
40+这份文档只回答一件事:
41+
42+- 按 MVP 直接落地,具体先改什么,后改什么,哪些文件必须一起动
43+
44+## 执行原则
45+
46+### 1. 不做过渡版
47+
48+不新增临时 instruction queue,也不扩展旧 `renewal_jobs` 充当统一任务表。
49+
50+首版直接上:
51+
52+- `automation_tasks`
53+
54+### 2. 不保留现场执行主链
55+
56+收到 `browser.final_message` 后:
57+
58+- 解析
59+- 校验
60+- 入库
61+- 入队
62+
63+但不再:
64+
65+- 在 `firefox-ws.ts` 中现场执行
66+- 在 ingest 完成后立即 `deliver`
67+
68+### 3. 不走 DOM 主路径
69+
70+首版 outbound 只走:
71+
72+- `browser.proxy_delivery`
73+
74+`delivery-adapters.js` 保留代码,但不作为 dispatcher 路由目标。
75+
76+### 4. 不把“执行”和“回投”绑死
77+
78+必须拆成:
79+
80+- `instruction.execute`
81+- `instruction.deliver`
82+
83+否则 delivery 失败会导致 instruction 被重复执行。
84+
85+## 任务总览
86+
87+按实现顺序拆成 7 个任务:
88+
89+1. 存储层:落 `automation_tasks`
90+2. dispatcher:统一 lease / jitter / retry / concurrency
91+3. 指令入队:ingest 只解析入队
92+4. 指令执行:`instruction.execute -> instruction.deliver`
93+5. 续命入队:projector 改写为 `renewal.deliver`
94+6. 系统接线:停用旧 dispatcher 与旧直执行链
95+7. 验证与清理:测试、日志、兼容收口
96+
97+## Task 1: 存储层落地 `automation_tasks`
98+
99+### 目标
100+
101+提供统一任务表和最小读写接口,取代新写入的 `renewal_jobs`。
102+
103+### 必改文件
104+
105+- [schema.ts](/Users/george/code/baa-conductor/packages/artifact-db/src/schema.ts)
106+- [store.ts](/Users/george/code/baa-conductor/packages/artifact-db/src/store.ts)
107+- [index.test.js](/Users/george/code/baa-conductor/packages/artifact-db/src/index.test.js)
108+- [d1-setup.sql](/Users/george/code/baa-conductor/packages/d1-client/src/d1-setup.sql)
109+- [sync-worker.ts](/Users/george/code/baa-conductor/packages/d1-client/src/sync-worker.ts)
110+- [index.test.js](/Users/george/code/baa-conductor/packages/d1-client/src/index.test.js)
111+
112+### 交付内容
113+
114+- 新建表:`automation_tasks`
115+- 新建索引:
116+ - `(status, execute_after)`
117+ - `(kind, status, execute_after)`
118+ - `(target_client_id, target_platform, status, execute_after)`
119+ - `dedupe_key` 唯一索引
120+- Store 方法至少包括:
121+ - `createAutomationTask`
122+ - `getAutomationTask`
123+ - `listAutomationTasks`
124+ - `updateAutomationTask`
125+ - `leaseAutomationTasks`
126+ - `recoverAutomationRuntimeState` 对过期 lease 的恢复
127+
128+### 实现要求
129+
130+- `renewal_jobs` 表先保留,不立即删除
131+- 新代码开始只向 `automation_tasks` 写入
132+- `recoverAutomationRuntimeState` 必须同时恢复:
133+ - `automation_tasks` 里的过期 `leased/running`
134+ - `local_conversations.execution_state`
135+
136+### 完成标准
137+
138+- 本地 SQLite 与 D1 schema 一致
139+- D1 sync 能同步 `automation_tasks`
140+- store 测试覆盖创建、租约、恢复、状态推进
141+
142+## Task 2: 新建统一 dispatcher
143+
144+### 目标
145+
146+让 timed jobs 只保留一套真正执行自动化任务的 dispatcher。
147+
148+### 必改文件
149+
150+- 新增 [automation/](/Users/george/code/baa-conductor/apps/conductor-daemon/src/automation) 模块
151+- [index.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.ts)
152+- [timed-jobs/runtime.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/timed-jobs/runtime.ts)
153+- [index.test.js](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.test.js)
154+
155+### 建议新增文件
156+
157+- `apps/conductor-daemon/src/automation/dispatcher.ts`
158+- `apps/conductor-daemon/src/automation/types.ts`
159+- `apps/conductor-daemon/src/automation/jitter.ts`
160+- `apps/conductor-daemon/src/automation/executor.ts`
161+
162+### 交付内容
163+
164+- dispatcher 扫描 `pending` / 到期 `retry_wait`
165+- 抢 lease,写:
166+ - `lease_owner`
167+ - `lease_until`
168+- 执行前统一抖动:
169+ - `0ms ~ 800ms`
170+- 统一重试:
171+ - `5s / 15s / 45s`
172+ - 重试前附加 `0ms ~ 1000ms`
173+- 单 `(target_client_id, target_platform)` 并发上限 `1`
174+
175+### 实现要求
176+
177+- 不复用 renewal dispatcher 里的 inter-job jitter 逻辑
178+- 不叠加 browser request policy 的第二套节流
179+- dispatcher 对 executor 只认三类结果:
180+ - `success`
181+ - `retryable_failure`
182+ - `terminal_failure`
183+
184+### 完成标准
185+
186+- timed jobs 能注册并跑新的 dispatcher
187+- dispatcher 测试覆盖 lease、抖动、并发限制、重试推进
188+
189+## Task 3: 指令 ingest 改为“只解析入队”
190+
191+### 目标
192+
193+把当前 `final_message -> parse -> execute -> deliver` 改成 `final_message -> parse -> enqueue`。
194+
195+### 必改文件
196+
197+- [firefox-ws.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/firefox-ws.ts)
198+- [ingest.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/instructions/ingest.ts)
199+- [loop.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/instructions/loop.ts)
200+- [index.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.ts)
201+- [index.test.js](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.test.js)
202+
203+### 交付内容
204+
205+- `BaaInstructionCenter` 不再直接调用 `executeBaaInstruction`
206+- 解析、归一化、policy、route 仍然保留
207+- 对每个合法 instruction 创建一个 `instruction.execute`
208+- ingest summary 增加队列语义
209+ 建议把原 `executed` 收敛为 `queued`
210+
211+### 实现要求
212+
213+- 消息级 dedupe 保留
214+- instruction 级 dedupe 保留
215+- system pause / conversation pause / policy deny 仍在入队前判定
216+- `firefox-ws.ts` 不再在 ingest 后直接调用 `deliveryBridge.deliver`
217+
218+### 完成标准
219+
220+- Claude / ChatGPT / Gemini 的 `final_message` 到达后,只会入队,不会现场执行
221+- ingest 日志能看到入队数
222+
223+## Task 4: 指令执行与结果回投
224+
225+### 目标
226+
227+让统一 dispatcher 承接原来 instruction 的执行责任。
228+
229+### 必改文件
230+
231+- [executor.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/instructions/executor.ts)
232+- [loop.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/instructions/loop.ts)
233+- 新增 `apps/conductor-daemon/src/automation/handlers/instruction-execute.ts`
234+- 新增 `apps/conductor-daemon/src/automation/handlers/instruction-deliver.ts`
235+- [firefox-ws.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/firefox-ws.ts)
236+- [index.test.js](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.test.js)
237+
238+### 交付内容
239+
240+- `instruction.execute`
241+ - 抢对话执行锁
242+ - 调现有 instruction executor
243+ - 持久化 execution/result
244+ - 成功后创建 `instruction.deliver`
245+- `instruction.deliver`
246+ - 从持久化结果渲染回投 payload
247+ - 只走 `browser.proxy_delivery`
248+
249+### 实现要求
250+
251+- conversation execution lock 从 websocket 主链挪到任务执行阶段
252+- delivery 失败不能重跑 instruction
253+- 可重试失败:
254+ - 页面暂停
255+ - 无 client
256+ - 下游超时
257+ - proxy ack 非成功
258+- 不可重试失败:
259+ - payload 缺失
260+ - route 缺失
261+ - 平台不支持
262+
263+### 完成标准
264+
265+- 一条 `@conductor::describe` 会先生成 `instruction.execute`
266+- execute 成功后生成 `instruction.deliver`
267+- `instruction.deliver` 重试不会重复执行 `describe`
268+
269+## Task 5: renewal 改写为 `renewal.deliver`
270+
271+### 目标
272+
273+把 renewal 从独立 job 模型改成统一任务生产者。
274+
275+### 必改文件
276+
277+- [projector.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/renewal/projector.ts)
278+- [dispatcher.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/renewal/dispatcher.ts)
279+- [conversations.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/renewal/conversations.ts)
280+- [local-api.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/local-api.ts)
281+- [index.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.ts)
282+- [index.test.js](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.test.js)
283+
284+### 交付内容
285+
286+- renewal projector 不再创建 `renewal_jobs`
287+- 改为直接创建:
288+ - `renewal.deliver`
289+- renewal payload 仍沿用现有 projector 模板与 route snapshot
290+
291+### 实现要求
292+
293+- renewal 的去重语义要保留
294+ 建议把原 `message_id` 唯一性迁到 `automation_tasks.dedupe_key`
295+- 旧 `renewal.dispatcher` 停止注册
296+- 旧文件可以保留一段时间,但不能继续在主链生效
297+
298+### 完成标准
299+
300+- 新 assistant message 满足 renewal 条件时,会创建 `renewal.deliver`
301+- renewal 不再依赖 `renewal_jobs`
302+
303+## Task 6: 系统接线与 cutover
304+
305+### 目标
306+
307+把 runtime 入口彻底切到统一任务模型。
308+
309+### 必改文件
310+
311+- [index.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.ts)
312+- [firefox-ws.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/firefox-ws.ts)
313+- [local-api.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/local-api.ts)
314+- [browser-types.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/browser-types.ts)
315+- [index.test.js](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.test.js)
316+
317+### 交付内容
318+
319+- 启动时注册新的 automation dispatcher
320+- 停止注册旧 renewal dispatcher
321+- browser state / ingest state / timed-jobs 日志更新为新语义
322+- 旧的直执行链和直回投链不再触发
323+
324+### 实现要求
325+
326+- 如果保留旧 `/v1/renewal/jobs` 读取接口:
327+ - 必须明确改读 `automation_tasks`
328+ - 只返回 `kind = renewal.deliver`
329+- 如果不保留:
330+ - 文档和接口返回必须清楚标注废弃
331+
332+### 完成标准
333+
334+- runtime 内只有一条自动化发送主链
335+- 从最终消息到指令执行、从 renewal 生成到发送,都经过同一个 dispatcher
336+
337+## Task 7: 验证、日志、清理
338+
339+### 目标
340+
341+把可观测性和测试补足,避免重构后只能靠手工试。
342+
343+### 必改文件
344+
345+- [index.test.js](/Users/george/code/baa-conductor/apps/conductor-daemon/src/index.test.js)
346+- [artifacts.test.js](/Users/george/code/baa-conductor/apps/conductor-daemon/src/artifacts.test.js)
347+- [index.test.js](/Users/george/code/baa-conductor/packages/artifact-db/src/index.test.js)
348+- 需要时补充新的 automation 模块测试文件
349+
350+### 必测场景
351+
352+- final_message 到达后只入队,不现场执行
353+- `instruction.execute -> instruction.deliver` 正常链路
354+- delivery 失败只重试 deliver
355+- renewal projector 正常创建 `renewal.deliver`
356+- paused page / missing client / timeout 的 retry 语义
357+- 进程重启后 lease 恢复
358+- 同 `(client, platform)` 并发限制生效
359+
360+### 清理要求
361+
362+- 不删 `delivery-adapters.js`
363+- 不删旧 `renewal_jobs` 表定义,除非确认没有读路径依赖
364+- 但旧 renewal dispatcher 主链必须停用
365+
366+## 推荐实施顺序
367+
368+严格按下面顺序做,避免中途半切状态:
369+
370+1. Task 1:先把表和 store 打好
371+2. Task 2:把统一 dispatcher 跑起来
372+3. Task 3:切断指令现场执行
373+4. Task 4:接回 instruction execute/deliver
374+5. Task 5:renewal 改写为 unified tasks
375+6. Task 6:做 runtime cutover
376+7. Task 7:补测试和清理
377+
378+## 一次性交付边界
379+
380+这次 MVP 实施完成时,应该满足:
381+
382+- `conductor` 不再现场执行指令
383+- `automation_tasks` 成为唯一新任务写入点
384+- renewal 与 instruction 共用一套 dispatcher
385+- outbound 主链只走 HTTP proxy
386+- delivery 失败不会导致 instruction 重跑
387+
388+如果这 5 条有任何一条没落地,就不算这份 MVP 完成。