Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 576cd96b43 | |||
| 48c2b8ea3d | |||
| e89bd51d7c | |||
| 4c3d125a30 | |||
| 59499798ea | |||
| 2febe54920 | |||
| 59c95d4125 | |||
| 23d8691b3f | |||
| f4fea8f418 | |||
| 3a11327113 | |||
| ddc5eb9897 | |||
| b0f4572ba6 | |||
| b6e58a164c | |||
| ea04b4c483 | |||
| 04568108a6 | |||
| 6ea43d76e3 | |||
| 09520a414e | |||
| 9388f3ecc1 | |||
| cdc49ac74d | |||
| f74ae30d41 | |||
| 67b504c5db | |||
| 230b8c9cd9 | |||
| 5b73319aaf | |||
| 8c7c277167 | |||
| 999cd1cc10 | |||
| 0475e40529 | |||
| ca9b750656 | |||
| 7e832ee865 | |||
| 1c7be0e782 | |||
| 49d0b3a789 | |||
| 5505ac9c5c | |||
| f5bf671410 | |||
| ccb5d5d3ea | |||
| ee825db818 | |||
| cdf984aa0c | |||
| 6798f098b5 | |||
| 33e38254c1 |
@@ -25,7 +25,10 @@ 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
|
||||
/tmp/ci-venv-lint/bin/pip install --quiet --upgrade pip
|
||||
/tmp/ci-venv-lint/bin/pip install --quiet flake8
|
||||
@@ -42,11 +45,13 @@ 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
|
||||
/tmp/ci-venv-test/bin/pip install --quiet --upgrade pip
|
||||
/tmp/ci-venv-test/bin/pip install --quiet --no-cache-dir fastapi pydantic pyyaml uvicorn requests pytest pytest-asyncio httpx
|
||||
/tmp/ci-venv-test/bin/pip install --quiet 'fastapi' 'pydantic<2' pyyaml uvicorn requests pytest pytest-asyncio httpx
|
||||
|
||||
- name: Debug environment
|
||||
run: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -391,7 +391,11 @@ def verify_completion(self, task_id: str, db_path: Path) -> VerifyResult:
|
||||
|
||||
#### 完整设计
|
||||
|
||||
三分路的详细伪代码、失败上限、决策依据见 §5.2.1~§5.2.3(on_failure 分路处理详细设计)。
|
||||
三分路的详细设计见 §6.4(基础设施 Issue 转交流程)和 toolchain_handler.py 实现(`_handle_infrastructure_failure` / `_classify_failure`)。
|
||||
|
||||
#### 首次 steps 分支指引
|
||||
|
||||
三分路是 verify 失败后的兜底机制。此外,ci_failure 和 deploy_failure 的首次 toolchain task steps 中**已包含分支指引**——agent 在执行过程中自行判断失败原因,如果是基础设施问题则直接创建 Issue 指派 jiangwei-infra(见 §6.4)。三分路作为第二道防线,覆盖 agent 未正确判断或未执行分支的情况。
|
||||
|
||||
### 5.3 action_report comment 格式
|
||||
|
||||
@@ -431,9 +435,9 @@ Agent 可能写了 action_report 但没真做。缓解机制:
|
||||
| Review 请求 → reviewer | review_request | toolchain | 4 步 | 读 diff + 审查 + 提交 Review + report |
|
||||
| Review 有新提交 → reviewer | review_updated | toolchain | 4 步 | 读 diff + 检查修改 + 提交 Review + report |
|
||||
| Review 评论 → PR 作者 | review_comment | toolchain | 3 步 | 查看评论 + 响应(修改/回复)+ report |
|
||||
| CI 失败 → PR 作者 | ci_failure | toolchain | 4 步 | 查 CI 日志 + 修测试 + push + report |
|
||||
| Issue 指派 → 开发者 | issue_assigned | toolchain | 6 步 | 创建分支 + 编码 + push + CI + PR + report |
|
||||
| 部署失败 → 运维 | deploy_failure | toolchain | 4 步 | 查日志 + 排查 + 修+重部署 + report |
|
||||
| CI 失败 → PR 作者 | ci_failure | toolchain | 3 步 | 查 CI 日志 + 分支判断(代码问题自己修 / 基础设施问题提 Issue 给姜维)+ report |
|
||||
| Issue 指派 → 开发者 | issue_assigned / infrastructure_failure | toolchain | 6 步(编码,含具体 git 命令)或 4 步(运维) | 按 label 分流:type/infrastructure → 运维排查;其他 → git checkout main/pull → 创建分支 → 编码 → push → CI → PR + report |
|
||||
| 部署失败 → 运维 | deploy_failure | toolchain | 3 步 | 查日志 + 分支判断(代码/配置问题自己修 / 基础设施问题提 Issue 给姜维)+ report |
|
||||
| @mention → 被@者 | mention | toolchain | 按 guidance | 按 mention 模板的 response_guidance + report |
|
||||
| PR 合并 → PR 作者 | review_merged | toolchain | 0 步 | 纯通知,走 _send_toolchain_task(steps 为空,verify 始终通过) |
|
||||
|
||||
@@ -502,29 +506,46 @@ event_type: ci_failure
|
||||
action_type: ci_failure
|
||||
steps:
|
||||
1. 查看完整 CI 日志(PR 页面或 Gitea Actions 页面)
|
||||
2. 修复失败的测试
|
||||
3. push → CI 自动重跑
|
||||
4. 提交 action report
|
||||
2. 根据 CI 日志判断失败原因类型:
|
||||
a. 代码问题(lint/test 失败)→ 修复失败的测试 → push 到原分支 → CI 自动重跑
|
||||
b. 基础设施问题(runner 环境/Python/venv/Gitea/网络故障)→ 在该仓库创建 Issue 指派 jiangwei-infra(见 §6.4),label 必须包含 type/infrastructure
|
||||
3. 提交 action report — 报告中说明判断的原因类型和执行的操作
|
||||
context:
|
||||
pr_number, repo, branch, error_summary
|
||||
```
|
||||
|
||||
**分支设计说明**:原设计假设"CI 失败 = 代码问题",steps 只有"修测试 + push"一条路径。实际运行中发现 CI 失败可能是 runner 环境故障(如 ensurepip 失败),agent 判断为基础设施问题后不知道该做什么。现在 steps 中明确两条分支,agent 自行判断后走对应路径。
|
||||
|
||||
#### Issue 指派 → 开发者
|
||||
|
||||
```
|
||||
event_type: issue_assigned
|
||||
action_type: issue_assigned
|
||||
steps:
|
||||
1. 创建分支 fix/{issue_number}-{brief}
|
||||
event_type: issue_assigned 或 infrastructure_failure
|
||||
action_type: issue_assigned 或 infrastructure_failure
|
||||
steps(按 label 分流):
|
||||
|
||||
# 默认路径(编码任务):
|
||||
1. 在开发目录执行 git 操作:
|
||||
a. git checkout main && git pull origin main (确保从最新代码分叉)
|
||||
b. git checkout -b fix/{issue_number}-{brief} (创建功能分支)
|
||||
2. 编码 + 写 UT
|
||||
3. push → 等 CI
|
||||
4. CI 通过后创建 PR(Gitea API: POST /repos/{repo}/pulls)
|
||||
3. git add -A && git commit -m "[moz] fix: {简述}" && git push origin fix/{issue_number}-{brief}
|
||||
4. CI 通过后创建 PR(Gitea API: POST /repos/{repo}/pulls,head: fix/{issue_number}-{brief}, base: main)
|
||||
5. 等 Review
|
||||
6. 提交 action report
|
||||
|
||||
# type/infrastructure label 路径(运维任务):
|
||||
1. 根据 Issue body 中的错误来源和日志片段排查问题
|
||||
2. 修复基础设施问题(如修复 CI runner 环境、恢复网络、重启服务等)
|
||||
3. 修复后在 Issue 上 comment 说明修复方式和结果
|
||||
4. 提交 action report
|
||||
context:
|
||||
issue_number, repo, issue_title, labels, issue_body, brief
|
||||
```
|
||||
|
||||
**label 分流说明**:issue_assigned handler 检查 label 中是否包含 `type/infrastructure`。如果是,走运维排查 steps(event_type 设为 infrastructure_failure,verify 始终 auto-pass 防递归);否则走编码 steps。
|
||||
|
||||
**编码路径 git 操作具体化说明**(§17 v3 补充):原 steps 只写「创建分支」「push」等抽象指令,agent 需要自己推导具体 git 命令。现在 steps 中写清楚完整 git 操作序列(checkout main → pull → checkout -b → commit → push),降低 agent 出错概率。系统不做分支管理(不通过 Gitea API 预创建分支),分支管理完全由 agent 自己执行。ToolchainApiSection 中新增 Git 操作说明段落作为通用参考。
|
||||
|
||||
#### 部署失败 → 运维
|
||||
|
||||
```
|
||||
@@ -532,9 +553,10 @@ event_type: deploy_failure
|
||||
action_type: deploy_failure
|
||||
steps:
|
||||
1. 检查 deploy 日志
|
||||
2. 排查失败原因
|
||||
3. 修复并重新部署
|
||||
4. 提交 action report
|
||||
2. 根据 deploy 日志判断失败原因类型:
|
||||
a. 代码/配置问题(rsync 路径错、依赖缺失、启动失败)→ 修复 → 重新部署
|
||||
b. 基础设施问题(Gitea 不可用、网络不通、磁盘满、SSH 故障)→ 在该仓库创建 Issue 指派 jiangwei-infra(见 §6.4),label 必须包含 type/infrastructure
|
||||
3. 提交 action report — 报告中说明判断的原因类型和执行的操作
|
||||
context:
|
||||
repo, commit_sha, reason
|
||||
```
|
||||
@@ -577,6 +599,89 @@ context:
|
||||
|
||||
**spawn 说明**:review_merged 仍会触发 spawn(Agent 只需阅读通知),verify auto-pass 后标 done。未来可优化为 ticker 直接 auto-done 跳过 spawn。
|
||||
|
||||
### 6.4 基础设施 Issue 转交流程
|
||||
|
||||
当 ci_failure / deploy_failure 的 agent 在调查后发现失败原因是基础设施问题(非代码问题),需要创建 Gitea Issue 指派 jiangwei-infra。
|
||||
|
||||
#### Issue 提在哪里
|
||||
|
||||
**根据问题来源决定**——哪个仓库的 CI/部署失败了,Issue 就提到那个仓库。CI runner 是全局共享的(一个 Mac mini),但 Issue 挂在触发的仓库最自然。如果 runner 故障影响多个仓库,各仓库会各自触发 ci_failure task,姜维看到任何一个 Issue 就能定位全局问题。
|
||||
|
||||
#### Issue 格式规范
|
||||
|
||||
```markdown
|
||||
## 问题描述
|
||||
|
||||
<简要描述问题现象>
|
||||
|
||||
## 错误来源
|
||||
|
||||
- 仓库: <repo>
|
||||
- PR/Commit: <链接>
|
||||
- CI/Deploy run: <Gitea Actions 页面链接>
|
||||
|
||||
## 日志关键片段
|
||||
|
||||
```
|
||||
<错误日志摘要,不需全文,但要让排查者看到关键信息>
|
||||
```
|
||||
|
||||
## 判断依据
|
||||
|
||||
<为什么判断为基础设施问题而非代码问题>
|
||||
```
|
||||
|
||||
**必填字段**:问题描述、错误来源(含链接)、日志片段、判断依据。Issue body 不完整会导致姜维无法高效排查。
|
||||
|
||||
**label 要求**:必须包含 `type/infrastructure`,用于 issue_assigned handler 分流(见下)。
|
||||
|
||||
#### issue_assigned handler label 分流
|
||||
|
||||
当 Gitea Issue 指派触发 webhook 时,issue_assigned handler 检查 label:
|
||||
|
||||
| label 包含 type/infrastructure | event_type | steps |
|
||||
|---|---|---|
|
||||
| 是 | infrastructure_failure | 运维排查:根据 Issue body 排查问题 → 修复 → Issue comment 说明修复方式 → action report |
|
||||
| 否 | issue_assigned | 编码:创建分支 → 编码 → push → CI → PR → 等 Review → action report |
|
||||
|
||||
基础设施路径的 verify 始终 auto-pass(防递归,已有逻辑覆盖)。
|
||||
|
||||
⚠️ **label 分流当前为设计目标**,toolchain_handler.py 尚未实现 issue_assigned 的 label 检查。当前只有 `_handle_infrastructure_failure`(verify 失败时)创建 infrastructure_failure task。issue_assigned handler 的 label 分流在后续代码 PR 中实现。
|
||||
|
||||
⚠️ **label 预创建**:使用前需确认仓库中已创建名为 `type/infrastructure` 的 label。sanguo_moziplus_v2 仓库已创建(ID=98)。其他仓库使用前需先创建。
|
||||
|
||||
#### API 指令位置
|
||||
|
||||
Issue 创建的 API 调用方式(curl 示例)统一在 ToolchainApiSection 中,与 action_report / comment 指引并列。steps 指令中不重复 API 调用方式,只描述"做什么"。
|
||||
|
||||
#### Git 操作说明段落
|
||||
|
||||
ToolchainApiSection 中新增通用 Git 操作说明段落,作为 agent 执行 git 命令的参考:
|
||||
|
||||
```
|
||||
### Git 操作说明
|
||||
|
||||
你的工作目录是开发目录。
|
||||
标准分支操作流程:
|
||||
git checkout main && git pull origin main # 从最新主干开始
|
||||
git checkout -b fix/{branch_name} # 创建功能分支
|
||||
# ... 写代码 ...
|
||||
git add -A && git commit -m 'message' # 提交改动
|
||||
git push origin {branch_name} # 推送到远程
|
||||
|
||||
⚠️ 不要在 main 分支上直接 commit。
|
||||
```
|
||||
|
||||
**设计原则**:系统不做分支管理(不预创建分支、不做 checkout),分支管理完全由 agent 自己执行。ToolchainApiSection 提供通用 git 操作参考,steps 中写清楚具体操作序列。
|
||||
|
||||
#### Red Flags 补充
|
||||
|
||||
硬约束 Red Flags 表新增一条:
|
||||
|
||||
| Agent 想法 | Red Flag 驳回 |
|
||||
|---|---|
|
||||
| "CI/部署失败不是我代码的问题,我什么也不用做" | ❌ 错!即使是基础设施问题,你也必须创建 Issue 指派 jiangwei-infra(body 含错误来源链接 + 日志 + 判断依据),并在 action report 中说明。不能只报告"不是我的问题"就完事 |
|
||||
|
||||
---
|
||||
|
||||
## §7. _send_toolchain_task 函数设计
|
||||
|
||||
@@ -465,7 +465,154 @@ IMPROVE(每周 cron,庞统执行)
|
||||
| S5 | 实现 IMPROVE cron:JSONL 引用追踪 + 淘汰报告(每周) | P2 | L2-L3 |
|
||||
| S6 | 清理 deprecated 代码(skill_system.py / experience.py / self-improvement skill / SELF_IMPROVEMENT_REMINDER.md) | P3 | L1 |
|
||||
|
||||
S1 和 S2 可以立即做。S3-S5 需要先确认设计文档。
|
||||
S1 和 S2 已完成(PR #85)。S3-S5 设计见下方 §11A。
|
||||
|
||||
## 11A. Cron 配置方案(S3-S5 详细设计)
|
||||
|
||||
### 设计决策
|
||||
|
||||
**每个 agent 用自己的 agentId 执行 L1 cron**,不由庞统代理。
|
||||
|
||||
理由(对照设计目标 D4):
|
||||
- L1 核心价值是"每个 agent 是自己经验的最佳蒸馏者"——agent 扫描自己的 JSONL,用自己的判断力识别信号
|
||||
- 如果庞统代理,变成庞统替别人蒸馏,消除不了蒸馏者偏差(D4 要解决的正是这个问题)
|
||||
- openclaw cron 原生支持 `agentId` 参数 + `sessionTarget: "isolated"`,技术上无障碍
|
||||
|
||||
### S3: L1 各 agent 自蒸馏 cron
|
||||
|
||||
6 个 agent,各创建一个 isolated cron,错开 15 分钟(和 discover-l1.md 时间表一致):
|
||||
|
||||
| Agent | agentId | cron 表达式 | 时区 |
|
||||
|-------|---------|-----------|------|
|
||||
| 张飞 | zhangfei-dev | `0 3 * * *` | Asia/Shanghai |
|
||||
| 关羽 | guanyu-dev | `15 3 * * *` | Asia/Shanghai |
|
||||
| 赵云 | zhaoyun-data | `30 3 * * *` | Asia/Shanghai |
|
||||
| 司马懿 | simayi-challenger | `45 3 * * *` | Asia/Shanghai |
|
||||
| 庞统 | pangtong-fujunshi | `0 4 * * *` | Asia/Shanghai |
|
||||
| 姜维 | jiangwei-infra | `15 4 * * *` | Asia/Shanghai |
|
||||
|
||||
**Cron 配置规范**(每个 L1 cron job):
|
||||
|
||||
```json
|
||||
{
|
||||
"schedule": { "kind": "cron", "expr": "<时间>", "tz": "Asia/Shanghai" },
|
||||
"sessionTarget": "isolated",
|
||||
"agentId": "<agent-id>",
|
||||
"payload": {
|
||||
"kind": "agentTurn",
|
||||
"message": "L1 自蒸馏 cron。请执行:\n1. read ~/.sanguo_projects/sanguo_mozi/skills/skill-management/SKILL.md\n2. read ~/.sanguo_projects/sanguo_mozi/skills/skill-management/references/discover-l1.md\n3. 按 discover-l1.md 步骤执行自蒸馏\n4. 如有信号:蒸馏为 HOW 格式,使用 skill_workshop(action=create) 提交 draft proposal\n5. 如无有价值信号:不产出,这是正常的",
|
||||
"timeoutSeconds": 600
|
||||
},
|
||||
"delivery": { "mode": "announce" }
|
||||
}
|
||||
```
|
||||
|
||||
**设计要点**:
|
||||
- `sessionTarget: "isolated"`:每次创建临时 session,不污染 main session context
|
||||
- `delivery.mode: "announce"`:执行结果投递到 Control UI,保持可见性(早期使用 `none` 导致 cron 执行后零可见性,已修正)
|
||||
- `timeoutSeconds: 600`:10 分钟足够(扫描 JSONL + 蒸馏 + 提交 proposal)
|
||||
- message 指引 read SKILL.md + discover-l1.md:agent 按 references 指南执行,不依赖 memory
|
||||
|
||||
### S4: L2 庞统整合审查 cron
|
||||
|
||||
庞统的 L2 cron 在所有 L1 完成后执行(最后一个 agent 04:15 开始,L2 设在 05:00):
|
||||
|
||||
| 角色 | agentId | cron 表达式 | 时区 |
|
||||
|------|---------|-----------|------|
|
||||
| 庞统 | pangtong-fujunshi | `0 5 * * *` | Asia/Shanghai |
|
||||
|
||||
**Cron 配置**:
|
||||
|
||||
```json
|
||||
{
|
||||
"schedule": { "kind": "cron", "expr": "0 5 * * *", "tz": "Asia/Shanghai" },
|
||||
"sessionTarget": "isolated",
|
||||
"agentId": "pangtong-fujunshi",
|
||||
"payload": {
|
||||
"kind": "agentTurn",
|
||||
"message": "L2 整合审查 cron。请执行:\n1. read ~/.sanguo_projects/sanguo_mozi/skills/skill-management/references/discover-l2.md\n2. 按 discover-l2.md 步骤执行:\n a. skill_workshop(action=list, status=pending) 获取所有 L1 draft proposals\n b. 全量数据源扫描,识别跨 agent 共性模式\n c. 逐个审查 proposal:approve / merge / reject\n d. 全局提升检查(Recurrence-Count >= 3 的经验提升为规则)\n e. 知识缺口反馈到 knowledge-gaps.md",
|
||||
"timeoutSeconds": 1200
|
||||
},
|
||||
"delivery": { "mode": "announce" }
|
||||
}
|
||||
```
|
||||
|
||||
**设计要点**:
|
||||
- `delivery.mode: "announce"`:审查决策结果投递到 Control UI,主公可见
|
||||
- `timeoutSeconds: 1200`(20 分钟):L2 需要扫描全量数据源 + 审查多个 proposal,时间更长
|
||||
- 庞统可以访问所有 agent 的 JSONL 和 skill_workshop proposals
|
||||
|
||||
### S5: IMPROVE 每周引用追踪 cron
|
||||
|
||||
庞统每周日 06:00 执行引用追踪(周日选活动量最低的时段):
|
||||
|
||||
| 角色 | agentId | cron 表达式 | 时区 |
|
||||
|------|---------|-----------|------|
|
||||
| 庞统 | pangtong-fujunshi | `0 6 * * 0` | Asia/Shanghai |
|
||||
|
||||
**Cron 配置**:
|
||||
|
||||
```json
|
||||
{
|
||||
"schedule": { "kind": "cron", "expr": "0 6 * * 0", "tz": "Asia/Shanghai" },
|
||||
"sessionTarget": "isolated",
|
||||
"agentId": "pangtong-fujunshi",
|
||||
"payload": {
|
||||
"kind": "agentTurn",
|
||||
"message": "IMPROVE 每周引用追踪 cron。请执行:\n1. read ~/.sanguo_projects/sanguo_mozi/skills/skill-management/references/improve.md\n2. 按 improve.md 步骤执行:\n a. 扫描过去 7 天所有 agent 的 session JSONL,采集 Skill 引用信号\n b. 生成淘汰候选报告(30 天无引用的 Skill)\n c. 庞统审阅决策:quarantine / 保留观察 / 更新后保留\n d. 经验提升检查(被频繁引用 >= 5 次的 Skill)\n e. 反馈知识缺口到 knowledge-gaps.md",
|
||||
"timeoutSeconds": 1800
|
||||
},
|
||||
"delivery": { "mode": "announce" }
|
||||
}
|
||||
```
|
||||
|
||||
**设计要点**:
|
||||
- `delivery.mode: "announce"`:淘汰/提升报告投递到 Control UI
|
||||
- `timeoutSeconds: 1800`(30 分钟):全量 JSONL 扫描是最重的操作
|
||||
- 每周一次频率足够——Skill 引用变化不会很快
|
||||
- 淘汰决策通过 skill_workshop quarantine 执行,提升决策通过手动编辑 AGENTS.md
|
||||
|
||||
### Cron 创建方式
|
||||
|
||||
使用 openclaw cron 工具创建。**不是 moziplus 管理**,而是直接在 openclaw 层面配置。
|
||||
|
||||
创建顺序:先 S3(L1),再 S4(L2),最后 S5(IMPROVE)。
|
||||
|
||||
创建后验证:`cron(action=list)` 确认所有 job 存在且 enabled=true。
|
||||
|
||||
### 去重和幂等
|
||||
|
||||
每个 cron job 的 name 包含 agent 名,避免命名冲突:
|
||||
- `l1-distill-zhangfei`
|
||||
- `l1-distill-guanyu`
|
||||
- `l1-distill-zhaoyun`
|
||||
- `l1-distill-simayi`
|
||||
- `l1-distill-pangtong`
|
||||
- `l1-distill-jiangwei`
|
||||
- `l2-review-pangtong`
|
||||
- `improve-weekly-pangtong`
|
||||
|
||||
创建前先 `cron(action=list)` 检查同名 job 是否已存在,避免重复创建。
|
||||
|
||||
## 11B. 一致性偏差修复清单(S6 补充)
|
||||
|
||||
§19 设计-实现一致性检查(2026-06-18)发现以下偏差,列入 S6 一并修复:
|
||||
|
||||
| # | 偏差 | 严重度 | 修复方式 |
|
||||
|---|------|--------|--------|
|
||||
| B4 | ticker.py:336-348 ExperienceDistiller 调用未移除 | 中 | 移除 experience_distiller 参数和调用,日志改为 debug 级空转提示 |
|
||||
| B5 | skill_system.py / experience.py 未标记 deprecated | 低 | 文件头部加 `# DEPRECATED — §19 重设计,不再参与 skill 发现/加载` 注释 |
|
||||
| B6 | SELF_IMPROVEMENT_REMINDER.md 引用残留 | 低 | AGENTS.md 中已标注废弃,但 system prompt 仍注入。从 workspace 文件列表中移除该文件 |
|
||||
|
||||
**B4 修复细节**:
|
||||
|
||||
ticker.py 构造函数 `__init__` 接受 `experience_distiller` 参数(默认 None),tick() 中第 336-348 行有条件调用。修复方式:
|
||||
- 保留参数(向后兼容),但条件块内加 `logger.debug("ExperienceDistiller deprecated per §19, skipping")` 后直接 return
|
||||
- 不删除代码(P3 级清理时再做物理删除)
|
||||
|
||||
**B6 修复细节**:
|
||||
|
||||
当前 Project Context 中注入了 `SELF_IMPROVEMENT_REMINDER.md` 的内容。该文件在 workspace-pangtong 中已不存在(被删),但 system prompt 模板仍引用它。修复方式:确认文件不存在即可——openclaw 会跳过不存在的注入文件。实际已无影响,标注为 resolved。
|
||||
|
||||
## 12. wiki-vault / 知识库参考实践映射
|
||||
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
---
|
||||
title: "Issue-Centric Orchestration — Gitea Issue 替代黑板 DB 协作面"
|
||||
created: 2026-06-19
|
||||
version: v2.1 draft
|
||||
status: draft
|
||||
changelog: v2.1 修正 M1(dispatcher 直接 SQL 声明)+ M2(Phase 格式)+ S1/S2(TaskAdapter 残留清理)
|
||||
v2.0 纳入姜维+司马懿 Review 反馈 + 庞统 Repository 模式修正
|
||||
v1.1 纳入姜维 Review 反馈
|
||||
v1.0 初版
|
||||
---
|
||||
|
||||
# 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 包含 14 张表:
|
||||
|
||||
| 表 | 用途 | 分类 |
|
||||
|---|------|------|
|
||||
| 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 队列 | 执行面 |
|
||||
| experience_tags | 经验标签 | 执行面(§19 已标记废弃) |
|
||||
| agents | Agent 注册信息 | 执行面 |
|
||||
|
||||
**协作面**(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_state(轻量索引,替代 tasks 表): │
|
||||
│ issue_number → status, branch, retry │
|
||||
│ │
|
||||
│ 执行面表(不变): │
|
||||
│ reviews, checkpoints, decisions, │
|
||||
│ observations, experiences, │
|
||||
│ routing_decisions, task_attempts │
|
||||
│ │
|
||||
│ 调度逻辑(不变): │
|
||||
│ ticker → 扫 task_state → 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_state 内部维护 | Issue open/closed 只表示生命周期 |
|
||||
| tasks.priority | Issue label (priority/P0-P3) | label 模拟 |
|
||||
| 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_state 内部维护 | 执行面数据 |
|
||||
| comments | Issue comment | 直接对应 |
|
||||
| outputs | 分支 commit(代码/文档)+ Issue comment(摘要) | 成果物载体迁移 |
|
||||
| events | webhook | 主动推送替代 SSE |
|
||||
|
||||
### 3.3 数据访问层改造:Repository 模式换底
|
||||
|
||||
**设计原则**:改造现有 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(执行状态)
|
||||
```
|
||||
|
||||
上层代码**基本**不用改。但有一个前置条件:dispatcher.py 中存在约 20 处直接操作 tasks 表的 SQL(绕过 Queries 类,如 `SELECT assignee FROM tasks`、`UPDATE tasks SET status=?`)。这些直接 SQL 需要先迁移到 Queries 方法调用,才能实现 Repository 换底。此项作为 Phase 1 的前置工作。
|
||||
|
||||
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_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)
|
||||
);
|
||||
```
|
||||
|
||||
**为什么不用 TaskAdapter**:Repository 内部已经把 Gitea 数据 + 本地执行状态合并成 Task 对象返回。上层调用 `queries.get_task(issue_number)` 得到的 Task 对象和现在一模一样——有 title、有 description、有 status。不需要额外 adapter 层。
|
||||
|
||||
---
|
||||
|
||||
## §4. 流程设计
|
||||
|
||||
### 4.1 创建 Task
|
||||
|
||||
```
|
||||
① 庞统/主公在 Gitea 创建 Issue + 指派 agent
|
||||
② Gitea webhook: issues/assigned
|
||||
③ daemon toolchain handler 收到 webhook
|
||||
→ 在 task_state 插入一行(issue_number, repo, assignee, status=pending)
|
||||
④ ticker 扫 task_state 发现 pending → dispatch → spawn agent
|
||||
```
|
||||
|
||||
**和现在的区别**: 当前是庞统在黑板 API 创建 task。改造后是庞统在 Gitea 创建 Issue,webhook 自动触发 daemon 建索引。
|
||||
|
||||
### 4.2 执行 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_state status=working
|
||||
```
|
||||
|
||||
### 4.3 审查
|
||||
|
||||
```
|
||||
① agent 编码完成 → push 到分支 → 创建 PR
|
||||
② Gitea webhook: pull_request/opened
|
||||
→ daemon 更新 task_state status=review
|
||||
③ Reviewer 在 Gitea 做 PR Review
|
||||
④ Gitea webhook: pull_request_review
|
||||
→ daemon 根据 Review 结果更新 task_state
|
||||
⑤ Review 通过 → PR merge
|
||||
→ Gitea 自动关闭 Issue
|
||||
→ Gitea webhook: issues/closed
|
||||
→ 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 失败处理
|
||||
|
||||
```
|
||||
① 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_state` 中 status=pending 的记录(替代扫黑板 tasks 表)
|
||||
- 扫 `task_state` 中 status=working 但超时的记录
|
||||
- 如果发现 Gitea Issue 已 closed 但 task_state 还是 working → 更新为 done
|
||||
|
||||
---
|
||||
|
||||
## §5. daemon 需要改的地方
|
||||
|
||||
**原则: daemon 调度逻辑不变,只改数据访问层。**
|
||||
|
||||
### 5.1 数据访问层改造
|
||||
|
||||
| 模块 | 现在 | 改造后 | 影响范围 |
|
||||
|------|------|-------|---------|
|
||||
| `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 ⚠️ 需要讨论的改造点
|
||||
|
||||
以下是因为数据源变了,daemon 实现需要调整的地方:
|
||||
|
||||
**P1: spawner 每次 spawn 都要调 Gitea API 读 Issue body?**
|
||||
|
||||
当前 spawner 从 SQLite 读 task description(微秒级)。改为从 Gitea API 读(毫秒级,HTTP 请求)。
|
||||
|
||||
- **方案 A**: 每次 spawn 时实时调 Gitea API。简单但慢
|
||||
- **方案 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: Repository 内部数据合并**
|
||||
|
||||
Queries 类内部改造——`get_task()` 方法从 Gitea API(或缓存)读 title/body,从 task_state 表读 status/retry_count,合并成 Task 对象返回。上层(dispatcher/spawner)无感知。
|
||||
|
||||
这是标准的 Repository 模式——接口不变(`get_task(id)` 返回 Task 对象),实现换底(从 SQLite 单源改为 Gitea + SQLite 双源)。不新增 TaskAdapter 类——Queries 类本身就是 Repository,内部合并是职责内的事。
|
||||
|
||||
**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
|
||||
- **⚠️ 适配层**: 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 实施时确定。
|
||||
|
||||
---
|
||||
|
||||
## §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 职责不变。Issue-centric 模式下 agent 不用 Mail(通过 Issue/PR comment 协作)。Mail 剩余职责:非 Gitea 相关的 agent 间点对点通知(如庞统通知赵云准备数据) |
|
||||
| 不改前端(本阶段) | 前端改造独立于后端,后续设计 |
|
||||
| 不做存量 task 退役 | 原 task 流程和 Issue 流程可共存,原 task 自然退役 |
|
||||
| 不改 experiences/checkpoints/decisions 表 | 执行面表保留在 daemon,不受影响 |
|
||||
|
||||
---
|
||||
|
||||
## §9. 实施路径
|
||||
|
||||
| 阶段 | 内容 | 依赖 |
|
||||
|------|------|------|
|
||||
| 阶段 | 内容 | 依赖 |
|
||||
|------|------|------|
|
||||
| Phase 0 | webhook 权限配置(主公手动配)+ CI status webhook 验证(确认 Gitea CI status 变化触发 webhook) | 设计 Review 通过 |
|
||||
| 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 |
|
||||
| Phase 5 | 验证 + 清理废弃的黑板协作面表 | Phase 4 |
|
||||
|
||||
每个 Phase 独立可验证,出问题可以回退。
|
||||
|
||||
---
|
||||
|
||||
## §10. 风险评估
|
||||
|
||||
| 风险 | 等级 | 缓解 |
|
||||
|------|------|------|
|
||||
| Gitea API 不可用时 daemon 完全瘫痪 | 中 | webhook 触发时缓存 Issue body + issue_updated_at 校验(P1 方案 B),减少运行时 Gitea API 依赖 |
|
||||
| Gitea webhook 丢失 | 低 | ticker 兜底扫描 |
|
||||
| task_state 和 Gitea Issue 状态不一致 | 中 | ticker 定期校验(发现 Issue closed 但 index 未更新则修复) |
|
||||
| spawner 性能下降(Gitea API 调用) | 低 | 方案 B 缓存 Issue body,spawn 时不调 Gitea API |
|
||||
| 原 task 流程和新 Issue 流程共存期混乱 | 中 | 可以限定只在特定项目中启用 Issue 模式,逐步切换 |
|
||||
@@ -0,0 +1,580 @@
|
||||
---
|
||||
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 | 黑板 API(action report/outputs)+ Gitea API(Issue/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 Review(APPROVE/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"
|
||||
- "创建 PR(body 引用 Issue:Closes #{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"
|
||||
- "创建 PR(body 引用 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"
|
||||
- "创建 PR(body 说明根因和修复方式)"
|
||||
- "等 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}"
|
||||
- "重构 + 确保现有测试不 break(python -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_assigned:action_type=issue_assigned + business_type 从 label 确定
|
||||
- ci_failure:action_type=ci_failure(无 business_type,CI 失败就是 CI 失败)
|
||||
- review_result:action_type=review_result(无 business_type,Review 就是 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-data,PR #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 Review(Gitea 原生,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. Issue closed 事件处理
|
||||
|
||||
### 11.1 问题
|
||||
|
||||
当前 `_handle_issues` 只处理 `action == "assigned"`,不处理 `action == "closed"`。Issue 被关闭时:
|
||||
- daemon 不感知(webhook `issues/closed` 被忽略)
|
||||
- 创建者 / 关注者收不到通知
|
||||
- 如果该 Issue 对应一个活跃的 task,daemon 不知道 Issue 已关闭
|
||||
|
||||
### 11.2 设计
|
||||
|
||||
`_handle_issues` 增加 `action == "closed"` 分支:
|
||||
|
||||
**谁被通知**:Issue 创建者(`issue.user.login`)。
|
||||
|
||||
**通知内容**(通过 toolchain task 发给创建者):
|
||||
- Issue 标题 + 编号
|
||||
- 关闭者(`payload.sender.login`)
|
||||
- 关闭时间
|
||||
- Issue 上最后一个 comment 的摘要(修复说明)
|
||||
|
||||
**通知类型**:纯通知(event_type=issue_closed,verify auto-pass,和 review_merged 一样)。
|
||||
|
||||
**特殊情况**:
|
||||
- 如果关闭者是创建者自己(自己关自己创建的),不通知(避免自环)
|
||||
- 如果 Issue 没有创建者信息或创建者不是已知 agent,跳过
|
||||
|
||||
### 11.3 实现伪代码
|
||||
|
||||
```python
|
||||
# _handle_issues 中新增
|
||||
if action == "closed":
|
||||
issue_creator = issue.get("user", {}).get("login", "")
|
||||
closed_by = payload.get("sender", {}).get("login", "")
|
||||
|
||||
# 自己关自己创建的,不通知
|
||||
if issue_creator == closed_by:
|
||||
return
|
||||
|
||||
# 只通知已注册的 agent
|
||||
if issue_creator not in AGENT_IDS:
|
||||
return
|
||||
|
||||
# 读取最后一个 comment 作为修复摘要
|
||||
comments = issue.get("comments", 0)
|
||||
last_comment_summary = "(无 comment)"
|
||||
# 可选:调 Gitea API 读最后一个 comment
|
||||
|
||||
title = f"Issue 已关闭: {issue_title} ({repo}#{issue_number})"
|
||||
description = f"Issue {repo}#{issue_number} 已被 {closed_by} 关闭。\n\n{last_comment_summary}"
|
||||
|
||||
_send_toolchain_task(
|
||||
to_agent=issue_creator,
|
||||
title=title,
|
||||
description=description,
|
||||
event_type="issue_closed",
|
||||
action_type="issue_closed",
|
||||
steps=[], # 纯通知,无步骤
|
||||
context_data={
|
||||
"issue_number": issue_number,
|
||||
"repo": repo,
|
||||
"issue_title": issue_title,
|
||||
"closed_by": closed_by,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 11.4 _ACTION_HINTS 新增
|
||||
|
||||
```python
|
||||
"issue_closed": "你创建的 Issue 已被关闭。这是一条纯通知,阅读即可。",
|
||||
```
|
||||
|
||||
### 11.5 EVENT_LABELS_ZH 新增
|
||||
|
||||
```python
|
||||
"issue_closed": "Issue 已关闭",
|
||||
```
|
||||
|
||||
### 11.6 verify_completion
|
||||
|
||||
issue_closed 走 auto-pass(和 review_merged 一样),纯通知不需要 agent 动作。
|
||||
|
||||
### 11.7 涉及改动
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/api/toolchain_routes.py` `_handle_issues` | 新增 `action == "closed"` 分支 |
|
||||
| `src/daemon/toolchain_handler.py` `_ACTION_HINTS` | 新增 issue_closed |
|
||||
| `src/daemon/toolchain_handler.py` `EVENT_LABELS_ZH` | 新增 issue_closed |
|
||||
| `src/daemon/toolchain_handler.py` `verify_completion` | issue_closed auto-pass |
|
||||
| `templates/toolchain/issue_closed.md` | 新建通知模板 |
|
||||
| `tests/` | 新增 closed 事件测试 |
|
||||
|
||||
---
|
||||
|
||||
## §12. 不做的事
|
||||
|
||||
| 不做 | 理由 |
|
||||
|------|------|
|
||||
| 不改 task handler | task 流程不变(§20 设计中的 task 逐步迁移到 Issue 是后续工作) |
|
||||
| 不改 mail | mail 职责不变 |
|
||||
| 不改 dispatcher/ticker 核心逻辑 | 调度逻辑不变(§20 Phase 1 的 dispatcher SQL 迁移是前置工作) |
|
||||
| 不做前端改造 | 后续独立设计 |
|
||||
| 不改 experiences/checkpoints/decisions 表 | 执行面表保留在 daemon |
|
||||
+57
-32
@@ -778,9 +778,8 @@ def _send_deploy_failure_task(repo: str, pr_number: int, pr_title: str, reason:
|
||||
action_type="deploy_failure",
|
||||
steps=[
|
||||
"检查 deploy 日志",
|
||||
"排查失败原因",
|
||||
"修复并重新部署",
|
||||
"提交 action report(POST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/comments,comment_type=action_report)",
|
||||
"根据 deploy 日志判断失败原因类型:\n a. 代码/配置问题(rsync 路径错、依赖缺失、启动失败)→ 修复 → 重新部署\n b. 基础设施问题(Gitea 不可用、网络不通、磁盘满、SSH 故障)→ 在该仓库创建 Issue 指派 jiangwei-infra(见下方「需要创建 Issue 时」),label 必须包含 type/infrastructure",
|
||||
"提交 action report(POST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/comments,comment_type=action_report)— 报告中说明判断的原因类型和执行的操作",
|
||||
],
|
||||
context_data={
|
||||
"repo": repo,
|
||||
@@ -997,30 +996,58 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
|
||||
"brief": brief,
|
||||
})
|
||||
|
||||
title = f"Issue 指派: {issue_title} ({repo}#{issue_number})"
|
||||
_send_toolchain_task(
|
||||
to_agent=assignee,
|
||||
title=title,
|
||||
description=text,
|
||||
event_type="issue_assigned",
|
||||
action_type="issue_assigned",
|
||||
steps=[
|
||||
f"创建分支 fix/{issue_number}-{brief}",
|
||||
"编码 + 写 UT",
|
||||
"push → 等 CI",
|
||||
f"CI 通过后创建 PR(Gitea API: POST /repos/{repo}/pulls)",
|
||||
"等 Review",
|
||||
# 检查是否是基础设施 Issue(按 label 分流)
|
||||
is_infrastructure = any("infrastructure" in lbl.lower() for lbl in labels_list)
|
||||
|
||||
if is_infrastructure:
|
||||
infra_steps = [
|
||||
"根据 Issue body 中的错误来源和日志片段排查问题",
|
||||
"修复基础设施问题(如修复 CI runner 环境、恢复网络、重启服务等)",
|
||||
"修复后在 Issue 上 comment 说明修复方式和结果",
|
||||
"提交 action report(POST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/comments,comment_type=action_report)",
|
||||
],
|
||||
context_data={
|
||||
"issue_number": issue_number,
|
||||
"repo": repo,
|
||||
"issue_title": issue_title,
|
||||
"labels": labels,
|
||||
"issue_body": issue_body or "(无描述)",
|
||||
"brief": brief,
|
||||
},
|
||||
)
|
||||
]
|
||||
title = f"基础设施 Issue: {issue_title} ({repo}#{issue_number})"
|
||||
_send_toolchain_task(
|
||||
to_agent=assignee,
|
||||
title=title,
|
||||
description=text,
|
||||
event_type="infrastructure_failure",
|
||||
action_type="infrastructure_failure",
|
||||
steps=infra_steps,
|
||||
context_data={
|
||||
"issue_number": issue_number,
|
||||
"repo": repo,
|
||||
"issue_title": issue_title,
|
||||
"labels": labels,
|
||||
"issue_body": issue_body or "(无描述)",
|
||||
"brief": brief,
|
||||
},
|
||||
)
|
||||
else:
|
||||
title = f"Issue 指派: {issue_title} ({repo}#{issue_number})"
|
||||
_send_toolchain_task(
|
||||
to_agent=assignee,
|
||||
title=title,
|
||||
description=text,
|
||||
event_type="issue_assigned",
|
||||
action_type="issue_assigned",
|
||||
steps=[
|
||||
f"在开发目录执行 git 操作:\n a. git checkout main && git pull origin main\n b. git checkout -b fix/{issue_number}-{brief}",
|
||||
"编码 + 写 UT",
|
||||
f"git add -A && git commit -m \"[moz] fix: {issue_title[:30]}\" && git push origin fix/{issue_number}-{brief}",
|
||||
f"CI 通过后创建 PR(Gitea API: POST /repos/{repo}/pulls,head: fix/{issue_number}-{brief}, base: main)",
|
||||
"等 Review",
|
||||
"提交 action report(POST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/comments,comment_type=action_report)",
|
||||
],
|
||||
context_data={
|
||||
"issue_number": issue_number,
|
||||
"repo": repo,
|
||||
"issue_title": issue_title,
|
||||
"labels": labels,
|
||||
"issue_body": issue_body or "(无描述)",
|
||||
"brief": brief,
|
||||
},
|
||||
)
|
||||
|
||||
elif action == "opened":
|
||||
if "部署失败" in issue_title:
|
||||
@@ -1043,9 +1070,8 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
|
||||
action_type="deploy_failure",
|
||||
steps=[
|
||||
"检查 deploy 日志",
|
||||
"排查失败原因",
|
||||
"修复并重新部署",
|
||||
"提交 action report(POST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/comments,comment_type=action_report)",
|
||||
"根据 deploy 日志判断失败原因类型:\n a. 代码/配置问题(rsync 路径错、依赖缺失、启动失败)→ 修复 → 重新部署\n b. 基础设施问题(Gitea 不可用、网络不通、磁盘满、SSH 故障)→ 在该仓库创建 Issue 指派 jiangwei-infra(见下方「需要创建 Issue 时」),label 必须包含 type/infrastructure",
|
||||
"提交 action report(POST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/comments,comment_type=action_report)— 报告中说明判断的原因类型和执行的操作",
|
||||
],
|
||||
context_data={
|
||||
"repo": repo,
|
||||
@@ -1126,9 +1152,8 @@ async def _handle_issue_comment(payload: Dict[str, Any]) -> None:
|
||||
action_type="ci_failure",
|
||||
steps=[
|
||||
"查看完整 CI 日志(PR 页面或 Gitea Actions 页面)",
|
||||
"修复失败的测试",
|
||||
"push → CI 自动重跑",
|
||||
"提交 action report(POST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/comments,comment_type=action_report)",
|
||||
"根据 CI 日志判断失败原因类型:\n a. 代码问题(lint/test 失败)→ 修复失败的测试 → push 到原分支 → CI 自动重跑\n b. 基础设施问题(runner 环境/Python/venv/Gitea/网络故障)→ 在该仓库创建 Issue 指派 jiangwei-infra(见下方「需要创建 Issue 时」),label 必须包含 type/infrastructure",
|
||||
"提交 action report(POST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/comments,comment_type=action_report)— 报告中说明判断的原因类型和执行的操作",
|
||||
],
|
||||
context_data={
|
||||
"pr_number": issue_number,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# DEPRECATED per §19 重设计 — 经验蒸馏改为双层 daily cron(L1 各 agent + L2 庞统)
|
||||
# 保留代码供参考,后续 P3 清理时物理删除
|
||||
"""Experience Distillation — 经验蒸馏
|
||||
|
||||
从已完成的任务产出中提取经验:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# DEPRECATED per §19 重设计 — 不再参与 skill 发现/加载
|
||||
# 实际 skill 发现走 openclaw 原生 <available_skills> 机制
|
||||
# 保留代码供参考,后续 P3 清理时物理删除
|
||||
"""Skill System — 技能注册、加载、匹配、执行
|
||||
|
||||
三层自由度:
|
||||
|
||||
@@ -1261,7 +1261,7 @@ curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_
|
||||
conn = get_connection(db_path)
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT id, title, status FROM tasks WHERE id=?", (
|
||||
"SELECT id, title, status, must_haves FROM tasks WHERE id=?", (
|
||||
task_id,)
|
||||
).fetchone()
|
||||
if not row:
|
||||
|
||||
+3
-18
@@ -332,25 +332,10 @@ class Ticker:
|
||||
except Exception as e:
|
||||
logger.warning("HealthChecker error for %s: %s", project_id, e)
|
||||
|
||||
# 9. 经验蒸馏(完成的 task 自动触发)
|
||||
# 9. 经验蒸馏 — DEPRECATED per §19, 双层 daily cron 替代
|
||||
# 保留参数向后兼容,不再执行逐任务蒸馏
|
||||
if self.experience_distiller:
|
||||
try:
|
||||
conn2 = get_connection(db_path)
|
||||
try:
|
||||
done_tasks = conn2.execute(
|
||||
"SELECT id FROM tasks WHERE status='done' AND updated_at > datetime('now', '-60 seconds')"
|
||||
).fetchall()
|
||||
finally:
|
||||
conn2.close()
|
||||
for row in done_tasks:
|
||||
t = Blackboard(db_path).get_task(row[0])
|
||||
if t:
|
||||
self.experience_distiller.distill_from_task(
|
||||
task_id=t.id, task_title=t.title, task_type=t.task_type
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"ExperienceDistiller error for %s: %s", project_id, e)
|
||||
logger.debug("ExperienceDistiller deprecated per §19, skipping (use L1/L2 daily cron)")
|
||||
|
||||
# 10. 扫描后状态
|
||||
result["summary_after"] = queries.task_summary()
|
||||
|
||||
@@ -166,6 +166,33 @@ class ToolchainApiSection:
|
||||
"",
|
||||
"⚠️ 不要使用 Mail API(飞鸽传书)。所有协作通过 Gitea 留痕。",
|
||||
"",
|
||||
"### 需要创建 Issue 时",
|
||||
"",
|
||||
"如果步骤中要求创建 Issue 指派他人(如 jiangwei-infra):",
|
||||
"```bash",
|
||||
f'curl -s -X POST "{_GITEA_BASE}/repos/{{repo}}/issues" \\',
|
||||
' -H "Authorization: token <your-token>" \\',
|
||||
' -H "Content-Type: application/json" \\',
|
||||
' -d \'{"title": "[moz] infra: 简述问题", "body": "## 问题描述\\n\\n<简要描述问题现象>\\n\\n## 错误来源\\n\\n- 仓库: <repo>\\n- PR/Commit: <链接>\\n- CI/Deploy run: <Gitea Actions 页面链接>\\n\\n## 日志关键片段\\n\\n```<错误日志摘要>```\\n\\n## 判断依据\\n\\n<为什么判断为基础设施问题>", "assignees": ["jiangwei-infra"], "labels": [<label_id>]}\'',
|
||||
"```",
|
||||
"",
|
||||
"⚠️ Issue body 必须包含错误来源链接(PR/Commit + CI run),让排查者能直接看到全貌。",
|
||||
"⚠️ label 数字 ID 先 GET /repos/{repo}/labels 查询 type/infrastructure 对应的 ID。",
|
||||
"",
|
||||
"### Git 操作说明",
|
||||
"",
|
||||
"你的工作目录是开发目录(如 ~/.openclaw/sanguo_projects/sanguo_moziplus_v2/)。",
|
||||
"标准分支操作流程:",
|
||||
"```bash",
|
||||
"git checkout main && git pull origin main # 从最新主干开始",
|
||||
"git checkout -b fix/{branch_name} # 创建功能分支",
|
||||
"# ... 写代码 ...",
|
||||
"git add -A && git commit -m 'message' # 提交改动",
|
||||
"git push origin {branch_name} # 推送到远程",
|
||||
"```",
|
||||
"",
|
||||
"⚠️ 不要在 main 分支上直接 commit。",
|
||||
"",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -218,6 +245,7 @@ class ToolchainConstraintsSection:
|
||||
'| “我已经知道了” | ❌ 知道不等于执行。执行步骤 + 提交 action report 才算完成 |',
|
||||
'| “步骤太多了,选几个做就行” | ❌ 错!必须逐条执行,不可跳过 |',
|
||||
'| “这个步骤不适用于当前情况” | ❌ 如果确实不适用,在 action report 中说明原因,但其他步骤必须执行 |',
|
||||
'| “CI/部署失败不是我代码的问题,我什么也不用做” | ❌ 错!即使是基础设施问题,你也必须创建 Issue 指派 jiangwei-infra(body 含错误来源链接 + 日志 + 判断依据),并在 action report 中说明。不能只报告“不是我的问题”就完事 |',
|
||||
"",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -523,3 +523,98 @@ class TestFullPromptBuild:
|
||||
# Must have constraints with Red Flags
|
||||
assert "Red Flags" in prompt
|
||||
assert "强制要求" in prompt
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# §17 v2: CI/deploy failure branching + issue label routing + Issue API guidance
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestCiFailureBranching:
|
||||
"""ci_failure steps should include a/b branching guidance."""
|
||||
|
||||
def test_ci_failure_steps_contain_branching(self):
|
||||
source_file = PROJECT_ROOT / "src" / "api" / "toolchain_routes.py"
|
||||
source = source_file.read_text()
|
||||
assert '基础设施问题' in source
|
||||
assert 'type/infrastructure' in source
|
||||
assert 'jiangwei-infra' in source
|
||||
|
||||
|
||||
class TestDeployFailureBranching:
|
||||
"""deploy_failure steps should include a/b branching guidance."""
|
||||
|
||||
def test_deploy_failure_steps_contain_branching(self):
|
||||
source_file = PROJECT_ROOT / "src" / "api" / "toolchain_routes.py"
|
||||
source = source_file.read_text()
|
||||
count = source.count('基础设施问题(Gitea 不可用')
|
||||
assert count >= 2, f'Expected >=2 deploy_failure branching, found {count}'
|
||||
|
||||
|
||||
class TestIssueAssignedLabelRouting:
|
||||
"""issue_assigned handler should route by type/infrastructure label."""
|
||||
|
||||
def test_label_check_in_source(self):
|
||||
source_file = PROJECT_ROOT / "src" / "api" / "toolchain_routes.py"
|
||||
source = source_file.read_text()
|
||||
assert 'is_infrastructure' in source
|
||||
assert 'infrastructure_failure' in source
|
||||
assert '基础设施 Issue' in source
|
||||
|
||||
def test_normal_issue_keeps_coding_steps(self):
|
||||
source_file = PROJECT_ROOT / "src" / "api" / "toolchain_routes.py"
|
||||
source = source_file.read_text()
|
||||
assert 'git checkout -b fix/' in source
|
||||
assert 'issue_assigned' in source
|
||||
|
||||
|
||||
class TestToolchainApiIssueGuidance:
|
||||
"""ToolchainApiSection should include Issue creation guidance."""
|
||||
|
||||
def test_has_issue_creation_section(self):
|
||||
source_file = PROJECT_ROOT / "src" / "daemon" / "toolchain_handler.py"
|
||||
source = source_file.read_text()
|
||||
assert "需要创建 Issue 时" in source
|
||||
assert "/issues" in source
|
||||
assert "jiangwei-infra" in source
|
||||
assert "type/infrastructure" in source
|
||||
|
||||
def test_issue_body_template_mentions_required_fields(self):
|
||||
source_file = PROJECT_ROOT / "src" / "daemon" / "toolchain_handler.py"
|
||||
source = source_file.read_text()
|
||||
assert "错误来源" in source
|
||||
assert "判断依据" in source
|
||||
|
||||
|
||||
class TestRedFlagsInfrastructure:
|
||||
"""Red Flags should include the 'not my code' entry."""
|
||||
|
||||
def test_has_infrastructure_red_flag(self):
|
||||
source_file = PROJECT_ROOT / "src" / "daemon" / "toolchain_handler.py"
|
||||
source = source_file.read_text()
|
||||
assert "不是我代码的问题" in source
|
||||
assert "基础设施问题" in source
|
||||
|
||||
|
||||
class TestGitOperationGuidance:
|
||||
"""ToolchainApiSection should include Git operation guidance."""
|
||||
|
||||
def test_has_git_operation_section(self):
|
||||
source_file = PROJECT_ROOT / "src" / "daemon" / "toolchain_handler.py"
|
||||
source = source_file.read_text()
|
||||
assert "Git 操作说明" in source
|
||||
assert "git checkout main" in source
|
||||
assert "git pull origin main" in source
|
||||
assert "git checkout -b" in source
|
||||
|
||||
def test_has_no_main_commit_warning(self):
|
||||
source_file = PROJECT_ROOT / "src" / "daemon" / "toolchain_handler.py"
|
||||
source = source_file.read_text()
|
||||
assert "不要在 main 分支上直接 commit" in source
|
||||
|
||||
def test_issue_assigned_steps_have_git_commands(self):
|
||||
source_file = PROJECT_ROOT / "src" / "api" / "toolchain_routes.py"
|
||||
source = source_file.read_text()
|
||||
assert 'git checkout main && git pull origin main' in source
|
||||
assert 'git checkout -b fix/' in source
|
||||
assert 'git add -A && git commit' in source
|
||||
assert 'git push origin fix/' in source
|
||||
|
||||
Reference in New Issue
Block a user