[moz] docs(§20): v2.0 修订——Repository 模式换底 + Review 反馈整合
庞统修正: - 砍掉 task_index + TaskAdapter,改为 Repository 模式(Queries 类内部换底) - 协作数据从 Gitea API 读,执行数据从 task_state 表读 司马懿 Review S1-S4 + G2: - S1: 表数量 12→14 修正 - S2: P4 补充 action_report 识别方案(body 标记约定) - S3: PR merge 关 Issue 需要 Closes #N commit message 约定 - S4: CI status webhook 验证放到 Phase 0 - G2: Mail 剩余职责明确 姜维 Review: - task_state 加 issue_updated_at 缓存失效判断 - must_haves 改名 daemon_meta
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
---
|
||||
title: "Issue-Centric Orchestration — Gitea Issue 替代黑板 DB 协作面"
|
||||
created: 2026-06-19
|
||||
version: v1.1 draft
|
||||
version: v2.0 draft
|
||||
status: draft
|
||||
changelog: v1.1 纳入姜维 Review 反馈(issue_updated_at 缓存校验、daemon_meta 改名、TaskAdapter、mention 适配层)
|
||||
changelog: v2.0 纳入姜维+司马懿 Review 反馈 + 庞统 Repository 模式修正
|
||||
v1.1 纳入姜维 Review 反馈
|
||||
v1.0 初版
|
||||
---
|
||||
|
||||
# Issue-Centric Orchestration
|
||||
@@ -35,7 +37,7 @@ changelog: v1.1 纳入姜维 Review 反馈(issue_updated_at 缓存校验、dae
|
||||
|
||||
根据 PRD-v3.0,黑板是**共享意识空间**——所有 agent 通过它读写状态、感知变化、协调工作。
|
||||
|
||||
黑板 DB 包含 12 张表:
|
||||
黑板 DB 包含 14 张表:
|
||||
|
||||
| 表 | 用途 | 分类 |
|
||||
|---|------|------|
|
||||
@@ -51,6 +53,8 @@ changelog: v1.1 纳入姜维 Review 反馈(issue_updated_at 缓存校验、dae
|
||||
| routing_decisions | 路由记录 | 执行面 |
|
||||
| task_attempts | 重试历史 | 执行面 |
|
||||
| mention_queue | @mention 队列 | 执行面 |
|
||||
| experience_tags | 经验标签 | 执行面(§19 已标记废弃) |
|
||||
| agents | Agent 注册信息 | 执行面 |
|
||||
|
||||
**协作面**(tasks/comments/outputs/events)= 迁移到 Gitea Issue
|
||||
**执行面**(reviews/checkpoints/decisions 等)= 保留在 daemon 内部
|
||||
@@ -98,7 +102,7 @@ Gitea(协作介质,替代黑板 DB 协作面)
|
||||
┌──────────────────────────────────────────┐
|
||||
│ daemon(执行引擎,内部状态管理不变) │
|
||||
│ │
|
||||
│ task_index(轻量索引,替代 tasks 表): │
|
||||
│ task_state(轻量索引,替代 tasks 表): │
|
||||
│ issue_number → status, branch, retry │
|
||||
│ │
|
||||
│ 执行面表(不变): │
|
||||
@@ -107,7 +111,7 @@ Gitea(协作介质,替代黑板 DB 协作面)
|
||||
│ routing_decisions, task_attempts │
|
||||
│ │
|
||||
│ 调度逻辑(不变): │
|
||||
│ ticker → 扫 task_index → dispatch │
|
||||
│ ticker → 扫 task_state → dispatch │
|
||||
│ dispatcher → 读 Gitea Issue → spawn │
|
||||
│ spawner → 读 Gitea Issue → prompt │
|
||||
└──────────────────────────────────────────┘
|
||||
@@ -120,44 +124,61 @@ Gitea(协作介质,替代黑板 DB 协作面)
|
||||
| tasks.title | Issue.title | 直接对应 |
|
||||
| tasks.description | Issue.body | 直接对应 |
|
||||
| tasks.assignee | Issue.assignee | 直接对应 |
|
||||
| tasks.status (pending/working/review/done) | daemon task_index 内部维护 | Issue open/closed 只表示生命周期 |
|
||||
| tasks.status (pending/working/review/done) | daemon task_state 内部维护 | Issue open/closed 只表示生命周期 |
|
||||
| tasks.priority | Issue label (priority/P0-P3) | label 模拟 |
|
||||
| tasks.must_haves (JSON) | daemon task_index 内部存储 | daemon 专用元数据 |
|
||||
| tasks.must_haves (JSON) | daemon task_state 内部存储 | daemon 专用元数据 |
|
||||
| tasks.depends_on | Issue blocked_by | Gitea 原生 dependency |
|
||||
| tasks.parent_task | Issue body 引用(如 `Parent: #42`) | 约定 |
|
||||
| tasks.retry_count / dispatch_count | daemon task_index 内部维护 | 执行面数据 |
|
||||
| tasks.retry_count / dispatch_count | daemon task_state 内部维护 | 执行面数据 |
|
||||
| comments | Issue comment | 直接对应 |
|
||||
| outputs | 分支 commit(代码/文档)+ Issue comment(摘要) | 成果物载体迁移 |
|
||||
| events | webhook | 主动推送替代 SSE |
|
||||
|
||||
### 3.3 daemon task_index 结构
|
||||
### 3.3 数据访问层改造:Repository 模式换底
|
||||
|
||||
替代黑板 tasks 表的**轻量索引**,只存 Gitea Issue 没有的信息:
|
||||
**设计原则**:不新建 task_state + TaskAdapter,而是改造现有 Repository(Queries/Blackboard 类)的实现。
|
||||
|
||||
当前数据访问层已有 Repository 模式的基础——`Blackboard` 类和 `Queries` 类封装了所有数据访问,上层(dispatcher/spawner/ticker)通过方法调用(`queries.pending_dispatchable()`、`blackboard.get_task()`),不直接写 SQL。
|
||||
|
||||
改造做法:**Repository 接口不变,实现从 SQLite-only 改为 Gitea + SQLite。**
|
||||
|
||||
```
|
||||
现在:
|
||||
dispatcher → Queries(SQLite) → 黑板 DB
|
||||
|
||||
改造后:
|
||||
dispatcher → Queries(Gitea-backed) → Gitea API(协作数据)+ SQLite(执行状态)
|
||||
```
|
||||
|
||||
上层代码完全不用改。Queries 内部决定数据从哪来:
|
||||
|
||||
| 数据类型 | 来源 | 方式 |
|
||||
|---------|------|------|
|
||||
| title / description / assignee / labels | Gitea Issue | API 读取(webhook 触发时缓存) |
|
||||
| comment / 讨论 | Gitea Issue comment | API 读取 |
|
||||
| status / retry_count / dispatch_count | 本地 SQLite | 原有逻辑不变 |
|
||||
| daemon_meta(原 must_haves) | 本地 SQLite | 原有逻辑不变 |
|
||||
|
||||
**本地 SQLite 表(执行状态,Gitea 没有的)**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE task_index (
|
||||
issue_number INTEGER, -- Gitea Issue 编号
|
||||
repo TEXT, -- 仓库名(如 sanguo/sanguo_moziplus_v2)
|
||||
branch TEXT, -- 功能分支名(如 fix/42-feature-a)
|
||||
status TEXT DEFAULT 'pending', -- daemon 内部状态
|
||||
assignee TEXT, -- agent id(冗余,加速 dispatch 查询)
|
||||
daemon_meta TEXT, -- JSON(daemon 元数据:event_type, steps 等)
|
||||
issue_body_cache TEXT, -- webhook 触发时缓存的 Issue body
|
||||
issue_updated_at TEXT, -- Gitea Issue 的 last updated 时间戳,用于缓存失效判断
|
||||
CREATE TABLE task_state (
|
||||
issue_number INTEGER, -- Gitea Issue 编号
|
||||
repo TEXT, -- 仓库名
|
||||
status TEXT DEFAULT 'pending', -- daemon 内部状态机
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
dispatch_count INTEGER DEFAULT 0,
|
||||
max_retries INTEGER DEFAULT 2,
|
||||
daemon_meta TEXT, -- JSON(event_type, steps 等 daemon 元数据)
|
||||
issue_body_cache TEXT, -- 缓存的 Issue body(优化用,可失效重拉)
|
||||
issue_updated_at TEXT, -- Gitea Issue 的 updated_at(缓存失效判断)
|
||||
created_at TEXT,
|
||||
updated_at TEXT,
|
||||
PRIMARY KEY (issue_number, repo)
|
||||
);
|
||||
```
|
||||
|
||||
**注意**: task_index 不存 title/description/labels——这些从 Gitea Issue 实时读。
|
||||
|
||||
**缓存失效机制**: spawner 构建 prompt 时比对 `issue_updated_at` 和 Gitea API 的 Issue `updated_at`。不匹配说明 Issue body 被编辑过,重新拉取并更新缓存。正常情况下 Issue body 创建后不改,开销可忽略。
|
||||
|
||||
**字段命名说明**: `daemon_meta`(原 `must_haves`)存储 daemon 专用的元数据(event_type、steps 等),改名以避免和需求文档中的 must_haves 概念混淆。
|
||||
**和 task_state + TaskAdapter 的区别**:不新增表名(task_state 替代 task_state)、不新增 adapter 类。Repository 内部把 Gitea 数据 + 本地执行状态合并成 Task 对象返回。上层调用 `queries.get_task(issue_number)` 得到的 Task 对象和现在一模一样——有 title、有 description、有 status。
|
||||
|
||||
---
|
||||
|
||||
@@ -169,8 +190,8 @@ CREATE TABLE task_index (
|
||||
① 庞统/主公在 Gitea 创建 Issue + 指派 agent
|
||||
② Gitea webhook: issues/assigned
|
||||
③ daemon toolchain handler 收到 webhook
|
||||
→ 在 task_index 插入一行(issue_number, repo, assignee, status=pending)
|
||||
④ ticker 扫 task_index 发现 pending → dispatch → spawn agent
|
||||
→ 在 task_state 插入一行(issue_number, repo, assignee, status=pending)
|
||||
④ ticker 扫 task_state 发现 pending → dispatch → spawn agent
|
||||
```
|
||||
|
||||
**和现在的区别**: 当前是庞统在黑板 API 创建 task。改造后是庞统在 Gitea 创建 Issue,webhook 自动触发 daemon 建索引。
|
||||
@@ -178,13 +199,13 @@ CREATE TABLE task_index (
|
||||
### 4.2 执行 Task
|
||||
|
||||
```
|
||||
① dispatcher 扫 task_index 发现 pending task
|
||||
① dispatcher 扫 task_state 发现 pending task
|
||||
② spawner 从 Gitea API 读 Issue body(需求描述)
|
||||
③ spawner 用 Issue body 构建 prompt(替代从黑板 DB 读 description)
|
||||
→ prompt 结构: Issue body(需求)+ PromptSection 注入(工作流程、约束、API 指引)
|
||||
④ agent 收到 prompt → 执行
|
||||
⑤ agent 在 Gitea Issue comment 汇报进展(替代黑板 comment)
|
||||
⑥ daemon 更新 task_index status=working
|
||||
⑥ daemon 更新 task_state status=working
|
||||
```
|
||||
|
||||
### 4.3 审查
|
||||
@@ -192,16 +213,18 @@ CREATE TABLE task_index (
|
||||
```
|
||||
① agent 编码完成 → push 到分支 → 创建 PR
|
||||
② Gitea webhook: pull_request/opened
|
||||
→ daemon 更新 task_index status=review
|
||||
→ daemon 更新 task_state status=review
|
||||
③ Reviewer 在 Gitea 做 PR Review
|
||||
④ Gitea webhook: pull_request_review
|
||||
→ daemon 根据 Review 结果更新 task_index
|
||||
→ daemon 根据 Review 结果更新 task_state
|
||||
⑤ Review 通过 → PR merge
|
||||
→ Gitea 自动关闭 Issue
|
||||
→ Gitea webhook: issues/closed
|
||||
→ daemon 更新 task_index status=done
|
||||
→ daemon 更新 task_state status=done
|
||||
```
|
||||
|
||||
**commit message 约定**(司马懿 S3):Gitea PR merge 自动关闭 Issue 需要 commit message 包含 `Closes #N` 或 `Fixes #N` 关键词。agent 创建 PR 时在描述中加上此约定,确保 merge 后 Issue 自动关闭。
|
||||
|
||||
**审查统一走 PR Review**——不区分设计审查和代码审查,所有成果物都在分支上,Reviewer 一次性审。
|
||||
|
||||
### 4.4 CI 失败处理
|
||||
@@ -218,9 +241,9 @@ CREATE TABLE task_index (
|
||||
### 4.5 ticker 兜底
|
||||
|
||||
webhook 可能丢失或延迟。ticker 保留原有逻辑,改为:
|
||||
- 扫 `task_index` 中 status=pending 的记录(替代扫黑板 tasks 表)
|
||||
- 扫 `task_index` 中 status=working 但超时的记录
|
||||
- 如果发现 Gitea Issue 已 closed 但 task_index 还是 working → 更新为 done
|
||||
- 扫 `task_state` 中 status=pending 的记录(替代扫黑板 tasks 表)
|
||||
- 扫 `task_state` 中 status=working 但超时的记录
|
||||
- 如果发现 Gitea Issue 已 closed 但 task_state 还是 working → 更新为 done
|
||||
|
||||
---
|
||||
|
||||
@@ -232,9 +255,9 @@ webhook 可能丢失或延迟。ticker 保留原有逻辑,改为:
|
||||
|
||||
| 模块 | 现在 | 改造后 | 影响范围 |
|
||||
|------|------|-------|---------|
|
||||
| `queries.pending_dispatchable()` | `SELECT * FROM tasks WHERE status='pending'` | `SELECT * FROM task_index WHERE status='pending'` | SQL 改表名 |
|
||||
| `Task.from_row(row)` 构建 task 对象 | 从 tasks 表行直接取 title/description | 从 task_index 取 issue_number → 调 Gitea API 读 Issue title/body | 需要新增 Gitea API 调用 |
|
||||
| `UPDATE tasks SET status=?` | 直接更新 SQLite | 更新 task_index(SQLite) | SQL 改表名 |
|
||||
| `queries.pending_dispatchable()` | `SELECT * FROM tasks WHERE status='pending'` | `SELECT * FROM task_state WHERE status='pending'` | SQL 改表名 |
|
||||
| `Task.from_row(row)` 构建 task 对象 | 从 tasks 表行直接取 title/description | 从 task_state 取 issue_number → 调 Gitea API 读 Issue title/body | 需要新增 Gitea API 调用 |
|
||||
| `UPDATE tasks SET status=?` | 直接更新 SQLite | 更新 task_state(SQLite) | SQL 改表名 |
|
||||
| `INSERT INTO comments` | 写黑板 DB | 改为 Gitea Issue comment API | 需要新增 Gitea API 调用 |
|
||||
|
||||
### 5.2 ⚠️ 需要讨论的改造点
|
||||
@@ -246,16 +269,16 @@ webhook 可能丢失或延迟。ticker 保留原有逻辑,改为:
|
||||
当前 spawner 从 SQLite 读 task description(微秒级)。改为从 Gitea API 读(毫秒级,HTTP 请求)。
|
||||
|
||||
- **方案 A**: 每次 spawn 时实时调 Gitea API。简单但慢
|
||||
- **方案 B**: webhook 触发时缓存 Issue body 到 `task_index.issue_body_cache` + `issue_updated_at`。spawn 时从缓存读
|
||||
- **方案 B**: webhook 触发时缓存 Issue body 到 `task_state.issue_body_cache` + `issue_updated_at`。spawn 时从缓存读
|
||||
- **缓存失效**: spawner 构建 prompt 时比对 `issue_updated_at` 和 Gitea API 的 Issue `updated_at`,不匹配才重新拉取(正常情况 Issue body 创建后不改,开销可忽略)
|
||||
- **推荐**: 方案 B + updated_at 校验
|
||||
|
||||
**P2: dispatcher 的 `Task.from_row(row)` 返回的对象缺少字段**
|
||||
|
||||
当前 Task 对象有 30+ 个字段(title/description/priority/risk_level 等)。task_index 只存少量字段。dispatcher/dispatch 逻辑如果访问了 Task 对象的 title/description 字段,会取不到值。
|
||||
当前 Task 对象有 30+ 个字段(title/description/priority/risk_level 等)。task_state 只存少量字段。dispatcher/dispatch 逻辑如果访问了 Task 对象的 title/description 字段,会取不到值。
|
||||
|
||||
- **方案 A(lazy load)**: Task 对象新增 `issue_data` 字段,首次访问 title/description 时从 Gitea API 或缓存加载
|
||||
- **方案 B(TaskAdapter,推荐 ✅)**: 新增 `TaskAdapter` 类,封装 task_index + Gitea Issue 数据的合并访问。调用方代码不用改,只改 adapter 内部实现。比 lazy load 字段更干净——合并逻辑集中在一处,而不是散落在 Task 对象的多个 property 中
|
||||
- **方案 B(TaskAdapter,推荐 ✅)**: 新增 `TaskAdapter` 类,封装 task_state + Gitea Issue 数据的合并访问。调用方代码不用改,只改 adapter 内部实现。比 lazy load 字段更干净——合并逻辑集中在一处,而不是散落在 Task 对象的多个 property 中
|
||||
- **影响**: 需要检查 dispatcher 中所有访问 task.title/task.description 的地方
|
||||
|
||||
**P3: agent 的 prompt 中引用黑板 API 的地方需要改**
|
||||
@@ -272,6 +295,7 @@ PromptSection 中有多处 `POST localhost:8083/api/projects/.../tasks/.../comme
|
||||
- **方案**: mention_queue 保留,但数据来源从黑板 comment 改为 Gitea webhook payload
|
||||
- **⚠️ 适配层**: Gitea webhook payload 中的 comment body 是完整 markdown 文本(不像黑板 comment 是结构化 JSON,有 comment_type、author 等字段)。mention_queue 消费侧需要适配:从 webhook payload 的 `comment.body` 中正则提取 @mention,而非 SQL WHERE 精确查询。这层适配在 Phase 4 mention 迁移时细化
|
||||
- **影响**: ticker 中的 mention 处理逻辑需要适配
|
||||
- **action_report 识别**(司马懿 S2):黑板 comment 有结构化 `comment_type=action_report` 字段,Gitea comment 只有 markdown body。迁移后用 **body 中的固定标记**识别,如 `<!-- action_report -->` 或约定 body 以 `[Action Report]` 开头。具体格式在 Phase 4 实施时确定。
|
||||
|
||||
---
|
||||
|
||||
@@ -320,7 +344,7 @@ agent 需要知道工作方式变了。新增一个通用 section(或加入现
|
||||
|------|------|
|
||||
| 不做数据迁移 | 主公确认当前无正式使用数据 |
|
||||
| 不做 Issue 状态 label(status/xxx) | 中间状态 daemon 内部管,Issue 只有 open/closed |
|
||||
| 不改 Mail | Mail 职责不变。tc 和 task 都不使用 Mail |
|
||||
| 不改 Mail | Mail 职责不变。Issue-centric 模式下 agent 不用 Mail(通过 Issue/PR comment 协作)。Mail 剩余职责:非 Gitea 相关的 agent 间点对点通知(如庞统通知赵云准备数据) |
|
||||
| 不改前端(本阶段) | 前端改造独立于后端,后续设计 |
|
||||
| 不做存量 task 退役 | 原 task 流程和 Issue 流程可共存,原 task 自然退役 |
|
||||
| 不改 experiences/checkpoints/decisions 表 | 执行面表保留在 daemon,不受影响 |
|
||||
@@ -332,8 +356,9 @@ agent 需要知道工作方式变了。新增一个通用 section(或加入现
|
||||
| 阶段 | 内容 | 依赖 |
|
||||
|------|------|------|
|
||||
| **Phase 0** | **前置:webhook 权限** — jiangwei-infra 升级为 repo admin,或主公手动配置 webhook | 设计 Review 通过 |
|
||||
| Phase 1 | task_index 表创建 + webhook handler 适配(Issue assigned → 建索引)+ **验证 Gitea CI status webhook 是否触发** | Phase 0 |
|
||||
| Phase 2 | dispatcher/ticker 数据源从 tasks 表切换到 task_index + TaskAdapter 合并访问层 | Phase 1 |
|
||||
| Phase 0 | webhook 权限配置 + CI status webhook 验证(主公手动配 webhook;验证 Gitea CI status 变化是否触发 webhook) | 设计 Review 通过 |
|
||||
| Phase 1 | task_state 表创建 + Repository 改造(Queries 类内部从 Gitea + SQLite 双源读) | Phase 0 |(Issue assigned → 建索引)+ **验证 Gitea CI status webhook 是否触发** | Phase 0 |
|
||||
| Phase 2 | dispatcher/ticker 数据源从 tasks 表切换到 task_state + TaskAdapter 合并访问层 | Phase 1 |
|
||||
| Phase 3 | spawner 读 Issue body 构建 prompt(替代读黑板 description)+ issue_updated_at 缓存失效机制 | Phase 2 |
|
||||
| Phase 4 | prompt 改造(黑板 API → Gitea API)+ mention_queue 适配层(webhook payload → @mention 提取) | Phase 3 |
|
||||
| Phase 5 | 验证 + 清理废弃的黑板协作面表 | Phase 4 |
|
||||
@@ -348,7 +373,7 @@ agent 需要知道工作方式变了。新增一个通用 section(或加入现
|
||||
|------|------|------|
|
||||
| Gitea API 不可用时 daemon 完全瘫痪 | 中 | webhook 触发时缓存 Issue body + issue_updated_at 校验(P1 方案 B),减少运行时 Gitea API 依赖 |
|
||||
| Gitea webhook 丢失 | 低 | ticker 兜底扫描 |
|
||||
| task_index 和 Gitea Issue 状态不一致 | 中 | ticker 定期校验(发现 Issue closed 但 index 未更新则修复) |
|
||||
| task_state 和 Gitea Issue 状态不一致 | 中 | ticker 定期校验(发现 Issue closed 但 index 未更新则修复) |
|
||||
| spawner 性能下降(Gitea API 调用) | 低 | 方案 B 缓存 Issue body,spawn 时不调 Gitea API |
|
||||
| 原 task 流程和新 Issue 流程共存期混乱 | 中 | 可以限定只在特定项目中启用 Issue 模式,逐步切换 |
|
||||
| Gitea CI status 变化不触发 webhook | 中 | Phase 1 首先验证;如果不触发,ticker 兜底轮询 commit status API |
|
||||
|
||||
Reference in New Issue
Block a user