Files
sanguo_moziplus_v2/docs/design/22-end-to-end-flow.md
T

712 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "End-to-End Flow — 端到端任务流程设计"
created: 2026-06-22
version: v1.5
status: draft
changelog: v1.5 采纳司马懿 S1-S4+P1-P3:判断逻辑补 infrastructure 分支、flow/discuss deprecated、定向讨论 fallback、task_state.status 完整生命周期、parent_status 查表不硬编码
v1.4 §22.6/§22.8 设计补足:基础设施排除、定向讨论简化为单spawn自主模式、迁移策略、TC数据源/round_count分流通透
v1.3 §22.6 重构为「所有 TC 流程第一步走 Discuss」+ §22.8 TC Round Review Gitea 适配设计
v1.2 §22.3/§22.4 更新 Phase 1 为 ✅ 已实现(PR #124
v1.1 补充轻量路径设计(§22.6)、数据流澄清(§22.7)、修正设计原则3、统一Phase编号、Phase 1标注
v1.0 初版
---
# End-to-End Flow — 端到端任务流程设计
> 本文档描述一个任务从发起到结束的**完整系统行为链路**。
> §21 各章节按功能点分章,本文档把它们串成一条端到端流程。
> 每个 Phase 标注:触发源、daemon 函数、prompt 来源、agent 行为、留痕位置。
---
## §22.1 流程总览
```
Phase 0: 庞统创建 parent Issue(无 assignee, 有 type/* label
↓ webhook: issues/opened
Phase 1: Discussion 广播(所有空闲 agent
↓ agent 在 Gitea Issue comment 讨论 → 创建 sub Issueassign 自己)
Phase 2: sub Issue assigned → executor 分派
↓ webhook: issues/assigned
Phase 3: 编码 + PR + CI
↓ webhook: pull_request/opened → CI 自动触发
↓ CI 通过 → 等 Review / CI 失败 → agent 修复 → 重跑
Phase 4: Review(司马懿)
↓ APPROVED → 通知 agent 合并 / REQUEST_CHANGES → agent 修改 → 回 Phase 3
Phase 5: Merge + sub Issue 自动关闭
↓ webhook: pull_request/closed(merged)
Phase 6: Round Review(庞统三问)
↓ GOAL_ACHIEVED → 关闭 parent / 需要新轮 → 创建新 sub → 回 Phase 2
Phase 7: parent Issue 关闭
↓ webhook: issues/closed
```
**核心设计原则**
1. **协作面是 Gitea**Issue/PR comment),不是黑板 DB
2. **每个 Phase 有明确的前置条件和产出**,前一步未完成则后续不触发
3. **webhook 和 ticker 协同**——webhook 负责事件感知和 task 创建,ticker 负责广播调度(Phase 1)和聚合检测(Phase 6
4. **agent 在 Gitea 留痕**Issue comment、PR、Review),黑板 DB 只存 daemon 内部状态
---
## §22.2 阶段详解
### Phase 0: parent Issue 创建
| 维度 | 内容 |
|------|------|
| **触发** | 庞统(或用户)在 Gitea 创建 parent Issue |
| **条件** | 无 assignee + 有 `type/*` label |
| **daemon 函数** | `_handle_issues``toolchain_routes.py`),opened 分支 |
| **daemon 行为** | 检测无 assignee + 有 type/* label → 创建 toolchain task`assignee=None`, `action_type=issue_discussion`)写入 `_toolchain` DB |
| **agent 行为** | 无(此阶段不 spawn agent |
| **产出** | `_toolchain` DB 中一条 pending task |
**webhook 流转**
```
Gitea: Issue created (no assignee, label type/feat)
→ webhook: issues/opened
→ daemon: _handle_issues → action="opened"
→ 非部署失败 + 无 assignee + 有 type/* label
→ _send_toolchain_task(to_agent=None, action_type="issue_discussion")
```
---
### Phase 1: Discussion 广播
| 维度 | 内容 |
|------|------|
| **触发** | ticker 30s 扫到 Phase 0 创建的 pending task |
| **daemon 函数** | `ticker._dispatch_pending``dispatcher.decide``_broadcast_claim`。action_type=issue_discussion 时调用 `_build_discussion_prompt`Gitea API),否则用 `_build_claim_prompt`(黑板 API |
| **daemon 行为** | assignee=None → router 返回 mode=delegate → ticker 归入 broadcast_tasks → 广播给所有空闲 agent |
| **prompt 来源(设计期望)** | `discussion prompt`(§13.2):你是谁 + 你必须做什么(4 维度)+ Gitea API + Boids 行为准则 |
| **agent 行为(设计期望)** | 每个 agent 在 **Gitea Issue** comment(角色名开头,4 维度回应);需要参与的 agent 创建 sub IssueGitea APIassign 自己);在 parent Issue comment 注册 sub |
| **产出** | parent Issue 上有所有 agent 的讨论 commentGitea 上有若干 sub Issue |
**discussion prompt 核心(§13.2**
```
你被 spawn 来参与 Gitea Issue 讨论。
## 讨论主题
{parent Issue body 全文}
## 你是谁
你是 {agent_id}{display_name}),角色是 {role},能力是 {capabilities}
## 你必须做什么(每个 agent 必须 comment
1.【定位】这个需求和你有什么关系?
2.【建议】你对实现方案有什么建议?
3.【认领】如果你需要参与,创建 sub Issue 并在 parent comment 注册:
POST /repos/{repo}/issues
title: "[moz][sub][parent #{N}] 任务名"
body: "Parent: #{N}\nDepends: #M\n## 任务\n..."
assignees: ["{你的 agent_id}"]
4.【风险】如果发现风险或不合理假设,直接提出
## Comment 格式
[角色名] 你的观点
## APIGitea
- 读 Issue: GET /repos/{repo}/issues/{N}
- 写 comment: POST /repos/{repo}/issues/{N}/comments
- 创建 sub Issue: POST /repos/{repo}/issues
```
**Phase 1 的结束条件**
- 所有被广播的 agent 都已 comment(或 NO_REPLY
- 至少有一个 agent 创建了 sub Issue
- 如果没有任何 agent 创建 sub Issue → ticker 升级庞统(3 轮无 taker 机制)
---
### Phase 2: sub Issue assigned → executor 分派
| 维度 | 内容 |
|------|------|
| **触发** | agent 在 Phase 1 创建 sub Issue 时 assign 自己 → Gitea 发 `issues/assigned` webhook |
| **daemon 函数** | `_handle_issues``toolchain_routes.py`),assigned 分支 |
| **daemon 行为** | 解析 type/* label → 确定 business_type → 从 `toolchain-templates.yaml` 获取 steps → 创建 toolchain task`action_type=issue_assigned`)→ ticker dispatch |
| **prompt 来源** | `ToolchainHandler.build_prompt`(通过 PromptComposer 拼装 sections |
| **agent 行为** | 建分支 `{type}/{sub_issue_number}-{brief}` → 编码 → 写测试 → 创建 PR(body 含 `Closes #N` + `Parent #M` |
| **产出** | Gitea 上有 PR + 分支有代码 |
**steps 来源(`config/toolchain-templates.yaml`**
```yaml
issue_assigned:
feat:
steps:
- "理解需求(Issue body"
- "git checkout -b feat/{issue_number}-{brief}"
- "编码 + 写 UT"
- "文档同步检查"
- "git push + 创建 PRbody 含 Closes #{issue_number}"
- "等 Review"
```
---
### Phase 3: 编码 + PR + CI
| 维度 | 内容 |
|------|------|
| **触发** | agent 创建 PR → webhook `pull_request/opened` → CI 自动触发 |
| **daemon 函数(PR opened** | `_handle_pull_request` → 创建 `review_request` task → 通知司马懿 |
| **daemon 函数(CI** | CI 结果通过 Gitea comment 反馈 → `_handle_issue_comment` CI 路径 |
| **CI 通过** | 等待 ReviewPhase 5 |
| **CI 失败** | 创建 `ci_failure` task → 通知 agent 修复 |
| **agent promptCI 失败时)** | `ToolchainHandler.build_prompt`action_type=ci_failuresteps 含 CI 日志查看 + 根因判断 + 修复 |
| **产出** | CI 通过的 PR |
---
### Phase 4: Review(司马懿)
| 维度 | 内容 |
|------|------|
| **触发** | `review_request` task 被 ticker dispatch 给司马懿 |
| **daemon 函数** | ticker spawn 司马懿 → 司马懿通过 Review API 提交 verdict → webhook `pull_request_review``_handle_pull_request_review` |
| **prompt 来源** | `ToolchainHandler.build_prompt`action_type=review_request |
| **Review APPROVED** | 创建 `review_result_approved` task → 通知 agent 合并 |
| **Review REQUEST_CHANGES** | 创建 `review_result_request_changes` task → 通知 agent 修改 → agent push 同分支 → CI 重跑 → 回 Phase 3 |
| **产出** | Gitea Review 记录 |
---
### Phase 5: Merge + sub Issue 关闭
| 维度 | 内容 |
|------|------|
| **触发** | agent merge PR |
| **daemon 函数** | Gitea 自动关闭 sub IssuePR body 含 `Closes #N`)→ webhook `pull_request/closed(merged)``_handle_pull_request` closed 分支 |
| **daemon 行为** | 创建 `review_merged` task → 通知 agent(纯通知);ToolchainHandler verify: auto-pass |
| **产出** | sub Issue closed + 分支自动删除 |
---
### Phase 6: Round Review(庞统三问)
| 维度 | 内容 |
|------|------|
| **触发** | daemon 检测 parent Issue 下所有 sub Issue 终态(done/closed |
| **daemon 函数** | `ticker._check_round_complete` 扫描 parent/sub 映射 → 所有 sub 终态 → spawn 庞统 |
| **prompt 来源** | `ticker._build_review_prompt`(三问框架) |
| **agent 行为** | 庞统通过 Gitea API 读 parent Issue body + 所有 sub Issue comments/outputs → 三问评估 |
| **GOAL_ACHIEVED** | 庞统关闭 parent Issue → Phase 7 |
| **需要新轮** | 庞统创建新 sub Issues → 回 Phase 2 |
| **产出** | parent Issue closed 或新一轮 sub Issues |
**三问框架**
```
1. Goal 还清晰吗?(是否有 goal drift)
2. 成果物覆盖 goal 了吗?(逐条检查验收标准 + docs/design 同步确认)
3. 下一轮需要做什么?(创建新 sub / 标记完成 / 调整方向)
```
---
### Phase 7: parent Issue 关闭
| 维度 | 内容 |
|------|------|
| **触发** | 庞统关闭 parent Issue |
| **daemon 函数** | webhook `issues/closed``_handle_issues` closed 分支 |
| **daemon 行为** | 创建 `issue_closed` task → 通知(纯通知,auto-pass |
| **产出** | parent Issue closed,全流程结束 |
---
## §22.3 Prompt 模板对照表
| Phase | prompt 用途 | 设计指定的模板来源 | 当前实际来源 | 一致? |
|-------|-----------|----------------|------------|-------|
| 1 Discussion | 广播讨论 | discussion prompt(§13.2Gitea API | discussion prompt`_build_discussion_prompt`Gitea API | ✅ |
| 2 Executor | 编码执行 | `ToolchainHandler.build_prompt` + YAML steps | 同设计 | ✅ |
| 3 CI 失败 | 修复 CI | `ToolchainHandler.build_prompt` ci_failure | 同设计 | ✅ |
| 4 Review | 审查 PR | `ToolchainHandler.build_prompt` review_request | 同设计 | ✅ |
| 5 Merge | 合并通知 | `ToolchainHandler.build_prompt` review_merged | 同设计 | ✅ |
| 6 Round Review | 庞统三问 | `ticker._build_review_prompt` | 同设计 | ✅ |
| 7 Issue closed | 关闭通知 | `ToolchainHandler.build_prompt` issue_closed | 同设计 | ✅ |
**唯一偏差已修复**PR #124 将 ticker broadcast 改为根据 action_type 选择 discussion promptGitea API)或 claim prompt(黑板 API)。
---
## §22.4 当前实现差距
| Phase | 实现状态 | 差距描述 |
|-------|---------|---------|
| 0 parent Issue 创建 | ✅ 已实现 | PR #113 `_handle_issues` opened 分支,无 assignee + type/* label → toolchain task |
| 1 Discussion 广播 | ✅ **已实现** | PR #124 修复:ticker `_broadcast_claim` 判断 `action_type=issue_discussion` → 调用 `_build_discussion_prompt`Gitea API)。`_build_discussion_prompt``must_haves.context` 解析 `repo` / `issue_number` 注入模板 |
| 2 sub Issue → executor | ✅ 已实现 | assigned 路径 + YAML steps 已实现(PR #107),Phase 1 修复后 agent 会创建 sub Issue → 走到此阶段 |
| 3 PR + CI | ✅ 已实现 | toolchain handler 正常处理 PR opened + CI 失败 |
| 4 Review | ✅ 已实现 | Review 请求 + Review 结果通知正常 |
| 5 Merge + sub 关闭 | ✅ 已实现 | merge 通知正常。executor promptYAML steps)中已包含 `Closes #{issue_number}` |
| 6 Round Review | ✅ **已实现** | `_check_round_complete` 支持双源扫描:黑板 `tasks.parent_task` + toolchain `task_state.parent_issue` |
| 7 parent Issue 关闭 | ✅ 已实现 | PR #113 issue_closed auto-pass |
---
## §22.5 差距优先级排序
| 优先级 | 差距 | 影响范围 | 修复建议 |
|--------|------|---------|---------|
| **P0** | Phase 1 discussion broadcast | ✅ **已完成**PR #124 | ~~核心断裂~~ 已修复:ticker 判断 action_type=issue_discussion → discussion prompt |
| **P1** | Phase 5 PR body Closes #N | ✅ **已完成** | YAML steps 已包含 Closes #NP0 修复后自然走通 |
| **P2** | Phase 6 Round Review Gitea 适配 | ✅ **已完成** | `_check_round_complete` 双源扫描 task_state.parent_issue + tasks.parent_task |
**关键结论**P0/P1/P2 全部完成。§22 端到端流程设计已全部实现。
---
## §22.6 轻量路径设计(Direct Assignment
> **核心原则:所有 TC 流程第一步都走 Discuss(基础设施和 flow/direct 除外)。**
> Discussion 不是可选项,而是 TC 流程的必经阶段——确保方案对齐、风险暴露后再进入 exec。
> 区别只在于「谁参与讨论」。
### 路径决策矩阵
| 条件 | 讨论范围 | 流程 |
|------|---------|------|
| Issue 无 assignee + type/* label | **广播所有空闲 agent** | Phase 0 → 1(广播讨论)→ 2 → … |
| Issue 有 assignee(非 infrastructure | **assignee 自主定向讨论** | assignee 写方案 → @司马懿 review → 创建 sub Issue 进 exec |
| Issue 有 `type/infrastructure` label | **跳过讨论** | → executor(运维排障,无需方案审查) |
| Issue 有 `flow/direct` label | **跳过讨论** | → Phase 2(极小改动逃生舱) |
**设计原则**
1. 基础设施 Issue`type/infrastructure`)→ 直接到 executor(运维排障任务,不需要写方案+审查)
2. `flow/direct` label → 直接到 executor(明确不需要讨论的小改动)
3. 有 assignee(非 infrastructure)→ assignee 自主走「方案→review→exec」流程,daemon 不编排
4. 没有 assignee → 广播所有 agent 讨论,有 agent 认领后创建 sub Issue
> **v1.4 补充**v1.3 原设计将 infrastructure 也纳入 Discuss,但基础设施排障任务(Gitea 挂了、磁盘满等)不需要写实现方案和司马懿审查,应排除。
### 判断逻辑
```
parent Issue 创建
├─ 有 flow/direct label?→ Direct 路径(→ Phase 2,跳过讨论)
├─ 有 type/infrastructure label?→ Direct 路径(→ executor,运维排障)
├─ 有 assignee(非 infrastructure)?→ 定向 Discussionassignee 自主模式)
└─ 无 assignee(默认)→ 广播 Discussion(所有空闲 agent
```
### 定向讨论流程(有 assignee,非 infrastructure
> **v1.4 修正**v1.3 原设计「ticker 同时 spawn assignee + 司马懿」过于复杂,需要 daemon 协调两个 agent 生命周期。
> v1.4 简化为**单 spawn 自主模式**——daemon 只创建 discussion task 给 assigneeassignee 在一个 session 内自主完成全部流程。
```
Issue assigned webhook
→ daemon 创建 issue_discussion taskassignee=该 agentcontext_data 带 issue/repo
→ ticker dispatch:有 assignee → 确定性路由 spawn assignee 单人
→ discussion prompt 引导 assignee
1. 读 Issue 全文,在 Gitea Issue comment 写实现方案(技术选型、实现路径、影响范围)
2. @simayi-challenger 请求方案审查(利用现有 @mention 机制自动创建 review task
3. 等司马懿 review 结果(通过 @mention 回传)
4. review 通过 → 创建 sub Issueassign 自己)→ 进入 exec
5. review 驳回 → 修改方案重新提交(重新 comment + @司马懿)
```
**为什么不双 spawn**
1. assignee 写方案时间不确定,双 spawn 后司马懿可能干等
2. 两阶段串行更简单,assignee 完成后 @mention 自动触发司马懿
3. 和广播讨论一致——daemon 只创建初始 task,后续靠 agent 自主 + @mention
**与广播讨论的区别**
- 广播讨论:assignee=None → ticker 归入 broadcast_tasks → 广播所有空闲 agent
- 定向讨论:assignee=该 agent → ticker 归入 deterministic_tasks → 确定性路由 spawn 单人
### 定向讨论 fallbackassignee 无响应)
> **v1.4 补充(司马懿 S4)**:广播讨论有「3 轮无 taker → 升级庞统」机制,定向讨论也需要 fallback。
单 spawn assignee 后,如果 assignee NO_REPLY 或 session 超时:
```
ticker 定期 check(复用现有 broadcast _broadcast_tracker 机制)
→ discussion task 仍 pending/working 且超过 3 轮 check 未终态
→ 升级庞统(escalated 状态)
→ 庞统判断:重新分配 / 转为广播讨论 / 直接关闭
```
复用现有 `_broadcast_tracker``round_number >= 3 → escalated` 逻辑,不引入新机制。
### 广播讨论流程(无 assignee
```
Issue opened webhook
→ daemon 创建 issue_discussion taskassignee=None
→ ticker broadcast:广播所有空闲 agent
→ discussion prompt 引导每个 agent
1. 在 Gitea Issue comment 回应(定位/建议/认领/风险)
2. 需要参与的 agent 创建 sub Issue assign 自己
```
### 适用场景
| 路径 | 适用场景 | 示例 |
|------|---------|------|
| 广播讨论 | 需求不明确、跨模块协作、涉及 3+ agent、无人认领 | 新功能设计、架构变更 |
| 定向讨论 | 需求明确但需要方案确认 | 有明确 assignee 的 Issue |
| Direct | 改 typo、改配置值等极小改动 | `flow/direct` label |
### Discussion 路径中的降级
Discussion 进行中,如果所有 agent 都认为任务足够简单只涉及一个角色,任何 agent 可以在 comment 中建议:
```
@pangtong-fujunshi 建议直接指派 @agent-id,理由:...
```
庞统判断后创建 sub Issue 直接 assign → 该 agent 跳过讨论直接进入 Phase 2。
### 迁移策略
> **v1.4 补充**v1.3 未说明现有 `issue_assigned` 路径如何处理。
**保留 `issue_assigned` 给 infrastructure / flow-direct,新增定向讨论路径**
| 条件 | 旧路径(v1.2) | 新路径(v1.4) | 变化 |
|------|----------------|----------------|------|
| 有 assignee + 非 infrastructure | issue_assigned → executor | issue_discussion → discussion(定向) | **改** |
| 有 assignee + `type/infrastructure` | issue_assigned → executor | issue_assigned → executor(不变) | 无 |
| 有 `flow/direct` label | issue_assigned → executor | issue_assigned → executor(不变) | 无 |
| 无 assignee + type/* label | issue_discussion → broadcast | issue_discussion → broadcast(不变) | 无 |
`_ACTION_HINTS` / YAML steps / `ToolchainContextSection` 中的 `issue_assigned` 相关逻辑**保留**infrastructure/flow-direct 还用),新增 `issue_review_discussion` action_type。
> **`flow/discuss` label deprecated**v1.2 曾设计 `flow/discuss` label(强制 Discussion 即使有 assignee),v1.4 废弃。所有有 assignee 的 Issue(非 infrastructure)默认走定向讨论,无需显式 label。Gitea 上 `flow/discuss` labelid=101)保留但不影响路由。
### 实现方案
**toolchain_routes.py**
- `opened` 分支(无 assignee)→ discussion taskassignee=None)— 现有逻辑 ✅
- `assigned` 分支改造:
- `is_infrastructure=True` → executor task(现有逻辑保留 ✅)
- `has_flow_direct=True` → executor task(现有逻辑保留 ✅)
- **其他** → discussion taskassignee=该 agentcontext_data 带 issue/repo)— **需要改动**
**ticker.py**
- 定向讨论 task 有 assignee → `_dispatch_pending` 确定性路由直接 spawn assignee 单人
- 广播讨论 task 无 assignee → `_broadcast_claim` 广播所有空闲 agent(现有逻辑 ✅)
- `_broadcast_claim` 中的 action_type 判断逻辑保留(discussion task 走 `_build_discussion_prompt`
**讨论 prompt 差异化**
- 定向讨论 prompt 额外指引:你是 assignee,需要写方案并主动 @司马懿 review
- 广播讨论 prompt 保持现有模板(4 维度回应 + 自主认领)
**不影响**mail / task 流程(它们不走 toolchain_routes.py
---
## §22.7 数据流设计澄清
> 本文档涉及两个数据源(黑板 DB 和 Gitea Issue),parent/sub 映射的数据流必须明确。
### 当前状态 vs 设计目标
| 数据 | 当前存储 | §20/§21 设计目标 | 状态 |
|------|---------|-----------------|------|
| 协作面(title/body/comment | Gitea Issue/PRPhase 1 已修复) | Gitea Issue/PR | ✅ |
| parent/sub 映射 | `task_state.parent_issue`(已实现) + `tasks.parent_task`(兼容) | `task_state.parent_issue`(新表) | ✅ |
| 执行状态 | toolchain DB tasks.status + task_state.status | task_state.status | ✅ |
| 成果物 | git commit + PR | git commit + PR | ✅ |
### parent/sub Issue 映射数据流(设计)
```
Agent 在 Phase 1 创建 sub Issue:
POST /repos/{repo}/issues
title: "[moz][sub][parent #{N}] 任务名"
assignees: ["{agent_id}"]
Gitea webhook: issues/opened (with assignee)
daemon: _handle_issues → assigned 分支
→ 解析标题 [parent #{N}] → 提取 parent_issue_number
→ 写入 task_state (issue_number, parent_issue=N, status='pending')
ticker: _check_round_complete
→ SELECT DISTINCT parent_issue FROM task_state WHERE parent_issue IS NOT NULL
→ 对每个 parent_issue: SELECT status FROM task_state WHERE parent_issue=?
→ 全部终态 → spawn 庞统 review
```
### 前置条件(已全部实现 ✅)
1. **task_state 表创建**:已在 PR #125 中实现(`_init_task_state_table`CREATE IF NOT EXISTS)。
2. **parent_issue 解析**:已实现(`_ensure_task_state``_handle_issues` assigned 分支解析 `[parent #N]`)。
3. **_check_round_complete 双源扫描**:已实现(`task_state.parent_issue` + `tasks.parent_task`)。
### 混合期处理
task_state 表创建前,`_check_round_complete` 仍扫 `tasks.parent_task`。两种数据可以共存:
- **黑板路径**Phase 1 断裂时):agent 通过黑板 API 认领 → `tasks.parent_task` 有值
- **Gitea 路径**Phase 1 修复后):agent 创建 sub Issue → `task_state.parent_issue` 有值
`_check_round_complete` 需要同时扫两个源,直到黑板路径完全废弃。
### 各 Phase 数据读写汇总
| Phase | daemon 写 | daemon 读 | agent 读 | agent 写 |
|-------|----------|----------|----------|----------|
| 0 | toolchain DB: task(pending, action_type=issue_discussion) | Gitea webhook payload | — | — |
| 1 | toolchain DB: broadcast log | toolchain DB: pending tasks | Gitea Issue body | Gitea Issue comment + sub Issue |
| 2 | toolchain DB: task(pending, action_type=issue_assigned) | Gitea webhook payload, toolchain-templates.yaml | Gitea Issue body | git branch + PR |
| 3 | toolchain DB: task(review_request/ci_failure) | Gitea webhook (PR opened, CI status) | Gitea PR + CI logs | git push (修复) |
| 4 | toolchain DB: task(review_result_*) | Gitea webhook (pull_request_review) | Gitea PR diff + files | Gitea Review API |
| 5 | toolchain DB: task(review_merged) | Gitea webhook (PR closed/merged) | — | — |
| 6 | task_state: round_count++ | task_state: parent_issue + sub status | Gitea parent Issue body + sub Issue comments | Gitea Issue comment(三问结论)+ 关闭/创建 Issue |
| 7 | toolchain DB: task(issue_closed) | Gitea webhook (issues/closed) | — | — |
---
## §22.8 TC Round Review Gitea 适配
> **设计原则:三个流程已通过 Handler 架构分离(§14)。TC Round Review 的改造通过 ToolchainHandler 分流,不影响黑板流程。**
### 问题(v1.4 修正:不只是 prompt 不同,数据查询链路也不同)
`_check_round_complete` 中调用 `bb.get_subtasks_summary(parent_id)` / `bb.get_aggregate_outputs()` / `bb.get_round_comments()` 全部查黑板 DB。但 TC 的子任务信息在 `task_state` 表 + Gitea Issue/PR**根本不在黑板 tasks/outputs/comments 表**。
因此 handler 分流不只是 prompt 层面,需要贯穿**数据查询 → prompt 构建 → round_count 递增**三个环节。
### TC 数据源 vs 黑板数据源对照
| 环节 | 黑板路径 | TC 路径 |
|------|---------|---------|
| 子任务状态 | `bb.get_subtasks_summary()` → tasks 表 | 新增 `get_tc_subtask_summary()` → task_state 表 |
| 成果物 | `bb.get_aggregate_outputs()` → outputs 表 | 庞统自己读 Gitea APIPR/commit |
| 讨论历史 | `bb.get_round_comments()` → comments 表 | 庞统自己读 Gitea Issue comments |
| round_count | `bb.increment_round_count()` → tasks.round_count | `UPDATE task_state SET round_count+1` |
| parent status | tasks.status | task_state.status(需补充更新逻辑) |
### task_state.status 更新机制
> **v1.4 补充(司马懿 S3**:当前 `_ensure_task_state` 只做 `INSERT OR IGNORE`status 初始为 `pending`,后续没有任何代码更新。需补充。
**状态生命周期**
| 状态 | 触发来源 | 更新位置 |
|------|---------|----------|
| `pending` | `_ensure_task_state` 初始插入 | `toolchain_routes.py` assigned 分支 |
| `done` | Gitea Issue closed webhook | `toolchain_routes.py` closed 分支 |
| `failed` | executor task 验证失败且未重试 | `toolchain_handler.py` `post_complete``_update_task_state_status()` |
**关键路径说明**
1. **sub Issue 创建**`_ensure_task_state` 写入 `status='pending'`
2. **sub Issue 对应的 executor task 完成** → ToolchainHandler 通过 @mention 指引 agent 创建 PR(含 `Closes #N`)→ PR 合并时 Gitea 自动关闭 Issue → webhook `issues/closed` → 更新 `status='done'`
3. **executor task 失败**`ToolchainHandler.post_complete()` 中检测 verify 失败且 retry_count 达上限 → 调用 `_update_task_state_status(issue_number, 'failed')`
**`post_complete` 何时触发**spawner 完成 session 后(agent exit),ticker 在下个 tick 检测到 session 终态 → 调用 `handler.post_complete(task_id, agent_id, outcome, db_path)`。此时 handler 可以:
- 检查 verify 结果
- 如果 failed 且不可 retry → 查 task_state 找到对应 issue_number → 更新 status='failed'
**新增方法**
```python
# toolchain_handler.py
def _update_task_state_status(self, issue_number: int, status: str):
"""更新 task_state.statusexecutor 失败时调用)"""
tc_db = _toolchain_db_path()
conn = get_connection(tc_db)
try:
conn.execute(
"UPDATE task_state SET status=?, updated_at=datetime('now') "
"WHERE issue_number=?",
(status, issue_number)
)
conn.commit()
finally:
conn.close()
```
**toolchain_routes.py closed 分支补充**
```python
elif action == "closed":
# 更新 task_state status='done'
tc_db = _toolchain_db_path()
conn = get_connection(tc_db)
try:
conn.execute(
"UPDATE task_state SET status='done', updated_at=datetime('now') "
"WHERE issue_number=?",
(issue_number,)
)
conn.commit()
finally:
conn.close()
# ... 现有通知逻辑
```
> **注意**`get_tc_subtask_summary` 中的 `all_terminal` 只依赖 `done` 和 `failed` 两种终态。`pending`/`working` 计入 `other`,确保未完成的 sub 不会误触发 round review。
### 分流方案(全链路)
```
_check_round_complete:
handler = TaskTypeRegistry.get_by_project(project_id)
is_tc = handler and handler.virtual_project == "_toolchain"
# 1. 数据查询分流
if is_tc and parent_id in task_state_parents:
summary = get_tc_subtask_summary(tc_db, int(parent_id))
# 不查 outputs/comments — 庞统 spawn 后自己读 Gitea API
else:
summary = bb.get_subtasks_summary(parent_id)
outputs = bb.get_aggregate_outputs(parent_id)
comments = bb.get_round_comments(parent_id)
# 2. prompt 构建分流
if is_tc:
review_prompt = handler.build_round_review_prompt(parent_id, summary, ...)
else:
review_prompt = self._build_review_prompt(parent_task, summary, outputs, comments, ...)
# 3. round_count 递增分流
if spawned:
if is_tc:
UPDATE task_state SET round_count = round_count + 1 WHERE issue_number = ?
else:
bb.increment_round_count(parent_id)
```
### BaseTaskHandler 接口扩展
`BaseTaskHandler``task_type_registry.py`)中新增默认方法:
```python
def build_round_review_prompt(self, parent_id: str, summary: dict,
outputs: list, comments: list,
round_num: int, **kwargs) -> str:
"""构建 Round Review prompt。默认 raise NotImplementedError。"""
raise NotImplementedError(
f"{self.task_type} handler does not support round review")
```
普通 task 和 mail 不调用此方法(走 ticker 原有 `_build_review_prompt`),只有 ToolchainHandler override。
### ToolchainHandler.build_round_review_prompt
TC 版本的 Round Review prompt。核心区别:
| 维度 | 黑板版(原) | Gitea 版(新) |
|------|-----------|-------------|
| Goal 来源 | `parent_task.description`(黑板 DB | parent Issue body(庞统自己读 Gitea API |
| 成果物 | `bb.get_aggregate_outputs()`(黑板 DB | PR / commit(庞统自己读 Gitea API |
| 讨论历史 | `bb.get_round_comments()`(黑板 DB | Issue comments(庞统自己读 Gitea API |
| 创建新 sub | `POST localhost:8083/api/.../tasks`(黑板 API | `POST Gitea /repos/.../issues`Gitea API |
| 关闭 parent | 更新黑板 status | `PATCH Gitea /repos/.../issues/{N}` |
**prompt 设计**:只给 Issue 编号和 repo,让庞统 spawn 后自己调 Gitea API 读详情。daemon 不做 Gitea API 调用。
```
## 庞统 TC Round Review(第 {round_num} 轮)
### Parent Issue
- Repo: {repo}
- Issue: #{parent_issue_number}
### 本轮 Sub Issue 状态
- 完成: {done}
- 失败: {failed}
- 总计: {total}
### 你必须做什么
1. 读 parent Issue: GET /repos/{repo}/issues/{parent_issue_number}
2. 读所有 sub Issue 的 comments 和 PR
3. 三问评估:
- Goal 还清晰吗?
- 成果物覆盖 goal 了吗?(逐条检查 + docs/design 同步)
- 下一轮需要做什么?
4. 决策:
- GOAL_ACHIEVED → 关闭 parent Issue
- 需要新轮 → 创建新 sub Issues
### Gitea API
- 读 Issue: GET /repos/{repo}/issues/{N}
- 读 comments: GET /repos/{repo}/issues/{N}/comments
- 创建 sub Issue: POST /repos/{repo}/issues
- 关闭 Issue: PATCH /repos/{repo}/issues/{N} (state=closed)
```
### 不影响黑板流程的保证
1. **Handler 分流**`_check_round_complete` 通过 `TaskTypeRegistry.get_by_project(project_id)` 判断。普通项目(无 handler)走 `_build_review_prompt`(黑板版本不变)
2. **数据隔离**TC review 读 `task_state` 表 + Gitea API;黑板 review 读 `tasks` 表 + `outputs` / `comments`
3. **prompt 隔离**TC prompt 只含 Gitea API 指引;黑板 prompt 只含黑板 API 指引
4. **Handler 分流模式已在 `_dispatch_reviews` 中验证可行**L1379 `if handler: return []` 用 Handler 分流跳过 handler 项目的 PR Review 流程,TC Round Review 采用相同模式
### 实现方案
| 文件 | 改动 |
|------|------|
| `task_type_registry.py` | `BaseTaskHandler``build_round_review_prompt` 默认方法(raise NotImplementedError |
| `toolchain_handler.py` | `ToolchainHandler` override `build_round_review_prompt` → 返回 Gitea API 版 prompt;新增 `get_tc_subtask_summary()` 静态方法 |
| `ticker.py` | `_check_round_complete` 全链路分流:数据查询 + prompt 构建 + round_count 递增 |
| `toolchain_routes.py` | `_handle_issues` closed 分支:更新 `task_state.status = 'done'` |
### get_tc_subtask_summary 方法设计
```python
def get_tc_subtask_summary(tc_db: Path, parent_issue: int) -> Optional[dict]:
"""TC 路径:从 task_state 表查子任务状态摘要"""
conn = get_connection(tc_db)
try:
parent_row = conn.execute(
"SELECT round_count, status FROM task_state WHERE issue_number = ?",
(parent_issue,)
).fetchone()
if not parent_row:
return None
rows = conn.execute(
"SELECT status, COUNT(*) as cnt FROM task_state "
"WHERE parent_issue = ? GROUP BY status",
(parent_issue,)
).fetchall()
if not rows:
return None
summary = {
"parent_id": str(parent_issue),
"parent_status": parent_row["status"], # 从 task_state 读取实际状态
"round_count": parent_row["round_count"],
"total": 0, "done": 0, "failed": 0, "cancelled": 0, "other": 0,
}
for row in rows:
summary["total"] += row["cnt"]
if row["status"] in ("done", "failed", "cancelled"):
summary[row["status"]] += row["cnt"]
else:
summary["other"] += row["cnt"]
summary["all_terminal"] = summary["other"] == 0 and summary["total"] > 0
return summary
finally:
conn.close()
```
### TaskState round_count 同步
TC 路径的 `round_count` 存在 `task_state` 表中。`_check_round_complete` 需要在 spawn review 成功后递增:
```python
if handler and handler.virtual_project == "_toolchain":
# TC 路径:递增 task_state.round_count
conn.execute("UPDATE task_state SET round_count = round_count + 1 WHERE issue_number = ?", ...)
else:
# 黑板路径:原有 bb.increment_round_count(parent_id)
```