baa-conductor

git clone 

baa-conductor / plans / discuss
im_wower  ·  2026-03-30

DISCUSS-TIMED-JOBS-RENEWAL-REQUIREMENTS.md

  1# 定时任务模块与续命任务需求讨论稿
  2
  3> 日期: 2026-03-30
  4> 状态: 讨论中
  5
  6## 目标
  7
  8`conductor` 增加一个独立的定时任务模块,统一处理后台异步工作。首版目标不是做完整任务编排,而是先把“消息入库 → 续命任务生成 → API 代理发送 → 状态回写”这条链路跑通。
  9
 10这个模块可以视为系统内的轻量消息队列和兜底执行层。
 11
 12## 当前范围
 13
 14- 支持三类后台任务:消息同步任务、续命任务、执行类任务
 15- 浏览器插件拦截 AI 页面 SSE 结束消息后,通过 WS 推送给 `conductor`
 16- `conductor` 将消息保存到本地消息表
 17- 定时任务扫描新消息,生成续命任务
 18- 定时任务执行续命任务,调用浏览器 API 代理完成发送
 19- 执行完成后更新续命任务状态
 20
 21## 当前不做
 22
 23- 不做 GUI 自动化
 24- 不做操作系统辅助功能发送
 25- 不依赖 Safari、前台窗口、剪贴板等本机能力
 26- 不要求第一版直接复用现有开发任务执行模型
 27
 28GUI / a11y 作为后续扩展能力,留到下一阶段。
 29
 30## 消息需求
 31
 32系统需要一张本地消息表,使用我们自己的唯一消息 ID,不直接使用各 AI 平台的消息 ID 作为主键。
 33
 34本地消息表需要满足两个要求:
 35
 36- 能表达 `conductor` 自己的统一消息身份
 37- 能保存与 Claude、ChatGPT、Gemini 等平台消息的对应关系
 38
 39这意味着消息模型需要同时保存:
 40
 41- 本地消息 ID
 42- 平台类型
 43- 平台消息 ID
 44- 对话 ID
 45- 消息内容
 46- 观察时间
 47- 必要的平台上下文
 48
 49## 续命任务需求
 50
 51续命任务来自消息表,不直接来自插件。也就是说,插件只负责上报事实消息,续命动作由后台定时任务决定并执行。
 52
 53续命任务至少需要表达:
 54
 55- 由哪条本地消息触发
 56- 属于哪个平台和对话
 57- 要发往哪个浏览器代理目标
 58- 要发送什么续命内容
 59- 当前执行状态
 60- 已尝试次数
 61- 最近错误信息
 62
 63## 续命状态
 64
 65续命任务首版至少需要这些状态:
 66
 67- 待开始
 68- 执行中
 69- 已发送
 70- 重试中
 71- 已完成
 72- 失败
 73
 74这些状态要能支持后续补充超时、补偿、人工重放等能力。
 75
 76## 定时任务模块职责
 77
 78定时任务模块至少负责两条周期性链路:
 79
 801. 消息同步任务:扫描新消息,将满足条件的消息同步为续命任务
 812. 续命执行任务:扫描待执行续命任务,调用 API 代理执行发送,并回写状态
 82
 83后续如果要纳入“执行指令 / 开发任务”,也应复用同一套定时任务框架,但不要求第一版一次完成全部能力。
 84
 85## 与现有 tasks 的关系
 86
 87这套需求与当前 `conductor` 已有的 `tasks/runs` 概念有关,但不应强行等同。
 88
 89当前讨论倾向是:
 90
 91- 消息同步和续命任务先独立建模
 92- 开发类任务继续沿用现有 `tasks` 方向
 93- 后续再决定是否将两套模型对接,或由定时任务模块桥接到现有 tasks 接口
 94
 95## 第一版验收目标
 96
 97第一版只验证下面这条最小闭环:
 98
 991. 插件拦截 SSE 结束消息
1002. `conductor` 将消息写入本地消息表
1013. 定时任务将消息同步成续命任务
1024. 定时任务调用 API 代理发送续命消息
1035. 续命任务状态从待开始推进到已完成,失败时进入重试或失败状态
104
105## 讨论重点
106
107当前需要重点核对的是:
108
109- 本地消息表与平台消息的映射关系是否合理
110- 续命任务是否应单独建模,而不是直接复用现有 task 结构
111- 续命状态集合是否足够支撑首版和后续扩展
112- API 代理是否可以作为第一阶段唯一执行路径
113
114
115## Claude 回复 — 2026-03-30
116
117### 整体判断
118
119方向对,拆分合理。插件定位为"事实上报者",续命决策权收归 conductor 后台,这个架构决策解耦了感知和执行,后续扩展(超时补偿、人工重放、多策略续命)都有空间。
120
121以下是需要进一步讨论的六个点:
122
123### 讨论点 1:本地消息表的映射 — 建议加一层"对话身份"
124
125当前模型是 `本地消息ID + 平台类型 + 平台消息ID + 对话ID`。但三家 AI 的"对话"概念不统一 — Claude 有 conversation,ChatGPT 有 conversation 但 SSE 结构不同,Gemini 更特殊。
126
127**建议:** 消息表之外再建一张轻量的**本地对话表**(local_conversations),统一映射三家的对话身份。这样续命任务的"属于哪个对话"不用每次去消息上反查,也为后续"对话级策略"(比如某个对话暂停续命)留了锚点。
128
129**待确认:** 三家平台的对话 ID 格式和生命周期是否已经摸清?是否存在同一个对话在平台侧 ID 会变的情况?
130
131### 讨论点 2:续命任务独立建模 — 同意,但建议共享状态机基础设施
132
133不强行和现有 `tasks/runs` 等同是对的。但状态机本身(状态流转、重试计数、错误记录)是通用能力。
134
135**建议:** 抽一个轻量的 `job` 基础结构(状态 + 重试 + 错误 + 时间戳),续命任务和未来的执行任务都基于它扩展。不是复用 task 模型,而是共享状态机骨架,避免后续对接时再做一次合并。
136
137**待确认:** 现有 `tasks/runs` 的数据结构是什么样的?是否有字段可以直接复用到 job 基础结构中?
138
139### 讨论点 3:状态集合 — 建议首版精简
140
141原文列了六个状态:待开始 / 执行中 / 已发送 / 重试中 / 已完成 / 失败。
142
143**建议首版压到四个:** `pending → running → done → failed`
144
145理由:
146- "已发送"和"已完成"的边界模糊 — 发送成功就是完成,发送失败就进重试
147- "重试中"本质上是再次 running,用 `attempt_count` 字段区分即可
148- 四状态足够支撑首版闭环,后续要加"已发送待确认"这种中间态再扩展不迟
149
150**待确认:** 是否有场景需要区分"已发送但未确认对方收到"?如果 API 代理返回 200 就算完成,四状态够用。
151
152### 讨论点 4:API 代理超时兜底
153
154API 代理受 Cloudflare 27-30s 超时限制。续命消息发送如果卡住,需要明确处理策略。
155
156**建议:** 定时任务对每个续命执行设一个 execution timeout(建议 20s),超时直接标记为需重试,不要等 Cloudflare 断连后再被动处理。
157
158**待确认:** 续命发送走的是公网 `conductor.makefile.so` 还是内网 `100.71.210.78:4317`?如果是内网直连则没有 Cloudflare 超时问题,但仍需要自己的超时保护。
159
160### 讨论点 5:定时任务扫描周期
161
162文档未提扫描频率。续命场景对时效性有要求(太慢了对话上下文就断了)。
163
164**建议:** 首版用固定间隔 10-15 秒扫一次。后续可演进为事件驱动 — 插件 WS 推送直接触发扫描,而不是等轮询。
165
166**待确认:** 各 AI 平台对话的"上下文窗口"有多长的容忍度?即从 SSE 结束到下一条消息发出,最多能间隔多久不影响连续性?这决定了扫描间隔的上限。
167
168### 讨论点 6:谁决定"要不要续命"?(核心问题)
169
170文档说"将满足条件的消息同步为续命任务",但"满足条件"的规则未展开。这是核心业务逻辑。
171
172需要回答的问题:
173- 是所有 SSE 结束消息都续命?还是只有特定对话 / 特定 AI?
174- 是否有冷却期?同一个对话 N 秒内收到多条 final-message,是否只续一次?
175- 是否需要"续命开关"?某些对话可能是一次性问答,不需要续命。
176- 续命内容是什么?固定模板还是根据上下文动态生成?
177
178**建议:** 首版先做最简单的规则("所有 final-message 都生成续命任务"),但把规则判断抽成独立函数(如 `shouldRenew(message): boolean`),方便后续加策略。
179
180### 建议的讨论优先级
181
1821. **讨论点 6**(续命判断规则)— 直接影响消息表到续命任务的转换逻辑
1832. **讨论点 1**(对话身份映射)— 直接影响表结构设计
1843. **讨论点 3**(状态精简)— 影响首版实现复杂度
1854. 其余点可以在实现过程中逐步确认
186
187
188## Claude 代码审核回复 — 2026-03-30
189
190> 基于对以下代码的完整阅读:
191> - `ops/sql/schema.sql` — D1 控制面 schema
192> - `state/artifact.db` — 本地 SQLite schema(messages / executions / sessions / d1_sync_queue)
193> - `apps/conductor-daemon/src/instructions/dedupe.ts` — 指令级去重
194> - `apps/conductor-daemon/src/instructions/store.ts` — 持久化去重 + journal
195> - `apps/conductor-daemon/src/instructions/ingest.ts` — 消息摄入全流程
196> - `packages/d1-client/src/sync-queue.ts` — D1 同步队列
197> - `packages/d1-client/src/sync-worker.ts` — D1 同步 worker(轮询 + 指数退避 + 批量推送)
198> - `plugins/baa-firefox/final-message.js` — 三家 AI SSE 拦截(ChatGPT / Claude / Gemini)
199> - 当前数据:messages 31 条(chatgpt 4, gemini 27),executions 1 条,sync_queue pending 0
200
201### 现有基础设施盘点
202
203以下能力已经在代码中实现,续命模块可以直接复用,不需要重建:
204
205**存储层:** `artifact.db`(SQLite)已有 messages / executions / sessions 三张表 + `d1_sync_queue` 同步队列。`D1SyncWorker` 以可配置间隔轮询,批量推送到 Cloudflare D1,支持指数退避(`baseDelayMs × 2^attempts`,上限 `maxDelayMs`),超过 `maxAttempts` 标记 failed。续命任务表直接加到 `artifact.db`,同步到 D1 复用 `SyncQueue.enqueueSyncRecord()`206
207**去重层:** 三层去重已就位。插件侧 `recentRelayKeys`(内存,最近 20 条,key = `platform|conversation_id|assistant_message_id|raw_text`)。消息级 `baa_message_dedupes` 表(`dedupe_key` = `sha256(platform + assistant_message_id + raw_text)`,UNIQUE 约束)。指令级 `baa_instruction_dedupes` 表(`dedupe_key` = `sha256(platform + conversation_id + assistant_message_id + target + tool + params)`)。续命任务去重以 `message_dedupe_key` 为锚点即可。
208
209**插件拦截层:** `final-message.js`(28KB)已完整实现三家 AI 的 SSE 结束消息拦截。ChatGPT 支持候选人评分 + 流式合并 + terminal status 判断。Claude 支持 completion event 累积 + URL 提取 conversation_id。Gemini 支持 `)]}'` 前缀处理 + 深度遍历 + prompt 排除。SSE 流式和完整响应两种模式均已覆盖。当前已有 31 条消息入库,说明链路在跑。
210
211**消息摄入层:** `BaaLiveInstructionIngest.ingestAssistantFinalMessage()` 已完成从 WS 接收 → 去重判断 → 持久化到 artifact.db → 指令提取 → 执行 → journal 记录的完整流程。
212
213### 对讨论稿五个澄清点的逐条确认
214
215**1. SQLite + D1 同步:已就绪,无需额外设计。**
216
217续命任务表加到 `artifact.db`,`D1SyncWorker` 的 `SYNC_COLUMN_WHITELIST` 里加一个 `renewal_jobs` 条目即可自动同步到 D1。
218
219**2. 去重:现有机制覆盖了需求。**
220
221George 提出的"平台类型 + 平台消息ID + 内容 hash"正是 `buildBaaLiveInstructionMessageDedupeKey` 的实现逻辑。续命任务生成时,用 `message_dedupe_key` 查重,同一条消息不重复生成续命任务。
222
223**3. 上下文摘要 + 公开链接:需要新建,但基础字段已有。**
224
225`messages` 表已有 `summary`(当前大部分为 null)、`static_path`、`page_url` 字段。续命 payload 建议:
226
227- 摘要:`raw_text` 前 200 字截断 + `...`(首版),后续可改为 AI 摘要
228- 链接:拼 `https://conductor.makefile.so/artifacts/messages/{message_id}` 或直接用 `page_url`(原始对话页面 URL)
229- 格式:固定模板,如 `"[续命] 上下文摘要:{summary}\n对话链接:{link}"`
230
231**4. 失败 + 通道暂停:退避机制可复用,通道状态需新建。**
232
233`D1SyncWorker` 的重试模式(attempts 计数 + 指数退避 + maxAttempts 后 failed)可以直接用在续命任务上。新增需求是**通道级状态管理**:
234
235- 新建 `renewal_channels` 表或在续命任务表上加 `channel_status` 字段
236- 状态:`active` / `paused` / `disabled`
237- 触发:续命任务 failed → 该 `platform + conversation_id` 通道自动 paused
238- 恢复:手动调用 conductor API(如 `POST /v1/renewal/channels/{id}/resume`239- 日志:扩展 `baa_execution_journal`,加 `summary_kind = 'renewal'`
240
241**5. 插件侧:已完成,不是前置依赖。**
242
243讨论稿第一版验收目标的第 1 步"插件拦截 SSE 结束消息"已在生产中运行。31 条消息已入库证明链路通畅。
244
245### 真正需要新建的三件事
246
247基于代码审核,续命模块的新建工作范围收窄为:
248
249**A. `renewal_jobs` 表** — 续命任务持久化
250
251```sql
252CREATE TABLE IF NOT EXISTS renewal_jobs (
253  job_id          TEXT PRIMARY KEY,
254  message_id      TEXT NOT NULL,
255  platform        TEXT NOT NULL,
256  conversation_id TEXT,
257  channel_key     TEXT NOT NULL,  -- "platform|conversation_id" 复合键
258  payload         TEXT NOT NULL,  -- 续命消息内容(摘要 + 链接)
259  status          TEXT NOT NULL DEFAULT 'pending',  -- pending / running / done / failed
260  attempt_count   INTEGER NOT NULL DEFAULT 0,
261  max_attempts    INTEGER NOT NULL DEFAULT 3,
262  last_error      TEXT,
263  created_at      INTEGER NOT NULL,
264  updated_at      INTEGER NOT NULL,
265  started_at      INTEGER,
266  finished_at     INTEGER,
267  FOREIGN KEY (message_id) REFERENCES messages(id)
268);
269CREATE INDEX IF NOT EXISTS idx_renewal_jobs_status ON renewal_jobs(status, created_at);
270CREATE INDEX IF NOT EXISTS idx_renewal_jobs_channel ON renewal_jobs(channel_key, status);
271```
272
273同步到 D1:在 `SYNC_COLUMN_WHITELIST``renewal_jobs` 条目。
274
275**B. 续命判断函数 `shouldRenew(message): boolean`**
276
277```typescript
278// 首版最简规则,独立函数方便后续加策略
279function shouldRenew(message: IngestedMessage): boolean {
280  // 1. 检查通道是否 paused/disabled
281  // 2. 检查是否已有 pending/running 的续命任务(防重复)
282  // 3. 首版:所有 final-message 都续命
283  return true;
284}
285```
286
287后续扩展点:冷却期、对话级开关、平台级开关、内容过滤。
288
289摘要生成函数:
290
291```typescript
292function buildRenewalPayload(message: ArtifactMessage): string {
293  const summary = message.raw_text.length > 200
294    ? message.raw_text.slice(0, 200) + '...'
295    : message.raw_text;
296  const link = message.page_url || `https://conductor.makefile.so/artifacts/messages/${message.id}`;
297  return `[续命] 摘要:${summary}\n链接:${link}`;
298}
299```
300
301**C. 通道状态管理**
302
303可以不单独建表,直接在 `renewal_jobs` 的查询逻辑里实现:
304
305- 查某个 `channel_key` 最近 N 条任务是否全部 failed → 自动暂停
306- 或者在 `system_state` 表里存 `renewal_channel:{channel_key}``{"status": "paused", "paused_at": ...}`
307- 恢复端点:`POST /v1/renewal/channels/:channelKey/resume`
308
309### 对 Claude 上一轮六个讨论点的修正
310
311基于代码实际,修正之前的建议:
312
313- **讨论点 1(对话身份映射):** 不需要单独建 `local_conversations` 表。`messages` 表已有 `conversation_id`,续命任务用 `channel_key = platform|conversation_id` 做复合键即可。三家平台的 conversation_id 提取逻辑在 `final-message.js` 里已经完整实现。
314- **讨论点 2(共享状态机):** `D1SyncWorker``attempts + status + exponential backoff` 就是现成的状态机骨架。`renewal_jobs` 直接复用同样模式。
315- **讨论点 4(超时):** 续命发送是 conductor 内部调用浏览器 API 代理(走内网 `localhost``100.71.210.78:4317`),不经过 Cloudflare tunnel,无 27-30s 超时问题。但仍建议设 20s 内部超时。
316- **讨论点 5(扫描周期):** 参考 `D1SyncWorker``DEFAULT_POLL_INTERVAL_MS`,续命 worker 用同样模式,首版 10s 间隔。
317
318### 建议的实现顺序
319
3201.`artifact.db` schema 里加 `renewal_jobs`3212.`SYNC_COLUMN_WHITELIST` 加白名单
3223. 实现 `shouldRenew()` + `buildRenewalPayload()`
3234. 实现 `RenewalWorker`(复用 `D1SyncWorker` 的 poll + backoff 模式)
3245.`ingestAssistantFinalMessage` 流程末尾挂钩:摄入成功 → shouldRenew → 写 renewal_jobs
3256. 加通道暂停/恢复 API 端点
3267. 端到端验证
327
328## 当前对齐结论 — 2026-03-30
329
330以下结论优先级高于上文中的早期草案,作为当前版本的讨论共识。
331
332### 1. 首版范围
333
334- 首版只做 API 代理路径
335- 不做 GUI 自动化
336- 不做操作系统辅助功能发送
337- 插件继续负责 SSE final-message 上报
338- `conductor` 负责消息落库、任务生成、任务分发、状态回写
339
340### 2. 定时任务模块
341
342定时任务模块继续作为独立后台模块存在,负责:
343
3441. 消息同步任务
3452. 续命任务
3463. 后续扩展的执行类任务
347
348它本质上仍然是系统内部的轻量消息队列和兜底执行层。
349
350### 3. 扫描周期与批量限制
351
352扫描周期需要做成可配置项,不追求高频。
353
354当前共识:
355
356- 默认扫描周期可以设在 `5-10 秒`
357- 周期再长一些也可以接受
358- 每轮只扫描小批量数据
359- 每轮消息同步最多处理约 `10` 条旧消息
360- 每轮续命分发最多处理约 `10` 个到期任务
361
362这里的“旧消息”和“到期任务”也要有明确约束:
363
364- 消息需要经过一个可配置的 settle delay,再参与同步
365- 任务只处理已到执行时间的记录
366
367周期、批量、settle delay 都应作为配置项。
368
369### 4. 本地对话模型
370
371在本地消息表之外,增加一张本地对话表,并再增加一张对话关联表。
372
373对话表负责:
374
375- 表达本地对话身份
376- 保存当前对话的自动化状态
377- 作为未来 `baa` 指令系统的统一控制锚点
378
379对话关联表负责:
380
381- 建立本地对话与平台对话的映射
382- 保存平台、client、页面、路由等关联信息
383- 支撑续命任务在执行时定位目标
384
385### 5. 对话自动化状态
386
387每个对话需要有一个自动化状态,当前先按三态理解:
388
389- `manual`
390- `auto`
391- `paused`
392
393语义是:
394
395- `manual`:该对话不自动续命
396- `auto`:该对话允许自动续命
397- `paused`:该对话自动化已暂停,可后续恢复
398
399这个状态需要同时支持两个入口:
400
401- 当前 Firefox 插件右下角控制面板
402- 后续 `baa` 指令系统
403
404插件面板只是第一阶段控制入口,后端保存的是统一状态。
405
406### 6. 续命规则
407
408当前共识是不采用“所有 final-message 一律续命”的策略,而是由对话状态决定是否生成续命任务。
409
410只有在满足下面条件时,final-message 才会被同步为续命任务:
411
412- 消息是 assistant final-message
413- 所属对话当前为 `auto`
414- 所属对话不是 `paused`
415- 通过冷却时间判断
416- 不是重复消息
417- 当前目标路由可用
418
419暂停语义也先收敛为:
420
421- `paused` 后不再生成新的续命任务
422- 已经待执行但尚未发送的任务不再继续推进
423- 已经进入执行中的任务可以自然结束
424
425### 7. 续命任务状态
426
427续命任务首版状态从之前的六态收敛为四态:
428
429- `pending`
430- `running`
431- `done`
432- `failed`
433
434重试不再单独建状态,而是通过任务上的重试字段表达,例如:
435
436- 尝试次数
437- 下次执行时间
438- 最近错误
439
440如果未来需要“已发送但待确认”的中间态,再追加扩展。
441
442### 8. 续命任务表
443
444续命任务继续单独建模,但第一版不再扩太多表,只在续命任务表上增加必要列即可。
445
446这张表至少需要表达:
447
448- 来源消息
449- 来源对话
450- 当前状态
451- 重试次数
452- 下次执行时间
453- 最近错误
454- 目标快照
455- 外部日志文件位置
456
457当前倾向是不额外拆出订阅表和 attempt 明细表,先用最小模型跑通。
458
459### 9. 外部日志
460
461定时任务模块需要将执行过程写入外部日志文件,这是正式需求,不是可选增强。
462
463数据库只保留摘要状态,完整执行细节进入外部日志。
464
465日志至少需要能表达:
466
467- 时间
468- runner 类型
469- tick 或批次标识
470- 消息 ID / 对话 ID / 任务 ID
471- 当前阶段
472- 执行结果
473- 耗时
474- 错误信息
475
476第一版优先满足“可排障、可回看、可 tail”的目标。
477
478### 10. 当前最小对象集合
479
480当前讨论已经收敛到下面这组最小对象:
481
482- 本地消息表
483- 本地对话表
484- 对话关联表
485- 续命任务表
486- 外部日志目录
487
488首版先不做:
489
490- GUI / a11y 执行器
491- 订阅表
492- attempt 明细表
493- 与现有 `tasks/runs` 的直接复用
494
495## 对 `main@3f5e0e4` 的逐条核对结论 — 2026-03-30
496
497下面这组结论,基于对当前代码和本地工作树的再次核对。
498
499### 可以直接采纳的部分
500
501- 可以复用 `artifact.db + SyncQueue + D1SyncWorker` 这套本地落盘和 D1 同步机制,不需要重造同步队列
502- 可以复用现有消息去重机制,尤其是 `message_dedupe_key`
503- 可以复用插件侧 final-message 拦截,不再把它当作前置工作
504- 可以复用现有浏览器 API 代理发送能力
505- 可以复用 `attempts + backoff + maxAttempts` 这一套重试思路
506- 续命内部执行仍然建议设置单次超时,建议 20 秒左右
507
508### 只能部分采纳的部分
509
510- “续命任务表直接加到 artifact.db”这件事技术上可行,但它只解决持久化,不解决对话自动化状态和目标定位
511- “续命任务去重以 message_dedupe_key 为锚点即可”首版可用,但不应当作长期唯一约束
512- “摘要 + 公开链接基础字段已有”成立,但续命 payload 不应写死为单一模板,首版应保留模板化空间
513- `renewal_jobs` 表必须存在,但 `3f5e0e4` 里的字段还不够,至少还缺 `next_attempt_at`、目标快照、外部日志路径,以及更稳定的对话锚点
514- `D1SyncWorker` 可以提供重试骨架,但它不是完整的业务状态机;续命任务仍然需要自己的 `pending / running / done / failed` 语义
515- 扫描周期可以参考现有 worker,但最终仍以“可配置、默认 5-10 秒、小批量扫描”为准
516
517### 当前不采纳的部分
518
519- 不采纳“只建 renewal_jobs,不建本地对话表和对话关联表”
520- 不采纳“所有 final-message 一律续命”
521- 不采纳“用 channel_key = platform|conversation_id 直接替代本地对话身份”
522- 不采纳“把通道暂停只做成任务查询逻辑或 system_state 临时键”
523- 不采纳“首版新建范围只收敛为三件事”
524
525### 需要修正的事实表述
526
527- `artifact.db``d1_sync_queue` 的接线机制在代码里是成立的,但这属于运行时能力,不应直接表述为当前工作树里已经存在稳定数据
528- 当前仓库里的本地 [artifact.db](/Users/george/code/baa-conductor/state/artifact.db) 是空文件,因此“messages 31 条 / sync_queue pending 0”这类数字不能直接作为当前 checkout 的事实依据
529- `messages.summary` 不是天然大部分为空;按现有 `ArtifactStore` 实现,未显式传入时会自动从 `raw_text` 截断生成摘要
530
531### 当前最终采纳清单
532
533当前正式采纳的方向是:
534
535- 复用现有 `artifact.db`、`SyncQueue`、`D1SyncWorker`
536- 复用现有消息去重
537- 复用现有插件 final-message 拦截
538- 复用现有浏览器 API 代理发送
539- 新建本地对话表
540- 新建对话关联表
541- 新建续命任务表
542- 新增外部日志文件记录
543- 以对话自动化状态 `manual / auto / paused` 作为续命生成规则核心
544
545### 当前最终不采纳清单
546
547当前明确不采纳的方向是:
548
549-`platform|conversation_id` 直接替代本地对话身份
550- 所有 final-message 自动生成续命任务
551- 仅凭 `renewal_jobs` 一张表承载全部对话控制语义
552- 把暂停/恢复能力寄存在临时推断逻辑中,而不是正式模型中
553
554
555## Claude 确认对齐 — 2026-03-30
556
557> 已读 `main@57a6bba` 的完整对齐结论和逐条核对。
558
559### 接受的修正
560
561以下是我之前建议中被修正的部分,逐条确认接受:
562
563**1. 对话身份必须正式建表。** 我之前说"不需要单独建 `local_conversations` 表,用 `channel_key` 拼接即可",这个判断有误。对话表是对话级控制的正式锚点,承载 `manual/auto/paused` 自动化状态,不能用临时拼接的 key 替代。接受新建本地对话表 + 对话关联表的方案。
564
565**2. 续命规则不能一律续命。** 我之前建议首版"所有 final-message 都生成续命任务"过于粗放。接受以对话自动化状态为核心的判断逻辑:只有 `auto` 状态 + 通过冷却 + 去重 + 路由可用 才生成续命任务。
566
567**3. 新建范围是五个对象,不是三个。** 我之前说"收窄为三件事"遗漏了本地对话表、对话关联表、外部日志目录。接受最终的最小对象集合:本地消息表(已有)、本地对话表(新建)、对话关联表(新建)、续命任务表(新建)、外部日志目录(新建)。
568
569**4. renewal_jobs 表字段补全。** 我之前提的 schema 缺少 `next_attempt_at`、目标快照、外部日志路径。接受补充。
570
571**5. 外部日志是正式需求。** 数据库只存摘要,完整执行细节进外部日志文件。我之前没提这一点,接受作为首版必做项。
572
573**6. 事实纠正。** `artifact.db` 在工作树里是空文件,我查的是运行时实例数据,不能作为 checkout 事实依据。`messages.summary` 有自动截断生成逻辑,不是大部分为 null。已纠正。
574
575### 当前无分歧
576
577经过本轮对齐,以下各项 Claude 侧无异议,可以直接进入实现:
578
579- 复用:`artifact.db` + `SyncQueue` + `D1SyncWorker` + 消息去重 + 插件 final-message 拦截 + 浏览器 API 代理
580- 新建:本地对话表、对话关联表、续命任务表、外部日志目录
581- 对话自动化三态:`manual` / `auto` / `paused`
582- 续命任务四态:`pending` / `running` / `done` / `failed`,重试通过字段表达
583- 扫描周期:可配置,默认 5-10 秒,每轮小批量(约 10 条)
584- 续命判断:对话状态驱动,不是消息驱动
585- paused 语义:不生成新任务,待执行任务不推进,执行中任务自然结束
586- 首版不做:GUI/a11y 执行器、订阅表、attempt 明细表、与现有 tasks/runs 直接复用
587
588### Codex 可以开工了
589
590讨论阶段结束。当前文档作为设计共识,Codex 可以基于"当前对齐结论"和"当前最终采纳清单"两节开始实现。