From ea04b4c483179c5ab2b4009a1076ae1fd4f9c2ba Mon Sep 17 00:00:00 2001 From: cfdaily Date: Fri, 19 Jun 2026 21:05:17 +0800 Subject: [PATCH 1/6] =?UTF-8?q?[moz]=20docs(=C2=A720):=20Issue-Centric=20O?= =?UTF-8?q?rchestration=20=E2=80=94=20Gitea=20Issue=20=E6=9B=BF=E4=BB=A3?= =?UTF-8?q?=E9=BB=91=E6=9D=BF=20DB=20=E5=8D=8F=E4=BD=9C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 设计目标: 1. 黑板 DB 协作面迁移到 Gitea Issue 2. 成果物以 Gitea 为基础(分支+PR) 3. webhook 部分替代 ticker 4. task 状态+spawner 逻辑不变 5. prompt 改造(黑板 API → Gitea API) §3 目标架构:Gitea 做协作介质,daemon task_index 做轻量索引 §5 daemon 改造点:数据访问层(P1-P4)需讨论 §9 实施路径:5 个 Phase 分阶段实施 --- docs/design/20-issue-centric-orchestration.md | 342 ++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 docs/design/20-issue-centric-orchestration.md diff --git a/docs/design/20-issue-centric-orchestration.md b/docs/design/20-issue-centric-orchestration.md new file mode 100644 index 0000000..861114d --- /dev/null +++ b/docs/design/20-issue-centric-orchestration.md @@ -0,0 +1,342 @@ +--- +title: "Issue-Centric Orchestration — Gitea Issue 替代黑板 DB 协作面" +created: 2026-06-19 +version: v1.0 draft +status: draft +--- + +# Issue-Centric Orchestration + +> **作者**: 庞统(副军师)🐦 +> **日期**: 2026-06-19 +> **定位**: 将黑板 DB 的协作面迁移到 Gitea Issue,daemon 逻辑保持不变 +> **前置文档**: PRD-v3.0(共享意识空间)、§14 TaskTypeRegistry、§17 ToolchainHandler + +--- + +## §1. 设计目标 + +| # | 目标 | 说明 | +|---|------|------| +| 1 | 黑板 DB 协作面迁移到 Gitea Issue | 需求、讨论、产出从黑板 DB 迁到 Issue | +| 2 | 成果物以 Gitea 为基础存放 | 分支 commit + PR | +| 3 | webhook 部分替代 ticker | 主动触发替代轮询,ticker 保留兜底 | +| 4 | task 状态 + spawner 逻辑不变 | daemon 内部状态机不变 | +| 5 | prompt 改造 | 黑板 API 引用改为 Gitea API,告知 agent 使用 Gitea 协作 | + +**核心原则**: 只有数据存储位置变了(黑板 DB → Gitea Issue),daemon 的调度逻辑(dispatcher/ticker/spawner)不变。 + +--- + +## §2. 现状分析 + +### 2.1 黑板 DB 当前承担的角色 + +根据 PRD-v3.0,黑板是**共享意识空间**——所有 agent 通过它读写状态、感知变化、协调工作。 + +黑板 DB 包含 12 张表: + +| 表 | 用途 | 分类 | +|---|------|------| +| tasks | 任务(标题、描述、状态、指派、retry) | 协作面 + 执行面 | +| comments | 讨论、@mention、action_report | 协作面 | +| outputs | 产出物(文本摘要、文件路径) | 协作面 | +| events | 事件流(SSE 推送) | 协作面 | +| reviews | 审查记录(verdict、round、consensus) | 执行面 | +| checkpoints | 阶段审查(approve/reject) | 执行面 | +| decisions | 决策记录 | 执行面 | +| observations | 风险观察 | 执行面 | +| experiences | 经验沉淀 | 执行面 | +| routing_decisions | 路由记录 | 执行面 | +| task_attempts | 重试历史 | 执行面 | +| mention_queue | @mention 队列 | 执行面 | + +**协作面**(tasks/comments/outputs/events)= 迁移到 Gitea Issue +**执行面**(reviews/checkpoints/decisions 等)= 保留在 daemon 内部 + +### 2.2 daemon 数据访问方式 + +当前 daemon 三个核心模块如何读写黑板 DB: + +| 模块 | 读什么 | 怎么读 | +|------|-------|-------| +| ticker | pending task 列表 | `SELECT * FROM tasks WHERE status='pending'` (SQLite) | +| dispatcher | task 详情(title/description/must_haves) | `Task.from_row(row)` 从 SQLite 行构建 | +| spawner | task 上下文构建 prompt | 从 task 对象的 title/description/must_haves 字段 | + +| 模块 | 写什么 | 怎么写 | +|------|-------|-------| +| dispatcher | task status(pending→claimed→working) | `UPDATE tasks SET status=?` | +| spawner | task status(working→done/failed) | `UPDATE tasks SET status=?` | +| handler | comment / output | `INSERT INTO comments/outputs` | + +**关键发现**: daemon 大量依赖 `SELECT * FROM tasks WHERE status=?` 这种 SQL 查询来发现和调度 task。如果数据源迁到 Gitea Issue,这些查询的方式会变(从 SQLite 变为 Gitea API 或本地索引),但**查询的语义和返回的数据结构不变**。 + +--- + +## §3. 目标架构 + +### 3.1 分层 + +``` +Gitea(协作介质,替代黑板 DB 协作面) +┌──────────────────────────────────────────┐ +│ Issue #42: "实现功能 A" │ +│ body: 需求描述 + 验收标准 │ +│ assignee: zhangfei-dev │ +│ labels: type/feat, priority/P2 │ +│ comments: 讨论、@mention、进展汇报 │ +│ │ +│ 分支: fix/42-feature-a │ +│ PR #43: fix/42 → main │ +│ CI: lint + test │ +│ Review: APPROVE / REQUEST_CHANGES │ +│ → merge → Issue auto-close │ +└──────────────────────────────────────────┘ + ↕ webhook(被动) ↕ API(主动) +┌──────────────────────────────────────────┐ +│ daemon(执行引擎,内部状态管理不变) │ +│ │ +│ task_index(轻量索引,替代 tasks 表): │ +│ issue_number → status, branch, retry │ +│ │ +│ 执行面表(不变): │ +│ reviews, checkpoints, decisions, │ +│ observations, experiences, │ +│ routing_decisions, task_attempts │ +│ │ +│ 调度逻辑(不变): │ +│ ticker → 扫 task_index → dispatch │ +│ dispatcher → 读 Gitea Issue → spawn │ +│ spawner → 读 Gitea Issue → prompt │ +└──────────────────────────────────────────┘ +``` + +### 3.2 数据映射 + +| 黑板 DB | Gitea 对应 | 迁移方式 | +|---------|-----------|---------| +| 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.priority | Issue label (priority/P0-P3) | label 模拟 | +| tasks.must_haves (JSON) | daemon task_index 内部存储 | daemon 专用元数据 | +| tasks.depends_on | Issue blocked_by | Gitea 原生 dependency | +| tasks.parent_task | Issue body 引用(如 `Parent: #42`) | 约定 | +| tasks.retry_count / dispatch_count | daemon task_index 内部维护 | 执行面数据 | +| comments | Issue comment | 直接对应 | +| outputs | 分支 commit(代码/文档)+ Issue comment(摘要) | 成果物载体迁移 | +| events | webhook | 主动推送替代 SSE | + +### 3.3 daemon task_index 结构 + +替代黑板 tasks 表的**轻量索引**,只存 Gitea Issue 没有的信息: + +```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 查询) + must_haves TEXT, -- JSON(daemon 元数据:event_type, steps 等) + retry_count INTEGER DEFAULT 0, + dispatch_count INTEGER DEFAULT 0, + max_retries INTEGER DEFAULT 2, + created_at TEXT, + updated_at TEXT, + PRIMARY KEY (issue_number, repo) +); +``` + +**注意**: task_index 不存 title/description/labels——这些从 Gitea Issue 实时读。 + +--- + +## §4. 流程设计 + +### 4.1 创建 Task + +``` +① 庞统/主公在 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 +``` + +**和现在的区别**: 当前是庞统在黑板 API 创建 task。改造后是庞统在 Gitea 创建 Issue,webhook 自动触发 daemon 建索引。 + +### 4.2 执行 Task + +``` +① dispatcher 扫 task_index 发现 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 +``` + +### 4.3 审查 + +``` +① agent 编码完成 → push 到分支 → 创建 PR +② Gitea webhook: pull_request/opened + → daemon 更新 task_index status=review +③ Reviewer 在 Gitea 做 PR Review +④ Gitea webhook: pull_request_review + → daemon 根据 Review 结果更新 task_index +⑤ Review 通过 → PR merge + → Gitea 自动关闭 Issue + → Gitea webhook: issues/closed + → daemon 更新 task_index status=done +``` + +**审查统一走 PR Review**——不区分设计审查和代码审查,所有成果物都在分支上,Reviewer 一次性审。 + +### 4.4 CI 失败处理 + +``` +① PR 创建 → CI 自动跑 +② CI 失败 → Gitea webhook: pull_request(CI status) + → daemon toolchain handler 创建 ci_failure toolchain task + → 指派给 PR 作者 + → agent 按 ci_failure steps 处理(已有逻辑,不变) +③ agent 修复 → push 到同分支 → PR 自动更新 → CI 重跑 +``` + +### 4.5 ticker 兜底 + +webhook 可能丢失或延迟。ticker 保留原有逻辑,改为: +- 扫 `task_index` 中 status=pending 的记录(替代扫黑板 tasks 表) +- 扫 `task_index` 中 status=working 但超时的记录 +- 如果发现 Gitea Issue 已 closed 但 task_index 还是 working → 更新为 done + +--- + +## §5. daemon 需要改的地方 + +**原则: daemon 调度逻辑不变,只改数据访问层。** + +### 5.1 数据访问层改造 + +| 模块 | 现在 | 改造后 | 影响范围 | +|------|------|-------|---------| +| `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 改表名 | +| `INSERT INTO comments` | 写黑板 DB | 改为 Gitea Issue comment API | 需要新增 Gitea API 调用 | + +### 5.2 ⚠️ 需要讨论的改造点 + +以下是因为数据源变了,daemon 实现需要调整的地方: + +**P1: spawner 每次 spawn 都要调 Gitea API 读 Issue body?** + +当前 spawner 从 SQLite 读 task description(微秒级)。改为从 Gitea API 读(毫秒级,HTTP 请求)。 + +- **方案 A**: 每次 spawn 时实时调 Gitea API。简单但慢 +- **方案 B**: webhook 触发时缓存 Issue body 到 task_index.must_haves 或单独字段。spawn 时从缓存读 +- **推荐**: 方案 B。webhook 触发时 daemon 已经有 Issue body(在 payload 中),直接缓存 + +**P2: dispatcher 的 `Task.from_row(row)` 返回的对象缺少字段** + +当前 Task 对象有 30+ 个字段(title/description/priority/risk_level 等)。task_index 只存少量字段。dispatcher/dispatch 逻辑如果访问了 Task 对象的 title/description 字段,会取不到值。 + +- **方案**: Task 对象新增 `issue_data` 字段(lazy load),首次访问 title/description 时从 Gitea API 或缓存加载 +- **影响**: 需要检查 dispatcher 中所有访问 task.title/task.description 的地方 + +**P3: agent 的 prompt 中引用黑板 API 的地方需要改** + +PromptSection 中有多处 `POST localhost:8083/api/projects/.../tasks/.../comments`(黑板 API)。这些要改为 Gitea API: +- `task_handler.py` TaskApiSection: `POST .../status` 和 `POST .../outputs` → 不需要(daemon 通过 webhook 自动感知状态) +- `toolchain_handler.py` ToolchainApiSection: `POST .../comments`(action report)→ 改为 `POST Gitea API .../issues/.../comments` +- `toolchain_handler.py` ToolchainApiSection: `POST .../outputs` → 改为"push 到分支"指引 + +**P4: comments 表的 @mention 机制** + +当前 @mention 通过黑板 `mention_queue` 表排队。改造后 @mention 通过 Gitea Issue/PR comment(webhook 自然触发)。但 mention_queue 的消费逻辑(ticker 扫描 → 通知 → agent 处理)需要适配。 + +- **方案**: mention_queue 保留,但数据来源从黑板 comment 改为 Gitea webhook payload +- **影响**: ticker 中的 mention 处理逻辑需要适配 + +--- + +## §6. prompt 改造 + +### 6.1 受影响的 PromptSection + +| Section | 文件 | 当前内容 | 改造后 | +|---------|------|---------|-------| +| TaskApiSection | task_handler.py | 黑板 API(status 回写、outputs 提交) | 删除 status 回写(daemon 自动管);outputs 改为 git push | +| ToolchainApiSection | toolchain_handler.py | 黑板 API(action_report comment、outputs) | action_report 改为 Issue comment;outputs 改为 git push | +| TaskConstraintsSection | task_handler.py | "blackboard comment" 引用 | 改为 "Issue/PR comment" | +| ToolchainConstraintsSection | toolchain_handler.py | 已禁止 Mail | 同时告知 agent 使用 Gitea 协作 | + +### 6.2 agent prompt 新增指引 + +agent 需要知道工作方式变了。新增一个通用 section(或加入现有 constraints section): + +``` +## 协作方式 +- 你的任务通过 Gitea Issue 管理 +- 需求描述在 Issue body 中 +- 进展汇报通过 Issue comment +- 代码产出通过分支 commit + PR +- 审查通过 PR Review +- 不要使用黑板 API,不要使用 Mail API +``` + +--- + +## §7. Issue ↔ 分支 ↔ PR 关系 + +| 场景 | Issue : 分支 : PR | +|------|------------------| +| 简单任务(bugfix、小功能) | 1 : 1 : 1 | +| 复杂任务(多阶段) | 1 : 1 : N(分阶段提交 PR,同一个分支) | +| 极复杂任务(需拆解) | 1 : N : N(Issue body 列出子任务,每个一个分支+PR) | + +**默认 1:1:1**。分支命名规范不变:`fix/{issue_number}-{brief}`。 + +--- + +## §8. 不做的事 + +| 不做 | 理由 | +|------|------| +| 不做数据迁移 | 主公确认当前无正式使用数据 | +| 不做 Issue 状态 label(status/xxx) | 中间状态 daemon 内部管,Issue 只有 open/closed | +| 不改 Mail | Mail 职责不变。tc 和 task 都不使用 Mail | +| 不改前端(本阶段) | 前端改造独立于后端,后续设计 | +| 不做存量 task 退役 | 原 task 流程和 Issue 流程可共存,原 task 自然退役 | +| 不改 experiences/checkpoints/decisions 表 | 执行面表保留在 daemon,不受影响 | + +--- + +## §9. 实施路径 + +| 阶段 | 内容 | 依赖 | +|------|------|------| +| Phase 1 | task_index 表创建 + webhook handler 适配(Issue assigned → 建索引) | 设计 Review 通过 | +| Phase 2 | dispatcher/ticker 数据源从 tasks 表切换到 task_index | Phase 1 | +| Phase 3 | spawner 读 Issue body 构建 prompt(替代读黑板 description) | Phase 2 | +| Phase 4 | prompt 改造(黑板 API → Gitea API) | Phase 3 | +| Phase 5 | 验证 + 清理废弃的黑板协作面表 | Phase 4 | + +每个 Phase 独立可验证,出问题可以回退。 + +--- + +## §10. 风险评估 + +| 风险 | 等级 | 缓解 | +|------|------|------| +| Gitea API 不可用时 daemon 完全瘫痪 | 中 | webhook 触发时缓存 Issue body(P1 方案 B),减少运行时 Gitea API 依赖 | +| Gitea webhook 丢失 | 低 | ticker 兜底扫描 | +| task_index 和 Gitea Issue 状态不一致 | 中 | ticker 定期校验(发现 Issue closed 但 index 未更新则修复) | +| spawner 性能下降(Gitea API 调用) | 低 | 方案 B 缓存 Issue body,spawn 时不调 Gitea API | +| 原 task 流程和新 Issue 流程共存期混乱 | 中 | 可以限定只在特定项目中启用 Issue 模式,逐步切换 | From b6e58a164c4cdaa612663ac6f01180d6e6a74c3e Mon Sep 17 00:00:00 2001 From: cfdaily Date: Fri, 19 Jun 2026 21:21:46 +0800 Subject: [PATCH 2/6] =?UTF-8?q?docs(=C2=A720):=20=E7=BA=B3=E5=85=A5?= =?UTF-8?q?=E5=A7=9C=E7=BB=B4=20Review=20=E5=8F=8D=E9=A6=88=20v1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - task_index 加 issue_updated_at + issue_body_cache 字段 - must_haves 改名 daemon_meta(避免概念混淆) - P2 推荐 TaskAdapter 替代 lazy load - P4 补充 mention 解析适配层说明 - 新增 Phase 0 前置(webhook 权限) - Phase 1 加 CI status webhook 验证 - 风险表补充 CI status 不触发 + 权限不足两项 --- docs/design/20-issue-centric-orchestration.md | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/docs/design/20-issue-centric-orchestration.md b/docs/design/20-issue-centric-orchestration.md index 861114d..87f81f6 100644 --- a/docs/design/20-issue-centric-orchestration.md +++ b/docs/design/20-issue-centric-orchestration.md @@ -1,8 +1,9 @@ --- title: "Issue-Centric Orchestration — Gitea Issue 替代黑板 DB 协作面" created: 2026-06-19 -version: v1.0 draft +version: v1.1 draft status: draft +changelog: v1.1 纳入姜维 Review 反馈(issue_updated_at 缓存校验、daemon_meta 改名、TaskAdapter、mention 适配层) --- # Issue-Centric Orchestration @@ -140,7 +141,9 @@ CREATE TABLE task_index ( branch TEXT, -- 功能分支名(如 fix/42-feature-a) status TEXT DEFAULT 'pending', -- daemon 内部状态 assignee TEXT, -- agent id(冗余,加速 dispatch 查询) - must_haves TEXT, -- JSON(daemon 元数据:event_type, steps 等) + daemon_meta TEXT, -- JSON(daemon 元数据:event_type, steps 等) + issue_body_cache TEXT, -- webhook 触发时缓存的 Issue body + issue_updated_at TEXT, -- Gitea Issue 的 last updated 时间戳,用于缓存失效判断 retry_count INTEGER DEFAULT 0, dispatch_count INTEGER DEFAULT 0, max_retries INTEGER DEFAULT 2, @@ -152,6 +155,10 @@ CREATE TABLE task_index ( **注意**: 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 概念混淆。 + --- ## §4. 流程设计 @@ -239,14 +246,16 @@ webhook 可能丢失或延迟。ticker 保留原有逻辑,改为: 当前 spawner 从 SQLite 读 task description(微秒级)。改为从 Gitea API 读(毫秒级,HTTP 请求)。 - **方案 A**: 每次 spawn 时实时调 Gitea API。简单但慢 -- **方案 B**: webhook 触发时缓存 Issue body 到 task_index.must_haves 或单独字段。spawn 时从缓存读 -- **推荐**: 方案 B。webhook 触发时 daemon 已经有 Issue body(在 payload 中),直接缓存 +- **方案 B**: webhook 触发时缓存 Issue body 到 `task_index.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 对象新增 `issue_data` 字段(lazy load),首次访问 title/description 时从 Gitea API 或缓存加载 +- **方案 A(lazy load)**: Task 对象新增 `issue_data` 字段,首次访问 title/description 时从 Gitea API 或缓存加载 +- **方案 B(TaskAdapter,推荐 ✅)**: 新增 `TaskAdapter` 类,封装 task_index + Gitea Issue 数据的合并访问。调用方代码不用改,只改 adapter 内部实现。比 lazy load 字段更干净——合并逻辑集中在一处,而不是散落在 Task 对象的多个 property 中 - **影响**: 需要检查 dispatcher 中所有访问 task.title/task.description 的地方 **P3: agent 的 prompt 中引用黑板 API 的地方需要改** @@ -261,6 +270,7 @@ PromptSection 中有多处 `POST localhost:8083/api/projects/.../tasks/.../comme 当前 @mention 通过黑板 `mention_queue` 表排队。改造后 @mention 通过 Gitea Issue/PR comment(webhook 自然触发)。但 mention_queue 的消费逻辑(ticker 扫描 → 通知 → agent 处理)需要适配。 - **方案**: 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 处理逻辑需要适配 --- @@ -321,10 +331,11 @@ agent 需要知道工作方式变了。新增一个通用 section(或加入现 | 阶段 | 内容 | 依赖 | |------|------|------| -| Phase 1 | task_index 表创建 + webhook handler 适配(Issue assigned → 建索引) | 设计 Review 通过 | -| Phase 2 | dispatcher/ticker 数据源从 tasks 表切换到 task_index | Phase 1 | -| Phase 3 | spawner 读 Issue body 构建 prompt(替代读黑板 description) | Phase 2 | -| Phase 4 | prompt 改造(黑板 API → Gitea API) | Phase 3 | +| **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 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 | 每个 Phase 独立可验证,出问题可以回退。 @@ -335,8 +346,10 @@ agent 需要知道工作方式变了。新增一个通用 section(或加入现 | 风险 | 等级 | 缓解 | |------|------|------| -| Gitea API 不可用时 daemon 完全瘫痪 | 中 | webhook 触发时缓存 Issue body(P1 方案 B),减少运行时 Gitea API 依赖 | +| Gitea API 不可用时 daemon 完全瘫痪 | 中 | webhook 触发时缓存 Issue body + issue_updated_at 校验(P1 方案 B),减少运行时 Gitea API 依赖 | | Gitea webhook 丢失 | 低 | ticker 兜底扫描 | | task_index 和 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 | +| webhook 管理权限不足 | 中 | Phase 0 前置解决:升级 jiangwei-infra 为 repo admin 或主公手动配置 | From b0f4572ba6655fd5f471d81e83fc3d5ef5fe8bfa Mon Sep 17 00:00:00 2001 From: cfdaily Date: Sat, 20 Jun 2026 00:40:04 +0800 Subject: [PATCH 3/6] =?UTF-8?q?[moz]=20docs(=C2=A720):=20v2.0=20=E4=BF=AE?= =?UTF-8?q?=E8=AE=A2=E2=80=94=E2=80=94Repository=20=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E6=8D=A2=E5=BA=95=20+=20Review=20=E5=8F=8D=E9=A6=88=E6=95=B4?= =?UTF-8?q?=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 庞统修正: - 砍掉 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 --- docs/design/20-issue-centric-orchestration.md | 113 +++++++++++------- 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/docs/design/20-issue-centric-orchestration.md b/docs/design/20-issue-centric-orchestration.md index 87f81f6..733cc42 100644 --- a/docs/design/20-issue-centric-orchestration.md +++ b/docs/design/20-issue-centric-orchestration.md @@ -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 中的固定标记**识别,如 `` 或约定 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 | From ddc5eb9897626e064ed1718c712b04bd92fb8cf9 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Sat, 20 Jun 2026 00:43:49 +0800 Subject: [PATCH 4/6] =?UTF-8?q?[moz]=20docs(=C2=A720):=20v2.1=20fix=20M1?= =?UTF-8?q?=20dispatcher=20=E7=9B=B4=E6=8E=A5SQL=E5=A3=B0=E6=98=8E=20+=20M?= =?UTF-8?q?2=20Phase=E6=A0=BC=E5=BC=8F=20+=20S1/S2=20TaskAdapter=E6=AE=8B?= =?UTF-8?q?=E7=95=99=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/design/20-issue-centric-orchestration.md | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/design/20-issue-centric-orchestration.md b/docs/design/20-issue-centric-orchestration.md index 733cc42..ca5e4c6 100644 --- a/docs/design/20-issue-centric-orchestration.md +++ b/docs/design/20-issue-centric-orchestration.md @@ -1,9 +1,10 @@ --- title: "Issue-Centric Orchestration — Gitea Issue 替代黑板 DB 协作面" created: 2026-06-19 -version: v2.0 draft +version: v2.1 draft status: draft -changelog: v2.0 纳入姜维+司马懿 Review 反馈 + 庞统 Repository 模式修正 +changelog: v2.1 修正 M1(dispatcher 直接 SQL 声明)+ M2(Phase 格式)+ S1/S2(TaskAdapter 残留清理) +v2.0 纳入姜维+司马懿 Review 反馈 + 庞统 Repository 模式修正 v1.1 纳入姜维 Review 反馈 v1.0 初版 --- @@ -136,7 +137,7 @@ Gitea(协作介质,替代黑板 DB 协作面) ### 3.3 数据访问层改造:Repository 模式换底 -**设计原则**:不新建 task_state + TaskAdapter,而是改造现有 Repository(Queries/Blackboard 类)的实现。 +**设计原则**:改造现有 Repository(Queries/Blackboard 类)的实现,不新增中间层。 当前数据访问层已有 Repository 模式的基础——`Blackboard` 类和 `Queries` 类封装了所有数据访问,上层(dispatcher/spawner/ticker)通过方法调用(`queries.pending_dispatchable()`、`blackboard.get_task()`),不直接写 SQL。 @@ -150,7 +151,9 @@ Gitea(协作介质,替代黑板 DB 协作面) dispatcher → Queries(Gitea-backed) → Gitea API(协作数据)+ SQLite(执行状态) ``` -上层代码完全不用改。Queries 内部决定数据从哪来: +上层代码**基本**不用改。但有一个前置条件:dispatcher.py 中存在约 20 处直接操作 tasks 表的 SQL(绕过 Queries 类,如 `SELECT assignee FROM tasks`、`UPDATE tasks SET status=?`)。这些直接 SQL 需要先迁移到 Queries 方法调用,才能实现 Repository 换底。此项作为 Phase 1 的前置工作。 + +Queries 内部决定数据从哪来: | 数据类型 | 来源 | 方式 | |---------|------|------| @@ -178,7 +181,7 @@ CREATE TABLE task_state ( ); ``` -**和 task_state + TaskAdapter 的区别**:不新增表名(task_state 替代 task_state)、不新增 adapter 类。Repository 内部把 Gitea 数据 + 本地执行状态合并成 Task 对象返回。上层调用 `queries.get_task(issue_number)` 得到的 Task 对象和现在一模一样——有 title、有 description、有 status。 +**为什么不用 TaskAdapter**:Repository 内部已经把 Gitea 数据 + 本地执行状态合并成 Task 对象返回。上层调用 `queries.get_task(issue_number)` 得到的 Task 对象和现在一模一样——有 title、有 description、有 status。不需要额外 adapter 层。 --- @@ -273,13 +276,11 @@ webhook 可能丢失或延迟。ticker 保留原有逻辑,改为: - **缓存失效**: spawner 构建 prompt 时比对 `issue_updated_at` 和 Gitea API 的 Issue `updated_at`,不匹配才重新拉取(正常情况 Issue body 创建后不改,开销可忽略) - **推荐**: 方案 B + updated_at 校验 -**P2: dispatcher 的 `Task.from_row(row)` 返回的对象缺少字段** +**P2: Repository 内部数据合并** -当前 Task 对象有 30+ 个字段(title/description/priority/risk_level 等)。task_state 只存少量字段。dispatcher/dispatch 逻辑如果访问了 Task 对象的 title/description 字段,会取不到值。 +Queries 类内部改造——`get_task()` 方法从 Gitea API(或缓存)读 title/body,从 task_state 表读 status/retry_count,合并成 Task 对象返回。上层(dispatcher/spawner)无感知。 -- **方案 A(lazy load)**: Task 对象新增 `issue_data` 字段,首次访问 title/description 时从 Gitea API 或缓存加载 -- **方案 B(TaskAdapter,推荐 ✅)**: 新增 `TaskAdapter` 类,封装 task_state + Gitea Issue 数据的合并访问。调用方代码不用改,只改 adapter 内部实现。比 lazy load 字段更干净——合并逻辑集中在一处,而不是散落在 Task 对象的多个 property 中 -- **影响**: 需要检查 dispatcher 中所有访问 task.title/task.description 的地方 +这是标准的 Repository 模式——接口不变(`get_task(id)` 返回 Task 对象),实现换底(从 SQLite 单源改为 Gitea + SQLite 双源)。不新增 TaskAdapter 类——Queries 类本身就是 Repository,内部合并是职责内的事。 **P3: agent 的 prompt 中引用黑板 API 的地方需要改** @@ -355,12 +356,13 @@ agent 需要知道工作方式变了。新增一个通用 section(或加入现 | 阶段 | 内容 | 依赖 | |------|------|------| -| **Phase 0** | **前置:webhook 权限** — jiangwei-infra 升级为 repo admin,或主公手动配置 webhook | 设计 Review 通过 | -| 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 0 | webhook 权限配置(主公手动配)+ CI status webhook 验证(确认 Gitea CI status 变化触发 webhook) | 设计 Review 通过 | +| Phase 1 | dispatcher.py 直接 SQL(约 20 处)迁移到 Queries 方法调用 + task_state 表创建 + Queries/Blackboard 内部改造(Gitea + SQLite 双源) | Phase 0 | +| Phase 2 | dispatcher/ticker 数据源从 tasks 表切换到 task_state | 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 4 | prompt 改造(黑板 API → Gitea API)+ mention_queue 适配(action_report body 标记约定) | Phase 3 | | Phase 5 | 验证 + 清理废弃的黑板协作面表 | Phase 4 | 每个 Phase 独立可验证,出问题可以回退。 @@ -376,5 +378,3 @@ agent 需要知道工作方式变了。新增一个通用 section(或加入现 | 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 | -| webhook 管理权限不足 | 中 | Phase 0 前置解决:升级 jiangwei-infra 为 repo admin 或主公手动配置 | From 3a11327113dca72cafd345a85ca27e271c9d96fa Mon Sep 17 00:00:00 2001 From: cfdaily Date: Sat, 20 Jun 2026 00:45:31 +0800 Subject: [PATCH 5/6] =?UTF-8?q?[moz]=20docs(=C2=A720):=20v2.2=20=E7=BA=B3?= =?UTF-8?q?=E5=85=A5=E5=A7=9C=E7=BB=B4=20v2.0=20Review=20S3=EF=BC=88must?= =?UTF-8?q?=5Fhaves=20=E6=94=B9=E5=90=8D=E5=BD=B1=E5=93=8D=E9=9D=A2?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/design/20-issue-centric-orchestration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/20-issue-centric-orchestration.md b/docs/design/20-issue-centric-orchestration.md index ca5e4c6..86e10af 100644 --- a/docs/design/20-issue-centric-orchestration.md +++ b/docs/design/20-issue-centric-orchestration.md @@ -359,7 +359,7 @@ agent 需要知道工作方式变了。新增一个通用 section(或加入现 | 阶段 | 内容 | 依赖 | |------|------|------| | Phase 0 | webhook 权限配置(主公手动配)+ CI status webhook 验证(确认 Gitea CI status 变化触发 webhook) | 设计 Review 通过 | -| Phase 1 | dispatcher.py 直接 SQL(约 20 处)迁移到 Queries 方法调用 + task_state 表创建 + Queries/Blackboard 内部改造(Gitea + SQLite 双源) | Phase 0 | +| Phase 1 | dispatcher.py 直接 SQL(约 20 处)迁移到 Queries 方法调用 + task_state 表创建 + Queries/Blackboard 内部改造(Gitea + SQLite 双源)+ must_haves→daemon_meta 重命名(17 文件 80 处,机械替换,需 CI 覆盖) | Phase 0 | | Phase 2 | dispatcher/ticker 数据源从 tasks 表切换到 task_state | Phase 1 | | Phase 3 | spawner 读 Issue body 构建 prompt(替代读黑板 description)+ issue_updated_at 缓存失效机制 | Phase 2 | | Phase 4 | prompt 改造(黑板 API → Gitea API)+ mention_queue 适配(action_report body 标记约定) | Phase 3 | From f4fea8f418194c68f05ef440a320183c521a60ce Mon Sep 17 00:00:00 2001 From: cfdaily Date: Sat, 20 Jun 2026 07:44:37 +0800 Subject: [PATCH 6/6] =?UTF-8?q?[moz]=20fix(ci):=20pip=20install=20?= =?UTF-8?q?=E5=8A=A0=20no=5Fproxy=3D*=20=E7=BB=95=E8=BF=87=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E4=BB=A3=E7=90=86=E7=99=BD=E5=90=8D=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI runner 继承了系统 Wi-Fi 代理(127.0.0.1:7890),代理是白名单机制, pip install 走代理被拒绝。加 env no_proxy=* 让 pip 直连。 --- .gitea/workflows/ci.yml | 4 ++++ .gitea/workflows/deploy.yml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 916d31f..a7ed798 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -25,6 +25,8 @@ jobs: - uses: actions/checkout@v4 - name: Setup Python + env: + no_proxy: "*" run: | rm -rf /tmp/ci-venv-lint python3 -m venv /tmp/ci-venv-lint @@ -43,6 +45,8 @@ jobs: - uses: actions/checkout@v4 - name: Setup Python + env: + no_proxy: "*" run: | rm -rf /tmp/ci-venv-test python3 -m venv /tmp/ci-venv-test diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index f113871..d0b1e77 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -22,6 +22,8 @@ jobs: - uses: actions/checkout@v4 - name: Setup Python + env: + no_proxy: "*" run: | python3 -m venv /tmp/ci-venv-deploy /tmp/ci-venv-deploy/bin/pip install --quiet flake8 fastapi pydantic pyyaml uvicorn requests pytest pytest-asyncio httpx