Files
sanguo_moziplus_v2/docs/design/21-unified-toolchain-design.md
T
cfdaily f4761ffef6
CI / lint (pull_request) Successful in 16s
CI / test (pull_request) Failing after 1m22s
CI / frontend (pull_request) Successful in 16s
CI / notify-on-failure (pull_request) Successful in 2s
[moz] docs(§21): fix 姜维 S1-S4(模板关系说明 + ci_failure 终态修正 + action report 识别规范 + infrastructure 模板)
2026-06-20 10:19:14 +08:00

485 lines
19 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: "Unified Toolchain Design — 统一工具链工作流设计"
created: 2026-06-20
version: v1.0 draft
status: draft
changelog: v1.0 初版
---
# Unified Toolchain Design
> **范围**: 仅 toolchain 流程。task 和 mail 不变。
> **前置**: §17 ToolchainHandler、§20 Issue-Centric Orchestration
> **目标**: 把割裂的单点优化整合为统一的 AI native 工作流
---
## §1. 背景与问题
### 1.1 当前割裂点
| # | 割裂 | 现状 | 本文解决 |
|---|------|------|---------|
| 1 | agent 同时引用两套 API | 黑板 APIaction report/outputs+ Gitea APIIssue/PR comment | §3 统一到 Gitea |
| 2 | steps 硬编码 | 9 处 `steps=[...]` 写死在 toolchain_routes.py | §4 模板化 |
| 3 | issue_assigned 只分 2 路 | 只按 infrastructure label 分流,不分 type/feat vs type/docs | §5 按 type/* 6 路分流 |
| 4 | action_type 和 business_type 混在一起 | issue_assigned 内部混入业务场景 | §6 分离两个维度 |
| 5 | verify 依赖黑板 comment_type | 迁移到 Gitea 后失效 | §7 重新定义完成检测 + 输出约束 |
### 1.2 黑板中的"无缝接续"设计(不能丢弃)
黑板 DB 中有多个机制确保后续 agent 可以无缝接续:
| 机制 | 当前实现 | 用途 | 迁移到 Issue 后 |
|------|---------|------|----------------|
| **任务描述** | tasks.title + tasks.description | agent 知道"做什么" | Issue title + body ✅ 直接对应 |
| **验收标准** | tasks.must_haves | agent 知道"怎样算完成" | Issue body 中结构化字段(模板定义)✅ |
| **前序产出** | outputs 表 + depends_on | agent 知道"之前做了什么" | Issue body 引用前序 Issue/PR(如 `Depends: #42`)⚠️ 需约定 |
| **handoff comment** | comment_type=handoff | agent 之间交接上下文(≥50 字符) | Issue/PR comment ✅ 天然支持 |
| **讨论历史** | comments 表 | agent 知道"讨论了什么" | Issue/PR comment 全部可读 ✅ |
| **审查结果** | reviews 表(verdict/round/consensus | agent 知道"审查结论" | PR ReviewAPPROVE/REQUEST_CHANGES+ Issue comment 记录 |
| **agent 声明式交接** | next_capability 字段 | agent 声明"我搞不定,需要 XX 能力的人" | Issue comment @对方(已有 mention 机制)✅ |
| **retry 历史** | task_attempts 表 | daemon 知道"试了几次" | daemon 内部 task_state 表 ✅ 不变 |
| **风险观察** | observations 表 | agent 标记"发现风险" | Issue comment(约定标记)⚠️ 需约定 |
**结论**:大部分机制可以自然迁移到 Issue。两个需要约定:
1. **前序产出引用**Issue body 中用 `Depends: #N``Parent: #N` 引用前序 Issue
2. **风险观察**Issue comment 中用约定标记(如 `⚠️ [观察]`
---
## §2. 设计原则
1. **Gitea only**toolchain agent 只操作 Gitea,不引用黑板 API、不用 Mail
2. **模板驱动**:steps 从硬编码改为模板,不同 Issue type 对应不同流程
3. **action_type 和 business_type 分离**:事件类型决定 action_hint,业务类型决定 steps
4. **完成检测:终态事件 + 输出约束**:用 Gitea 终态事件检测完成 + 结构化输出约束 agent 汇报
5. **无缝接续不丢弃**:handoff/前序产出/审查结果等机制迁移到 Issue 语义
---
## §3. 统一到 Gitea(割裂 1 解决)
### 3.1 agent API 引用变更
| 操作 | 现在(黑板 API | 改造后(Gitea API |
|------|----------------|-------------------|
| 提交 action report | `POST localhost:8083/.../comments` (comment_type=action_report) | `POST Gitea .../issues/{N}/comments`(结构化 body,§7 定义) |
| 提交产出 | `POST localhost:8083/.../outputs` | git push 到分支(代码/文档/测试) |
| 讨论 / @mention | `POST localhost:8083/.../comments` | `POST Gitea .../issues/{N}/comments` |
| 创建 PR | 已是 Gitea API(不变) | 不变 |
| 创建 Issue | 已是 Gitea API(不变) | 不变 |
### 3.2 agent prompt 中的 API 指引
ToolchainApiSection 改造——去掉所有 `localhost:8083` 引用,只保留 Gitea API
```
## 操作指令
### 汇报执行结果
执行完步骤后,在关联的 Issue 上 comment 汇报:
```bash
curl -X POST "{GITEA}/repos/{repo}/issues/{N}/comments" \
-H "Authorization: token <token>" \
-d '{"body": "[Action Report]\n\n**操作**...\n**结果**...\n**CI**..."}'
```
### 需要其他角色支持
在关联的 Issue/PR 上 comment @对方(已有机制)
### 代码产出
git push 到功能分支 → 创建 PR
```
---
## §4. steps 模板化(割裂 2 解决)
### 4.1 模板存储
steps 模板存放在 daemon 配置文件 `config/toolchain-templates.yaml`
```yaml
# 每种 business_type 对应一套 steps + output_template
issue_assigned:
feature:
steps:
- "理解需求(Issue body)→ 如有不明确在 Issue comment 追问"
- "git checkout main && git pull origin main"
- "git checkout -b fix/{issue_number}-{brief}"
- "编码实现 + 写 UT"
- "git add -A && git commit -m '[moz] feat: {title}' && git push"
- "创建 PRbody 引用 IssueCloses #{issue_number}"
- "等 CI + Review"
output_template: |
[Action Report]
**分支**fix/{issue_number}-{brief}
**PR**#{pr_number}
**改动文件**{files}
**CI**{ci_status}
impl:
steps:
- "读设计文档(Issue body 中的路径)→ 理解实现范围"
- "git checkout main && git pull origin main"
- "git checkout -b impl/{issue_number}-{brief}"
- "按设计编码实现 + 写 UT"
- "git add -A && git commit -m '[moz] impl: {title}' && git push"
- "创建 PRbody 引用 Issue + 设计文档路径)"
- "等 CI + Review"
output_template: |
[Action Report]
**设计文档**{design_doc}
**分支**impl/{issue_number}-{brief}
**PR**#{pr_number}
**改动文件**{files}
**CI**{ci_status}
bug:
steps:
- "读 Bug 描述 + 复现步骤(Issue body"
- "定位根因(读代码/日志,不要猜测)"
- "git checkout main && git pull origin main"
- "git checkout -b fix/{issue_number}-{brief}"
- "修复 + 写回归测试"
- "git add -A && git commit -m '[moz] fix: {title}' && git push"
- "创建 PRbody 说明根因和修复方式)"
- "等 CI + Review"
output_template: |
[Action Report]
**根因**{root_cause}
**修复方式**{fix_approach}
**分支**fix/{issue_number}-{brief}
**PR**#{pr_number}
**CI**{ci_status}
docs:
steps:
- "读文档目标(Issue body"
- "git checkout main && git pull origin main"
- "git checkout -b docs/{issue_number}-{brief}"
- "编写文档到 docs/ 对应目录"
- "git add -A && git commit -m '[moz] docs: {title}' && git push"
- "创建 PR"
- "等 Review"
output_template: |
[Action Report]
**文档路径**{doc_path}
**分支**docs/{issue_number}-{brief}
**PR**#{pr_number}
refactor:
steps:
- "读重构目标 + 影响范围(Issue body"
- "git checkout main && git pull origin main"
- "git checkout -b refactor/{issue_number}-{brief}"
- "重构 + 确保现有测试不 breakpython -m pytest tests/unit/ -q"
- "git add -A && git commit -m '[moz] refactor: {title}' && git push"
- "创建 PR(body 说明重构内容和影响范围)"
- "等 CI + Review"
output_template: |
[Action Report]
**重构范围**{scope}
**测试结果**{test_result} passed
**分支**refactor/{issue_number}-{brief}
**PR**#{pr_number}
**CI**{ci_status}
test:
steps:
- "读测试目标(Issue body"
- "git checkout main && git pull origin main"
- "git checkout -b test/{issue_number}-{brief}"
- "编写测试脚本到 tests/ 对应目录"
- "运行测试验证(python -m pytest {test_file} -v"
- "git add -A && git commit -m '[moz] test: {title}' && git push"
- "创建 PR"
- "等 CI + Review"
output_template: |
[Action Report]
**测试文件**{test_file}
**测试结果**{test_result}
**分支**test/{issue_number}-{brief}
**PR**#{pr_number}
infrastructure:
steps:
- "根据 Issue body 中的错误来源和日志片段排查问题"
- "修复基础设施问题(CI runner/网络/Gitea/磁盘等)"
- "修复后在 Issue 上 comment 说明修复方式和结果"
- "汇报执行结果"
output_template: |
[Action Report]
**问题**{problem}
**根因**{root_cause}
**修复方式**{fix}
**验证**{verification}
# toolchain 事件(非 issue_assigned)的模板
ci_failure:
steps:
- "查看 CI 日志(PR 页面或 Gitea Actions"
- "判断失败原因:a.代码问题→修复→push b.基础设施→创建 Issue 指派 jiangwei-infra"
- "汇报结果"
output_template: |
[Action Report]
**原因类型**{cause_type}
**操作**{action}
**CI 重跑**{ci_status}
# ... 其他 toolchain 事件(review_result/review_request/...)各自定义
```
### 4.2 模板加载
daemon 启动时加载 YAML 配置,运行时按 `action_type + business_type` 查找模板:
```python
def get_steps(action_type: str, business_type: str = "") -> list[str]:
"""从模板配置获取 steps"""
section = TEMPLATES.get(action_type, {})
if isinstance(section, dict) and business_type:
return section.get(business_type, {}).get("steps", section.get("default", {}).get("steps", []))
return section.get("steps", [])
```
---
## §5. 按 type/* 6 路分流(割裂 3 解决)
### 5.1 issue_assigned handler 改造
当前只按 `infrastructure` label 分 2 路。改为按 `type/*` label 分流:
```python
# 伪代码
labels_list = [lbl.get("name", "") for lbl in (issue.get("labels") or [])]
# 1. 基础设施(不变)
if any("infrastructure" in lbl.lower() for lbl in labels_list):
business_type = "infrastructure"
# 2. 按 type/* 确定 business_type
elif "type/feat" in labels_list:
business_type = "feature"
elif "type/impl" in labels_list:
business_type = "impl"
elif "type/bug" in labels_list:
business_type = "bug"
elif "type/docs" in labels_list:
business_type = "docs"
elif "type/refactor" in labels_list:
business_type = "refactor"
elif "type/test" in labels_list:
business_type = "test"
else:
business_type = "feature" # 默认走编码流程
# 从模板获取 steps + output_template
template = get_template("issue_assigned", business_type)
steps = template["steps"]
output_template = template["output_template"]
```
### 5.2 action_hint 差异化
当前 action_hint 按 action_type 固定("你收到一个 Issue 指派...")。改为同时体现 business_type
```python
_ACTION_HINTS = {
"issue_assigned": {
"feature": "你收到一个功能需求,理解需求后编码实现。",
"impl": "你收到一个实现任务,按设计文档编码实现。",
"bug": "你收到一个 Bug 报告,定位根因后修复。",
"docs": "你收到一个文档任务,编写文档。",
"refactor": "你收到一个重构任务,重构并确保测试通过。",
"test": "你收到一个测试任务,编写测试脚本。",
"infrastructure": "你收到一个基础设施问题报告,请排查并修复。",
},
"ci_failure": "你收到一个 CI 失败通知,这是一个需要你修复失败测试的事件。",
"review_result": "你收到一个 Review 结果通知,这是一个需要你执行动作的事件。",
# ... 其他 action_type 不分 business_type
}
```
---
## §6. action_type 和 business_type 分离(割裂 4 解决)
### 6.1 两个维度
| 维度 | 来源 | 决定什么 | 示例 |
|------|------|---------|------|
| **action_type** | webhook 事件类型 | action_hint"你收到一个 XX 通知" | ci_failure / review_result / issue_assigned |
| **business_type** | Issue label type/* | steps + output_template | feature / impl / bug / docs / refactor / test |
### 6.2 组合规则
- issue_assignedaction_type=issue_assigned + business_type 从 label 确定
- ci_failureaction_type=ci_failure(无 business_typeCI 失败就是 CI 失败)
- review_resultaction_type=review_result(无 business_typeReview 就是 Review
- 只有 issue_assigned 需要 business_type 维度(因为同一个 action_type 下不同业务的流程不同)
---
## §7. 完成检测 + 输出约束(割裂 5 解决)
### 7.1 设计原则
| 关注点 | 方案 |
|--------|------|
| 完成检测 | Gitea 终态事件优先 + Issue comment 兜底 |
| 输出约束 | 按 business_type 定义的 output_template(不是空泛的"简要描述" |
### 7.2 完成检测:按 action_type 分类
| action_type | 终态信号 | 检测方式 | 兜底 |
|-------------|---------|---------|------|
| issue_assigned | PR merged 或 Issue closed | webhook: pull_request/closed(merged=true) 或 issues/closed | — |
| ci_failure | agent Issue comment 汇报 | Issue comment 检测([Action Report] 标记) | ⚠️ Gitea 1.26.2 不触发 CI status webhook,只能靠 comment 兜底。ticker 可选轮询 Gitea commit status API 作为补充 |
| review_result(APPROVED) | PR merged | webhook: pull_request/closed(merged=true) | — |
| review_result(CHANGES) | agent push 到分支 | webhook: pull_request/synchronize | — |
| review_request | Review 提交 | webhook: pull_request_review | — |
| review_updated | Review 提交 | webhook: pull_request_review | — |
| review_comment | agent comment | webhook: issue_comment/created | — |
| mention | agent comment | webhook: issue_comment/created | — |
| deploy_failure | agent Issue comment 汇报 | Issue comment 检测 | ✅ |
| infrastructure_failure | agent Issue comment 汇报 | Issue comment 检测 | ✅ |
| review_merged | — | auto-pass | — |
### 7.3 状态流转:单一终态保证
一个 task 只有一个终态触发。daemon 内部状态机保证:
```
pending → working → done(终态事件触发,只触发一次)
→ failed(超时/异常)
```
终态事件到来时检查 task 当前状态:
- 如果已经 done/failed → 忽略(幂等)
- 如果 working → 标 done
- 如果 pending → 异常,记日志
**中间事件**push/comment/Review submitted**不改变 task 状态**——它们是过程中的信号,不是终态。
### 7.4 输出约束:output_template
每种 business_type 有自己的 output_template(§4.1 定义)。agent 完成后在 Issue comment 中按模板汇报。
daemon 的 verify 通过 webhook 事件检测终态(不需要检查 comment 内容)。但 output_template 的价值是**约束 agent 的汇报质量**——不是检测完成用的,而是给后续 agent/审查者提供结构化信息。
output_template 作为 steps 的最后一步注入 prompt
### 7.5 action report 识别规范
daemon 通过 webhook `issue_comment/created` 感知到新 comment 后,需要判断是否为 action report。
**匹配规则**
- 精确匹配:comment body 以 `[Action Report]` 开头(允许前导空白)
- 容错策略:如果 body 包含 `[Action Report]`(不要求开头),也接受
- 大小写不敏感
**匹配失败处理**
- 不匹配的 comment 不触发完成检测
- 作为普通讨论 comment 处理(agent 之间的 handoff/讨论)
```
最后一步:汇报执行结果,在 Issue 上 comment,格式:
[Action Report]
**根因**<根因描述>
**修复方式**<做了什么>
**分支**fix/42-xxx
**PR**#43
**CI**:✅ 通过
```
---
## §8. 无缝接续机制迁移
### 8.1 前序产出引用
当前黑板用 `depends_on` 字段 + `PriorOutputsSection` 注入前序产出摘要。
迁移到 Issue 后,在 Issue body 中用约定引用:
```markdown
## 依赖
Depends: #42(前序任务)
Parent: #40(父 Issue
## 前序产出摘要
- #42 完成了数据获取模块(分支 fix/42-dataPR #43
- 数据路径:/Volumes/stock/xxx
```
agent 读 Issue body 自然获得前序上下文。daemon 的 spawner 在构建 prompt 时,可以解析 Issue body 中的 `Depends: #N`,调 Gitea API 读取前序 Issue 的 comment(包含 action report)作为上下文注入。
### 8.2 handoff comment
当前 handoff 通过黑板 `comment_type=handoff` + ≥50 字符约束。
迁移到 Issue 后,agent 的 handoff 就是**Issue/PR comment**。不需要 comment_type 字段——所有有实质内容的 comment 都是为后续 agent 提供上下文的"handoff"。
### 8.3 审查结果
当前黑板 reviews 表存 verdict/round/consensus。
迁移后:
- **代码审查**PR ReviewGitea 原生,APPROVE/REQUEST_CHANGES
- **方案审查**(设计 PR):同上
- **庞统 round review**:保留在 daemon 内部(不迁移,这是编排逻辑)
---
## §9. prompt 层级(L0-L4 不变,L2 重组)
| 层 | 内容 | 不变? |
|---|------|-------|
| L0 铁律 | 安全底线 | ✅ 不变 |
| L1 角色 | SOUL.md / IDENTITY.md | ✅ 不变 |
| L2 引擎注入 | **本文重组** | 改造 |
| L3 被参考 | Skill 列表 | ✅ 不变 |
L2 重组后的 section 列表:
| priority | Section | 内容 | 来源 |
|----------|---------|------|------|
| 10 | ToolchainContextSection | action_hint + Issue body(需求)+ steps | 改造:从模板加载 steps |
| 20 | PriorContextSection | 前序产出(解析 Issue body 中的 Depends | 改造现有 PriorOutputsSection |
| 30 | RoleSkillSection | 角色 Skill | 不变 |
| 35 | GitOperationSection | Git 操作说明(PR #95 已有) | 不变 |
| 40 | GiteaApiSection | Gitea API 指引(Issue comment + PR 创建) | 改造:去掉黑板 API |
| 50 | ToolchainConstraintsSection | 约束 + Red Flags | 不变 |
| 55 | GiteaConventionSection | Gitea 标题规范 | 不变 |
| 60 | WikiGuideSection | 知识查询引导 | 不变 |
| 65 | DeliveryChecklistSection | 交付检查 | 改造:output_template 替代空泛的"简要描述" |
---
## §10. 涉及改动
| 文件 | 改动 | 工作量 |
|------|------|-------|
| `config/toolchain-templates.yaml` | 新建:6 种 business_type steps + output_template | 新文件 |
| `src/daemon/toolchain_handler.py` | ToolchainApiSection 改为 GiteaApiSection(去黑板 API);action_hint 支持 business_type | 改造 |
| `src/daemon/toolchain_handler.py` | verify_completion 改为终态事件检测 + Issue comment 兜底 | 改造 |
| `src/api/toolchain_routes.py` | issue_assigned handler 按 type/* 6 路分流 | 改造 |
| `src/api/toolchain_routes.py` | steps 从模板加载(替代硬编码) | 改造 |
| `src/daemon/toolchain_handler.py` | webhook handler 增加终态事件检测 | 新增 |
| `.gitea/ISSUE_TEMPLATE/` | 新增 impl.yml / docs.yml / refactor.yml | 新文件 |
| `tests/` | 更新测试 | 改造 |
---
## §11. 不做的事
| 不做 | 理由 |
|------|------|
| 不改 task handler | task 流程不变(§20 设计中的 task 逐步迁移到 Issue 是后续工作) |
| 不改 mail | mail 职责不变 |
| 不改 dispatcher/ticker 核心逻辑 | 调度逻辑不变(§20 Phase 1 的 dispatcher SQL 迁移是前置工作) |
| 不做前端改造 | 后续独立设计 |
| 不改 experiences/checkpoints/decisions 表 | 执行面表保留在 daemon |