diff --git a/docs/design/07-spawner-acquire-first.md b/docs/design/07-spawner-acquire-first.md index ce846e9..8e39df0 100644 --- a/docs/design/07-spawner-acquire-first.md +++ b/docs/design/07-spawner-acquire-first.md @@ -233,22 +233,21 @@ def _revive_session(agent_id: str) -> bool: pass ``` -### 4.5 O5: compact 扫描条件 — 去掉 status 过滤(2026-06-05 修订) +### 4.5 O5: compact 扫描条件收紧 -**原设计**:只在 status=running 时扫描 compact,理由是"其他状态不需要检查"。 +当前 compact 扫描在 status 非 idle/done/unknown/None 时都触发,范围过宽。 -**Bug 发现**(2026-06-05):done 状态下 compaction 完全可能正在进行(openclaw turn 结束后自动触发 fromHook compaction)。此时 spawner spawn 新 agent → 撞上 compact 中的 session → exit=1 crash。实测 case:mail-1780656469707。 - -**修正方案**:去掉 status 前置条件,对所有状态都检测: +**改后**:只在 status 为 running 或 compacting 相关时扫描: ```python -# compact 检测:对所有状态都扫描 -# done 状态下 compaction 可能正在进行,不能跳过 -if sf: +# 只在这些状态下检查 compact +if result["status"] in ("running",) and sf: result["recent_compact"] = AgentSpawner._check_recent_compaction_jsonl(sf) ``` -代价:每次 _check_session_state 都读 jsonl 末尾 50KB。但 Phase 2 已在 counter 锁内执行,30 秒才一次,I/O 开销可忽略。 +注:Gateway 的 sessions.json status 实际值主要是 `idle/running/timeout/failed`。 +`running` 时检查 compact 有意义(agent turn 执行中可能触发 compact)。 +其他状态不需要检查。 ## 五、改动范围 diff --git a/docs/design/08-classify-outcome-optimization.md b/docs/design/08-classify-outcome-optimization.md index 77ce066..6402885 100644 --- a/docs/design/08-classify-outcome-optimization.md +++ b/docs/design/08-classify-outcome-optimization.md @@ -205,15 +205,10 @@ if cls.get("cooldown_seconds"): **方案**:在 `_do_retry` 开始时设 cooldown,cooldown 期间 `asyncio.sleep`,然后继续 spawn。这样不需要改 counter 逻辑。 -### 3.7 stderr 记录增强 — 已实施(2026-06-05) +### 3.7 stderr 记录增强 — 暂缓 -非正常完成(outcome ≠ completed)时,在 task_attempts metadata 中追加: -- `stderr_preview`:stderr 末尾 1000 字符(tail,关键错误在末尾) -- `exit_signal`:信号名映射(SIGINT/SIGKILL/SIGTERM/SIGFPE/SIGSEGV),仅 exit_code > 128 时 - -不改表结构,不存文件。`log_path` 字段保留但未启用。 - -评审:司马懿通过。 +stderr_preview 和 exit_signal 写 metadata 的方案暂缓,本轮不实施。 +已有 metadata 记录 fallback_count、api_retry_count、retry_count 等计数器。 ## 4. Registry 清理 — 最终决策:逻辑删除 + 归档,不做物理删除 @@ -312,3 +307,36 @@ if cls.get("cooldown_seconds"): - 新增 §3.6 cooldown 参数汇总 - 新增 §4 Registry 清理 - 新增 §5 并发控制一致性检查(TODO 标记) + +## 9. v2.1 修订:失败处理一致性修复(2026-06-06) + +> 审计发现设计文档描述的 failed 处理在代码中有 3 处 gap,已修复并通过司马懿评审。 + +### 9.1 发现的问题 + +| BUG | 设计要求 | 代码实际 | 影响 | +|-----|---------|---------|------| +| BUG-1 | A6 auth_failed / A11 agent_error → 标 failed + 原因写黑板 | 只设 cooldown 300s,任务留 working 等 ticker 30 分钟 | 不可恢复错误白等 30 分钟 | +| BUG-2 | Mail 幻觉门控无回复 → 标 failed(no_reply_found) | 留 working 等 ticker recheck | Agent 未回复 Mail 卡 30 分钟 | +| BUG-3 | 所有设计只描述到标 failed 为止 | 标完 failed 无任何通知 | failed 任务无人关注 | + +### 9.2 修复内容 + +**F1: auth_failed + agent_error → 立刻标 failed**(spawner.py:855-860) + +在 `_handle_exit()` "其他"分支中,cooldown 之后、`_do_on_complete_async` 之前,新增不可恢复 outcome 立刻标 failed 逻辑。 + +**F2: 所有 failed → comment @pangtong-fujunshi**(spawner.py:1456-1470) + +在 `_mark_task()` 中 conn.close() 之后,检查 `status == "failed"` 时通过 Blackboard comment + mention_queue 通知庞统。不走 Mail,走黑板 comment + mention 标准流程。 + +**F3: Mail 幻觉门控失败 → 立刻标 failed**(dispatcher.py:679-707) + +在 `_mail_auto_complete()` 中,无回复时用与标 done 同样的重试模式(3 次尝试)标 failed。不 retry(Agent turn 已结束,同样 prompt 大概率同样结果)。 + +### 9.3 不改的 + +- compact 检测:Fix-1 session jsonl 扫描是主要防线,Phase 2 无上限 skip + Monitor B2 最多 31.5 分钟,合理 +- compact_failed (有JSON+stderr):暂缓(08 §1.1 已标注) +- agent_error 不改为 retry:未知错误 retry 无意义 +- api_error:当前推回 pending + cooldown 是对的 diff --git a/docs/design/13-toolchain-and-dev-workflow.md b/docs/design/13-toolchain-and-dev-workflow.md index a3881d3..c36b972 100644 --- a/docs/design/13-toolchain-and-dev-workflow.md +++ b/docs/design/13-toolchain-and-dev-workflow.md @@ -1057,6 +1057,29 @@ Text: | S3 | 建议 | 缺 Bug 确认模板 | §15.5 新增模板 6(Bug 确认通知) | - §14 待讨论更新:已确认事项标记,新增 Webhook 模块待讨论项 +### v2.0 实施记录(2026-06-06) + +| # | 实施项 | 状态 | 备注 | +|---|--------|------|------| +| 1 | Gitea Actions 能力调研 | ✅ | docs/research/gitea-actions-research.md | +| 2 | Mail API 回复能力调研 | ✅ | docs/research/mail-reply-research.md | +| 3 | 路径硬编码修复 | ✅ | 4处改为环境变量,411 tests passed | +| 4 | CI workflow 编写 | ✅ | ci.yml + deploy.yml + e2e.yml | +| 5 | Skill 文件编写 | ✅ | 7个共通技能 | +| 6 | Gitea 仓库创建 | ✅ | sanguo/moziplus-v2(姜维) | +| 7 | act-runner 部署 | ✅ | arm64 裸机 + LaunchAgent(姜维) | +| 8 | 分支保护配置 | ✅ | main: CI通过+1人Review+禁止force push(姜维) | +| 9 | Webhook 配置 | ✅ | 已配未启用(等 daemon 集成)(姜维) | +| 10 | CI Secret 配置 | ✅ | CI_TOKEN(注意:Gitea 保留 GITEA_* 前缀) | +| 11 | Workflow 变量前缀验证 | ✅ | gitea.* 正确,无需修改 | +| 12 | Mail 模板验证 | ✅ | 5项能力全验证通过 | +| 13 | PR #1 创建 | ✅ | feat/initial-setup → main | +| 14 | 开发→安装目录同步 | ✅ | diff 确认一致 | +| - | Webhook daemon 集成 | ⏳ | 需改 daemon 代码,暂不做 | +| - | Mail 模板自动发送(Webhook 触发) | ⏳ | 依赖 Webhook daemon 集成 | +| - | act-runner LaunchAgent 网络就绪优化 | ⏳ | 偶尔 gRPC 连接失败 | +| - | Gitea 升级到 v1.24+ | 💡 | 建议,非必须 | + --- ## §13. 项目维度组织 diff --git a/docs/design/agent-api-contract.md b/docs/design/agent-api-contract.md new file mode 100644 index 0000000..d80d6ec --- /dev/null +++ b/docs/design/agent-api-contract.md @@ -0,0 +1,198 @@ +# Agent-Backend API 契约 (v2.6) + +Agent 通过 HTTP API 与黑板交互。本文档是 prompt 模板和后端路由的共同 Source of Truth。 + +## 通用约定 + +- Base URL: `http://{api_host}:{api_port}` +- Content-Type: `application/json` +- 错误响应格式(422/409): + ```json + { + "error": "error_code", + "detail": "人类+Agent 可读的错误描述", + "valid_values": { "field": ["allowed", "values"] }, + "hint": "修复建议" + } + ``` + +--- + +## 1. POST /api/projects/{project_id}/tasks/{task_id}/outputs + +写入产出物。 + +### 请求体 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `agent` | string | ✅ | Agent ID(如 `zhangfei-dev`) | +| `type` | string | ✅ | 产出类型。合法值:`code`, `document`, `data`, `config`, `other` | +| `title` | string | ✅ | 产出标题(简要描述) | +| `content` | string | ⬜ | 产出内容(文本直传模式)。与 `content_path` 二选一 | +| `content_path` | string | ⬜ | 产出文件路径(路径引用模式)。与 `content` 二选一 | +| `summary` | string | ⬜ | 内容摘要 | +| `metadata` | object | ⬜ | 附加元数据 | + +### 行为 + +- 如果传了 `content` 而没传 `content_path`:后端自动将内容写入 `artifacts/{task_id}/{title}` 并填充 `content_path` +- `type` 的别名 `content_type` 也被接受(兼容),但优先用 `type` + +### 成功响应 (200) + +```json +{"ok": true, "output_id": 42} +``` + +### 错误响应 + +- **422**: 字段缺失或值不合法。`valid_values` 会列出合法值 +- **500**: 内部错误(不应出现,出现说明后端有 bug) + +### 示例 + +```bash +curl -X POST http://127.0.0.1:8083/api/projects/demo/tasks/task-001/outputs \ + -H 'Content-Type: application/json' \ + -d '{ + "agent": "zhangfei-dev", + "type": "code", + "title": "csv_sorter.py", + "content": "import csv\nimport argparse\n...", + "summary": "CSV排序小程序,支持按任意列降序/升序排序" + }' +``` + +--- + +## 2. POST /api/projects/{project_id}/tasks/{task_id}/status + +更新任务状态。 + +### 状态机 + +``` +pending ──→ claimed ──→ working ──→ review ──→ done + │ │ │ │ + │ │ │ └──→ pending(驳回重做) + │ │ ├──→ blocked ──→ pending + │ │ ├──→ failed ──→ pending(重试) + │ │ └──→ cancelled + │ ├──→ pending(超时回收) + │ └──→ cancelled + └──→ cancelled +``` + +### 请求体 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `status` | string | ✅ | 目标状态 | +| `agent` | string | ✅ | Agent ID | +| `detail` | string | ⬜ | 状态变更说明(失败原因、驳回理由等) | + +### Agent 常用转换 + +| 从 | 到 | 何时 | +|----|-----|------| +| claimed | working | 开始执行任务 | +| working | review | 完成任务,提交审查 | +| working | failed | 执行失败 | + +### 成功响应 (200) + +```json +{"ok": true, "old_status": "working", "new_status": "review"} +``` + +### 错误响应 + +- **409**: 状态转换不合法。响应会包含合法的目标状态列表 + +```json +{ + "error": "invalid_transition", + "detail": "Cannot transition from claimed to review", + "valid_transitions": {"claimed": ["working", "pending", "cancelled"]}, + "hint": "You must go through 'working' before 'review'" +} +``` + +### 示例 + +```bash +# 开始工作 +curl -X POST http://127.0.0.1:8083/api/projects/demo/tasks/task-001/status \ + -H 'Content-Type: application/json' \ + -d '{"status": "working", "agent": "zhangfei-dev"}' + +# 完成,提交审查 +curl -X POST http://127.0.0.1:8083/api/projects/demo/tasks/task-001/status \ + -H 'Content-Type: application/json' \ + -d '{"status": "review", "agent": "zhangfei-dev"}' + +# 失败 +curl -X POST http://127.0.0.1:8083/api/projects/demo/tasks/task-001/status \ + -H 'Content-Type: application/json' \ + -d '{"status": "failed", "agent": "zhangfei-dev", "detail": "无法连接数据库"}' +``` + +--- + +## 3. GET /api/projects/{project_id}/tasks/{task_id}?expand=all + +获取任务完整信息(含产出、评论、事件、审查)。 + +### 查询参数 + +| 参数 | 类型 | 说明 | +|------|------|------| +| `expand` | string | `all` 返回所有关联数据 | + +### 响应字段 + +包含任务基本信息 + 聚合字段: + +| 字段 | 说明 | +|------|------| +| `status` | 当前状态 | +| `outputs` | 产出列表 | +| `reviews` | 审查记录 | +| `events` | 事件时间线 | +| `comments` | 评论 | +| `review_status` | 审查状态(null/pending/approved/rejected/rebuttal) | + +--- + +## 4. POST /api/projects/{project_id}/tasks/{task_id}/comments + +添加评论(Agent fallback 时可用)。 + +### 请求体 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `author` | string | ✅ | 评论者 | +| `body` | string | ✅ | 评论内容 | + +--- + +## 5. GET /api/projects/{project_id}/tasks + +列出项目任务。 + +### 查询参数 + +| 参数 | 类型 | 说明 | +|------|------|------| +| `status` | string | 按状态过滤 | +| `assignee` | string | 按负责人过滤 | + +--- + +## 变更日志 + +| 日期 | 变更 | +|------|------| +| 2026-05-17 | 初始版本。基于端到端实测发现的问题制定 | diff --git a/docs/design/agent-integration-v2.6.md b/docs/design/agent-integration-v2.6.md new file mode 100644 index 0000000..a8afa36 --- /dev/null +++ b/docs/design/agent-integration-v2.6.md @@ -0,0 +1,344 @@ +# Agent 编排集成方案 — D1+D2+D4 + +> 版本: v2.6.1 +> 日期: 2026-05-17 +> 作者: 庞统 🐦 +> 状态: 待评审 + +## 1. 背景 + +v2.6 已完成 F1-F18 全部模块编码(301 测试通过),前端 Mock UI 已就绪。当前断层: + +- **Ticker** 在跑,但不调 Dispatcher/Spawner(只做依赖推进 + daemon_tick) +- **Spawner** 代码有,`spawn_full_agent()` 走 `openclaw agent` CLI,但从未被 Ticker 调用过 +- **Agent** 不知道 v2.0 API 的存在,无法读写黑板 +- **前端** 展示 mock 数据,未对接真实 API + +本文档设计:Ticker → Dispatcher → Spawner → Agent → 回写黑板的完整闭环。 + +## 2. 设计目标 + +1. **端到端可跑通**:用户通过 API 创建任务 → Agent 被调起 → Agent 执行 → Agent 回写产出 → 审查流水线触发 → 前端实时展示 +2. **最小改动**:复用现有 Spawner/Dispatcher 代码,只补 Ticker 的调度触发和 Agent 端的 API 调用 +3. **不破坏现有测试**:301 测试全部继续通过 +4. **渐进式上线**:先跑通 Full Agent(注册角色),Subagent 后续完善 + +## 3. 架构总览 + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Daemon (uvicorn) │ +│ │ +│ Ticker (30s) │ +│ ├─ 1. 依赖推进 (blocked → pending) ← 已有 │ +│ ├─ 2. 扫描 pending 任务 ← 新增 │ +│ ├─ 3. Dispatcher.decide() 决策 ← 新增调用 │ +│ ├─ 4. Spawner.spawn() 调起 Agent ← 新增调用 │ +│ └─ 5. 状态 pending → claimed/working ← 新增 │ +│ │ +│ API (FastAPI) │ +│ ├─ POST /api/projects/{id}/tasks ← 已有 │ +│ ├─ GET /api/projects/{id}/tasks/{tid}?expand=all ← 已有 │ +│ ├─ POST /api/projects/{id}/tasks/{tid}/outputs ← 已有 │ +│ └─ PATCH /api/projects/{id}/tasks/{tid}/status ← 已有 │ +│ │ +│ SSE (/api/events/stream) ← 已有 │ +└──────────────────────────────────────────────────────────────────┘ + │ + │ openclaw agent --agent {id} --message "..." + ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ OpenClaw Agent (独立 session) │ +│ │ +│ 1. 读任务:GET /api/projects/{pid}/tasks/{tid}?expand=all │ +│ 2. 执行任务:根据 task_type + description 自主完成 │ +│ 3. 写产出:POST /api/projects/{pid}/tasks/{tid}/outputs │ +│ 4. 更新状态:PATCH /api/projects/{pid}/tasks/{tid}/status │ +│ → working → review(触发审查流水线) │ +└──────────────────────────────────────────────────────────────────┘ +``` + +## 4. 详细设计 + +### 4.1 Ticker 集成调度(D4) + +**改动文件**: `src/daemon/ticker.py` 的 `_tick_project()` + +**当前流程**: +``` +tick → 依赖推进 → daemon_tick event → 结束 +``` + +**新增流程**: +``` +tick → 依赖推进 → 调度扫描 → daemon_tick event → 结束 + │ + ├─ 扫描所有 pending 任务 + ├─ 对每个 pending 任务调 Dispatcher.decide() + ├─ 如果是 FULL_AGENT → Spawner.spawn_full_agent() + ├ 同时将任务状态改为 claimed + └─ 如果是 LOCAL → 直接执行 +``` + +**关键约束**: +- 每个 tick 最多调度 `max_dispatch_per_tick` 个任务(默认 3,可配置) +- 只调度 `pending` 状态的任务(`claimed`/`working` 说明已有 Agent 在处理) +- 检查并发限制(`ActiveAgentCounter.can_acquire()`) +- dispatch 失败不阻塞 tick 循环 + +**代码改动(伪代码)**: +```python +# ticker.py _tick_project() 新增步骤 +async def _tick_project(self, project_id, project_info): + # ... 现有: 依赖推进 ... + + # 新增: 调度扫描 + if self.dispatcher and self.spawner: + await self._dispatch_pending(project_id, db_path) + + # ... 现有: daemon_tick event ... + +async def _dispatch_pending(self, project_id, db_path): + """扫描 pending 任务并调度""" + queries = Queries(db_path) + pending = queries.tasks_by_status("pending") + + dispatched = 0 + for task in pending[:self.max_dispatch_per_tick]: + try: + result = await self.dispatcher.dispatch(task, project_config={...}) + if result["status"] == "dispatched" and result["level"] in ("full", "escalate"): + # 标记为 claimed + self._transition_status(conn, task.id, "claimed", + agent="daemon", + detail={"dispatched_to": result["agent_id"], + "session_id": result.get("session_id")}) + dispatched += 1 + except Exception: + logger.exception("Dispatch failed for %s", task.id) +``` + +**Ticker 构造器新增参数**: +```python +def __init__(self, ..., dispatcher=None, spawner=None, max_dispatch_per_tick=3): + self.dispatcher = dispatcher + self.spawner = spawner + self.max_dispatch_per_tick = max_dispatch_per_tick +``` + +### 4.2 Spawner 对接 OpenClaw(D1) + +**改动文件**: `src/daemon/spawner.py` + +**现状分析**: `spawn_full_agent()` 已经调用 `openclaw agent` CLI,参数完全匹配: +```python +cmd = ["openclaw", "agent", + "--agent", agent_id, + "--session-id", session_id, + "--message", message, + "--json"] +``` + +`openclaw agent --help` 确认支持 `--agent`、`--session-id`、`--message`、`--json`。 + +**改动点**: + +1. **丰富 message 内容** — 当前 `_build_message()` 只传 title + description。需要补充: + - 项目 ID、任务 ID(让 Agent 知道调哪个 API) + - API 端点信息(让 Agent 知道怎么回写) + - `--deliver` 标志(可选,让 Agent 回复发送到频道) + +2. **新增 prompt 模板** — Agent 收到的 message 需要包含完整的执行指令: + +```python +SPAWN_PROMPT_TEMPLATE = """你收到一个 v2.6 黑板任务: + +项目: {project_id} +任务ID: {task_id} +标题: {title} +描述: {description} +类型: {task_type} +优先级: {priority} +必要条件: {must_haves} + +请按以下步骤执行: + +1. 了解任务需求,确认理解无误 +2. 执行任务(编码/回测/数据检查等) +3. 完成后,调用以下 API 写入产出: + POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/outputs + Body: {{"agent": "{agent_id}", "content": "<产出内容>", "content_type": "report"}} +4. 更新任务状态为 review: + PATCH http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/status + Body: {{"status": "review", "agent": "{agent_id}"}} +5. 如果遇到问题,更新状态为 failed: + PATCH http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/status + Body: {{"status": "failed", "agent": "{agent_id}", "detail": "<失败原因>"}} + +注意: +- API 地址: http://{api_host}:{api_port} +- 你可以通过 GET /api/projects/{project_id}/tasks/{task_id}?expand=all 查看完整任务信息 +- 产出写入后,审查流水线会自动触发 +""" +``` + +3. **配置化 API 地址** — 从 `config/default.yaml` 或环境变量读取: +```yaml +# config/default.yaml 新增 +daemon: + api_host: "127.0.0.1" + api_port: 8083 +``` + +### 4.3 Agent 执行回写流程(D2) + +**Agent 端无需代码改动** — Agent 本身已有 `exec` 工具可以调用 `curl`,也有 `web_fetch` 可以调 HTTP API。关键是在 spawn 的 prompt 里告诉 Agent: + +1. 任务详情(title、description、must_haves) +2. API 端点和调用方式 +3. 回写产出的具体步骤 + +**回写 API 已就绪**: + +| API | 方法 | 用途 | +|-----|------|------| +| `/api/projects/{id}/tasks/{tid}/outputs` | POST | 写入产出 | +| `/api/projects/{id}/tasks/{tid}/status` | PATCH | 更新状态 | +| `/api/projects/{id}/tasks/{tid}/comments` | POST | 添加评论 | +| `/api/projects/{id}/tasks/{tid}?expand=all` | GET | 查看完整信息 | + +**审查触发**: Agent 将状态改为 `review` 后,Ticker 在下次 tick 检测到 `review` 状态的任务,触发审查流水线(F12 + F13)。 + +### 4.4 审查流水线集成 + +**改动文件**: `src/daemon/ticker.py` 的 `_tick_project()` + +**新增逻辑**: +```python +# 检测 review 状态的任务,触发审查 +review_tasks = queries.tasks_by_status("review") +for task in review_tasks: + # 检查是否已有 review 记录 + reviews = bb.get_reviews(task.id) + if not reviews: + # 调度审查 Agent(司马懿) + await self.dispatcher.dispatch(task, action_type="review") +``` + +### 4.5 僵尸检测 + 超时处理 + +**已有基础**: F8 Health Monitor 有 zombie 检测逻辑。 + +**新增**: Ticker 在每次 tick 检查 `claimed`/`working` 状态超时的任务: +- `claimed` 超过 `claim_timeout`(默认 5 分钟)→ 重置为 `pending` +- `working` 超过 `task_timeout`(从任务 `deadline` 或默认 30 分钟计算)→ 标记 `failed` + +这部分复用现有 `_transition_status()` 方法。 + +## 5. 配置变更 + +```yaml +# config/default.yaml 新增字段 +daemon: + # Agent 调度 + max_dispatch_per_tick: 3 + claim_timeout_minutes: 5 + default_task_timeout_minutes: 30 + + # API(供 Agent 回写) + api_host: "127.0.0.1" + api_port: 8083 +``` + +## 6. main.py 启动集成 + +**改动文件**: `src/main.py` + +当前 Ticker 只接收 `registry` 参数。需要: +1. 创建 `Dispatcher` 实例(传入 registered_agents、spawner、counter) +2. 创建 `AgentSpawner` 实例 +3. 创建 `ActiveAgentCounter` 实例 +4. 将 dispatcher + spawner 传给 Ticker + +```python +# main.py lifespan 中 +from src.daemon.spawner import AgentSpawner +from src.daemon.dispatcher import Dispatcher +from src.daemon.counter import ActiveAgentCounter + +spawner = AgentSpawner(dry_run=False, agent_timeout=600) +counter = ActiveAgentCounter(max_global=5, max_per_agent=2) +dispatcher = Dispatcher( + registered_agents=["pangtong-fujunshi", "simayi-challenger", ...], + spawner=spawner, + counter=counter, +) + +ticker = Ticker( + registry=registry, + dispatcher=dispatcher, + spawner=spawner, + max_dispatch_per_tick=config.get("daemon", {}).get("max_dispatch_per_tick", 3), +) +``` + +## 7. 改动量评估 + +| 文件 | 改动类型 | 行数估计 | 风险 | +|------|---------|---------|------| +| `src/daemon/ticker.py` | 新增 `_dispatch_pending()` + 构造器参数 | +80 行 | 低(纯新增方法) | +| `src/daemon/spawner.py` | 新增 prompt 模板 + 配置读取 | +40 行 | 低 | +| `src/main.py` | 注入 dispatcher/spawner | +20 行 | 低 | +| `config/default.yaml` | 新增配置项 | +8 行 | 无 | +| `tests/test_ticker.py` | 新增调度测试 | +60 行 | 无 | + +**总改动**: ~210 行,全部是新增代码,不修改现有逻辑。 + +## 8. 不在本方案范围内 + +| 项目 | 原因 | +|------|------| +| Subagent spawn (sessions_spawn) | 留后续,先跑通 Full Agent | +| 前端对接真实 API | 依赖本方案完成后才有真实数据 | +| Checkpoint | v2.7 | +| Agent bootstrap prompt 定制 | 当前用通用模板,后续按角色定制 | + +## 9. 端到端流程示例 + +用户通过 API 创建一个回测任务: + +``` +1. POST /api/projects/quant-demo/tasks + → 任务入库,status=pending,assignee=zhangfei-dev + +2. Ticker tick(30s 内) + → 扫描 pending 任务 + → Dispatcher: assignee 是注册角色 → FULL_AGENT + → Spawner: openclaw agent --agent zhangfei-dev --message "..." + → 任务 status → claimed + +3. 张飞 Agent 收到 prompt + → 读取任务详情(GET expand=all) + → 执行回测 + → 写入产出(POST /outputs) + → 更新状态为 review + +4. Ticker 下次 tick + → 检测 review 状态任务 + → 调度司马懿审查 + → 司马懿 Agent 收到 prompt → 查看产出 → 写 review → APPROVE/REJECT + +5. 审查通过 → status → done + 审查驳回 → status → pending(重做) + +6. 前端 SSE 实时收到事件更新 +``` + +## 10. 验收标准 + +1. 通过 API 创建任务 → 30s 内 Agent 被调起 +2. Agent 能读取任务详情、执行、写入产出 +3. Agent 更新状态为 review → 审查 Agent 被调起 +4. 审查通过 → 任务完成 +5. 前端展示真实数据(非 mock) +6. 现有 301 测试全部继续通过 diff --git a/docs/design/agent-routing-redesign.md b/docs/design/agent-routing-redesign.md new file mode 100644 index 0000000..9c52b74 --- /dev/null +++ b/docs/design/agent-routing-redesign.md @@ -0,0 +1,518 @@ +# Agent 路由机制重设计方案 + +**版本**: v2.0 +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-17 +**状态**: 待评审 +**触发**: E2E 测试暴露 review 阶段派错 Agent(张飞被派去审查自己),根因是 Daemon 硬编码路由 +**评审**: 司马懿 + +--- + +## 1. 问题诊断 + +### 1.1 Bug 根因 + +任务生命周期中 `assignee` 只在执行阶段被设置(张飞 claim → assignee="zhangfei-dev")。到 review 阶段,`decide()` 走 Level 2:`task.assignee` 在注册列表中 → 又派给张飞。 + +### 1.2 更深层的问题 + +**Daemon 在做 AI 该做的决策。** v2.6 架构明确定义: + +| 维度 | v2.6 设计目标 | 当前实现 | +|------|-------------|---------| +| 决策者 | Agent(在黑板上自主决策) | Daemon(if-else 硬编码) | +| Daemon 角色 | 投递员(执行黑板上的决策) | 调度器(决定谁干什么) | +| 编排方式 | AI agent 在黑板上自主领活 | 配置表驱动(非 AI 判断) | + +T3-10 设计原文写着"配置表驱动非 AI 判断"——与 v2.6 核心原则矛盾。 + +--- + +## 2. 调研发现 + +### 2.1 学术前沿 + +| 来源 | 核心发现 | 对我们的价值 | +|------|---------|-------------| +| **bMAS** arXiv 2507.01701 | Control Unit(LLM 驱动)根据黑板当前内容动态选择 Agent | 路由本身可以是 LLM 调用,不是 if-else | +| **Self-Selection** arXiv 2510.01285 | 任务不显式分配,Agent 根据自己能力自主决定是否参与 | 最 AI Native 的模式,我们的演进目标 | +| **MasRouter** arXiv 2601.04861 | 根据任务复杂度动态选模型规模 + confidence 机制 | confidence 阈值 + 历史表现动态评分 | +| **AgentGate** arXiv 2604.06696 | 3B-7B 小模型做结构化路由决策 | 验证"路由可以是 AI"的可行性 | + +### 2.2 生产实践 + +| 项目 | 模式 | 启发 | +|------|------|------| +| **Microsoft Conductor**(2026.05 开源) | YAML 确定性编排 | 确定性流程 + LLM 动态路由分层混合 | +| **Azure Agent Patterns** | 5 种模式:顺序/并发/群聊/**Handoff**/Magentic | **Handoff**:Agent 完成后自己决定交接给谁 | +| **AWS 动态分派** | 事件驱动 + 上下文感知路由 | 路由变成事件,不是轮询 | +| **Claude Code Agent Teams** | Lead coordinator + context 隔离 | Lead 做分解+分配+监控,subagent 只拿相关 context | + +### 2.3 已有调研的线索 + +- architecture-v2.6.md:**"Agent 决策,Daemon 执行"**;Daemon 是投递员不是决策者 +- shared-consciousness-research.md:Control Unit 是 LLM 驱动的,不是规则路由 +- v2.6-research-01:Hermes 幻觉门控——不信任 Agent 完成声明 + +--- + +## 3. 设计原则 + +| # | 原则 | 说明 | +|---|------|------| +| P1 | 路由决策在 Agent 层,不在 Daemon 层 | "谁该做这个任务"由 Agent 自己或 LLM 决定,Daemon 只执行 | +| P2 | 当前 Agent 最清楚下一步需要谁 | 刚做完工作的人最清楚该交接给谁(Azure Handoff) | +| P3 | 路由可审计 | 每次路由决策记录到黑板,可回溯 | + +--- + +## 4. 三种路由模式 + +### 4.1 模式总览 + +``` +┌───────────────────────────────────────────────────────────┐ +│ 路由决策入口 │ +│ Dispatcher.decide(task) │ +└────────┬──────────────┬──────────────────┬────────────────┘ + │ │ │ + ┌────▼────┐ ┌────▼─────┐ ┌────────▼────────┐ + │ Mode A │ │ Mode B │ │ Mode C │ + │ LLM路由 │ │ Agent交接 │ │ Agent自主领活 │ + │(中心化) │ │(去中心化) │ │ (去中心化) │ + └────┬────┘ └────┬─────┘ └────────┬────────┘ + │ │ │ + LLM选Agent 执行者说需要谁 Agent自己来领 +``` + +### 4.2 Mode A:LLM 路由(中心化) + +**场景**:首次分配(pending → claimed)、异常升级(failed/blocked)、无明确 handoff 指令时。 + +**机制**:Daemon 调用一次轻量 LLM API,传入任务信息 + Agent 能力画像 + 负载状态,LLM 返回选择的 Agent + 理由 + 置信度。 + +**关键**:不是 spawn 一个 Agent session,是一次 ~300 token 的 API 调用(~1-2s,<¥0.01)。 + +``` +输入: 任务描述 + 6个Agent画像 + 负载 +输出: {"agent_id": "xxx", "reason": "...", "confidence": 0.9} +约束: ~200 token response, temperature=0.1 +``` + +### 4.3 Mode B:Agent 声明式交接(去中心化)⭐ 最高频 + +**场景**:Agent 完成当前阶段后,明确声明下一步需要什么。 + +**机制**:Agent 在 POST /status 时附带 `next_capability` 字段: + +```json +{ + "status": "review", + "agent": "zhangfei-dev", + "next_capability": "review", + "handoff_note": "代码已实现,请审查质量和安全性" +} +``` + +Daemon 读 `next_capability`,查 Agent 能力画像找到匹配者(排除当前执行者),直接 spawn。 + +**这是最 AI Native 的模式**——刚做完工作的人最清楚下一步需要谁。不需要 LLM 调用,0ms 延迟。 + +### 4.4 Mode C:Agent 自主领活(去中心化)— 未来演进 + +**场景**:Daemon 广播任务需求,Agent 自己决定是否 claim。 + +**当前阶段不实现**,保留演进空间。数据结构(agent_profiles、capabilities)不变,只需把"Daemon 查表派发"改为"Daemon 广播 + Agent 自己 claim"。 + +### 4.5 模式选择逻辑 + +```python +def decide(self, task, action_type=""): + # 确定性快速路径(0ms,不调 LLM) + if self._is_deterministic(task, action_type): + return self._deterministic_route(task, action_type) + + # Mode B: Agent 声明了 next_capability → 直接匹配 + if task.next_capability: + return self._match_capability(task.next_capability, + exclude={task.assignee}) + + # Mode A: 无明确 handoff → LLM 路由 + return self._llm_route(task, action_type) +``` + +**确定性快速路径**包括: +- 机械检查(L1_guardrail、format_check)→ Daemon 本地执行 +- 已有 assignee 且非生命周期流转(如 crashed → retry 同一人)→ 直接用 + +--- + +## 5. 核心组件设计 + +### 5.1 Agent 能力画像(Agent Profile) + +每个 Agent 在配置中声明自己的能力(**不是 Daemon 硬编码**): + +```yaml +# config/default.yaml → agents 段扩展 +agents: + zhangfei-dev: + capabilities: [coding, implementation, scripting] + can_review: false + max_concurrent: 1 + + simayi-challenger: + capabilities: [review, quality_check, debate] + can_review: true + max_concurrent: 2 + + guanyu-dev: + capabilities: [risk, compliance, position_check] + can_review: true + max_concurrent: 1 + + zhaoyun-data: + capabilities: [data, acquisition, cleaning, verification] + can_review: false + max_concurrent: 1 + + jiangwei-infra: + capabilities: [deploy, infrastructure, docker, vnpy] + can_review: false + max_concurrent: 1 + + pangtong-fujunshi: + capabilities: [planning, coordination, escalation, strategy] + can_review: true + is_fallback: true + max_concurrent: 3 +``` + +Daemon 启动时读取配置,写入黑板 `agent_profiles` 表。未来可演进为 Agent 自己注册。 + +### 5.2 LLM 路由器(LLMDriver) + +```python +class LLMDriver: + """bMAS Control Unit — 轻量 LLM 路由决策""" + + def __init__(self, model: str, api_base: str, api_key: str): + self.model = model + self.client = OpenAI(base_url=api_base, api_key=api_key) + + def route(self, task, agent_profiles, active_agents) -> RouteDecision: + prompt = self._build_prompt(task, agent_profiles, active_agents) + + response = self.client.chat.completions.create( + model=self.model, + messages=[{"role": "user", "content": prompt}], + response_format={"type": "json_object"}, + max_tokens=200, + temperature=0.1, + ) + + result = json.loads(response.choices[0].message.content) + return RouteDecision( + agent_id=result["agent_id"], + reason=result["reason"], + confidence=result.get("confidence", 0.5), + ) +``` + +**Routing Prompt 模板**: + +``` +你是任务路由器。根据任务需求和 Agent 能力,选择最合适的 Agent。 + +## 当前任务 +- ID: {task_id} +- 标题: {task_title} +- 状态: {task_status} +- 描述: {task_description} +- 上一步执行者: {previous_assignee} + +## 可用 Agent +{每个Agent: ID, 能力列表, 当前负载} + +## 约束 +1. review/quality_check 不能选上一步执行者 +2. 同等能力优先选负载最低的 +3. 必须匹配任务所需能力 + +## 输出 +{"agent_id": "...", "reason": "...", "confidence": 0.0-1.0} +``` + +### 5.3 Dispatcher 重写 + +```python +class Dispatcher: + def __init__(self, config, counter): + self.counter = counter + self.agent_profiles = config.get("agent_profiles", {}) + self.llm = LLMDriver( + model=config.get("routing", {}).get("model", "zhipu/glm-5.1"), + api_base=config.get("routing", {}).get("api_base", ""), + api_key=config.get("routing", {}).get("api_key", ""), + ) + self.LOCAL_ACTIONS = {"L1_guardrail", "format_check", "file_exists_check"} + + def decide(self, task, action_type="") -> dict: + # ── 快速路径:确定性路由 ── + if action_type in self.LOCAL_ACTIONS: + return {"level": "local", "reason": "机械检查,Daemon本地执行"} + + # retry 同一人 + if action_type == "retry" and task.assignee: + return {"level": "full_agent", "agent_id": task.assignee, + "reason": "retry原执行者", "mode": "deterministic"} + + # ── Mode B: Agent 声明式交接 ── + if task.next_capability: + agent = self._match_capability(task.next_capability, + exclude={task.assignee}) + if agent: + return {"level": "full_agent", "agent_id": agent, + "reason": f"执行者handoff: 需要{task.next_capability}", + "mode": "agent_handoff"} + + # ── Mode A: LLM 路由 ── + decision = self.llm.route(task, self.agent_profiles, + self.counter.active_agents) + + # 合法性校验 + if (decision.agent_id not in self.agent_profiles + or decision.confidence < 0.7): + return {"level": "full_agent", "agent_id": "pangtong-fujunshi", + "reason": f"LLM低置信度({decision.confidence}): {decision.reason}", + "mode": "fallback"} + + return {"level": "full_agent", "agent_id": decision.agent_id, + "reason": decision.reason, "mode": "llm_route", + "confidence": decision.confidence} + + def _match_capability(self, capability, exclude=None): + """从能力画像中匹配 Agent""" + candidates = [ + aid for aid, prof in self.agent_profiles.items() + if aid not in (exclude or set()) + and capability in prof.get("capabilities", []) + ] + if not candidates: + return None + if len(candidates) == 1: + return candidates[0] + return min(candidates, key=lambda a: self.counter.active_agents.get(a, 0)) +``` + +### 5.4 assignee 语义变更 + +| 维度 | 当前 | 改为 | +|------|------|------| +| `assignee` 含义 | 任务负责人(贯穿全生命周期) | **当前阶段执行者**(随状态流转更新) | +| 新增 `previous_assignee` | 无 | 保存前一阶段执行者(用于排除和审计) | + +```python +# 状态流转时更新 +task.previous_assignee = task.assignee +task.assignee = new_agent_id +``` + +--- + +## 6. 路由审计 + +每次路由决策写入黑板 `routing_decisions` 表。 + +### 6.1 表结构 + +```sql +CREATE TABLE IF NOT EXISTS routing_decisions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL, + from_status TEXT, -- 前一状态 + to_status TEXT, -- 目标状态 + mode TEXT NOT NULL, -- deterministic / agent_handoff / llm_route / fallback + selected_agent TEXT NOT NULL, + previous_agent TEXT, -- 前一阶段执行者 + reason TEXT, -- 路由理由 + confidence REAL, -- LLM 置信度(Mode A 才有) + model TEXT, -- 使用的 LLM 模型(Mode A 才有) + latency_ms INTEGER, -- 路由耗时 + created_at TEXT DEFAULT (datetime('now')), + FOREIGN KEY (task_id) REFERENCES tasks(id) +); + +CREATE INDEX idx_routing_task ON routing_decisions(task_id); +``` + +### 6.2 审计日志示例 + +``` +task=test-e2e-001 | pending→claimed | mode=llm_route + → zhangfei-dev (confidence=0.95, reason="编码任务匹配coding能力") + → model=zhipu/glm-5.1, latency=1200ms + +task=test-e2e-001 | working→review | mode=agent_handoff + → simayi-challenger (reason="执行者handoff: 需要review") + → latency=2ms + +task=test-e2e-001 | review→done | mode=agent_handoff + → pangtong-fujunshi (reason="审查通过,交接给协调者收尾") + → latency=1ms +``` + +--- + +## 7. 路由模型配置 + +### 7.1 后端配置 + +```yaml +# config/default.yaml 新增 +routing: + model: "zhipu/glm-5.1" # 默认路由模型 + api_base: "" # 空=用 OpenClaw Gateway + api_key: "" # 空=用 OpenClaw 默认 + confidence_threshold: 0.7 # 低于此值 fallback + max_tokens: 200 + temperature: 0.1 +``` + +### 7.2 前端配置入口 + +在现有 `ModelConfig.tsx` 页面顶部新增"路由模型"配置区域: + +``` +┌─────────────────────────────────────────────┐ +│ 🎯 路由模型(Control Unit) │ +│ ┌─────────────────────┐ ┌────┐ │ +│ │ zhipu/glm-5.1 ▾ │ │应用│ │ +│ └─────────────────────┘ └────┘ │ +│ 任务路由使用的 LLM(推荐轻量快速模型) │ +├─────────────────────────────────────────────┤ +│ 🐦 庞统 pangtong-fujunshi │ +│ 当前: zhipu/glm-5.1 │ +│ ... │ +``` + +- 模型下拉列表复用 OpenClaw 已注册的 `knownModels`(和 Agent 模型选的是同一个数据源) +- 通过后端 API `PATCH /api/config/routing-model` 保存 +- 调用 `api.setModel` 同理,走 Gateway 模型配置 + +### 7.3 API + +```python +# blackboard_routes.py 新增 +@api_route("GET", "/api/config/routing") +def get_routing_config(request): + return {"model": config.routing.model, + "confidence_threshold": config.routing.confidence_threshold} + +@api_route("PATCH", "/api/config/routing") +def set_routing_config(request): + new_model = request.json.get("model") + # 校验模型在 OpenClaw 已注册模型列表中 + config.routing.model = new_model + config.save() + return {"ok": True} +``` + +--- + +## 8. 改动清单 + +### 8.1 数据模型 + +| 变更 | 类型 | 说明 | +|------|------|------| +| 新增 `agent_profiles` 配置段 | 配置 | 每个 Agent 声明能力列表 | +| 新增 `routing` 配置段 | 配置 | 路由模型 + 参数 | +| tasks 新增 `next_capability` 字段 | DDL | Agent 声明下一步需要的能力 | +| tasks 新增 `previous_assignee` 字段 | DDL | 保存前一阶段执行者 | +| 新增 `routing_decisions` 表 | DDL | 路由审计日志 | +| `assignee` 语义变更 | 逻辑 | 从"任务负责人"改为"当前阶段执行者" | + +### 8.2 代码 + +| 文件 | 变更 | +|------|------| +| `dispatcher.py` | 重写:新增 LLMDriver + Mode A/B/C 路由逻辑 | +| `config/default.yaml` | 新增 `agent_profiles` + `routing` 配置段 | +| `blackboard_routes.py` | status API 接受 `next_capability`;新增路由配置 API | +| `ticker.py` | 使用新 dispatcher;路由结果写 routing_decisions | +| `blackboard/db.py` | 新增 routing_decisions 表 DDL;tasks 表新增字段 | +| `ModelConfig.tsx` | 新增路由模型配置区域 | + +### 8.3 不变 + +| 不变 | 原因 | +|------|------| +| 状态机(pending→claimed→working→review→done) | 状态流转语义正确 | +| Agent prompt 模板(S2) | Agent 仍按 4 步流程,只在 POST /status 时多传一个字段 | +| Spawner 逻辑 | spawn 机制不变 | +| 前端 Dashboard 核心布局 | 只在 ModelConfig 加一个区域 | + +--- + +## 9. 和现有实践的对标 + +| 实践 | 本方案对应 | +|------|----------| +| bMAS Control Unit(LLM 驱动) | Mode A: LLMDriver 实现,轻量 API 调用 | +| Azure Handoff(Agent 交接) | Mode B: next_capability + handoff_note | +| 自主选择(arXiv 2510.01285) | Mode C: 未来演进,数据结构预留 | +| MasRouter(confidence) | confidence 阈值 + fallback 机制 | +| Microsoft Conductor(确定性 + 动态混合) | 快速路径(确定性)+ LLM 路由(动态)分层 | +| 幻觉门控(Hermes) | LLM 输出合法性校验 + confidence 阈值 | +| "Agent 决策,Daemon 执行"(v2.6 原则) | Mode B 是最直接的实现:Agent 自己决定交接给谁 | + +--- + +## 10. 演进路线 + +``` +Phase 1(本次实现): Mode A + Mode B + - LLMDriver 路由(首次分配、异常场景) + - Agent 声明式交接(最高频场景) + - 路由审计表 + - 前端路由模型配置 + +Phase 2(未来): Mode C + - 同样的 agent_profiles 和 capabilities 数据结构 + - Daemon 广播需求 → Agent 自己 claim + - 迁移成本极低(数据结构不变,只改消费方式) + +Phase 3(更远): 经验驱动的路由 + - 路由审计数据反哺 LLM prompt(历史匹配成功率) + - Agent 可靠性评分(参考 MasRouter) + - 动态能力发现(Agent 完成新类型任务后自动更新画像) +``` + +--- + +## 11. 司马懿评审要点 + +请重点关注: + +1. **LLMDriver 的异常处理**:API 超时/失败时的 fallback 策略是否合理 +2. **Mode B 的安全性**:Agent 声明 `next_capability` 时是否需要校验(防恶意指定) +3. **assignee 语义变更**的影响范围:是否有其他模块依赖"assignee = 任务负责人" +4. **routing_decisions 表设计**:字段是否充分,索引是否合理 +5. **配置 API 的安全性**:修改路由模型是否需要鉴权 +6. **性能影响**:Mode A 的 ~2s 延迟在 tick cycle 中是否可接受 + +--- + +## 12. 参考 + +- bMAS: arXiv 2507.01701 — Blackboard LLM Multi-Agent System +- Self-Selection: arXiv 2510.01285 — Agent 自主选择模式 +- MasRouter: arXiv 2601.04861 — Confidence-Aware Routing +- AgentGate: arXiv 2604.06696 — 结构化路由引擎 +- Microsoft Conductor: github.com/microsoft/conductor — 确定性编排 +- Azure Agent Patterns: learn.microsoft.com — Handoff 模式 +- v2.6 调研报告: docs/research/shared-consciousness-research.md +- v2.6 架构设计: docs/design/architecture-v2.6.md +- T3-10 调度判据: docs/design/topic3-challenge-review-proposal.md §5.4 diff --git a/docs/design/architecture-v2.6.md b/docs/design/architecture-v2.6.md new file mode 100644 index 0000000..bb23a0b --- /dev/null +++ b/docs/design/architecture-v2.6.md @@ -0,0 +1,2070 @@ +# AI原生DevOps Platform 架构设计 v2.6 + +**版本**: v2.6(Shared Workspace + Blackboard 架构) +**基于**: architecture-v2.md + v2.0 AI Native 调研 + 技术验证 +**作者**: 庞统(副军师) +**日期**: 2026-05-15 + +--- + +## 变更历史 + +| 版本 | 日期 | 变更内容 | +|------|------|---------| +| v2.0 | 2026-05-04 | 初始版本:SQLite 4表 + 状态机 + DAG 引擎 | +| v2.6 | 2026-05-15 | **架构重构**:Shared Workspace(Blackboard)取代 DAG 引擎为编排核心 | +| v2.6.1 | 2026-05-15 | 司马懿评审反馈 + Mail 退役决策 + 质量门控 + 决策记录 + 工程修正 | +| v2.6.2 | 2026-05-15 | 课题1设计决策:三层执行模型、续杯机制、AI驱动retry、Guardrail体系、must_haves三件套、分级审查矩阵 | +| v2.6.2.1 | 2026-05-15 | 司马懿评审反馈:L2/L3区分标准、timeout修正、outputs关联attempt、Scope Guard异步、risk_level自动 | +| v2.6.3 | 2026-05-15 | 课题2设计决策:Tick核心+Inbox JSONL加速、Handoff Comment无缝接手、L1/L2/L3对应Opal-Bridge Fidelity、黑板AI Native内容规范+三层约束体系(Schema校验+Skill引导+L1截取)、依赖驱动并行/串行、Phase规划更新 | +| v2.6.4 | 2026-05-15 | 课题3设计决策:分级审查流水线(四级风险→三/二/一阶段)、审查协议注册表(Review Protocol Registry)、反驳权(Rebuttal Phase)、reviews表结构化存储、声明式guardrails.yaml、Full Agent vs Subagent vs Daemon直接执行判据、对抗辩论模式 | +| v2.6.5 | 2026-05-15 | 课题4设计决策:模板组件库(+custom)替代固定DAG、四层上下文架构(L0铁律/L1角色/L2引擎注入三段式/L3被动参考)、prompt_templates按角色拼装、L2按角色精确注入不多不少、L3 Skill description四要素优化写法 | +| v2.6.6 | 2026-05-15 | 课题7+9设计决策:四种交互模式(沉浸观察/轻触确认/即时对话/被动通知)、推送级别分级(🔴🟡🟢🔵)、三层信息架构(L1一眼/L2看板/L3详情)、5页Dashboard(任务看板/全局监控/产出档案/系统配置/AI Briefing) | +| v2.6.7 | 2026-05-15 | 课题6设计决策:经验三种载体(Memory→Skill→Rule)、两级蒸馏(实时一级+周期二级)、验证(格式+内容+实用性)、四层架构反哺(Daemon注入+被动参考)、experiences表、Skill生命周期(draft→active→deprecated) | +| v2.6.8 | 2026-05-16 | 课题10结论:不需要复杂压缩/摘要、续杯问题已由课题2+4解决、单黑板单Daemon多任务并行、Context预算35-60K远小于128K、用户级多项目待课题11解决 | +| v2.6.9 | 2026-05-16 | 7项目调研结论纳入:Guardrail验证脚本层(Aider)、对抗性审计映射(claude-goal→L1/L2/L3)、防偏离三防线+Runaway Guard、双重Hook(OpenClaw+moziplus)、Shadow Checkpoint(Cline)、潜在课题(模型策略/信任分级/Worktree隔离)、Blackboard Map按需触发 | +| v2.6.9.1 | 2026-05-16 | 司马懿评审回应:PRD目标不改+架构记录差距、blocked状态保留+设计完整、verification_commands安全模型明确(exec approvals)、Daemon逻辑健康自检纳入§14 | +| v2.6.10 | 2026-05-16 | 课题11多项目:方案C单Daemon多数据库 + per-project线程并发 + ActiveAgentCounter异步计数器 + session命名规则(sequential复用/parallel独立) + 三层资源控制 + 项目归档/删除安全流程 | +| v2.6.11 | 2026-05-16 | 课题7+9方案完成:v1.0已有11个Tab全部保留(仅任务看板重设计)、Checkpoint三种交互、推送SSE+降级轮询、AI Briefing日报/周报、项目切换器、推送通知中心 | +| v2.6.12 | 2026-05-16 | 全量遗留清理(51项→0) + 课题3四项新方案(对抗辩论/评审Schema/反驳权/调度判据) + 司马懿评审通过(comments表comment_type) + 工具链课题初始化 + 技术架构/部署架构重写(v2.6.2) | +| v2.8 | 2026-05-18 | v2.8 状态增强:新增 paused/escalated/waiting_human 3状态(共11个)+ 归档机制 + M3 Checkpoint/Artifact + 前端两行筛选栏 + 卡片快捷按钮 | + +### 课题 1-2 遗留 TODO(需后续课题解决) + +| # | 待解决事项 | 归属课题 | 说明 | +|---|----------|---------|------| +| ~~T1-1~~ | ~~spawn sub 是否阻塞?需要调查~~ | ~~课题 2~~ | ✅ 课题 2 解决:不阻塞,signal file 异步 | +| ~~T1-2~~ | ~~事件驱动取代 polling tick~~ | ~~课题 2~~ | ✅ 课题 2 解决:双层事件架构 | +| ~~T1-3~~ | ~~依赖推进(done→自动解锁下游)~~ | ~~课题 2~~ | ✅ 课题 2 解决:task_completed 事件即时解锁 | +| ~~T2-1~~ | ~~files_modified 冲突检测~~ | ~~课题 2~~ | ✅ D2-4 决策:不做,Agent 评论自然协调 | +| ~~T2-2~~ | ~~Auto-compact~~ | ~~课题 2~~ | ✅ D2-6 决策:不做,隔离 session 天然无 context rot | +| ~~T1-4~~ | ~~Agent 间自主协商机制~~ | ~~课题 3~~ | ✅ 课题3解决:审查协议+反驳权+评论协商 | +| ~~T1-5~~ | ~~Scope Guard 的 Skill 定义~~ | ~~课题 4~~ | ✅ 课题4解决:executor.md 步骤2(scope_declaration) | +| ~~T1-6~~ | ~~truths 验证的具体实现~~ | ~~课题 4~~ | ✅ 课题4解决:executor.md 步骤1+Plan Checker(plan_check.yaml) | +| ~~T1-7~~ | ~~outputs attempt_number 过滤规则~~ | ~~课题 4~~ | ✅ 课题4解决:executor.md 步骤4(output提交)+Daemon build_bootstrap() | +| ~~T1-8~~ | ~~状态机细化(review 轮次、sub_status)~~ | ~~课题 3~~ | ✅ 课题3解决:§9.7状态机对齐 | +| ~~T2-3~~ | ~~blackboard.py 写操作自动写 inbox JSONL~~ | ~~Phase 1~~ | ✅ 开发实现 | +| ~~T2-4~~ | ~~Tick Loop + Inbox JSONL Watcher~~ | ~~Phase 1~~ | ✅ 开发实现 | +| ~~T2-5~~ | ~~L2/L3 分层读取 API~~ | ~~Phase 2~~ | ✅ 开发实现 | +| ~~T2-6~~ | ~~仓库级上下文(Agent Context Pack)~~ | ~~Phase 3~~ | ✅ P3 保留,不合并不关闭 | +| ~~T2-7~~ | ~~Handoff Comment 的 Skill 引导~~ | ~~Phase 2~~ | ✅ 课题4解决:executor.md 步骤5写死 | +| ~~T2-8~~ | ~~Handoff Comment 的 Skill 解析规则~~ | ~~课题 4~~ | ✅ 课题4解决:executor.md "前序信息"部分+schemas/handoff.schema.json | + +### 课题 4 Skill 体系设计 TODO + +> **课题4设计决策**:关键操作不靠 Skill 被动触发,走引擎注入(L2 prompt_templates)。以下 28 项全部通过 prompt_templates/ + review_protocols/ + schemas/ 覆盖。此表保留作为设计推导留痕。 +> +> **覆盖映射**: +> - S-01~S-12(执行者操作)→ prompt_templates/executor.md +> - S-13~S-20(审查者操作)→ prompt_templates/reviewer.md + review_protocols/*.yaml +> - S-21~S-27(庞统操作)→ prompt_templates/planner.md / adjudicator.md +> - S-28(L1 消息构建)→ Daemon build_bootstrap() 代码 +> +> 详见课题4方案:`docs/design/topic4-decomposition-skill-proposal.md` D4-6/D4-7 + +| # | Skill 内容 | 适用角色 | Phase | 来源 | 覆盖方式 | +|---|----------|---------|-------|------|----------| +| S-01 | blackboard.py CLI 使用手册 | 所有 Agent | P1 | 课题1 §5.2 | ✅ executor.md 各步骤中写死 | +| S-02 | L1→L2/L3 按需读取判断 | 所有 Agent | P2 | 课题2 §4.4 | ✅ executor.md "可选参考"部分 | +| S-03 | 写 Handoff Comment(格式+时机) | 所有 Agent | P1 | 课题2 §5.1 | ✅ executor.md 步骤5 | +| S-04 | 读 Handoff Comment(利用上一个 Agent 交接) | 所有 Agent | P2 | 课题2 §5.1 | ✅ executor.md "前序信息"部分 | +| S-05 | 写 observation(时机+severity 格式) | 所有 Agent | P2 | 课题1 §4.7 | ✅ executor.md 步骤3 | +| S-06 | 写 decision(时机+rationale 格式) | 所有 Agent | P2 | 课题1 §9.4 | ✅ executor.md 步骤2 | +| S-07 | 写 output 的 Schema 约束 | 所有 Agent | P1 | 课题2 §3.7 | ✅ executor.md 步骤4 + schemas/ | +| S-08 | Guardrail 打回时的处理流程 | 执行者 | P2 | 课题3 §9.3 | ✅ executor.md + guardrails.yaml | +| S-09 | @mention 使用规范 | 所有 Agent | P2 | 课题1 §5.2 | ✅ executor.md 步骤3 | +| S-10 | claim 后写 scope_declaration(格式+内容) | 执行者 | P1 | 课题1 §4.7 | ✅ executor.md 步骤2(含JSON格式) | +| S-11 | must_haves 三件套自检(truths/artifacts/constraints) | 执行者 | P1 | 课题1 §9 | ✅ executor.md 步骤1 | +| S-12 | 收到 review needs_revision 的反驳流程(ACCEPT/REJECT/PARTIAL) | 执行者 | P2 | 课题3 §9.5 | ✅ rebuttal.md | +| S-13 | 审查者 Investigation Protocol 五阶段执行 | 审查者 | P2 | 课题3 §9.4 | ✅ reviewer.md 五步骤 | +| S-14 | 多视角审查方法(代码/方案/分析三套视角集) | 审查者 | P2 | 课题3 §9.4 | ✅ reviewer.md + review_protocols/ | +| S-15 | 写 review 的 Schema 约束(verdict+evidence) | 审查者 | P2 | 课题3 §9.6 | ✅ reviewer.md + schemas/ | +| S-16 | 信心度自评(confidence 打分标准) | 审查者 | P2 | 课题3 §9.6 | ✅ reviewer.md 步骤5(自审) | +| S-17 | 收到反驳(REJECT)后的评估方法 | 审查者 | P2 | 课题3 §9.5 | ✅ reviewer.md(反方回应处理) | +| S-18 | plan_review 协议(假设提取/pre-mortem/依赖审计) | 审查者 | P2 | 课题3 §9.4 | ✅ review_protocols/plan_review.yaml | +| S-19 | output_review 协议(需求追踪/缺口分析) | 审查者 | P2 | 课题3 §9.4 | ✅ review_protocols/output_review.yaml | +| S-20 | analysis_review 协议(逻辑跳跃/数据来源) | 审查者 | P2 | 课题3 §9.4 | ✅ review_protocols/analysis_review.yaml | +| S-21 | 创建任务(truths/artifacts/constraints 定义) | 庞统 | P1 | 课题1 §9 | ✅ planner.md | +| S-22 | 风险等级自动判断(task_type→risk_level) | 庞统 | P1 | 课题3 §9.2 | ✅ planner.md(映射规则) | +| S-23 | 挑战者选择(按任务类型选挑战者) | 庞统 | P2 | 课题3 §9.10 | ✅ planner.md(选择表) | +| S-24 | 对抗辩论裁决方法 | 庞统 | P3 | 课题3 §9.10 | ✅ adjudicator.md | +| S-25 | escalated 任务的用户沟通 | 庞统 | P3 | 课题3 §9.7 | ✅ planner.md / adjudicator.md | +| S-26 | confidence 低时的升级判断 | 庞统 | P2 | 课题3 T3-5 | ✅ reviewer.md(< 0.7 升级庞统) | +| S-27 | 任务拆解方法(依赖声明/子任务创建) | 庞统 | P2 | 课题1 §5.1 | ✅ planner.md(四步+组件库+PlanChecker) | +| S-28 | L1 消息构建逻辑 | 庞统/Daemon | P1 | 课题2 §4.4 | ✅ Daemon build_bootstrap() 代码 | +| ~~T2-9~~ | ~~inbox 并发写入的竞态处理~~ | ~~Phase 1 验证~~ | ✅ 已解决:truncate(清空不删除)替代 unlink(删除),实测 200 次并发写入 0 丢失。演进方向:向 webhook 实时化走 | +| ~~T2-10~~ | ~~inbox 文件的 rotate/truncate 策略~~ | ~~Phase 2~~ | ✅ 关闭:Inbox 每秒被清空,正常运行不膨胀。Daemon 崩溃积压也仅 ~200KB/1000条 | +| ~~T2-11~~ | ~~Tick 频率 30s vs 60s 的性能验证~~ | ~~开发后实测~~ | ✅ 开发后实测 | +| ~~T2-12~~ | ~~CLI Schema 校验的 schemas/ 目录定义~~ | ~~Phase 1~~ | ✅ 开发实现 | + +### 课题 3 遗留 TODO + +| # | 待解决事项 | 归属 | 说明 | +|---|----------|------|------| +| ~~T3-1~~ | ~~reviews 表的 CLI 命令~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-2~~ | ~~Guardrail YAML 解析 + 执行引擎~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-3~~ | ~~对抗辩论模式的具体黑板协议~~ | ~~Phase 3~~ | ✅ 方案已定(topic3 §5.1),仅用户明确要求时启用 | +| ~~T3-4~~ | ~~挑战者池的选择策略~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-5~~ | ~~confidence 低于阈值自动升级~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-6~~ | ~~评审详情文件的 Schema 定义~~ | ~~Phase 2~~ | ✅ 方案已定(topic3 §5.2),待司马懿评审确认 | +| ~~T3-7~~ | ~~low 风险任务 Guardrail 自动通过~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-8~~ | ~~Review Protocol 模板文件编写~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-9~~ | ~~反驳权 Daemon 流控~~ | ~~Phase 2~~ | ✅ 方案已定(topic3 §5.3),轮次上限兜底不设超时 | +| ~~T3-10~~ | ~~Agent 调度判据~~ | ~~Phase 2~~ | ✅ 方案已定(topic3 §5.4),配置表驱动非 AI 判断 | + +--- + +## 1. v2.6 核心变革:从 DAG 状态机到 Shared Workspace + +### 1.1 为什么变? + +v2.0 的核心是 **DAG 引擎 + 状态机 + 邮件通信**,本质是给 AI 团队做了一套 ERP: +- 编排是确定性状态机(固定流程) +- 交互是点按钮(Dashboard) +- Agent 间靠邮件异步通信(信息分散在 mail 目录) +- 人的参与密度不变(全程驾驶) + +v2.6 的核心是 **Shared Workspace(Blackboard)+ Agent 自主决策 + Daemon 投递**: +- 编排是 AI agent 在黑板上自主领活(动态协作) +- 交互是自然语言对话 +- Agent 间通过黑板共享一切(信息集中在任务空间) +- 人只做方向决策和验收 + +### 1.2 核心原则 + +> **黑板是唯一真相源,所有 agent 读它、想、行动,写回结果。Daemon 是投递员,不是决策者。** + +1. **Agent 决策,Daemon 执行** - 庞统做 plan、张飞领任务、关羽发现风险,都写在黑板上。Daemon 读黑板,执行 spawn/通知。 +2. **产出在黑板,不在邮件** - 所有任务产出、讨论、观察都在任务的黑板空间里,Sanguo Mail 不介入任务协作。 +3. **Daemon 不阻塞 Agent** - Daemon 是常驻管家,定期 tick 检查黑板,spawn agent 执行,不占用任何 agent 的主 session。 +4. **Session 用完即清** - Agent 通过 `openclaw agent --agent --session-id ` spawn 隔离 session,执行完 daemon 存档 jsonl 并清理 sessions.json。 +5. **双入口,对等地位** - Agent 对话和 Dashboard 是两个对等入口,共享同一套黑板数据。Dashboard 是 AI Native 的可视化入口,不是降级的监控面板。 + +--- + +## 2. 架构总览 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 用户 / 触发器 │ +│ (Web / CLI / Cron) │ +└──────────────────────────┬──────────────────────────────────┘ + │ 写入黑板或触发 daemon +┌──────────────────────────▼──────────────────────────────────┐ +│ Shared Workspace(黑板) │ +│ │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ SQLite (blackboard.db) │ │ +│ │ tasks / comments / outputs / agents / events │ │ +│ │ 原子读写(propose→validate→commit 或 SQLite 事务) │ │ +│ └────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ 任务列表 │ │ 评论线程 │ │ 产出空间 │ │ 讨论区域 │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +└──────────────────────────┬──────────────────────────────────┘ + │ daemon tick 读写 +┌──────────────────────────▼──────────────────────────────────┐ +│ Daemon(管家) │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ +│ │ Tick 循环 │ │ Session 管理 │ │ 健康检查 │ │ +│ │ (60s 轮询) │ │ spawn/archive │ │ zombie/reclaim │ │ +│ │ 读黑板→决策 │ │ /cleanup │ │ /stale 任务 │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────────────────┘ │ +│ │ │ │ +│ Daemon 只做三件事: │ │ +│ 1. 读黑板,发现需要介入的 │ │ +│ 2. Spawn 对应 agent │ │ +│ 3. 清理完成的 session │ │ +└──────────────────────────┬──────────────────────────────────┘ + │ openclaw agent --agent --session-id + │ 执行完 → 存档 jsonl → 清理 sessions.json +┌──────────────────────────▼──────────────────────────────────┐ +│ Agent 层(将军们) │ +│ │ +│ Agent 不常驻。被 spawn 时: │ +│ 1. 读黑板 → 了解全局状态 │ +│ 2. 想和做 → 根据职责自主决策 │ +│ 3. 写回黑板 → 产出、评论、领任务 │ +│ 4. 退出 → session 被 daemon 清理 │ +│ │ +│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ +│ │庞统 │ │司马懿│ │姜维 │ │关羽 │ │张飞 │ │赵云 │ │ +│ │策划 │ │质量 │ │平台 │ │风控 │ │编码 │ │数据 │ │ +│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │ +│ │ +│ 每个 Agent: SOUL.md + IDENTITY.md + Skills + Workspace │ +│ Agent 主 session 不参与任务执行(不被污染) │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 关键区别:v2.0 vs v2.6 + +| 维度 | v2.0 | v2.6 | +|------|------|------| +| 编排核心 | DAG 引擎 + 状态机 | Blackboard(Shared Workspace) | +| 决策者 | Daemon(状态机驱动) | Agent(在黑板上自主决策) | +| Daemon 角色 | 调度器(决定谁干什么) | 投递员(执行黑板上的决策) | +| Agent 通信 | Sanguo Mail(异步邮件) | 黑板 Comment 线程(共享空间) | +| 信息位置 | 分散(mail + task目录 + session) | 集中(黑板 SQLite) | +| Agent 生命周期 | 固定节点执行 | Spawn 隔离 session,用完即清 | +| 通知机制 | Mail 轮询 | Daemon tick + spawn | +| 协作模式 | 指令式(庞统分配→将军执行) | 自主式(看黑板→领活→写回) | + +--- + +## 3. Shared Workspace(黑板)设计 + +### 3.1 参考系统对比 + +| 系统 | 存储 | 原子性 | 讨论 | 状态机 | 发现 | +|------|------|--------|------|--------|------| +| Claude Code Agent Teams | JSON 文件 | 无(last-write-wins) | inbox 点对点 | pending/in_progress/completed | Agent 轮询 | +| Hermes Kanban v0.13 | SQLite | SQLite 事务 | Comment 线程 | 7 状态完整机 | Dispatcher 60s tick | +| Network-AI | Markdown 文件 | flock 三阶段提交 | signal key | 无 | Agent 主动读 | +| agent-blackboard | SQLite + Ontology | SQLite 事务 | 本体条目 | 无 | Coordinator 分发 | +| **我们的方案** | **SQLite** | **SQLite 事务** | **Comment 线程** | **简化状态机** | **Daemon tick** | + +### 3.2 SQLite Schema + +```sql +-- ===== 任务表 ===== +CREATE TABLE IF NOT EXISTS tasks ( + id TEXT PRIMARY KEY, -- task-001 + title TEXT NOT NULL, + description TEXT, + status TEXT NOT NULL DEFAULT 'pending', + CHECK (status IN ('pending','claimed','working','review','done','failed','blocked','cancelled')), + + -- 分配(谁领了或被指派) + assignee TEXT, -- agent id: zhangfei-dev + assigned_by TEXT, -- 谁分配的:pangtong-fujunshi / user + + -- 依赖 + depends_on TEXT, -- JSON array of task IDs + parent_task TEXT, -- 父任务(子任务分解时) + + -- 优先级和类型 + priority INTEGER NOT NULL DEFAULT 5, -- 1(最高)-10(最低) + task_type TEXT, -- coding/review/data/deploy/research/discuss + + -- 时间 + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + claimed_at TEXT, + started_at TEXT, + completed_at TEXT, + deadline TEXT, + + -- 重试 + retry_count INTEGER NOT NULL DEFAULT 0, + max_retries INTEGER NOT NULL DEFAULT 2, + + -- must_haves 与风险等级(课题1设计决策) + must_haves TEXT, -- JSON: {truths: [], artifacts: [], constraints: []} + risk_level TEXT DEFAULT 'standard', -- high/standard/low/research + estimated_duration_minutes INTEGER -- 预估工时(续杯硬上限 = 3x 此值) +); + +CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status); +CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee); +CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task); + +-- ===== 评论线程表 ===== +-- 参考 Hermes kanban_comment:追加写入,所有参与者可见 +CREATE TABLE IF NOT EXISTS comments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL, + author TEXT NOT NULL, -- agent id 或 'user' + comment_type TEXT NOT NULL DEFAULT 'general', -- general/handoff/observation/rebuttal/rebuttal_response/debate_argument/debate_rebuttal/debate_judgment + body TEXT NOT NULL, + mentions TEXT, -- JSON array: ["zhangfei-dev", "guanyu-dev"] + created_at TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (task_id) REFERENCES tasks(id), + CHECK (comment_type IN ('general', 'handoff', 'observation', 'rebuttal', 'rebuttal_response', 'debate_argument', 'debate_rebuttal', 'debate_judgment')) +); + +CREATE INDEX IF NOT EXISTS idx_comments_task ON comments(task_id); +CREATE INDEX IF NOT EXISTS idx_comments_type ON comments(task_id, comment_type); +CREATE INDEX IF NOT EXISTS idx_comments_author ON comments(author); +-- 注意:mentions 是 JSON 数组,无法直接建索引。daemon tick 查询用 json_each(mentions)。 +-- 数据量小时够用,后续可拆 comment_mentions 关联表优化。 + +-- ===== 产出表 ===== +CREATE TABLE IF NOT EXISTS outputs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL, + agent TEXT NOT NULL, -- 谁写的 + output_type TEXT NOT NULL, -- code/document/data/config/other + title TEXT NOT NULL, + content_path TEXT, -- 文件路径(产出物在 task 目录下) + summary TEXT, -- 一句话摘要 + metadata TEXT, -- JSON: {files_changed, lines_added, ...} + attempt_number INTEGER DEFAULT 1, -- 关联 task_attempts.attempt_number + created_at TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (task_id) REFERENCES tasks(id) +); + +CREATE INDEX IF NOT EXISTS idx_outputs_task ON outputs(task_id); + +-- ===== 决策记录表 ===== +-- Agent 执行过程中的关键决策必须记录。哪怕是自己做的决策也要填一条。 +CREATE TABLE IF NOT EXISTS decisions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL, + decider TEXT NOT NULL, -- 谁做的决策 + decision TEXT NOT NULL, -- 决策内容:"选 A 方案" + rationale TEXT NOT NULL, -- 为什么:"B 方案内存开销更大" + alternatives TEXT, -- JSON array: 被排除的选项 + created_at TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (task_id) REFERENCES tasks(id) +); + +CREATE INDEX IF NOT EXISTS idx_decisions_task ON decisions(task_id); + +-- ===== 观察表 ===== +-- Agent 执行过程中发现的问题、风险、建议 +CREATE TABLE IF NOT EXISTS observations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL, + observer TEXT NOT NULL, -- 谁观察到的 + severity TEXT NOT NULL DEFAULT 'info', + CHECK (severity IN ('blocking','warning','info','audit')), + body TEXT NOT NULL, + resolved_by TEXT, -- 谁处理的 + resolved_at TEXT, -- 何时处理的 + created_at TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (task_id) REFERENCES tasks(id) +); + +-- ===== 事件日志(审计追踪)===== +CREATE TABLE IF NOT EXISTS events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT, + agent TEXT, + event_type TEXT NOT NULL, + detail TEXT, -- JSON + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX IF NOT EXISTS idx_events_task ON events(task_id); +CREATE INDEX IF NOT EXISTS idx_events_time ON events(created_at); + +-- 合法 event_type 清单: +-- 任务:task_created, task_claimed, task_started, task_completed, task_failed, +-- task_blocked, task_unblocked, task_reviewed, task_cancelled, task_retried +-- 协作:comment_added, output_written, observation_added, decision_recorded +-- Agent:agent_spawned, agent_completed, agent_zombie_detected +-- Session:session_spawned, session_archived, session_cleanup +-- 系统:daemon_tick, daemon_manual_tick + +-- ===== Agent 注册表 ===== +CREATE TABLE IF NOT EXISTS agents ( + agent_id TEXT PRIMARY KEY, + role TEXT, + current_status TEXT DEFAULT 'idle', -- idle/working/offline + current_task TEXT, + last_active TEXT, + capabilities TEXT -- JSON array: ["coding", "review", "deploy"] +); + +-- ===== 任务尝试记录(参考 Hermes task_runs)===== +CREATE TABLE IF NOT EXISTS task_attempts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL, + attempt_number INTEGER NOT NULL, + agent TEXT NOT NULL, + outcome TEXT NOT NULL, -- completed/blocked/crashed/timed_out/spawn_failed/reclaimed + exit_code INTEGER, + log_path TEXT, + summary TEXT, + metadata TEXT, -- JSON: {duration_seconds, token_count, ...} + started_at TEXT NOT NULL DEFAULT (datetime('now')), + completed_at TEXT, + + FOREIGN KEY (task_id) REFERENCES tasks(id) +); + +CREATE INDEX IF NOT EXISTS idx_attempts_task ON task_attempts(task_id); + +-- agents 表更新规则: +-- Agent claim 任务时:自己更新 current_status='working', current_task=task_id +-- Agent 完成退出时:daemon 更新 current_status='idle', current_task=NULL +-- Daemon tick 检测到 zombie:daemon 更新 current_status='offline' +``` + +**连接配置:** + +```python +def get_connection(): + conn = sqlite3.connect(str(DB_PATH)) + conn.row_factory = sqlite3.Row + conn.execute("PRAGMA journal_mode=WAL") + conn.execute("PRAGMA foreign_keys=ON") + conn.execute("PRAGMA busy_timeout=5000") + return conn +``` + +### 3.3 状态机(v2.8:11 个状态) + +``` +pending → claimed → working → review → done + ↑ │ ↕ ├→ blocked ──┘ ├→ failed + │ │ paused ├→ paused └→ cancelled + └─────────┘ ├→ escalated + └→ waiting_human ← M3 Checkpoint + + (review→pending: 审核不通过,打回重做) + (review→waiting_human: 审查阶段需人工裁定) + (blocked→pending: 阻塞解除) + (blocked→escalated: 阻塞升级求助) + (failed→pending: 重试) + (failed→escalated: 失败升级求助) + (escalated→working: 用户介入后恢复) + (waiting_human→working: Checkpoint reject) + (waiting_human→done: verify Checkpoint approve) +``` + +**v2.8 变更:** v2.6 有 8 个状态,v2.8 新增 3 个: paused(用户暂停)、escalated(Agent 升级求助)、waiting_human(Checkpoint 人工确认)。 + +| 状态 | 含义 | 谁触发 | 调度 | 聚合 | +|------|------|--------|------|------| +| pending | 待领取 | 任何 Agent 或用户创建 | ✅ 正常调度 | ✅ 参与 | +| claimed | 已认领 | Agent 自己或被指派 | ✅ 超时回收 | ✅ 参与 | +| working | 执行中 | Agent | ✅ 超时监控 | ✅ 参与 | +| review | 待审核 | Agent 完成产出 | ✅ review 调度 | ✅ 参与 | +| **paused** | **已暂停** | **用户主动** | **❌ 跳过** | **❌ 不参与** | +| **escalated** | **已升级** | **Agent 求助** | **❌ 跳过** | **✅ 视同 working** | +| **waiting_human** | **等人工** | **Agent Checkpoint** | **❌ 跳过** | **✅ 视同 working** | +| done | 完成 | 审核通过 | 终态 | ✅ 参与 | +| failed | 失败 | Agent 或 daemon | ❌ 不调度 | ✅ 参与 | +| blocked | 需要帮助 | Agent | ❌ 不调度 | ✅ 参与 | +| cancelled | 取消 | 用户 | 终态 | ❌ 不参与 | + +**聚合规则(v2.8):** 优先级 escalated > waiting_human > review > working/claimed > pending > failed > blocked。paused/cancelled 不参与。全部 done → done。 + +**完整合法流转矩阵:** + +```python +VALID_TRANSITIONS = { + "pending": {"claimed", "cancelled"}, + "claimed": {"working", "paused", "pending", "cancelled"}, + "working": {"review", "blocked", "failed", "paused", "escalated", "waiting_human", "cancelled"}, + "paused": {"working", "cancelled"}, + "review": {"done", "pending", "failed", "escalated", "waiting_human", "cancelled"}, + "blocked": {"pending", "escalated", "cancelled"}, + "failed": {"pending", "escalated", "cancelled"}, + "escalated": {"working", "pending", "cancelled"}, + "waiting_human": {"working", "done", "cancelled"}, + "done": set(), # 终态 + "cancelled": set(), # 终态 +} +``` + +**字段迁移(v2.8):** `escalated` 从布尔字段改为 status 判断。DB 保留 `escalated INTEGER` 做向后兼容,前端/Ticker 改用 `task.status == 'escalated'`。 + +**归档机制(v2.8):** tasks 表新增 `archived INTEGER DEFAULT 0`、`archived_at TEXT`。归档不改变状态,只影响前端显示(默认不显示已归档任务)。 + +**Checkpoint 机制(M3):** Agent 执行到需人工确认的节点 → 创建 checkpoint + 设置 waiting_human → 用户 approve/reject → 自动推进状态。详见 `docs/design/v2.8-state-enhancement.md`。 + +### 3.4 原子操作 + +**任务认领(claim)** - 原子 CAS,防止两个人同时领: + +```python +def claim_task(task_id: str, agent_id: str) -> bool: + conn = get_connection() + try: + cursor = conn.execute( + "UPDATE tasks SET status='claimed', assignee=?, claimed_at=datetime('now') " + "WHERE id=? AND status='pending' AND (assignee IS NULL OR assignee=?)", + (agent_id, task_id, agent_id) + ) + conn.commit() + return cursor.rowcount > 0 # 0 表示被别人抢了或不是指定分配给自己的人 + finally: + conn.close() +``` + +**产出写入** - SQLite 事务保证原子: + +```python +def write_output(task_id: str, agent_id: str, output: dict): + conn = get_connection() + try: + conn.execute("BEGIN IMMEDIATE") # 立即获取写锁 + conn.execute( + "INSERT INTO outputs (task_id, agent, output_type, title, content_path, summary, metadata) " + "VALUES (?, ?, ?, ?, ?, ?, ?)", + (task_id, agent_id, output['type'], output['title'], + output['path'], output['summary'], json.dumps(output.get('metadata', {}))) + ) + conn.execute( + "INSERT INTO events (task_id, agent, event_type, detail) VALUES (?, ?, 'output_written', ?)", + (task_id, agent_id, json.dumps({'output_id': output['title']})) + ) + conn.commit() + finally: + conn.close() +``` + +### 3.5 评论线程(讨论机制) + +参考 Hermes 的 `kanban_comment` 模式: + +```python +def add_comment(task_id: str, author: str, body: str, mentions: list = None): + conn = get_connection() + try: + conn.execute( + "INSERT INTO comments (task_id, author, body, mentions) VALUES (?, ?, ?, ?)", + (task_id, author, body, json.dumps(mentions or [])) + ) + conn.execute( + "INSERT INTO events (task_id, agent, event_type, detail) VALUES (?, ?, 'commented', ?)", + (task_id, author, json.dumps({'body_preview': body[:100], 'mentions': mentions})) + ) + conn.commit() + finally: + conn.close() +``` + +**讨论示例:** + +``` +[16:30 庞统] 张飞,你的实现方案我看了,回测数据量大时内存会爆。 + 关羽,从风控角度也看看? @关羽 @张飞 +[16:35 关羽] 同意。建议加分批加载机制,单批不超过 50 万条。 +[16:40 张飞] 收到,改成分批加载。预计 30 分钟。 +[16:55 庞统] @张飞 注意止损逻辑也需要同步改,分批后止损触发时机变了。 +[17:10 张飞] 完成。产出在 output-zhangfei-v2.md。 +``` + +**核心原则:评论都在黑板上,不在任何 agent 的 session 里。Agent 的 session 是临时的。** + +### 3.6 竞态解决 + +任务认领的竞态通过 SQLite 原子 CAS 解决(先到先得)。 + +职责冲突的解决(张飞和关羽都认为自己该做某个任务): +1. **默认:先到先得** - SQLite CAS,谁先 claim 谁做 +2. **升级:庞统仲裁** - 如果争议,评论中 @庞统 请求仲裁 +3. **最终:用户拍板** - @user 请求用户决定 + +不需要复杂的分布式共识--职责分工已经自然避免了大部分冲突。 + +### 3.7 黑板是索引不是仓库(AI Native 内容规范) + +**核心原则:黑板只存元数据 + 摘要 + 文件路径,不存大段文本内容。** + +设计推导(课题 2): +- Network-AI 的核心洞察:Agent 只读黑板摘要,详细数据在文件中 +- Claude Code 的 file reference 模式:不内联,只引用 +- agent-chorus 的 Context Pack 实验证明:结构化上下文让 Agent 文件打开量降 70%、token 消耗降 60%、零生产风险答案 +- Opal-Bridge 的 Fidelity 三档:无损/摘要/混合,传递时按需降档 +- 一个典型任务全量黑板信息 ~1100-1750 tokens,极端 ~4000 tokens--远小于 128K context +- 问题不是空间不够,而是**信号噪声比**:全量注入让 Agent 在无关信息上浪费注意力 + +**AI Native 内容规范--不做硬限制,做软引导:** + +传统做法是给每个字段设长度上限(如 comments.body ≤ 2000 字符),这是 CRUD 应用的思维。 +AI Native 的做法是:Agent 是智能体,有能力判断"这段分析应该写文件还是直接写评论"。规范是指导性的,不是强制性的。 + +- **不做硬限制**--不设字段长度上限,不截断,不报错 +- **做软引导**--Agent 的 Skill 中写"评论应简洁明了,大段分析写文件后在评论中给路径" +- **做传递优化**--L1 传递时自动截取(最近 3 条评论、每条 100 字符),这是传递层面的优化,不是存储层面的限制 +- **做信息分层**--黑板上的 comments 表存完整内容(不截断),但 L1 传递时只取摘要 + +**为什么这样做是 AI Native**: +1. Agent 是智能体,不是 API 客户端--它有能力判断"这段分析应该写文件还是直接写评论" +2. 如果硬限制导致信息丢失,Agent 会绕过限制(拆成多条评论、用文件存储),反而更混乱 +3. 真正需要控制的是传递时的信息量(L1 预算),不是存储时的信息量 + +**黑板上"必要信息"的定义(指导性)**: + +| 类别 | 上黑板 | 不上黑板 | +|------|--------|----------| +| 决策 | ✅ 谁、选了什么、为什么 | ❌ 完整备选方案对比表 | +| 产出 | ✅ title + summary + content_path | ❌ 代码全文、数据文件 | +| 状态 | ✅ 当前 status + 最新 observation | ❌ 完整事件日志(可归档) | +| 讨论 | ✅ 结论 + 关键论据 | ❌ 来回辩论的完整过程 | +| 风险 | ✅ severity + 一句话描述 | ❌ 详细影响分析报告 | + +**防爆炸机制**: +- 产出物只存路径(outputs.content_path) +- 事件日志有 TTL(events 表定期归档旧数据) +- 大段分析建议写文件,黑板只存摘要+路径 + +#### 三层约束体系(AI Native 结构化约束) + +Skill 软引导的问题是"可看可不看",等于没有约束。数据库硬限制是传统 CRUD 思维。中间地带是 **CLI 层 Schema 校验**——参考 agent-chorus 的 JSON Schema 模式(每个操作都有 `schemas/*.schema.json`),在写入接口层校验结构。 + +| 层 | 机制 | 约束力 | 适用对象 | +|---|------|--------|----------| +| **Schema 校验** | blackboard.py CLI 写入时校验 JSON Schema | 强(不符合返回明确错误) | 结构化操作(handoff / output / decide / observe) | +| **Skill 引导** | Agent Skill 中的行为规范文本 | 弱(可看可不看) | 非结构化操作(普通评论、讨论) | +| **L1 截取** | Daemon 构建 L1 时自动截取 | 代码层面,Agent 无感 | 传递优化 | + +**为什么这是 AI Native**: +1. Schema 是可执行文档——Agent 不需要读 Skill 就能知道格式要求(CLI 错误信息会告诉它缺了什么) +2. 错误信息是建设性的——"Handoff must include 'completed' field" 让 Agent 知道该补什么 +3. 约束的是结构,不是内容——不限制写多长,只限制必须包含哪些字段 +4. Agent 可以自主决定深度——可选字段可以不写 +5. 和 OpenAI Agent SDK 的 handoff `input_type` 同理——Pydantic schema 校验交接数据 + +**Schema 定义**(`schemas/` 目录): + +```json +// schemas/handoff.schema.json +{ + "type": "object", + "required": ["completed", "artifacts"], + "properties": { + "completed": { "type": "string", "description": "完成了什么" }, + "artifacts": { + "type": "array", + "items": { "type": "string" }, + "description": "产出物路径列表" + }, + "remaining": { "type": "string", "description": "未完成事项(可选)" }, + "next_steps": { "type": "string", "description": "对接手者的建议(可选)" } + } +} + +// schemas/observe.schema.json +{ + "type": "object", + "required": ["severity", "body"], + "properties": { + "severity": { + "type": "string", + "enum": ["info", "warning", "critical"], + "description": "info=只记录不触发; warning=下次tick触发庞统; critical=立即spawn庞统" + }, + "body": { "type": "string", "description": "风险描述" } + } +} +``` + +**severity 枚举与处理方式对齐**(与 §4.7 Guardrail 体系一致): + +| severity | Daemon 处理 | 对应 Guardrail 行为 | +|----------|-----------|-------------------| +| `info` | 只记录,不触发 | 不介入 | +| `warning` | 下次 tick 统一处理 | spawn 庞统(L3)判断 | +| `critical` | inbox 通知 → 立即 spawn 庞统 | 立即介入 | + +| 操作 | Schema 文件 | 必填字段 | Schema 校验 | CLI 附加校验 | +|------|-----------|---------|------------|------------| +| `--handoff` | handoff.schema.json | completed + artifacts | 结构完整,类型正确 | artifacts 中路径是否存在 | +| `--output` | output.schema.json | summary + content_path | summary 非空字符串 | content_path 文件是否存在 | +| `--decide` | decide.schema.json | decision + rationale | 两个字段非空字符串 | 无 | +| `--observe` | observe.schema.json | severity + body | severity 枚举值(info/warning/critical) | 无 | +| `--comment`(普通) | 无 | body | 无 Schema 校验 | 无 | + +**Agent 使用方式**: +```bash +# 结构化操作:CLI 校验 Schema +blackboard.py comment --task task-001 --author zhangfei-dev \ + --handoff '{"completed": "分批加载实现", "artifacts": ["task-001/output.md"], "remaining": "止损逻辑"}' +# 校验失败 → 返回具体错误:"Handoff must include 'completed' field" +# 校验通过 → 写入 comments 表,body 自动格式化为结构化文本 + +# 普通评论:无 Schema 校验 +blackboard.py comment --task task-001 --author zhangfei-dev \ + --body "@赵云 task-002 需要分钟线数据" +``` + +**落地到 schema(存储层)**: +- `outputs` 表:`content_path` + `summary`,不存文件内容 +- `comments` 表:`body` 存完整内容(不截断),handoff 类型自动格式化 +- `decisions` 表:`decision` + `rationale` 是结构化文本 +- `observations` 表:`body` 是风险描述 + +Agent 获取信息的分层策略(L1/L2/L3)详见 §4.4,对应 Opal-Bridge Fidelity 三档。 + +--- + +## 4. Daemon(管家)设计 + +### 4.1 Daemon 的角色定位 + +> **Daemon 是投递员,不是决策者。所有决策发生在黑板上,daemon 只执行。** + +Daemon 做三件事: +1. **读黑板** - 定期 tick,检查黑板状态 +2. **Spawn Agent** - 根据黑板上的指示,spawn 对应的 agent +3. **清理 Session** - agent 执行完后,存档 jsonl + 清理 sessions.json + +Daemon **不做**: +- ❌ 不决定谁做什么(agent 自己决定或庞统在黑板上分配) +- ❌ 不维护状态机(黑板就是状态) +- ❌ 不做业务逻辑(不解析产出、不做评审) + +**三层执行模型**:Daemon 的操作按成本和复杂度分为三层: + +| 层级 | 方式 | 成本 | 适用场景 | 例子 | +|------|------|------|---------|------| +| **L1 Daemon 直接操作** | SQLite 读写、文件操作 | 几乎为零 | 纯机械动作 | 更新状态、记录事件、机械验证(文件存在、JSON格式、字段非空) | +| **L2 spawn sub** | `openclaw agent --agent --session-id ` | 轻量(隔离 session,单任务) | 轻量 AI 判断 | scope guard、格式校验、快速评估、假死 reminder | +| **L3 run agent** | spawn 完整 Agent 到黑板上工作 | 完整(读黑板+思考+写回) | 重度 AI 工作 | 庞统拆解、张飞编码、司马懿 review、庞统纠错 | + +关键区别: +- L2 的 sub 是一次性、单任务的("帮我检查这个输出是否在 scope 内"),执行完就退出 +- L3 的 agent 是完整的黑板参与者(读全局、自主决策、写回多个表) + +**L2 与 L3 的区分标准**:是否读黑板全局。 +- L2:不读黑板全局上下文,只拿当前任务的特定字段做判断。spawn 时传递局部数据(如 scope_declaration 文本 + task.truths),sub 返回结果后退出。 +- L3:读黑板全局(tasks + comments + outputs + decisions + observations),做全局决策。spawn 时只传任务 ID + 触发原因,Agent 自己读黑板。 + +这个区分决定了 spawn 时的消息内容--L2 传数据,L3 传指针。 + +### 4.2 事件驱动架构(课题 2 设计决策) + +#### 设计推导过程 + +**三个参考系统的做法**: + +| 系统 | 架构 | 事件通知方式 | 启示 | +|------|------|------------|------| +| **open-multi-agent** | 单进程 TypeScript | 纯 EventEmitter--`queue.on('task:ready', handler)`。TaskQueue 内部维护 listeners Map,complete() 时同步触发 emit。零基础设施 | 和我们的黑板同构:TaskQueue.complete() = 我们的任务完成,unblockDependents() = 我们的依赖解锁 | +| **agent-chorus** | 多进程(Claude/Codex/Gemini 各自独立) | 本地 JSONL 文件队列--`chorus send` 写入 `.agent-chorus/messages/.jsonl`,`chorus messages --agent --clear` 读并清空。纯文件系统,无网络 | Standup+Conclude 模式:Agent 开始时读 inbox,结束时广播状态。JSONL inbox 做跨进程通信 | +| **Edict** | 分布式(API Gateway + Orchestrator + Agent Pool) | Redis Streams Event Bus--`task.created` 等主题 + WebSocket 推送 Dashboard | 我们是单机单进程,不需要 Redis | + +**推导结论**: +1. open-multi-agent 证明:单进程内 EventEmitter 完全够用,但它是单进程,我们是跨进程 +2. agent-chorus 证明:跨进程通信用 JSONL 文件就行,不需要 HTTP/Redis/MQ +3. Edict 的 Redis Streams 是分布式场景所需,我们不需要 +4. **真正需要即时响应的场景只有 4 个**:task_completed / task_failed / @mention / user_action。其他 ≤30s 延迟完全可接受 +5. **60s Tick 本身不是问题,问题是 Tick 的效率**--应该 Tick 是核心,加速器可选 + +**用户反馈与设计迭代**: +- 初始设计:Signal File 跨进程通知 → 用户质疑"Signal File 存在的意义是什么" +- 第二版:HTTP 端点 → 用户要求"基于优秀实践推导,不是拍脑袋换方案" +- 最终版:Tick 核心 + Inbox JSONL 加速(agent-chorus 模式)--基于三个参考系统的实际代码推导 + +#### D2-1:Tick 核心 + Inbox 加速(最终方案) + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Daemon (Python asyncio) │ +│ │ +│ 核心:Tick Loop(30s 主循环) │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ 读黑板全量状态(SQLite 查询) │ │ +│ │ 发现需要处理的(mention / blocked / done → pending) │ │ +│ │ 执行对应操作(spawn / 通知 / 清理) │ │ +│ │ 健康检查(zombie / stale / heartbeat) │ │ +│ │ │ │ +│ │ 设计推导:Hermes 60s tick 证明 polling 可靠稳定。 │ │ +│ │ 我们从 60s 降到 30s,因为黑板查询比 Hermes 轻量。 │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ ↑ │ +│ 加速:Inbox JSONL(agent-chorus 模式) │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Agent 写黑板后,可选:追加一行 JSON 到 daemon inbox │ │ +│ │ Daemon 主循环每 1s 检查 inbox 是否有新内容 │ │ +│ │ 有新内容 → 立即执行一次 mini-tick(只处理触发的事件) │ │ +│ │ 处理完清空 inbox │ │ +│ │ │ │ +│ │ 设计推导:agent-chorus 用 JSONL inbox 做跨 Agent 通信, │ │ +│ │ 我们用 JSONL inbox 做 Agent→Daemon 通知。同理同构。 │ │ +│ │ inbox 是加速器,不是核心。Tick 兜底所有场景。 │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ ↑ │ +│ 恢复:启动时全量扫描 │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Daemon 重启后立即做一次完整 Tick(PM2 自动重启) │ │ +│ │ 消除重启后的 30s 空窗 │ │ +│ │ 不需要 EventBus 持久化--黑板(SQLite)是唯一真相源 │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────┘ +``` + +**为什么不选的替代方案**: +- EventBus + Signal File(初始设计):Signal File 需要额外的扫描/读/删循环,增加了耦合链 +- HTTP 端点(第二版):引入网络依赖,Daemon 需要跑 HTTP 服务,不够简单 +- Redis pub/sub(Edict 方案):引入新依赖,v2.6 已去掉 Redis;我们不需要分布式 +- SQLite update-hook:需要 C API 绑定 +- fswatch/watchdog:跨平台兼容性差 + +**Inbox JSONL 具体实现**(参考 agent-chorus `chorus send` 模式): + +```python +# blackboard.py 写完 SQLite 后,可选追加一行 JSON +INBOX_PATH = Path("~/.sanguo_projects/sanguo_moziplus_v2/inbox/daemon.jsonl") + +# 写入格式(参考 agent-chorus message schema: from/to/timestamp/content/cwd) +async def notify_daemon(event_type: str, task_id: str, agent: str): + line = json.dumps({ + "from": agent, + "to": "daemon", + "timestamp": datetime.now(timezone.utc).isoformat(), + "event": event_type, # comment_added / task_completed / task_failed + "task_id": task_id, + }) + async with aiofiles.open(INBOX_PATH, mode='a') as f: + await f.write(line + '\n') + +# Daemon 主循环中检查 inbox +async def check_inbox(): + if not INBOX_PATH.exists(): + return + lines = INBOX_PATH.read_text().strip().split('\n') + INBOX_PATH.write_text('') # truncate:清空内容不删除文件,避免并发追加写入时文件不存在 + for line in lines: + msg = json.loads(line) + await handle_event(msg['event'], msg['task_id'], msg['from']) +``` + +**Daemon 主循环**: + +```python +async def daemon_main_loop(): + # 启动时全量扫描 + await full_tick() + + while True: + # 1. 检查 inbox(每 1s) + await check_inbox() # 有内容则立即执行 mini-tick + + # 2. 定期 Tick(每 30s) + if time.time() - last_tick > 30: + await full_tick() + last_tick = time.time() + + await asyncio.sleep(1) +``` + +#### D2-2:依赖声明的并行/串行自动决策 + +> **设计推导**:open-multi-agent 的 TaskQueue.complete() → unblockDependents() 是核心模式--complete→auto-unlock,纯依赖声明驱动。其 scheduler.ts 还提供了 4 种调度策略(round-robin / least-busy / capability-match / dependency-first)。 + +**串行触发链**(Tick + Inbox 加速版): +``` +Agent A 完成 task-001 + → 写黑板 outputs + 更新 status → done + 写 handoff comment + → 通知 Daemon(inbox JSONL) + → Daemon 下次循环(~1s 内)收到通知 → mini-tick: + 查询所有 depends_on 包含 task-001 的 pending 任务 + → task-002 depends_on: [task-001],检查 task-001 done ✅ + → spawn Agent B 执行 task-002 + (如果 inbox 通知丢失 → 30s Tick 兜底补上) +``` + +**并行**:`depends_on` 为空且 assignee 不同的任务,自然并行(Daemon 分别 spawn)。不需要额外逻辑。 + +**不做 files_modified 冲突检测**(D2-4):Agent 通过黑板评论自然协调("我在改 main.py,你别碰"),不需要系统强制。Scope Guard(课题 1)作为兜底。实际覆盖:depends_on 覆盖 80%+ 的显式依赖场景,边角场景通过黑板评论 + 庞统仲裁补充。 + +#### 与课题 1 的兼容性 + +| 课题 1 设计 | 事件驱动后变化 | 改善 | +|-----------|--------------|------| +| 续杯机制 | task_completed 通知加速依赖解锁 | @mention 从 ≤60s 降到 ≤1s | +| retry 由 AI 决策 | task_failed 通知加速 retry 链 | 庞统更快介入 | +| Guardrail 吹哨人 | observation 写入后通知 Daemon | Daemon 即时感知问题 | +| 三层执行模型 | 不变,Tick/inbox 处理仍按 L1/L2/L3 分层 | ✅ 一致 | + +### 4.3 Session 生命周期 + +``` +1. Daemon spawn + openclaw agent --agent zhangfei-dev --session-id \ + --message "请检查黑板 task-001..." + ↓ +2. Agent 执行 + - 读黑板(SQLite 查询) + - 做任务(编码/审核/数据分析) + - 写回黑板(产出、评论、状态更新) + ↓ +3. Agent 退出(自然结束) + ↓ +4. Daemon 清理 + - mv .jsonl → task-001/archive/ + - mv .trajectory.jsonl → task-001/archive/ + - 编辑 sessions.json 删除该 session 记录 +``` + +**技术验证结论:** +- `openclaw agent --agent --session-id ` 可创建完全隔离的 session ✅ +- 直接编辑 `sessions.json` 可安全删除 session 记录 ✅(已验证) +- Gateway WS `sessions.delete` 需要 `operator.admin` scope(token 模式不授予,不可用)❌ +- 回退方案:直接编辑 `sessions.json` 是安全可靠的 ✅ + +### 4.4 Agent Spawn 的上下文分层传递(课题 2 设计决策) + +> **设计推导**:GSD Wave Execution 证明隔离 session + 新鲜 context > 单一 session + 压缩。Claude Code 的 file reference 模式证明"引用而非内联"是最优策略。Opal-Bridge 的 Fidelity 三档提供了理论框架。agent-chorus 的 Context Pack 实验证明结构化上下文让 Agent 效率提升 60-70%。问题不是 context 不够大,而是信号噪声比。 + +**L1/L2/L3 对应 Opal-Bridge Fidelity 三档**: + +| Opal-Bridge Fidelity | 我们的映射 | 场景 | +|---------------------|----------|------| +| Mode A:无损(完整上下文) | L1 + L2 + L3 | 复杂任务,Agent 需要完整了解讨论历史和产出 | +| Mode B:LLM 摘要 | L1 + L2 | 标准任务,核心信息 + 扩展摘要 | +| Mode C:混合保留最近N条 | L1 | 简单任务,只传核心,Agent 按需取更多 | + +Agent 自己决定用哪个 Fidelity 档位--收到 L1 后判断信息是否足够,不够就 L2/L3。 + +**D2-5:三层上下文传递(L1 必传 / L2 按需 / L3 按需)** + +| 层级 | 内容 | Token 估算 | 谁决定 | +|------|------|-----------|--------| +| **L1(spawn message)** | 任务核心 + 角色 + 触发原因 + 依赖状态 + 最近评论 + must_haves | ~300-500 | Daemon 自动 | +| **L2(CLI 按需)** | 完整评论线程 + 产出摘要 + 决策记录 + 观察记录 | ~500-1500 | Agent 自主决定 | +| **L3(文件按需)** | 产出物文件完整内容 + 完整事件日志 + 子任务详情 | ~2000-10000 | Agent 自主决定 | + +**L1 Spawn Message 模板**: + +```python +def build_spawn_message_L1(task_id: str, agent_id: str, trigger: str) -> str: + task = get_task(task_id) + + # 依赖状态摘要(1行/依赖任务) + deps_status = [] + for dep_id in json.loads(task['depends_on'] or '[]'): + dep = get_task(dep_id) + deps_status.append(f" {dep_id}: {dep['status']} - {dep['title']}") + + # 最近 3 条评论摘要(截断 100 字符) + recent_comments = get_comments(task_id, limit=3) + comments_str = "" + for c in recent_comments: + comments_str += f" [{c['created_at'][:16]} {c['author']}] {c['body'][:100]}\n" + + # must_haves 摘要 + must_haves = json.loads(task.get('must_haves') or '{}') + truths_str = ', '.join(must_haves.get('truths', [])) + + return f"""黑板任务通知(L1): +任务:{task['title']}({task['id']}) +状态:{task['status']} | 类型:{task['task_type']} | 风险:{task['risk_level']} +触发原因:{trigger} +描述:{task['description'] or '(无)'} +验收标准(truths):{truths_str or '(未定义)'} + +依赖状态: +{chr(10).join(deps_status) if deps_status else ' (无依赖)'} + +最近评论: +{comments_str if comments_str else ' (无评论)'} + +请使用以下命令获取更多信息: + L2(扩展):blackboard.py read --task {task_id} --level L2 + L3(全量产出):blackboard.py read --task {task_id} --type outputs +""" +``` + +**D2-6:不需要 Auto-compact**:v2.6 的 Agent 每次 spawn 都是隔离的新鲜 session,天然没有 context rot。唯一可能有累积的是庞统主 session(长期在线协调),属 Phase 3 优化。 + +**D2-7:Context 预算分配**(128K 模型): + +| 组件 | 预算 | 说明 | +|------|------|------| +| System Prompt + SOUL.md + IDENTITY.md | ~3K-5K tokens | 固定开销 | +| Skills + AGENTS.md | ~2K-4K tokens | 固定开销 | +| L1 spawn message | ~300-500 tokens | 必传 | +| L2 黑板扩展(按需) | ~500-1500 tokens | Agent 自主决定 | +| L3 产出物文件(按需) | ~2K-10K tokens | Agent 自主决定 | +| 工作空间(Agent 思考+输出) | ~30K-50K tokens | 预留 | +| **总计** | **~40K-70K tokens** | 远小于 128K,安全 | + +### 4.5 续杯与心跳 + +参考 v1.0 实践 + Hermes v0.13 Claim TTL。 + +**正常流(大多数情况):** +1. Agent spawn → 开始工作 +2. Agent 每个关键进展写黑板 observation(既是进度汇报,也是心跳信号) +3. Daemon tick 看到 working 状态 + 有新 observation → 不干预(健康状态) +4. Agent 完成产出 → 写 output + 状态流转 → Daemon 检测到继续下一步 + +**异常流:** + +| 情况 | Daemon 检测到 | 行为 | 层级 | +|------|-------------|------|------| +| Agent 有进展 | 黑板有新 observations | 不干预(无限续) | L1 | +| Agent 没进展但 session 活跃 | 无新 observations 但 session 还在 | 不干预(可能正在思考) | L1 | +| ↑ 判断信号:`observations 最后写入时间 < estimated_duration_minutes`,纯 L1 查询,不依赖 AI 判断 | | | | +| timeout(agent run 返回超时)+ 产出达标 | agent run 返回超时 + outputs 表有内容 | 幻觉门控验证产出 → 通过则继续流转 | L1→L2 | +| timeout(agent run 返回超时)+ 产出不达标 | agent run 返回超时 + outputs 为空 | L2 spawn sub 发 reminder 让 Agent 继续(假死处理) | L2 | +| timeout + 产出不达标 + reminder 后仍无进展 | 二次 timeout | 回收到 pending,记录 failure_detail | L1 | +| 非timeout 错误(进程退出) | 进程已死 | 进入 AI 纠错流程 | L3 | +| 硬上限超时 | working 状态超过 3x 预估工时 | 强制回收,记录事件 | L1 | + +**设计推导**: +- v1.0 实践证明:看结果不看过程(即使 CLI 报错/超时,产出文件存在且有效就算成功) +- 续命和重试是两个独立预算:续命(Agent有进度→无限续),重试(Agent真挂→有限次) +- Hermes 的 Claim TTL(默认15分钟)提供了超时回收的参考值 + +**timeout 的检测**:timeout 信号来自 `openclaw agent --agent ` 的返回值(阻塞调用)。Agent 在执行过程中通过写黑板 observations 维持活跃信号--Daemon tick 检查 observations 的最后写入时间,如果有新 observation 说明 Agent 还在工作。但最终判断 Agent 是否超时,以 agent run 的返回值为准。 + +reminder 后的硬时间上限:reminder 后如果超过 `estimated_duration_minutes` 仍未完成(从 reminder 时间算起),才回收任务。 + +### 4.6 AI 驱动的 Retry(纠错协商) + +参考 v1.0 _handle_blocked_node() + Hermes task_runs + Claude Code Teams "before retrying, answer what failed"。 + +**核心原则**:Retry 原因由 AI 判断,Daemon 只执行。 + +**流程:** + +1. Agent 失败(产出 status=failed 或 Daemon 检测到异常终止) +2. Daemon 不判断原因,只在黑板上记录这次 attempt(task_runs 模式,每次 attempt 独立记录) +3. Daemon spawn 庞统(L3)看黑板上的失败记录 + 之前所有 attempts +4. 庞统在黑板上写决策(四种选择之一): + - "同一 Agent 重试" + 失败原因分析 + 改进建议 + - "换 Agent 重试" + 为什么换 + 新 Agent 优势 + - "任务需要用户介入" + 卡在哪 + 建议 + - "任务无法完成,建议取消" + 为什么 +5. Daemon 读庞统决策,执行对应操作 +6. 如果重试后仍失败 → 新 attempt 记录 → 再次 spawn 庞统 +7. Circuit breaker:同一 task 总 attempt 数达到 N 次(默认3次,不区分是哪个 Agent)→ 自动 block + 通知用户。理由:3 次尝试都不成功说明问题在任务本身而非 Agent 能力。 + +**失败记录**:谁记录什么? + +| 记录者 | 记录内容 | 黑板位置 | +|--------|---------|---------| +| Daemon | 机械类失败(进程退出码、超时) | events 表,event_type=task_failed | +| 司马懿 | 内容类失败(评审不通过) | reviews 表(verdict=needs_revision + issues) | +| 庞统 | 方向类失败(需求偏离) | decisions 表(重规划原因) | +| Agent 自己 | 能力不足/专业外,主动报告失败 | comments 表(说明原因)+ tasks status→failed | +| Agent(重试时) | 新 attempt 的产出 | outputs 表(带 attempt_number) | + +**Agent 重试时能看到什么**:黑板上的 events(失败记录)+ reviews(评审意见)+ comments(讨论)。全部在黑板上,spawn 时自然读到。 + +**设计推导**: +- v1.0 实践:庞统分析原因 → 司马懿 challenge → 三轮协商 → 执行,方向正确但由引擎硬编码调用 +- v2.6 改进:Agent 在黑板上自主协商(需要事件驱动支持,见课题2),Daemon 只 spawn 不调度 +- Hermes task_runs:每次 attempt 独立记录(attempt_number, outcome, log_path, exit_code),可追溯可审计 + +### 4.7 Guardrail 体系(吹哨人机制) + +参考 OpenAI Agent SDK OutputGuardrail + GSD must_haves + v1.0 M4 Guard 机制。 + +**核心原则**:Guardrail 是吹哨人不是终结者。检测到问题写黑板(observation),触发后续 AI 判断链。决策权在黑板上,执行权在 Daemon。 + +**三个 Guardrail:** + +| Guardrail | 触发时机 | 检测方式 | 发现问题后 | +|-----------|---------|---------|-----------| +| **Scope Guard** | Agent claim 任务后在工作过程中写 decisions(scope 相关)时 | L2 sub 异步对比 scope_declaration vs task.truths | 写 observation(severity=warning),Daemon 下次 tick 触发庞统判断 | +| **Output Guard** | Agent 写 output 时 | L1 机械检查(文件存在、格式正确、字段非空)+ L2 语义检查 | 机械失败直接打回,语义问题写 observation | +| **Format Guard** | Agent 写任何结构化数据时 | L1 JSON Schema 校验 | 格式错误直接打回重做 | + +**后续动作链(问题升级):** + +``` +Guardrail 检测到问题 → 写黑板 observation + ↓ +Daemon tick 读到 observation + ↓ +根据 severity 分级处理: + - blocking → L3 立即 spawn 庞统 + - warning → L3 spawn 庞统(下次 tick 统一处理) + - info → 只记录,不触发 + ↓ +庞统在黑板上写决策: + - "确认偏离,打回" → Daemon 改状态回 pending + - "方向扩展合理,批准继续" → 继续 + - "需要用户判断" → 通知用户 +``` + +**设计推导**: +- OpenAI Agent SDK:Guardrail 本身是轻量 AI Agent(并行运行,专门做检查),不是 if-else 规则 +- GSD must_haves truths:面向可观测行为,不是实现步骤 +- v1.0 M4 Guard:entry/exit guard + skill 化检查逻辑,方向正确但绑定在 DAG 节点上 + +**Scope Guard(异步检查,不阻塞 Agent 执行)**: +- 触发时机:Agent claim 任务后在工作过程中写 decisions(scope 相关)时 +- 检查方式:L2 sub 异步对比 scope_declaration vs task.truths +- 不阻塞:Agent 写完 scope_declaration 后继续工作,不等 Guard 结果 +- 发现问题:写 observation(severity=warning),Daemon 下次 tick 触发庞统判断 +- 兜底:即使 Scope Guard 漏报,庞统在 review 阶段仍会检查方向正确性 + +--- + +## 5. Agent 与黑板的交互 + +### 5.1 Agent 被_spawn_后的工作流程 + +``` +Agent 被 spawn + ↓ +1. 读黑板 → 了解任务全局状态 + - 读 tasks 表:当前任务的状态、描述、依赖 + - 读 comments 表:讨论历史 + - 读 outputs 表:已有产出 + - 读 observations 表:已知风险 + ↓ +2. 想 → 根据自己的职责自主决策 + - 我是编码先锋,这个 pending 任务适合我 → claim + - 我是风控守将,这个 comment @ 我 → 回复 + - 我是副军师,这个任务需要分解 → 创建子任务 + - Agent claim 任务后、开始工作前,写 scope_declaration 到 decisions 表: + "我计划做什么,产出什么" + Scope Guard(L2 sub)会对比 scope_declaration vs task.truths + ↓ +3. 做 → 执行任务 + - 编码、审核、数据分析等 + - 过程中发现风险 → 写 observation + - 需要其他人协助 → 写 comment @mention + ↓ +4. 写回黑板 → 产出、评论、状态更新、决策记录 + - 写 outputs 表:产出文件路径 + 摘要 + - 写 comments 表:完成说明 + - 写 decisions 表:关键决策(哪怕自己的决策也要填一条) + - 更新 tasks 表:status → done/review + - must_haves 三件套(任务创建时由庞统定义): + - truths:用户视角的可观测行为("用户能看到回测结果"),不是实现步骤("编写回测脚本") + - artifacts:必须存在的产出文件 + - constraints:继承的约束(如"不超过500行"、"必须用vnpy") + ↓ +5. 写 Handoff Comment → 退出 + - Agent 结束前必须写一条结构化的交接评论(借鉴 agent-chorus checkpoint) + - 内容:完成什么、产出在哪、还剩什么、建议下一步 + - 这条 comment 会出现在下一个 Agent 的 L1 消息中(最近 3 条评论),实现无缝接手 + - 示例: + ``` + blackboard.py comment --task task-001 --author zhangfei-dev \ + --body "## Handoff\n完成:分批加载实现\n产出:task-001/output-zhangfei-v2.md\n未完成:止损逻辑分批适配\n建议下一步:关羽 review 止损逻辑" + ``` + ↓ +6. Daemon 自动清理 session + - 通知 Daemon(inbox JSONL) + - Daemon 检测到完成 → 继续下一步(解锁下游 / spawn review / 清理 session) +``` + +**设计推导(Handoff Comment)**: +- agent-chorus 的核心机制是 Standup + Conclude:Agent 开始时读 inbox,结束时广播状态 +- 映射到黑板:Standup = Agent spawn 后读黑板(L1),Conclude = Agent 结束时写 handoff comment +- agent-chorus 的 checkpoint 广播给所有其他 Agent → 我们的 handoff comment 通过 L1 自然传递给下一个 Agent +- 关键价值:**黑板上的状态足够让 Agent B 无缝接手 Agent A 的工作**--这正是 agent-chorus 解决的核心问题 + +### 5.2 Agent 工具集 + +Agent 通过 `exec` 工具调用 CLI 命令操作黑板: + +```bash +# 读黑板(全部) +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py read --task task-001 + +# 读黑板(过滤:只读和自己相关的) +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py read --task task-001 --agent zhangfei-dev + +# 读黑板(过滤:只读最近 20 条) +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py read --task task-001 --last 20 + +# 读黑板(过滤:只读特定类型) +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py read --task task-001 --type comments + +# 认领任务 +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py claim --task task-001 --agent zhangfei-dev + +# 写产出 +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py output --task task-001 --agent zhangfei-dev \ + --type code --title "分批加载实现" --path task-001/output-zhangfei.md \ + --summary "实现分批加载,单批50万条" + +# 写评论 +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py comment --task task-001 --author zhangfei-dev \ + --body "完成分批加载实现" --mentions "[]" + +# 写观察 +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py observe --task task-001 --observer guanyu-dev \ + --severity warning --body "止损逻辑需适配分批模式" + +# 记录决策 +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py decide --task task-001 --decider zhangfei-dev \ + --decision "使用分批加载而非流式" --rationale "流式需要改底层框架,分批只需改回测模块" + +# 创建任务(任何 Agent 都可以创建) +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py create --title "分钟线数据下载" \ + --creator zhaoyun-data --task-type data + +# 写 Handoff Comment(结构化,CLI 校验 Schema) +python3 ~/.sanguo_projects/sanguo_moziplus/cli/blackboard.py comment --task task-001 --author zhangfei-dev \ + --handoff '{"completed": "分批加载实现", "artifacts": ["task-001/output.md"], "remaining": "止损逻辑分批适配"}' +# --handoff 使用 schemas/handoff.schema.json 校验 +# 缺必填字段 → CLI 返回具体错误,如 "Handoff must include 'completed' field" +# 校验通过 → 自动格式化为结构化评论写入 comments 表 +``` + +--- + +## 6. 关键场景流程 + +### 6.1 庞统规划 + Agent 领任务(事件驱动版) + +``` +用户 → 庞统(主session):"设计一个动量因子策略" + ↓ +庞统在黑板上写: + - 创建 task-001(数据准备,pending,无依赖) + - 创建 task-002(因子计算,pending,depends_on: [task-001]) + - 创建 task-003(回测验证,pending,depends_on: [task-002]) + - 评论:"建议赵云领 001,张飞领 002 和 003" + ↓ +庞统写 inbox 通知: task_created + ↓ +Daemon Tick 发现 task-001 pending + 庞统评论建议赵云 + → spawn 赵云(L1 消息含任务核心 + 庞统建议) + ↓ +赵云读黑板 → claim task-001 → 执行 → 写产出 + → 写 Handoff Comment: "完成:分钟线数据下载 | 产出:task-001/data/ | 无未完成事项" + → 更新 status→done → 通知 Daemon(inbox JSONL) + ↓ +Daemon ~1s 内收到 inbox 通知 → mini-tick: + → 查询 depends_on 包含 task-001 的 pending 任务 → task-002 + → task-002 的依赖全部满足 → spawn 张飞(L1 消息含赵云的 handoff 摘要) + ↓ +(同理 task-002 done → 即时触发 task-003) +``` + +**对比 polling 版**:task-001 done 到 task-002 spawn 的延迟从 ≤60s 降到 ≤1s。张飞的 L1 消息中包含赵云的 Handoff Comment,无需额外查询即可无缝接手。 + +### 6.2 Agent 间协作讨论(事件驱动版) + +``` +张飞执行 task-002 时发现需要分钟线数据 + ↓ +张飞写评论:"@赵云 task-002 需要分钟线数据,能帮忙下载吗?" +张飞更新任务状态 → blocked + → 通知 Daemon(inbox JSONL) + ↓ +Daemon ~1s 内收到 inbox 通知 → mini-tick: + → 解析 @mention → 赵云 + → spawn 赵云(L1 消息含评论摘要) + ↓ +赵云读黑板 → 看到评论 → 下载数据 → 写产出 +赵云写评论:"@张飞 数据就绪,可以继续" + 写产出 + → 通知 Daemon(inbox JSONL) + ↓ +Daemon 收到通知 → @mention → spawn 张飞 + ↓ +张飞读黑板 → 看到数据就绪 → 继续 task-002 +``` + +**对比 polling 版**:@mention 响应从 ≤60s 降到 ≤1s。 + +### 6.3 Agent 发现风险 + +``` +张飞在 task-002 中发现止损逻辑有 bug + ↓ +张飞写 observation(severity: warning): + "止损逻辑在分批模式下可能漏触发" +张飞写评论:"@关羽 止损逻辑需要你从风控角度确认" + ↓ +Daemon tick 发现 observation + 评论 @ 关羽 + ↓ +Daemon spawn 关羽 → 关羽读黑板 → 审查 → 写评论 + observation +``` + +### 6.4 用户直接参与 + +``` +用户读黑板 → 发现 task-002 进度慢 + ↓ +用户在黑板上写评论:"task-002 优先级提高,需要今天完成" + ↓ +Daemon tick 发现用户评论 → 如果张飞未 active → spawn 张飞通知 +``` + +--- + +## 7. Session 隔离与清理 + +### 7.1 技术实现 + +```python +class SessionManager: + def async_spawn_agent(self, agent_id: str, message: str) -> str: + """异步 spawn 隔离 session,不等待完成。返回 session_id。""" + session_id = str(uuid.uuid4()) + cmd = [ + "openclaw", "agent", + "--agent", agent_id, + "--session-id", session_id, + "--message", message, + "--json" + ] + # Popen 异步启动,不阻塞 daemon tick + subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + log_event(agent=agent_id, event_type='agent_spawned', detail={'session_id': session_id}) + return session_id + + def cleanup_session(self, agent_id: str, session_id: str, archive_dir: str): + """存档 jsonl + 文件锁保护下清理 sessions.json""" + sessions_dir = f"/Users/chufeng/.openclaw/agents/{agent_id}/sessions" + store_path = f"{sessions_dir}/sessions.json" + lock_path = f"{sessions_dir}/.cleanup.lock" + + # 1. 存档 jsonl 文件 + os.makedirs(archive_dir, exist_ok=True) + for ext in ['.jsonl', '.trajectory.jsonl', '.trajectory-path.json']: + src = f"{sessions_dir}/{session_id}{ext}" + if os.path.exists(src): + shutil.move(src, f"{archive_dir}/{session_id}{ext}") + + # 2. 文件锁保护下编辑 sessions.json(防止和 Gateway 并发写入冲突) + with open(lock_path, 'w') as lock_file: + fcntl.flock(lock_file, fcntl.LOCK_EX) + try: + with open(store_path) as f: + store = json.load(f) + + keys_to_remove = [k for k in store if session_id in k] + for k in keys_to_remove: + del store[k] + + with open(store_path, 'w') as f: + json.dump(store, f, indent=2) + finally: + fcntl.flock(lock_file, fcntl.LOCK_UN) + os.unlink(lock_path) +``` + +### 7.2 验证结论 + +| 验证项 | 结果 | +|--------|------| +| `openclaw agent --session-id ` 创建隔离 session | ✅ 通过 | +| 连续 spawn 多个 session 互不干扰 | ✅ 通过 | +| 并行 spawn 成功 | ✅ 通过 | +| 直接编辑 sessions.json 删除记录安全 | ✅ 通过 | +| jsonl 存档后从原目录删除 | ✅ 通过 | +| Gateway WS sessions.delete(需 admin scope) | ❌ 不可用 | +| `openclaw sessions cleanup --fix-missing --enforce` | ❌ 对 agent main session 报错 | +| Agent 主 session 对 CLI spawn 的 sub 完全无感 | ✅ 确认(设计如此)| + +--- + +## 8. Sanguo Mail:退役 + +v2.6 中 Mail 完全退役。黑板的两个操作替代了 Mail 的所有功能: + +| Mail 功能 | 黑板替代 | +|----------|--------| +| 庞统分配任务 | 庞统在黑板创建 task + 评论 @指定 agent | +| Agent 间通信 | 评论 @mention | +| 结果回传 | 产出写入 outputs 表 + 评论通知 | +| 讨论 | 评论线程 | + +黑板比 Mail 更可靠:信息集中在 SQLite(不分散在 mail 目录)、有状态追踪、评论线程保持上下文完整、SQLite 读写比 Mail poller 更可靠。 + +如果需要系统级通知(daemon 异常、Gateway 状态),在黑板上创建 `system` 类型任务处理。 + +--- + +## 9. 质量门控(挑战/评审体系) + +### 9.1 设计目标 + +课题1定义了"谁来判断"(双轨制)和"判断什么"(must_haves三件套、Guardrail吹哨人)。课题3解决"判断结果怎么记录、怎么协商、怎么流转"。 + +v1.0 三层体系(PAV自律 → 司马懿review → 庞统+司马懿终审)的问题: +1. PAV 形同虚设——Skill是"菜单"不是"触发器",Agent可忽略 +2. 每个节点都审过重——没有分级,简单/复杂任务同一个审查标准 +3. 方案阶段没审查——只审产出不审方案 +4. 评审结果不结构化——自然语言评论,无法机器判断 +5. 司马懿角色重叠——节点审一次+终审一次 + +### 9.2 分级审查流水线 + +> **参考实践**:superpowers 串行双审、TradingAgents 对抗辩论、v1.0 PRD 三层体系 + +| 任务风险等级 | 流水线 | 方案审查 | 产出审查 | 审查模式 | 最大轮次 | +|------------|--------|---------|---------|---------|---------| +| **high**(部署/策略/资金) | 三阶段 | ✅ 独立审查 | ✅ 独立审查 + Guardrail | 对抗辩论 | 5 | +| **standard**(编码/设计) | 二阶段 | ✅ 方案过挑战 | ✅ 产出过挑战 | 单审 | 3 | +| **low**(文档/格式化/搬运) | 一阶段 | ❌ 跳过 | ⚡ Guardrail 自动检查 | 自动 | 0 | +| **research**(调研/分析) | 一阶段 | ❌ 跳过 | ✅ 庞统确认方向 | 单审 | 2 | + +**风险等级**:庞统创建任务时标注。默认 `standard`。task_type 为 `strategy`/`deploy` 自动设 `high`,`research` 自动设 `research`。 + +### 9.3 三阶段审查流程 + +> **参考实践**:superpowers(implementer → spec reviewer → code quality reviewer串行)、TradingAgents(Bull vs Bear辩论) + +**阶段 1:方案审查(Plan Review)**——high/standard 任务 + +``` +执行者提交 scope_declaration 到黑板 decisions 表 + ↓ +挑战者审查方案 + ├── high:对抗辩论(正方 vs 反方 → 庞统裁决) + └── standard:单审(指定挑战者,默认司马懿) + ↓ +通过 → 进入执行 +未通过 → 协商修改(max_rounds 轮) + └── 超轮次 → 升级用户 +``` + +方案审查 vs Scope Guard(课题1): +- Scope Guard 是过程中的软检查(L2 sub 异步,发现问题写 observation) +- 方案审查是正式评审(L3 Agent 审查,通过/打回有 verdict) + +**阶段 2:执行 + Guardrail 自动检查**——所有任务 + +``` +执行者按方案执行 + ↓ +每次写 output 时 Daemon 自动触发 Guardrail + ├── L1 机械检查(文件存在/JSON格式/字段非空)→ 不通过直接打回 + ├── L2 轻量 AI 检查(Schema校验/artifacts路径验证)→ 不通过写 observation + └── L3 安全红线(tripwire:如直接操作生产环境)→ 立即中断 + ↓ +Guardrail 通过 → 进入产出审查 +``` + +**阶段 3:产出审查(Output Review)**——high/standard 任务 + +``` +执行者提交产出到黑板 outputs 表 + ↓ +挑战者审查产出(质量/完整性/与方案一致性) + ↓ +评审结果结构化记录到 reviews 表 + ↓ +通过 → status: review → done +未通过 → status: review → working(打回重做) + └── 协商在 comments 表 + └── 超轮次 → 升级用户 +``` + +### 9.4 审查协议注册表(Review Protocol Registry) + +> **参考实践**: +> - **superpowers**:三个独立 prompt 文件(implementer/spec-reviewer/code-quality-reviewer),每个角色有专属模板 +> - **oh-my-claudecode Critic**:Investigation Protocol 分 Phase(预判→验证→多视角→缺口分析→自审),不同 artifact type 自动切换视角 +> - **superpowers spec-reviewer**:prompt 注入对抗性指令("DO NOT trust the report. Read the actual code.") + +**问题**:审查者容易陷入局部审查(编码规范/编译通过),漏掉本质问题(需求一致性/语义正确性)。被挑战后一律照改不加思考。 + +**方案**:审查协议是代码注入的,不是靠 Agent 自己找 Skill。Daemon spawn 审查者时动态加载协议模板。 + +``` +review_protocols/ +├── plan_review.yaml # 方案审查协议 +├── output_review.yaml # 产出审查协议 +├── guardrail_l2.yaml # L2 轻量AI检查协议 +└── analysis_review.yaml # 分析/调研审查协议 +``` + +每个协议定义四个维度: + +| 维度 | 内容 | 参考 | +|------|------|------| +| 审查维度(审什么) | dimensions 列表,每个有 weight(critical/major/minor)和 method | superpowers 三种 reviewer 各自的关注点 | +| 审查方法(怎么审) | investigation_protocol 分阶段执行 | oh-my-claudecode Critic 五阶段 Protocol | +| 多视角 | 按 artifact type 切换视角集 | Critic 代码视角(安全/新人/运维) vs 方案视角(执行者/利益相关者/怀疑论者) | +| 对抗性指令 | adversarial_instructions 防止走过场 | superpowers spec-reviewer "DO NOT trust" 指令 | + +**Investigation Protocol 通用模式**(参考 Critic,各协议按场景裁剪): + +``` +Phase 1 预判(Pre-commitment):先不读产出,凭领域经验预测3-5个最可能出问题的点 +Phase 2 验证(Verification):读实际产出(不是报告),逐条验证 +Phase 3 多视角(Multi-perspective):从不同角色看产出 +Phase 4 缺口分析(Gap Analysis):不只看"什么有问题",还看"什么缺失了" +Phase 5 自审(Self-audit):给每个 finding 打 confidence,低 confidence 降级 +``` + +**多视角矩阵**: + +| 审查类型 | 多视角集合 | +|---------|----------| +| output_review(代码) | 安全 / 新人 / 运维 | +| plan_review(方案) | 执行者 / 利益相关者 / 怀疑论者 | +| analysis_review(调研) | 领域专家 / 实践者 / 反方辩手 | +| guardrail_l2(轻量) | 无多视角 | + +### 9.5 反驳权(Rebuttal Phase) + +> **参考实践**:TradingAgents Bull vs Bear 模式——双方都有表达权 + +审查不是单向的。但不是每次都触发反驳——有跳过条件: + +**跳过条件**(不需 spawn 反驳): +- 审查者 verdict=approved → 直接 done,跳过 rebuttal +- 审查者 verdict=needs_revision,但 issues 全是 minor severity → 执行者自然在 comments 接受并修改,不 spawn 反驳 + +**触发条件**(spawn 反驳): +- 审查者 verdict=needs_revision,且 issues 中有 critical 或 major severity → spawn 原执行者反驳 +- 审查者 verdict=rejected → spawn 原执行者反驳 + +``` +审查者提交 review + ↓ +verdict=approved → 直接 done(跳过 rebuttal) +verdict=needs_revision 且只有 minor → 执行者直接修改(跳过 rebuttal) +verdict=needs_revision 且有 critical/major → spawn 反驳 + ↓ +Daemon spawn 原执行者,注入反驳指令: + "对每个 issue,明确表态:ACCEPT / REJECT / PARTIAL + 不允许全部接受不加思考。" + ↓ +执行者 response 写入 comments 表 + ↓ +有 REJECT → Daemon spawn 审查者看 response → 继续协商 +全部 ACCEPT → 修改后重提交 → 审查者 re-review +``` + +### 9.6 评审结果结构化存储(reviews 表) + +> **参考实践**: +> - **Hermes**:Comment 的 metadata JSON 承载结构化数据 +> - **SWE-agent**:Trajectory 线性追加,不修改历史 +> - **GitHub PR Review**:APPROVE / REQUEST_CHANGES / COMMENT 三种 verdict + +**设计原则**:追加写入不修改历史(SWE-agent),黑板索引+文件详情(课题2),结构化 verdict(GitHub PR Review)。 + +```sql +CREATE TABLE IF NOT EXISTS reviews ( + id TEXT PRIMARY KEY, + task_id TEXT NOT NULL, + output_id TEXT, + reviewer TEXT NOT NULL, -- agent id 或 'system' + review_type TEXT NOT NULL, + CHECK (review_type IN ('plan_review', 'output_review', 'guardrail', 'final_review')), + verdict TEXT NOT NULL, + CHECK (verdict IN ('approved', 'rejected', 'needs_revision')), + confidence REAL, -- 0.0-1.0 + round INTEGER NOT NULL DEFAULT 1, + max_rounds INTEGER NOT NULL DEFAULT 3, + consensus_reached BOOLEAN DEFAULT FALSE, + summary TEXT NOT NULL, -- 一句话结论(黑板索引) + detail_path TEXT, -- 完整评审报告文件 + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (task_id) REFERENCES tasks(id), + FOREIGN KEY (output_id) REFERENCES outputs(id) +); + +CREATE INDEX IF NOT EXISTS idx_reviews_task ON reviews(task_id); +CREATE INDEX IF NOT EXISTS idx_reviews_output ON reviews(output_id); +``` + +**Guardrail 检查结果也入 reviews 表**(`reviewer='system'`, `review_type='guardrail'`)。 + +**comments 表和 reviews 表职责分离**: + +| 表 | 职责 | 内容 | verdict | +|---|------|------|---------| +| comments | 讨论、@mention、协商过程 | 自然语言 | ❌ 无 | +| reviews | 正式评审结论 | 结构化 JSON | ✅ 必须有 | + +### 9.7 协商流程与状态机对齐(v2.8 更新) + +**现有状态机(v2.8,11个状态)**:`pending → claimed → working → review → done` + +| 状态转换 | 触发条件 | 对应阶段 | +|---------|---------|--------| +| working → working | 方案审查 needs_revision | 阶段 1 | +| working → review | 写 output + Guardrail 通过 | 阶段 2→3 | +| working → working | Guardrail rejected(自动打回) | 阶段 2 | +| working → escalated | Agent 遇到无法处理的问题 | 任意阶段 | +| working → waiting_human | Agent 触发 Checkpoint | 任意阶段 | +| review → done | 产出审查 approved | 阶段 3 | +| review → working | 产出审查 needs_revision | 阶段 3 | +| review → escalated | 超轮次升级用户 | 阶段 3 | +| review → waiting_human | 审查阶段需人工裁定 | 阶段 3 | +| waiting_human → working | Checkpoint reject(驳回) | 阶段 3 | +| waiting_human → done | verify Checkpoint approve(通过) | 阶段 3 | + +**v2.8 变更**: `escalated` 从布尔标志改为独立状态(`status="escalated"`),不再使用 `review + escalated flag` 模式。超轮次时直接进入 `escalated` 状态,通知用户裁定。用户裁定后恢复到 `working` 或 `pending`。 + + +low 风险任务:working → [Guardrail 自动检查] → done(跳过 review 状态) +research 任务:working → [庞统确认] → review → done + +### 9.8 声明式 Guardrail 配置 + +> **参考实践**:OpenAI Agent SDK `@output_guardrail` 装饰器、v1.0 M4 Guard、SonarQube rule_id 模式 + +```yaml +# guardrails.yaml +# +# L1 check 用 assert 字段(Python 表达式,Daemon eval 执行) +# L2 check 用 prompt 字段(传给 subagent 的检查指令) +# 两者本质不同,不用同一个字段 + +task_types: + coding: + output_guardrails: + - name: file_exists + assert: "len(output.get('files', [])) > 0" + severity: blocking + layer: L1 + - name: json_valid + assert: "output.get('json_schema_valid', False) == True" + severity: blocking + layer: L1 + - name: artifacts_exist + assert: "all(os.path.exists(p) for p in output.get('artifacts_paths', []))" + severity: blocking + layer: L1 + - name: scope_alignment + prompt: "Compare the agent's scope_declaration against task truths. Check: is every truth covered? Are there deviations not declared?" + severity: warning + layer: L2 + output_review: + required: true + mode: single_reviewer + max_rounds: 3 + + deploy: + plan_review: + required: true + mode: debate + max_rounds: 5 + output_guardrails: + - name: no_direct_production + assert: "output.get('target_env') != 'production'" + severity: tripwire + layer: L1 + - name: rollback_plan_exists + assert: "output.get('rollback_plan') is not None" + severity: blocking + layer: L1 + output_review: + required: true + mode: debate + max_rounds: 5 + + data: + output_guardrails: + - name: format_check + assert: "output.get('format') in ['csv', 'parquet', 'json']" + severity: blocking + layer: L1 + output_review: + required: false + + research: + output_review: + required: true + reviewer: "pangtong-fujunshi" + mode: single_reviewer + max_rounds: 2 +``` + +### 9.9 Full Agent vs Subagent vs Daemon 直接执行 + +> **参考实践**: +> - **superpowers**:Implementer/Spec Reviewer/Code Quality Reviewer 都是 Task tool dispatch 的 subagent,各自独立 prompt 和模型 +> - **oh-my-claudecode**:Critic 被禁止 Write/Edit 工具(disallowedTools),角色隔离 +> - **open-multi-agent**:TaskQueue 维护 agent pool,scheduler 按 capability-match 分配 + +**判据**: + +| 问题 | Full Agent | Subagent | Daemon 直接执行 | +|------|-----------|---------|----------------| +| 需要独立身份/人格? | ✅ | ❌ | ❌ | +| 需要 Agent 专属工具? | ✅ | ❌ 通用工具 | 不需要 AI | +| 任务复杂度 | 编码/审查/调研/决策 | 单一检查/快速评估 | 格式校验/文件检查 | +| 需要写黑板? | ✅ | ❌ 只返回 pass/fail | 只改状态 | +| 需要多轮交互? | ✅ | ❌ 一次性 | 一次性 | + +**场景映射**: + +| 场景 | 方式 | OpenClaw API | 理由 | +|------|------|-------------|------| +| 执行者编码/数据/部署 | Full Agent | `openclaw agent --agent ` | 需要身份+专属工具 | +| 挑战者审查 | Full Agent | `openclaw agent --agent simayi-challenger` | 需要质量守门人角色+多轮 | +| 执行者反驳 | Full Agent(原 Agent) | `openclaw agent --agent <原执行者>` | 需要原执行者身份 | +| 庞统规划/裁决 | Full Agent(隔离session) | `openclaw agent --agent pangtong-fujunshi --session-id ` | 避免主session上下文膨胀 | +| L2 Guardrail AI 检查 | Subagent | `sessions_spawn(task=...)` | 单一检查、无身份 | +| Scope Guard 异步检查 | Subagent | `sessions_spawn(task=...)` | 轻量一次性 | +| L1 机械校验 | 不走 AI | Daemon 直接执行 | 纯机械操作 | + +**简化规则**:黑板上有名字的角色走 Full Agent。无名字的一次性检查走 Subagent。纯机械检查 Daemon 自己做。 + +**庞统主 session 隔离策略**:主 session 做轻量调度(L1构建、状态检查)。复杂的任务拆解和裁决 spawn 隔离 session,避免上下文膨胀。 + +### 9.10 对抗辩论模式(high 风险任务) + +> **参考实践**:TradingAgents Bull vs Bear → Research Manager 裁决 + +``` +正方(执行者):scope_declaration,论证方案可行性 +反方(挑战者池):找方案漏洞、风险、遗漏 + ↓ +庞统裁决:综合双方观点 + ├── 认可正方 → 方案通过 + ├── 认可反方 → 方案打回 + └── 综合意见 → 要求修改后重新辩论 +``` + +**挑战者池**(按任务类型选择,不是固定司马懿): + +| 任务类型 | 默认挑战者 | +|---------|----------| +| 编码 | 司马懿 | +| 风控 | 关羽 | +| 数据 | 赵云(数据质量视角) | +| 部署 | 姜维 | + +--- + +## 10. 产出物目录约定 + +``` +~/.sanguo_projects/sanguo_moziplus/artifacts/ +└── {task-id}/ + ├── outputs/ # Agent 产出物(代码、文档、数据) + ├── archive/ # session jsonl 存档 + └── data/ # 数据文件 +``` + +Agent 写产出时,`content_path` 指向此目录。Daemon 存档 session jsonl 时也写入 `archive/` 子目录。 + +--- + +## 11. 保留 v2.0 的设计 + +以下 v2.0 的设计在 v2.6 中保留: + +1. **SQLite WAL 模式** - 黑板数据库同样使用 WAL +2. **结构化产出规范** - output.md frontmatter + 结论 JSON(写在黑板 outputs 表中) +3. **观察机制** - v2.0 Report Watcher 的思路升级为 observations 表 +4. **证据原则** - 结论必须有证据(代码行号、日志、文件内容) +5. **审核流程** - 可通过黑板评论 + 状态机实现 + +--- + +## 11. 上下文管理策略(课题10结论) + +> **核心结论**:不需要复杂的压缩/摘要机制。课题2+4的设计已覆盖上下文管理的主要场景。 + +### 11.1 黑板信息量测算 + +| 信息类型 | 单个任务估算 | +|---------|------------| +| 任务基础信息(tasks 表) | ~50-60 tokens | +| 10 条评论(comments 表) | ~300-500 tokens | +| 5 个产出摘要(outputs 表) | ~150-250 tokens | +| 10 条事件日志(events 表) | ~150-250 tokens | +| 3 条决策记录(decisions 表) | ~150-200 tokens | +| **单个任务全量** | **~1000-1500 tokens** | + +即使 10 个任务全量注入也仅 ~1-1.5 万 tokens,远小于 128K context window。 + +### 11.2 三个场景的结论 + +**场景1:续杯时上下文膨胀** + +已在课题2+4中解决: +- 黑板保留完整讨论链(comments/decisions/observations),新 Agent 随时可读 +- 课题4 D4-7 三段式注入:操作规范 + 任务上下文 + 前序信息(depends_on 产出) +- 比 GSD Wave Execution 更优——GSD 给每个 executor 全新 context 但丢弃思考过程,我们保留在黑板中 + +**场景2:多任务并行** + +所有优秀实践的共识:单黑板、单 Daemon、多任务并行调度。 +- open-multi-agent:一个 TaskQueue,事件驱动,无依赖任务自然并行 +- Hermes:一个 Dispatcher + 一个 SQLite,60s tick 扫描 +- GSD:一个 orchestrator,同 wave 并行,跨 wave 串行 + +moziplus 同此模式:一个 SQLite 黑板、一个 Daemon 进程、Tick 扫描可推进的任务并行 spawn。 +每个 Agent spawn 时只注入自己任务的信息(课题4 D4-7 按角色注入),不受其他任务干扰。 + +**⚠️ 待解决**:用户级多任务(跨项目/跨域)需要项目级隔离机制,与课题11一起设计。 + +**场景3:Agent 自主决定读 L3** + +在 L2 bootstrap 末尾注入"可选参考"提示(课题4 D4-11 已在示例中体现): +``` +═══ 可选参考 ═══ +根据任务需要,你可以用 read 加载以下 Skill: +- 编码:~/.openclaw/skills/coding-implementation/SKILL.md +- 回测:~/.openclaw/skills/quant-backtest/SKILL.md +只在需要时加载,不需要全部读取。 +``` + +未来增强:SkillRouter 检索 + OpenClaw 动态关键词触发(见课题12 SkillRouter 调研)。 + +### 11.3 Context 预算分配 + +| 组件 | 预算 | 说明 | +|------|------|------| +| System Prompt + SOUL.md + IDENTITY.md | ~3000-5000 | 固定开销(L0+L1) | +| L2 引擎注入(操作规范 + 任务上下文) | ~1000-2000 | 每次 spawn 强制注入 | +| L2 历史经验提醒 | ~150 | 最多 3 条 experience | +| L3 可选参考 | ~100 | Skill 列表提示 | +| 工作空间(Agent 思考+输出) | ~30000-50000 | 预留给 Agent | +| **总计** | **~35K-60K** | 远小于 128K,安全 | + +### 11.4 设计原则 + +1. 黑板是索引(做什么 + 在哪找),不是仓库(详细内容) +2. 产出物在文件中,黑板只存路径 +3. Agent spawn 时传最小充分上下文(L2),按需获取更多(L3) +4. 不做复杂压缩——信息量本身就很小 + +--- + +## 12. 开发策略(v2.6) + +> **核心原则:不分阶段,不妥协,直奔 AI Native。** 每个部分设计到清楚为止。 + +### 12.1 设计推进方式 + +按课题逐个推进。课题之间允许并行,不强求串行。每个课题设计清楚就定稿。 + +**已完成设计的课题**: +- ✅ 课题1:三层执行模型(v2.6.2) +- ✅ 课题2:事件驱动 + Inbox JSONL(v2.6.3) +- ✅ 课题3:挑战/评审体系(v2.6.4) +- ✅ 课题4:拆解 + 上下文架构(v2.6.5) +- ✅ 课题6:经验沉淀闭环(v2.6.7) +- ✅ 课题7+9:交互模式 + Dashboard(v2.6.6 → 方案文档 v2.6.11) +- ✅ 课题11:多项目支持(v2.6.10) + +### 待推进课题 + +| 课题 | 内容 | 方向 | 状态 | +|------|------|------|------| +| 课题7 | AI Native 人机交互 | 4种交互模式+推送4级+信息3层+双入口对等+Checkpoint交互+AI Briefing | 📐 方案完成 | +| 课题9 | Dashboard 设计 | v1.0已有11个Tab全部保留,任务看板重设计,SSE实时推送,新增AI Briefing Tab | 📐 方案完成 | +| 课题10 | 上下文管理 | — | ⏸️ 暂缓 | +| 课题11 | 用户级多项目 | 方案C:单Daemon多数据库 + per-project线程并发 + 全局LLM信号量 + per-agent互斥锁(借鉴open-multi-agent AgentPool) | 📐 方案完成(v2) | +| 节点级模型策略 | Aider architect/editor/weak 三层分工 | 审查节点用强模型、执行节点用快模型,成本与质量平衡 | P2 | +| 信任分级系统 | DeepSeek-TUI Plan/Agent/YOLO 三级 | Dashboard 按信任等级展示不同操作按钮 | P2 | +| Worktree 项目隔离 | Superset + Claude Squad | 同项目内多Agent并行改代码的物理隔离 | P1 | +| Blackboard Map | Aider Repo Map | 黑板结构化索引,Agent按任务ID只读关联子图,不全量读取 | P3(触发条件:单任务>5K tokens 或并行>15任务) | +| 工具链集成 | PRD C10 | DevOps工具链(代码管理/CI/CD/测试/沙箱/运维)集成到v2.0 | 📋 信息收集(`docs/design/toolchain-proposal.md`) | + +### 司马懿 v2.6.9 评审结论与回应 + +**评分**: 55/70。最高:上下文管理 9/10。最低:自主协作能力 + 异常自愈 7/10。 + +**3 个评审意见的回应**: + +| # | 司马懿意见 | 回应 | 处理 | +|---|-----------|------|------| +| 1 | PRD §2.1 B3"共享意识"与架构有差距,改PRD对齐 | PRD 目标正确不改。当前架构已基本实现共享意识(黑板+CLI读写+@mention+Handoff),唯一差距是 L2 prompt 需要告诉 Agent"你可以查全局" | 在 §15.1 借鉴4 的 prompt_template 中加入全局查询提示 | +| 2 | blocked 状态转换图不完整 + mentions/experience_tags 风格不统一 | blocked 定义完整(§3.3 转换矩阵含 blocked,§6.2 场景清晰:Agent执行中被卡→blocked→他人帮忙→pending)。mentions 做通知路由(轻查询),experience_tags 做统计分析(重查询),场景不同不需要统一格式 | 不修改 | +| 3 | verification_commands 安全性 | Agent 已有 exec 权限,verification_commands 走 Daemon exec,由 OpenClaw exec approvals 机制(allowlist+approval)保障安全,不引入新攻击面。参考:Claude Code 同模型、Aider 直接信任、NVIDIA 深度防御指南 | 在 §15.1 借鉴1 明确安全模型 | + +**采纳的改进建议**: +- ✅ Daemon 逻辑健康自检(连续 N tick 无变更则告警)→ 纳入 §14 风险缓解 +- ✅ PRD 与架构对齐描述 → 在架构文档中记录当前状态而非修改 PRD +- 📋 多项目 scope → 课题11 设计 +- 📋 AI Retry 基本版 → Phase 1 考虑 +- 📋 SkillRouter → 课题12 + +### 已完成调研 + +- ✅ 7 个新项目调研(Claude Squad / Superset Terminal / OpenCode / claude-goal / Cline / Aider / DeepSeek-TUI) + - 综合报告:`docs/research/research-seven-projects-synthesis.md` + - 详细报告:`docs/research/research-batch{1,2,3}-*.md` + +### 12.2 开发启动条件 + +所有核心课题设计完成后启动开发。开发顺序由设计依赖关系决定,不由阶段划分决定。 + +### 12.3 核心依赖链(设计 → 开发) + +``` +黑板基础设施(SQLite + CLI + Daemon) + → 事件驱动(Inbox + 依赖推进) + → Agent 交互(Handoff + 评论 + Observation) + → 审查体系(Review + Guardrail + 反驳) + → 智能化(AI规划 + 自主领活 + 经验沉淀) + → Dashboard 前端 +``` + +每层依赖前一层,但不等所有层设计完才开发。前层设计清楚即可开发。 + +--- + +## 13. 技术选型 + +| 需求 | 参考系统 | 我们的方案 | 理由 | +|------|---------|-----------|------| +| 共享状态 | Hermes SQLite + Network-AI flock | SQLite WAL + 事务 CAS | 原子性 + 无外部依赖 | +| 讨论 | Hermes kanban_comment | comments 表 + @mention | 简单追加写入,所有人可见 | +| 事件驱动 | open-multi-agent EventEmitter + agent-chorus JSONL inbox | Tick 核心 + Inbox JSONL 加速 + 启动全量扫描 | Tick 兜底可靠,inbox 加速即时响应,零新依赖 | +| 调度 | Hermes Dispatcher 60s tick | Tick 30s + Inbox JSONL 加速 + 启动全量扫描 | Tick 可靠 + inbox 即时 | +| 上下文传递 | GSD Wave Execution + Claude Code file ref + Opal-Bridge Fidelity | L1 必传 + L2/L3 按需读取 + Handoff Comment | 信号噪声比优化 + 无缝接手 | +| 通知 | Claude Code idle notification | Daemon spawn + L1 message | OpenClaw 原生能力 | +| 通信 | Hermes kanban_comment + Claude Code inbox | 黑板 comments + @mention | 替代 Sanguo Mail | +| 竞态 | Network-AI propose→validate→commit | SQLite CAS(first-commit-wins) | SQLite 事务足够 | +| Session | Hermes process-per-worker | openclaw agent --session-id | OpenClaw 原生隔离 | +| 清理 | 无参考 | 编辑 sessions.json | 已验证可行 | + +--- + +## 14. 风险和缓解 + +| 风险 | 概率 | 缓解 | +|------|------|------| +| Agent 上下文不足(隔离 session 没有历史)| 中 | spawn 时传递黑板关键信息 + agent 可主动读黑板 | +| Daemon 单点故障 | 低 | PM2 自动重启 + tick 无状态 | +| SQLite 并发写入 | 中 | WAL + busy_timeout + BEGIN IMMEDIATE | +| 黑板膨胀(大量评论/产出)| 低 | 定期 archive + agent 只读最近 N 条 | +| Agent 不知道该做什么 | 中 | Skill 指导 + 庞统 plan 评论 + daemon 消息含上下文 | +| Sanguo Mail 退役后的系统通知 | 低 | 黑板 system 类型任务替代 | +| Daemon 逻辑死循环 | 中 | Daemon 逻辑健康自检:连续 N tick(默认 20)无任何任务状态变更则写 observation 告警 + 通知用户。PM2 只能检测进程崩溃,逻辑死循环需应用层检测 | + +--- + +## 15. 7项目调研结论与设计借鉴 + +> **调研日期**: 2026-05-16 +> **调研范围**: Claude Squad / Superset Terminal / OpenCode / claude-goal / Cline / Aider / DeepSeek-TUI +> **详细报告**: `docs/research/research-seven-projects-synthesis.md` + +### 15.1 已纳入设计的借鉴点 + +#### 借鉴1:Guardrail 验证脚本层(纳入课题3) + +**来源**: Aider lint→test→commit 质量闭环 + +**当前设计**(topic3 方案 阶段2): +``` +L1 机械检查(文件存在、JSON格式)→ 不通过直接打回 +L2 轻量 AI 检查(Schema校验)→ 不通过写 observation +L3 安全红线(tripwire)→ 立即中断 +``` + +**增强后**:L1 增加验证脚本层 +``` +L1 机械检查 + 验证脚本 + ├── 文件存在性检查(原有) + ├── JSON 格式检查(原有) + └── 🆕 verification_commands 执行(新增) + ├── 任务 Plan 中声明的验证命令(如 pytest, flake8, mypy) + ├── 命令 exit 0 = 通过,非 0 = 打回 + └── stdout/stderr 附在打回原因中 +``` + +**黑板 Schema 变更**:tasks 表新增 `verification_commands` JSON 字段 +```json +{ + "verification_commands": [ + {"cmd": "pytest tests/test_foo.py -x", "description": "单元测试必须通过"}, + {"cmd": "flake8 src/", "description": "代码规范检查"} + ] +} +``` + +**触发时机**:Daemon 检测到 output 写入 → 执行 Guardrail → L1 验证脚本 → 脚本失败则自动打回。 + +**安全模型**:verification_commands 通过 Daemon 调用 `exec`,走 OpenClaw exec approvals 机制(allowlist + approval)。Agent 已有 exec 权限,verification_commands 不引入新的攻击面。参考:Claude Code 同模型(Agent 可执行命令,宿主机 exec approvals 拦截危险操作)、Aider 直接信任、NVIDIA 深度防御指南。 + +> 参考实践:Aider 的 lint→test→commit 是原子操作——验证不通过就不 commit。我们的 Guardrail 也是原子门控——验证不通过就不进入 review。 + +#### 借鉴2:对抗性审计映射(纳入课题3) + +**来源**: claude-goal 三种审计模式(none/adversarial/double) + +**映射到 Guardrail L1/L2/L3**: + +| claude-goal 模式 | moziplus Guardrail | 触发条件 | 行为 | +|-----------------|-------------------|---------|------| +| none(跳过审计) | L1 only(机械+脚本) | low 风险任务 | 自动检查通过即完成 | +| adversarial(对抗审计) | L1 + L2(AI审查) | standard 风险任务 | 一轮 AI 审查,通过/打回 | +| double(双重审计) | L1 + L2 + 反驳权 | high 风险任务 | 双轮审查 + 执行者可反驳 + 最多5轮 | + +> claude-goal 的完成审计 6 步流程(还原交付物→对照表→检查证据→找缺失→继续/完成)对 Output Guard 的 AI 审查层有参考价值,但不硬编码步骤——AI 审查者按 Skill 指导灵活执行。 + +#### 借鉴3:防偏离改进(纳入课题3+4) + +**来源**: claude-goal Stop Hook + 完成审计 + +**v2.0 的防偏离三层防线**: + +| 防线 | claude-goal 机制 | moziplus v2.0 对应 | 实现方式 | +|------|-----------------|-------------------|---------| +| 每轮重新注入目标 | Stop Hook 注入 `` | L2 引擎注入任务上下文 | Daemon spawn 时注入 truths + acceptance_criteria | +| 不信任 Agent 自述 | 完成审计 6 步(查证据) | Output Guard + Scope Guard | L1 验证脚本 + L2 AI 审查(prompt→artifact 对照) | +| Runaway Guard | 500 次续杯上限 | ❌ 当前缺失 | 新增:每个任务最大 tick 上限(配置项 `max_ticks`,默认 100) | + +**新增设计**:tasks 表增加 `max_ticks` 字段(默认 100)。Daemon 每次 tick 处理某任务时递增 `tick_count`,超过 `max_ticks` 时: +1. 自动暂停任务 +2. 写入 system observation:“任务超过最大 tick 上限,可能陷入循环” +3. 通知用户决定继续/取消 + +#### 借鉴4:双重 Hook 机制(纳入 Daemon 设计) + +**来源**: claude-goal 的 Claude Code Stop Hook + mozi v1 的 HookRegistry + +**v2.0 双重 Hook 架构**: + +``` +┌─────────────────────────────────────────┐ +│ Layer 1: OpenClaw Hook(系统级) │ +│ • Heartbeat(定时轮询,读 HEARTBEAT.md) │ +│ • Webhook(HTTP 入口,外部事件触发) │ +│ • Cron(定时任务) │ +│ → 触发 Agent session 内行为 │ +└─────────────────────────────────────────┘ + ↓ 事件到达 +┌─────────────────────────────────────────┐ +│ Layer 2: moziplus Hook(编排级) │ +│ • NODE_AFTER_EXECUTE → 触发 Guardrail │ +│ • NODE_ON_SUCCESS → 触发经验沉淀 │ +│ • TASK_AFTER_COMPLETE → 触发 Scope Guard │ +│ • CONCLUSION_AFTER_PARSE → 写入黑板 │ +│ → 触发编排层业务逻辑 │ +└─────────────────────────────────────────┘ +``` + +**mozi v1 已有 HookRegistry**(20 个 hook 点,覆盖任务/节点/结论/路由全生命周期),v2.0 继承并精简: +- v1 的 HookPoint 枚举 → v2 的 Daemon event handler +- v1 的 priority + stop_propagation → v2 保留(Hook 链可控) +- v1 的两级失败隔离(关键型暂停/辅助型跳过)→ v2 保留 +- 新增:Guardrail 验证脚本作为 NODE_AFTER_EXECUTE 的默认 Hook + +**与 claude-goal 的对比**: +| 维度 | claude-goal | moziplus v2.0 | +|------|-----------|-------------| +| Hook 注册位置 | Claude Code settings.json | Daemon HookRegistry | +| Hook 触发点 | Agent Stop 事件 | 节点完成/任务完成/产出写入 | +| Hook 实现 | Python 脚本 | Daemon event handler + 可插拔 Hook | +| 防偏离手段 | 拦截停止+注入目标 | L2注入上下文+Guardrail验证+Runaway Guard | + +#### 借鉴5:Shadow Checkpoint(纳入课题6 经验沉淀) + +**来源**: Cline Shadow Git Checkpoint + +**设计**:每次 Agent 产出写入黑板时,Daemon 自动对产出目录做 git commit(带 agent_id + task_id + timestamp tag)。 + +- **与课题4的关系**:不依赖课题4。Shadow Checkpoint 是 Daemon 层行为(产出写入后触发),课题4是 Agent spawn 时的上下文注入。两者独立。 +- **与课题6的关系**:Shadow Checkpoint 的变更历史是经验蒸馏的输入(课题6 DISCOVER 阶段可以 diff 两个 checkpoint 看Agent改了什么) +- **回滚能力**:用户可以在 Dashboard 上选择任意 checkpoint 一键回滚 +- **存储开销**:git commit 增量存储,开销可控 + +**实现**:在 `NODE_AFTER_EXECUTE` Hook 中增加 git checkpoint 逻辑。 + +### 15.2 经评估暂不纳入的借鉴点 + +#### Blackboard Map(按需触发,不预设) + +**来源**: Aider Repo Map + +**结论**:当前单个任务黑板信息 ~1000-1500 tokens(§11.1 测算),10 个任务全量注入 ~1-1.5 万 tokens,远小于 128K。**现阶段不需要 Blackboard Map**。 + +**触发条件**(达到以下任一时启动设计): +- 单个任务黑板信息超过 5000 tokens +- 并行任务数超过 15 个 +- Agent spawn 时注入上下文超过 10K tokens +- 或者 L2 注入需要做精准裁剪(只取关联子图而非全量) + +**预留设计**:当需要时,基于黑板 comments/outputs/decisions 的拓扑关系构建图索引,Agent 按任务 ID 只读关联子图。 + +### 15.3 技术选型更新 + +| 需求 | 原参考 | 新增参考 | 我们的方案 | +|------|--------|---------|----------| +| 质量门控 | Hermes 幻觉门控 | Aider lint→test→commit | L1 机械+脚本 → L2 AI → L3 红线 | +| 防偏离 | 无 | claude-goal Stop Hook + 完成审计 | L2 注入 + Guardrail + Runaway Guard | +| 产出追溯 | 无 | Cline Shadow Git | NODE_AFTER_EXECUTE Hook 自动 git commit | +| Hook 机制 | mozi v1 HookRegistry | claude-goal Stop Hook + OpenClaw Heartbeat/Webhook | 双重 Hook(系统级+编排级) | +| 多项目隔离 | 无 | Superset Worktree | 课题11 中设计(Worktree 物理隔离) | diff --git a/docs/design/architecture-v2.md b/docs/design/architecture-v2.md new file mode 100644 index 0000000..9cca4c8 --- /dev/null +++ b/docs/design/architecture-v2.md @@ -0,0 +1,2757 @@ +# moziplus v2.0 — AI 原生多Agent编排平台 架构设计 + +**版本**: v2.6(实现前终版) +**日期**: 2026-05-14 +**作者**: 庞统(副军师) +**状态**: 实现前终版,防降级机制 + 上下文管理 + spawn 首选 + Skill AI native +**调研基础**: `docs/research/shared-consciousness-research.md` +**变更记录**: +- v2.0 初版(四相循环 + Blackboard 设计) +- v2.1 技术架构修订(Daemon+HTTP API+SQLite, 废弃sessions_send/cron) +- v2.2 完整设计(物理结构、API定义、经验沉淀、状态机、实现清单) +- v2.3 评审修订(司马懿两轮评审:openclaw agent CLI调度、质量治理框架、双写简化) +- v2.4 AI native 修正(恢复三个被妥协的核心目标:Agent 自主协作、实时共享感知、AI 持续参与) +- v2.5 AI native 完善(Skill 三层体系 + 调度五原则 + Iterative Refinement + Observation 自由格式 + spawn 实验 + 双模式调度) +- v2.6 实现前终版(spawn 首选 + 庞统上下文管理 + 防降级机制 + Skill AI native 方案 + 并行感知识别机制) + +--- + +## 0. 设计哲学 + +> "可预测的骨架 + AI 驱动的填充" —— 不是纯 DAG,也不是纯 ReAct,而是混合模式。 + +六个核心信念: + +1. **AI 参与每一个决策层** —— 编排/路由/渲染/异常处理/经验沉淀都有 AI 参与 +2. **黑板是唯一真相源** —— 所有 Agent 通过黑板共享信息,没有私下通信 +3. **产出物 > 消息** —— 共享产出物比共享消息更重要 +4. **验证才算完** —— 不验证产出不算完成 +5. **有界并行** —— 默认最多 4 个 Agent 并行(有学术依据) +6. **闭环学习** —— 执行→经验沉淀→下次改进 + +--- + +## 1. 系统总览 + +### 1.1 宏观架构 + +``` +用户(自然语言) + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 对话入口(Conversation Layer) │ +│ 庞统的持久 session,用户唯一交互点 │ +│ 支持:WebChat / CLI / Cron 触发 / API 调用 │ +└───────────────────────┬─────────────────────────────────────┘ + │ +┌───────────────────────▼─────────────────────────────────────┐ +│ 庞统 AI 指挥官(Control Unit) │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Phase 1 │ │ Phase 2 │ │ Phase 3 │ │ Phase 4 │ │ +│ │ 需求探索 │ │ 动态规划 │ │ 自主执行 │ │ 主动汇报 │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ 内置机制: │ +│ - /goal Ralph Loop:持久目标跨 turn 保持 │ +│ - Scope Reduction Detection:防偷懒 │ +│ - 幻觉门控:验证产出再算完成 │ +│ - Fidelity 路由:按需分发信息 │ +│ - Boids 规则注入:Agent 协作行为塑造 │ +│ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Blackboard(共享意识空间) │ │ +│ │ │ │ +│ │ TaskCtx │ Moments │ Artifacts │ Decisions │ Plan │ │ +│ │ AgentStates │ Experience │ EventLog │ │ +│ └──────────────────────────────────────────────────────┘ │ +└───────────────────────┬─────────────────────────────────────┘ + │ Fidelity 三档读写 + ┌─────────────┼─────────────┐ + │ │ │ + ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ + │ 张飞 │ │ 关羽 │ │ 赵云 │ ... + │ 编码 │ │ 风控 │ │ 数据 │ + └─────────┘ └─────────┘ └─────────┘ + 每个 Agent: isolated session + SOUL.md + Skills + 写入保护: propose → validate → commit +``` + +### 1.2 与 v1.0 的关系 + +| 维度 | v1.0 | v2.0 | +|------|------|------| +| 编排 | 固定 DAG 模板 + 确定性状态机 | 庞统 AI 动态规划 + 持续指挥 | +| 通信 | Sanguo Mail(异步邮件轮询) | Blackboard(实时共享读写) | +| 入口 | CLI + Dashboard | 自然语言对话 | +| 计划 | 一次性生成不可变 | 持续演进,可随时调整 | +| Agent 调度 | 按模板固定分配 | 按能力画像动态选择 | +| 信息可见性 | 每个 Agent 只看自己 | Fidelity 三档按需 | +| 异常处理 | Report Watcher(规则) | 庞统 AI 判断 | +| 验证 | output.md frontmatter | 幻觉门控 + AI 验证 | +| 经验 | 无闭环 | DISCOVER→DISTILL→APPLY→IMPROVE | + +**v2.0 独立仓库、独立代码、独立部署**。v1.0 继续运行,互不干扰。 + +--- + +## 2. 核心模块详细设计 + +### 2.1 Blackboard(共享意识空间) + +#### 2.1.1 物理结构 + +``` +~/.sanguo_projects/sanguo_moziplus_v2/ +├── blackboard/ # 共享意识空间根目录 +│ ├── tasks/ # 任务空间 +│ │ └── {task-id}/ # 每个任务独立目录 +│ │ ├── context.json # 任务上下文(目标/约束/状态) +│ │ ├── moments.jsonl # 原子事件流(追加写入) +│ │ ├── plan.json # 动态计划图谱 +│ │ ├── decisions.jsonl # 决策记录(不可变) +│ │ ├── agents/ # 各 Agent 工作区 +│ │ │ ├── {agent-id}/ +│ │ │ │ ├── state.json # Agent 当前状态 +│ │ │ │ ├── output/ # Agent 产出物 +│ │ │ │ └── inbox/ # Agent 专属信箱(通知类) +│ │ │ └── ... +│ │ └── artifacts/ # 共享产出物索引 +│ │ └── index.json # 产出物注册表 +│ ├── global/ # 全局共享空间 +│ │ ├── agent-registry.json # Agent 能力画像注册表 +│ │ ├── experience/ # 跨任务经验库 +│ │ │ ├── {domain}.jsonl # 按领域组织 +│ │ │ └── index.json # 经验索引 +│ │ └── templates/ # 任务模板库 +│ │ └── {template-id}.json +│ ├── events/ # 不可变全局事件日志 +│ │ └── {date}.jsonl # 按日期分文件 +│ ├── inbox/ # 用户需求入口 +│ │ └── {req-id}.json # 待处理需求 +│ └── locks/ # 写入锁目录 +│ └── {resource-path}.lock # 文件锁 +├── daemon/ # 守护进程代码 +├── skills/ # Skill 包 +├── docs/ # 文档 +└── config/ # 配置 +``` + +#### 2.1.2 数据结构定义 + +**context.json** — 任务上下文: +```json +{ + "task_id": "task-20260514-001", + "title": "均线策略回测", + "goal": "对双均线交叉策略在沪深300上进行5年回测", + "intent": "验证该策略在A股市场的有效性", + "end_state": "回测报告完整,包含收益曲线、最大回撤、夏普比率", + "constraints": [ + "数据范围:2020-2025", + "标的:沪深300指数", + "初始资金:100万" + ], + "state": "executing", // exploring → planning → executing → reviewing → completed + "phase": 3, // 当前四相阶段 + "created_at": "2026-05-14T08:00:00+08:00", + "updated_at": "2026-05-14T08:15:00+08:00", + "parent_task": null, // 子任务指向父任务 + "tags": ["backtest", "strategy", "moving-average"], + "confidence": 0.0 // 庞统对"需求理解程度"的自评 +} +``` + +**moments.jsonl** — 原子事件流(每行一个 JSON): +```json +{"type":"task_created","ts":"...","agent":"pangtong","data":{"goal":"..."}} +{"type":"requirement_clarified","ts":"...","agent":"pangtong","data":{"clarifications":[...]}} +{"type":"plan_generated","ts":"...","agent":"pangtong","data":{"plan_id":"p1","steps":5}} +{"type":"plan_approved","ts":"...","agent":"pangtong","data":{"approved_by":"user"}} +{"type":"agent_assigned","ts":"...","agent":"pangtong","data":{"agent":"zhaoyun","step":"s1"}} +{"type":"agent_started","ts":"...","agent":"zhaoyun","data":{"step":"s1"}} +{"type":"artifact_produced","ts":"...","agent":"zhaoyun","data":{"file":"data.csv","summary":"5年日线数据","confidence":0.95}} +{"type":"agent_completed","ts":"...","agent":"zhaoyun","data":{"step":"s1","status":"success"}} +{"type":"anomaly_detected","ts":"...","agent":"pangtong","data":{"type":"data_quality","severity":"warning"}} +{"type":"plan_adjusted","ts":"...","agent":"pangtong","data":{"reason":"数据质量问题","added_step":{...}}} +{"type":"consensus_reached","ts":"...","agent":"pangtong","data":{"result":"回测通过"}} +{"type":"task_completed","ts":"...","agent":"pangtong","data":{"final_state":"success"}} +``` + +**plan.json** — 动态计划图谱: +```json +{ + "plan_id": "p1", + "task_id": "task-20260514-001", + "version": 3, + "steps": [ + { + "id": "s1", + "type": "data_fetch", + "intent": "获取沪深300的5年日线数据", + "end_state": "数据文件就绪,通过质量检查", + "constraints": ["使用AKShare", "保存为CSV"], + "agent": "zhaoyun", + "status": "completed", + "artifacts": ["data/hs300_daily.csv"], + "started_at": "...", + "completed_at": "...", + "confidence": 0.95 + }, + { + "id": "s2", + "type": "strategy_implementation", + "intent": "实现双均线交叉策略", + "end_state": "策略代码可运行,通过单元测试", + "constraints": ["使用vnpy框架", "参数可配置"], + "agent": "zhangfei", + "status": "executing", + "depends": ["s1"], + "started_at": "..." + }, + { + "id": "s2.5", + "type": "data_cleaning", + "intent": "清洗异常数据点", + "end_state": "异常值处理完毕,数据连续无缺失", + "agent": "zhaoyun", + "status": "pending", + "depends": [], + "added_dynamically": true, + "add_reason": "s1 完成后发现数据有缺失值" + } + ], + "changelog": [ + {"version":1,"change":"初始计划","ts":"..."}, + {"version":2,"change":"添加 s2.5 数据清洗步骤","reason":"发现数据质量问题","ts":"..."}, + {"version":3,"change":"调整 s3 约束","reason":"用户要求改为vnpy框架","ts":"..."} + ] +} +``` + +**agent-registry.json** — Agent 能力画像: +```json +{ + "agents": { + "zhaoyun-data": { + "name": "赵云", + "role": "数据总管", + "capabilities": ["data_fetch", "data_cleaning", "data_validation", "quality_check"], + "tools": ["exec", "read", "write", "web_fetch"], + "model_preference": "auto", + "max_parallel_tasks": 1, + "priority": 2, + "performance": { + "tasks_completed": 42, + "avg_confidence": 0.91, + "avg_duration_minutes": 8, + "strengths": ["data_quality", "python"], + "last_active": "..." + }, + "session_key": "agent:zhaoyun-data:main" + }, + "zhangfei-dev": { + "name": "张飞", + "role": "编码先锋", + "capabilities": ["coding", "backtest", "strategy_implementation", "scripting"], + "tools": ["exec", "read", "write", "edit"], + "model_preference": "auto", + "max_parallel_tasks": 1, + "priority": 1, + "performance": { ... }, + "session_key": "agent:zhangfei-dev:main" + }, + "guanyu-dev": { + "name": "关羽", + "role": "风控守将", + "capabilities": ["risk_check", "position_sizing", "stop_loss", "live_audit"], + "tools": ["exec", "read", "write", "edit"], + "model_preference": "auto", + "max_parallel_tasks": 1, + "priority": 3, // 风控最高优先级 + "performance": { ... }, + "session_key": "agent:guanyu-dev:main" + }, + "simayi-challenger": { + "name": "司马懿", + "role": "质量总监", + "capabilities": ["code_review", "challenge", "final_acceptance"], + "tools": ["exec", "read", "write", "edit"], + "model_preference": "auto", + "max_parallel_tasks": 1, + "priority": 2, + "performance": { ... }, + "session_key": "agent:simayi-challenger:main" + }, + "jiangwei-infra": { + "name": "姜维", + "role": "平台总督", + "capabilities": ["deployment", "docker", "nas", "backtest_server", "vnpy"], + "tools": ["exec", "read", "write", "edit"], + "model_preference": "auto", + "max_parallel_tasks": 1, + "priority": 1, + "performance": { ... }, + "session_key": "agent:jiangwei-infra:main" + } + } +} +``` + +**decisions.jsonl** — 决策记录(不可变): +```json +{"ts":"...","agent":"pangtong","decision":"assign_s2_to_zhangfei","reason":"张飞擅长策略编码","alternatives_considered":["关羽(风控优先)"]} +{"ts":"...","agent":"pangtong","decision":"add_s2.5","reason":"数据清洗步骤缺失","trigger":"s1 confidence=0.7 低于阈值"} +``` + +#### 2.1.3 写入保护:propose → validate → commit + +借鉴 Network-AI 的三阶段原子写入: + +``` +Agent A: + 1. propose: 写入 agents/{agent-id}/proposed/{change-id}.json + 包含:target_path, proposed_content, priority, reason + +Control Unit (庞统): + 2. validate: + a. 格式校验(JSON schema) + b. 冲突检测(target 是否被其他 propose 锁定) + c. 优先级检查(是否有更高优先级的 propose) + d. 业务校验(状态流转是否合法) + + 3. commit: + a. 获取文件锁 (locks/{resource}.lock) + b. 原子写入(tmp → rename) + c. 追加 Moment 事件 + d. 释放锁 + + 或 abort: + a. 记录拒绝原因 + b. 通知提议 Agent +``` + +**简化规则**: +- Agent 对自己工作区(`agents/{agent-id}/`)的写入:**自动 commit**,不需要 propose +- Agent 对共享区域(`artifacts/`, `plan.json`, `context.json`)的写入:**必须 propose → commit** +- Agent 对其他 Agent 工作区的写入:**禁止**(覆盖保护原则) + +#### 2.1.4 文件锁实现 + +借鉴 ClawTeam 的 fcntl + 原子 rename: + +```python +import fcntl, tempfile, os +from pathlib import Path + +def atomic_write(path: Path, content: str): + """原子写入:先写临时文件,再 rename""" + path.parent.mkdir(parents=True, exist_ok=True) + fd, tmp = tempfile.mkstemp(dir=path.parent, prefix=f"{path.stem}-", suffix=".tmp") + with os.fdopen(fd, 'w') as f: + f.write(content) + Path(tmp).replace(path) # atomic on same filesystem + +class BlackboardLock: + """文件系统互斥锁""" + def __init__(self, lock_dir: Path): + self.lock_dir = lock_dir + + def acquire(self, resource: str, holder: str, timeout_ms=10000) -> bool: + lock_path = self.lock_dir / f"{resource.replace('/', '_')}.lock" + lock_path.parent.mkdir(parents=True, exist_ok=True) + start = time.monotonic() + while (time.monotonic() - start) * 1000 < timeout_ms: + try: + fd = os.open(str(lock_path), os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + os.write(fd, json.dumps({"holder": holder, "acquired_at": time.time()}).encode()) + os.close(fd) + return True + except FileExistsError: + # stale lock check (> 30s) + age = time.time() - lock_path.stat().st_mtime + if age > 30: + lock_path.unlink(missing_ok=True) + time.sleep(0.1) + return False + + def release(self, resource: str, holder: str): + lock_path = self.lock_dir / f"{resource.replace('/', '_')}.lock" + if lock_path.exists(): + data = json.loads(lock_path.read_text()) + if data.get("holder") == holder: + lock_path.unlink() +``` + +--- + +### 2.2 Control Unit(庞统 AI 指挥官) + +#### 2.2.1 四相循环 + +``` +Phase 1: 需求探索 +├── 苏格拉底对话,帮用户发现真实需求 +├── 歧义评分(0-1),高歧义时深入追问 +├── 输出:context.json(goal/intent/end_state/constraints) +├── 自评 confidence,>0.8 才进入 Phase 2 +└── 人的参与:🔴 高(全程对话) + +Phase 2: 动态规划 +├── 根据 context.json 生成 plan.json +├── AI 挑战:庞统自己审视计划的弱点 +├── 三方共识(可选):庞统+司马懿+用户审核 +├── Plan 审批:用户确认后才执行 +├── 人的参与:🟡 可选(简单任务可跳过审批) +└── 输出:plan.json(version 1) + +Phase 3: 自主执行 +├── 按 plan.json 调度 Agent +├── 每步执行: +│ ├── 选择 Agent(能力画像匹配) +│ ├── 注入任务上下文(Fidelity 按角色) +│ ├── Agent 写入黑板 +│ ├── 幻觉门控(验证产出存在) +│ ├── 异常检测(超时/质量低/Agent 崩溃) +│ └── 动态调整计划(如需) +├── /goal Ralph Loop:跨 turn 保持目标专注 +├── 人的参与:🟢 几乎不参与(可随时介入 steer) +└── 输出:artifacts/ + moments 流 + +Phase 4: 主动汇报 +├── AI 推送进展摘要(不等人查) +├── 验收:庞统自审 + 司马懿终审 +├── 经验沉淀:提取关键经验写入 experience/ +├── 人的参与:🔵 验收 +└── 输出:最终报告 + experience 条目 +``` + +#### 2.2.2 庞统的运行模式 + +**事件驱动 + 持久 session**: + +``` +庞统 session 始终在线(OpenClaw persistent session) + +触发方式: +1. 用户发消息 → 直接在 session 中处理 +2. Agent 写入黑板 → daemon 更新状态 → `openclaw agent` CLI 通知庞统 +3. Agent 完成/失败 → 写入 moments → wake 庞统 +4. 异常检测 → daemon 事件循环 → `openclaw agent` CLI 通知庞统 +5. 用户 steer(中途干预)→ 直接注入 session + +空闲时: +- 不消耗资源 +- 被 wake 事件唤醒后立即恢复上下文 +- 通过黑板恢复状态(不需要重载全部历史) +``` + +#### 2.2.3 信息路由(Fidelity 三档) + +```python +def route_information(target_agent: str, task_ctx: dict, moments: list) -> dict: + """根据目标 Agent 的角色,选择合适的信息保真度""" + role = get_agent_role(target_agent) + + if role == "control_unit": # 庞统自己 + return { + "fidelity": "full", + "context": task_ctx, + "moments": moments, # 全量事件 + "artifacts": all_artifacts, # 全部产出物 + "agent_states": all_agent_states # 所有Agent状态 + } + + elif role in get_collaborators(task_ctx): # 同任务协作伙伴 + return { + "fidelity": "summary", + "context": task_ctx, # 完整任务上下文 + "relevant_steps": filter_relevant(moments, target_agent), + "artifacts": get_dependent_artifacts(target_agent), + "summary": ai_summarize(moments) # AI 压缩摘要 + } + + else: # 外围 Agent + return { + "fidelity": "signal", + "action_required": get_pending_actions(target_agent), + "final_result": task_ctx.get("result"), + "notification": True + } +``` + +#### 2.2.4 Agent 选择算法 + +```python +def select_agent(step: dict, registry: dict) -> str: + """根据步骤需求和Agent能力画像选择最合适的Agent""" + required_caps = step.get("required_capabilities", infer_caps(step)) + + candidates = [] + for agent_id, profile in registry["agents"].items(): + # 能力匹配 + cap_overlap = len(set(required_caps) & set(profile["capabilities"])) + if cap_overlap == 0: + continue + + # 可用性检查 + if profile["performance"]["tasks_in_progress"] >= profile["max_parallel_tasks"]: + continue + + # 评分:能力匹配度 * 历史表现 * 当前空闲度 + score = ( + cap_overlap / len(required_caps) * 0.4 + # 能力匹配 + profile["performance"]["avg_confidence"] * 0.3 + # 历史表现 + (1 - profile["performance"]["tasks_in_progress"] / + profile["max_parallel_tasks"]) * 0.3 # 当前空闲度 + ) + candidates.append((agent_id, score)) + + if not candidates: + return None # 需要排队或调整计划 + + return max(candidates, key=lambda x: x[1])[0] +``` + +--- + +### 2.3 Agent 层 + +#### 2.3.1 Agent 工作流程 + +``` +1. 接收任务 + ├── 庞统通过 daemon API 创建任务步骤 + ├── 消息包含:step intent + end_state + constraints + 相关黑板内容 + └── 不包含:完整计划、其他Agent的详情(Fidelity 控制) + +2. 执行任务 + ├── 读取黑板中自己需要的上下文 + ├── 执行实际工作(编码/数据分析/风控检查等) + ├── 写入产出到 agents/{agent-id}/output/ + └── 追加 Moments 事件 + +3. 提交产出 + ├── propose 共享产出物到 artifacts/ + ├── 庞统 validate + commit + ├── 自评 [confidence: 0.X] + └── 元认知:confidence < 0.6 时推荐人工审核 + +4. 等待下一步 + ├── 庞统根据执行结果决定下一步 + └── Agent 进入空闲状态 +``` + +#### 2.3.2 Agent 行为注入 + +每个 Agent 的 SOUL.md / prompt 中注入**Boids 协作规则**: + +```markdown +## 协作规则(Boids 群体智能) +1. **Separation(不重复)**:开始工作前检查黑板,确认没有其他 Agent 在做相同的事 +2. **Alignment(风格一致)**:遵循团队的编码规范、产出格式、命名约定 +3. **Cohesion(主动共享)**:发现重要信息时主动写入黑板共享区域 +4. **Boundary(不越界)**:只在自己的工作区和共享区域操作,不修改其他 Agent 的产出 +``` + +以及**元认知自评**: + +```markdown +## 自评要求 +完成任务后,标注置信度: +- `[confidence: 0.X]` 其中 X 为 0-10 +- confidence < 0.6 时说明不确定之处并推荐人工审核 +- 遇到专业外的问题主动上报,不硬撑 +``` + +以及**Auftragstaktik 任务式指挥**: + +```markdown +## 任务执行方式 +你会收到:Intent(意图)、End State(终态)、Constraints(约束) +- 自主决定如何达成目标 +- 可以选择任何合理的方法 +- 但必须遵守所有 Constraints +- 遇到 Constraints 阻碍目标时,上报而不是绕过 +``` + +--- + +### 2.4 事件系统 + +#### 2.4.1 Moments 事件类型 + +```python +class MomentType(str, Enum): + # 任务生命周期 + TASK_CREATED = "task_created" + REQUIREMENT_CLARIFIED = "requirement_clarified" + TASK_COMPLETED = "task_completed" + TASK_FAILED = "task_failed" + + # 计划 + PLAN_GENERATED = "plan_generated" + PLAN_APPROVED = "plan_approved" + PLAN_ADJUSTED = "plan_adjusted" + + # Agent 调度 + AGENT_ASSIGNED = "agent_assigned" + AGENT_STARTED = "agent_started" + AGENT_COMPLETED = "agent_completed" + AGENT_FAILED = "agent_failed" + AGENT_BLOCKED = "agent_blocked" + + # 产出 + ARTIFACT_PRODUCED = "artifact_produced" + ARTIFACT_VALIDATED = "artifact_validated" + + # 决策 + DECISION_MADE = "decision_made" + CHALLENGE_RAISED = "challenge_raised" + CHALLENGE_VERDICT = "challenge_verdict" + + # 异常 + ANOMALY_DETECTED = "anomaly_detected" + TIMEOUT_WARNING = "timeout_warning" + + # 用户交互 + USER_STEER = "user_steer" + USER_APPROVED = "user_approved" + USER_REJECTED = "user_rejected" + + # 经验 + EXPERIENCE_CAPTURED = "experience_captured" +``` + +#### 2.4.2 事件驱动唤醒 + +```python +# 庞统的唤醒条件 +WAKE_CONDITIONS = { + # 事件驱动:Agent 回报触发下一步 + "blackboard_change": { + "trigger": "moments.jsonl 有新行", + "action": "wake pangtong session", + "context": "新增的 moments" + }, + # Agent 完成 + "agent_completed": { + "trigger": "agents/{id}/state.json status=completed", + "action": "wake pangtong", + "context": "agent_id + step_id" + }, + # 超时检测 + "step_timeout": { + "trigger": "step started_at + 30min < now", + "action": "wake pangtong with alert", + "context": "step_id + duration" + }, + # 用户消息 + "user_message": { + "trigger": "inbox/ 有新文件", + "action": "wake pangtong", + "context": "消息内容" + } +} +``` + +--- + +### 2.5 经验沉淀系统 + +#### 2.5.1 闭环学习 + +``` +DISCOVER(发现) +├── 任务执行过程中 Agent 发现好做法 +├── 异常处理中发现新模式 +└── 写入 blackboard/tasks/{id}/agents/{id}/discoveries.json + +DISTILL(蒸馏) +├── 任务完成后庞统自动提取关键转折点 +├── 从 decisions.jsonl + moments.jsonl 提炼经验 +├── 压缩为经验条目:{pattern, context, outcome, applicability} +└── 写入 blackboard/global/experience/{domain}.jsonl + +APPLY(应用) +├── 新任务开始时,庞统检索相关经验 +├── 按任务类型+标签匹配 +├── 注入 Agent prompt 作为参考 +└── 标记"来自经验 X" + +IMPROVE(改进) +├── 验证经验是否真的有效 +├── 无效经验标记 deprecated +├── 有效经验提升 confidence +└── 定期合并相似经验 +``` + +#### 2.5.2 经验数据结构 + +```json +{ + "id": "exp-001", + "pattern": "数据清洗应在策略编码前完成", + "context": "量化策略开发任务", + "outcome": "减少返工率 40%", + "applicability": ["backtest", "strategy_development"], + "source_task": "task-20260514-001", + "confidence": 0.85, + "times_applied": 3, + "times_validated": 2, + "created_at": "...", + "last_validated_at": "..." +} +``` + +--- + +### 2.6 监控与运维 + +#### 2.6.1 健康检查 + +```python +class HealthChecker: + """定期检查黑板和 Agent 健康状态""" + + checks = [ + # Agent 存活检测 + "agent_heartbeat", # 检查 state.json 更新时间 + "agent_zombie", # 运行超过 2 小时的 Agent + "agent_stale_lock", # 超过 30 秒的锁 + + # 任务健康 + "step_timeout", # 步骤超时 + "plan_stuck", # 计划卡住(所有 pending 步骤都有未完成的依赖) + "artifact_missing", # Agent 声称产出但文件不存在(幻觉门控) + + # 系统健康 + "blackboard_disk", # 磁盘空间 + "moment_flood", # 事件洪泛检测 + ] +``` + +#### 2.6.2 Token 成本治理 + +借鉴 Network-AI FederatedBudget + ClawTeam 成本追踪: + +```python +class TokenBudget: + """Token 预算管理""" + + def __init__(self): + self.global_ceiling = 500_000 # 每任务全局上限 + self.per_agent_ceiling = 100_000 # 每 Agent 上限 + self.spent = {} # agent_id → tokens used + + def check(self, agent_id: str, estimated: int) -> bool: + total = sum(self.spent.values()) + if total + estimated > self.global_ceiling: + return False # 全局预算不足 + if self.spent.get(agent_id, 0) + estimated > self.per_agent_ceiling: + return False # Agent 预算不足 + return True +``` + +--- + +## 3. 技术实现方案(v2.1 修订) + +> ⚠️ v2.1 关键修正: +> - ❌ 废弃 sessions_send(不稳定、timeout) +> - ❌ 废弃 sessions_spawn(sub-agent 大爆炸、session 文件堆积) +> - ❌ 废弃 cron wake(不稳定) +> - ✅ 采用自建 Daemon HTTP API + SQLite(v1.0 已验证可靠) +> - ✅ Agent 复用主 session,通过 Daemon API 回报 +> - ✅ 所有状态/流转/事件类型从配置文件加载,不硬编码 + +### 3.1 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| 编排引擎 | **自建 Daemon** (FastAPI + uvicorn) | HTTP API + 事件循环,PM2 管理 | +| 数据存储 | **SQLite** (WAL mode) | 任务/计划/事件/Agent状态/经验 | +| 文件存储 | **文件系统** (artifacts) | 产出物(代码/数据/文档),git 可追踪 | +| Agent 运行时 | **OpenClaw Gateway** | Agent 的主 session 管理 | +| Agent 通信 | **Daemon HTTP API** | Agent 回报结果、查询黑板 | +| Agent 调度 | **`openclaw agent` CLI** | 发消息到 Agent 主 session(不创建 sub-agent) | +| 庞统通信 | **`openclaw agent` CLI** | Daemon → 庞统主 session 注入 systemEvent | +| 配置管理 | **YAML/JSON 配置文件** | 状态/流转/事件/模板全部配置化 | +| 文件锁 | **fcntl / O_EXCL** | 零依赖,跨进程安全 | +| 前端 | **OpenClaw Control Center** | 对话式入口(庞统主 session) | +| 经验检索 | **ripgrep + SQLite FTS** | 文本搜索 | +| 同步 | **sanguo_git_sync** | 已有的三端 Git 同步 | + +### 3.2 为什么不用 OpenClaw 原生调度? + +| 方案 | 问题 | 结论 | +|------|------|------| +| sessions_send | 不稳定,经常 timeout | ❌ 废弃 | +| cron wake | 各种问题,不可靠 | ❌ 废弃 | +| sessions_spawn | 每次创建新 session,文件堆积(庞统 296个/354MB),sub-agent 缺少 SOUL.md | ❌ 废弃 | +| **自建 Daemon HTTP API** | v1.0 已验证(FastAPI + SQLite + PM2),可靠 | ✅ 采用 | + +### 3.3 Agent 调度方式:主 session + HTTP 回报 + +**核心原则:不创建 sub-agent,复用 Agent 主 session。** + +``` +Daemon 需要调度张飞执行编码任务: + +1. Daemon 通过 `openclaw agent` CLI 发消息到 agent:zhangfei-dev:main + - 消息内容:step intent + end_state + constraints + - 张飞在自己的主 session 里收到消息 + +2. 张飞执行任务 + - 读取 daemon API 获取需要的上下文 + - 在自己的 workspace 里工作 + +3. 张飞通过 daemon HTTP API 回报结果 + - curl POST http://localhost:8080/api/step/{id}/complete + - body: { artifacts: [...], confidence: 0.9, summary: "..." } + - daemon 做幻觉门控(验证文件存在) + - daemon 更新 SQLite 状态 + - daemon 触发下一步 + +4. daemon 调 `openclaw agent` CLI 通知庞统进展 + - 庞统在主 session 收到 systemEvent + - 庞统决定下一步操作 +``` + +**为什么不用 sub-agent?** +- 每个 sub-agent 产生 3-5 个磁盘文件(.jsonl + .trajectory + .path) +- 庞统已有 296 个 session 文件 354MB,姜维 227 个 1.4GB +- sub-agent 没有 SOUL.md/IDENTITY.md,行为不够可控 +- cleanup: delete 只是从 UI 隐藏,文件仍然在磁盘上 + +**主 session 的上下文膨胀怎么办?** +- Agent 每完成一个任务步骤后,daemon 发 systemEvent 触发 reset +- OpenClaw 的 reset 会压缩历史,释放上下文空间 +- 或者:每 N 个步骤后自动 reset 一次 + +### 3.4 用户查看进展的流程 + +``` +用户: "任务进展如何?" + │ + ▼ +庞统主 session 收到消息 + │ + ▼ +庞统调用 Daemon API: + GET http://localhost:8080/api/task/{task_id}/status + │ + ▼ +Daemon 返回: + { + "task": { "title": "...", "state": "executing", "phase": 3 }, + "plan": { "steps": [...], "completed": 3, "total": 5 }, + "current_step": { + "agent": "zhangfei", + "status": "executing", + "started_at": "...", + "progress": "正在编码策略逻辑" + }, + "recent_moments": [ + { "type": "agent_completed", "agent": "zhaoyun", "summary": "数据获取完成" }, + { "type": "plan_adjusted", "reason": "发现数据质量问题" } + ], + "anomalies": [], + "token_budget": { "used": 120000, "total": 500000 } + } + │ + ▼ +庞统用 AI 生成人类可读的进展汇报,回复用户 +``` + +**关键设计:庞统是无状态的** +- 所有任务状态在 Daemon 的 SQLite 里 +- 庞统 session 不保存任务状态 +- 每次被问到进展,实时查询 Daemon API +- 这比 v1.0 好:v1.0 庞统需要在 session 里记住所有任务,上下文很快就爆了 + +### 3.5 配置化(零硬编码) + +``` +config/ +├── states.yaml # 任务状态定义 + 合法流转 +├── step-states.yaml # 步骤状态定义 + 合法流转 +├── events.yaml # 事件类型定义 +├── agent-registry.json # Agent 能力画像 +├── templates/ # 任务模板 +│ ├── backtest.yaml +│ ├── strategy-research.yaml +│ └── deployment.yaml +└── settings.yaml # 全局设置 +``` + +**states.yaml**: +```yaml +# 任务级状态定义(v2.0) +# 基于 v1.0 state_machine.py 的成熟经验,新增 AI native 场景 +# Daemon 启动时加载,代码里不允许出现硬编码的状态名 + +# ── 终态(不可变)── +terminal_states: [completed] + +# ── 任务状态 ── +states: + # === Phase 1: 需求探索 === + - name: exploring + description: "AI 与用户需求探索中" + phase: 1 + type: active + transitions_to: [planning, cancelled] + user_actions: [cancel, steer, takeover] + auto_triggers: + - trigger: confidence >= 0.8 + to: planning + description: "AI 判断需求已足够清晰" + + # === Phase 2: 动态规划 === + - name: planning + description: "AI 生成/调整执行计划" + phase: 2 + type: active + transitions_to: [executing, planning, paused, cancelled] + user_actions: [cancel, pause, steer, approve, reject] + auto_triggers: + - trigger: plan_approved + to: executing + description: "计划被批准(用户或 AI 自动)" + - trigger: plan_revise + to: planning # 自转换 + description: "challenge 驳回,修订计划" + + # === Phase 3: 自主执行 === + - name: executing + description: "Agent 自主执行中" + phase: 3 + type: active + transitions_to: [reviewing, paused, escalated, failed, executing, cancelled] + user_actions: [cancel, pause, steer, takeover, intervene] + auto_triggers: + - trigger: all_steps_done + to: reviewing + description: "所有步骤完成" + - trigger: critical_step_failed + to: escalated + description: "关键步骤失败,需人工" + + # === Phase 3.5: 执行中暂停 === + - name: paused + description: "用户暂停,等待恢复" + phase: 3 + type: active + transitions_to: [executing, planning, cancelled] + user_actions: [cancel, resume, replan] + auto_triggers: + - trigger: resume + to: executing + description: "用户恢复" + - trigger: goal_changed + to: planning + description: "用户改需求,重新规划" + + # === 人工介入 === + - name: escalated + description: "AI 主动升级,需要用户决策" + phase: 3 + type: active + transitions_to: [executing, planning, cancelled] + user_actions: [cancel, rollback, replan] + auto_triggers: + - trigger: user_decision + to: executing + description: "用户给出决策,继续执行" + - trigger: goal_changed + to: planning + description: "用户决定改需求" + + # === Phase 4: 验收 === + - name: reviewing + description: "AI 向用户汇报,等待验收" + phase: 4 + type: active + transitions_to: [completed, executing, cancelled] + user_actions: [accept, reject, cancel, steer] + auto_triggers: + - trigger: user_accept + to: completed + description: "用户验收通过" + - trigger: user_reject + to: executing + description: "用户不满意,继续执行" + + # === 终态 === + - name: completed + description: "任务完成" + phase: 4 + type: terminal + transitions_to: [] + + - name: cancelled + description: "已取消(可 resume 重新激活)" + phase: null + type: normal # 非终态,可恢复 + transitions_to: [executing] + user_actions: [resume] + + - name: failed + description: "任务失败(可 retry/escalate)" + phase: null + type: normal + transitions_to: [executing, escalated, cancelled] + user_actions: [retry, escalate, cancel] + +# ── 用户动作定义(AI native 扩展)── +user_actions: + cancel: + description: "取消任务" + available_from: "任何非终态" + pause: + description: "暂停,冻结所有执行" + available_from: [planning, executing] + resume: + description: "恢复执行" + available_from: [paused, cancelled] + steer: + description: "用户中途改方向(不改 goal,只调整执行策略)" + available_from: [exploring, planning, executing, reviewing] + ai_native: true # v2.0 新增:用户在对话中说“改成MACD”即触发 + takeover: + description: "用户接管某个步骤" + available_from: [executing, escalated] + ai_native: true # v2.0 新增:用户说“这部分我来” + intervene: + description: "用户主动干预(查看/修改产出物)" + available_from: [executing] + ai_native: true # v2.0 新增:用户随时可以介入 + approve: + description: "批准计划" + available_from: [planning] + reject: + description: "驳回计划" + available_from: [planning, reviewing] + replan: + description: "重新规划" + available_from: [paused, escalated] + ai_native: true + accept: + description: "验收通过" + available_from: [reviewing] + retry: + description: "失败后重试" + available_from: [failed] + escalate: + description: "升级到人工" + available_from: [failed] + rollback: + description: "回滚到执行态" + available_from: [escalated] +``` + +**step-states.yaml**: +```yaml +# 步骤级状态定义(v2.0) +# 基于 v1.0 NODE_STATES 成熟经验 + +step_states: + - name: pending + description: "待分配" + transitions_to: [assigned, cancelled] + + - name: assigned + description: "已分配 Agent" + transitions_to: [executing, cancelled] + + - name: executing + description: "Agent 执行中" + transitions_to: [completed, failed, blocked, reviewing, waiting_human, cancelled] + # reviewing: 执行完进入审查(v1.0 challenge 机制) + # waiting_human: Agent 请求人工确认 + + - name: reviewing + description: "产出审查中(challenge)" + transitions_to: [completed, pending, failed, escalated] + # v1.0 挑战循环:pass → completed, iterate → pending, fail → failed + + - name: blocked + description: "被阻塞" + transitions_to: [pending, failed, cancelled] + max_retries: 3 + + - name: waiting_human + description: "等待人工确认(Checkpoint)" + transitions_to: [executing, completed, cancelled] + # v2.0: AI 主动请求用户确认关键产出 + + - name: completed + description: "完成" + transitions_to: [] + type: terminal + + - name: failed + description: "失败(可重试)" + transitions_to: [pending, escalated, cancelled] + max_retries: 3 + + - name: escalated + description: "升级到人工" + transitions_to: [executing, pending, cancelled] + + - name: cancelled + description: "已取消" + transitions_to: [] + type: terminal + +# ── 步骤执行方式 ── +execution_modes: + - name: sub_agent + description: "创建 isolated sub-agent 执行(sessions_spawn)" + when: "复杂任务(编码、文档、调研)" + cleanup: "delete" + archive_transcript_to: "artifacts/task-{id}/steps/{step_id}/" + + - name: main_session + description: "在 Agent 主 session 中执行" + when: "简单任务(数据获取、文件操作)" + record_to: "artifacts/task-{id}/steps/{step_id}/transcript.jsonl" + + - name: human + description: "用户自己执行" + when: "用户说'这部分我来'" + trigger: "takeover" +``` + +**代码中禁止出现硬编码状态名**: +```python +# ❌ 禁止 +if task.state == "executing": + +# ✅ 正确 +EXECUTING = config.get_state("executing") +if task.state == EXECUTING: +``` + +### 3.6 核心代码模块 + +``` +sanguo_moziplus_v2/ +├── daemon/ # 守护进程 +│ ├── main.py # FastAPI 入口 +│ ├── api/ # HTTP API 路由 +│ │ ├── tasks.py # 任务 CRUD +│ │ ├── steps.py # 步骤 CRUD + 回报 +│ │ ├── board.py # 黑板查询 +│ │ ├── moments.py # 事件查询 +│ │ └── agents.py # Agent 状态/心跳 +│ ├── engine/ # 编排引擎 +│ │ ├── orchestrator.py # 编排主循环(事件驱动) +│ │ ├── planner.py # 动态规划 +│ │ ├── selector.py # Agent 选择 +│ │ ├── validator.py # 产出验证(幻觉门控) +│ │ ├── archiver.py # 执行历史归档 +│ │ └── experience.py # 经验沉淀引擎 +│ ├── cli_client.py # openclaw agent CLI 调用封装 +│ ├── db.py # SQLite 数据层 +│ ├── lock.py # 文件锁实现 +│ ├── health.py # 健康检查(daemon 内部定时) +│ ├── budget.py # Token 预算管理 +│ └── config_loader.py # YAML/JSON 配置加载 +├── config/ # 配置文件 +│ ├── states.yaml +│ ├── step-states.yaml +│ ├── events.yaml +│ ├── agent-registry.json +│ ├── templates/ +│ └── settings.yaml +├── artifacts/ # 产出物 + 执行历史(git 追踪) +│ └── task-{id}/ # 每个任务一个目录 +│ ├── context.json # 任务上下文 +│ ├── plan.json # 执行计划 +│ ├── steps/ # 各步骤产出 +│ │ ├── s1/ +│ │ │ ├── output.json # 步骤产出元数据 +│ │ │ ├── transcript.jsonl # 执行历史(从 OpenClaw 归档) +│ │ │ └── ... # 实际产出文件 +│ │ └── s2/ +│ ├── moments.jsonl # 关键事件流 +│ └── experience.md # 提取的经验教训 +├── skills/ # Skill 包(供 Agent 加载) +│ ├── task-bootstrap/ # Agent 任务引导 Skill +│ │ └── SKILL.md # Boids + 元认知 + Auftragstaktik +│ └── wiki-query/ # 复用已有 +├── docs/ +│ ├── design/ +│ └── research/ +├── scripts/ +│ ├── create-task.sh # CLI: 创建任务 +│ ├── status.sh # CLI: 查看状态 +│ └── bootstrap.sh # 初始化脚本 +└── README.md +``` + +### 3.6.1 执行历史归档机制 + +**目标**:每个步骤的完整执行过程都归档到任务目录,可追溯、可检索、可提炼经验。 + +``` +执行流程: + +1. Daemon 调度 Agent 执行步骤(sub-agent 或主 session) +2. Agent 执行完毕,回报结果 +3. Daemon archiver 做以下事情: + a. 读取 OpenClaw session transcript(.jsonl 文件) + b. 提取与该步骤相关的部分(按时间范围裁剪) + c. 写入 artifacts/task-{id}/steps/{step_id}/transcript.jsonl + d. 如果是 sub-agent,transcript 包含完整对话历史 + e. 如果是主 session,只裁剪该步骤执行期间的对话 +4. 经验沉淀时,读取 transcript.jsonl 做 AI 分析 +``` + +**transcript.jsonl 包含什么**: +- user 消息(用户的指令/反馈) +- assistant 消息(Agent 的回复/决策) +- tool 调用及结果(执行的命令、文件操作、API 调用) +- token 消耗统计 +- 错误和异常信息 + +**价值**: +- 🔍 可追溯:每个步骤的每个决策都有据可查 +- 📚 可学习:经验提取时能看完整上下文,不只是结论 +- 🐛 可复盘:坑和踩坑过程一目了然 +- 🧹 可清理:归档后 OpenClaw 原始 session 文件可以安全删除 + +### 3.6.2 Agent 调度混合方案 + +简单步骤用主 session,复杂步骤用 sub-agent: + +| 步骤类型 | 调度方式 | 原因 | +|---------|---------|------| +| 数据获取/文件操作 | 主 session + Gateway 发消息 | 简单快速,不需要隔离 | +| 编码/文档/调研 | sessions_spawn + cleanup=delete | 复杂任务需要隔离,防上下文污染 | +| 用户自己执行 | takeover,不调度 | 用户说"这部分我来" | + +sub-agent 完成后,archiver 自动把 transcript 归档到任务目录。 + +### 3.7 关键交互流程 + +#### 流程 1:完整任务生命周期 + +``` +用户: "帮我做一个均线策略回测" + │ + ▼ +庞统 Phase 1(需求探索) + ├── 苏格拉底对话 2-3 轮 + ├── 澄清:标的?周期?资金?评价指标? + ├── 写入 context.json,confidence=0.9 + └── 转入 Phase 2 + │ + ▼ +庞统 Phase 2(动态规划) + ├── 检索经验库 → 找到"数据清洗应先于策略编码" + ├── 生成 plan.json(5步) + ├── 用户确认(或跳过) + └── 转入 Phase 3 + │ + ▼ +庞统 Phase 3(自主执行) + │ + ├── Step s1: 数据获取 → 选择赵云(data_fetch 能力) + │ ├── `openclaw agent` CLI 调度赵云 + │ ├── 赵云执行,写入 output/hs300_daily.csv + │ ├── 赵云 propose → 庞统 validate → commit + │ ├── 幻觉门控:文件存在?大小合理? ✓ + │ └── confidence=0.95 ✓ + │ + ├── 发现:数据有缺失值(anomaly_detected) + │ └── 庞统动态添加 s1.5 数据清洗步骤 + │ + ├── Step s1.5: 数据清洗 → 选择赵云 + │ └── ... 执行并完成 + │ + ├── Step s2: 策略编码 → 选择张飞 + │ └── ... 执行并完成 + │ + ├── Step s3: 风控审查 → 选择关羽 + │ └── ... 执行并完成 + │ + ├── Step s4: 质量评审 → 选择司马懿 + │ ├── 司马懿发现问题 → challenge_raised + │ ├── 庞统裁决 → 要求张飞修正 + │ ├── 张飞修正 → 重新评审 + │ └── 司马懿通过 ✓ + │ + └── 转入 Phase 4 + │ + ▼ +庞统 Phase 4(主动汇报) + ├── 生成最终报告 + ├── 经验沉淀:提取 3 条经验写入 experience/ + ├── 向用户推送完成通知 + └── 用户验收 +``` + +#### 流程 2:异常处理 + +``` +场景:赵云执行超时 + +1. health scanner 检测到 zhaoyun state.json 30分钟未更新 +2. daemon 事件循环检测到超时,通知庞统 +3. 庞统: + a. 检查赵云 session 是否存活(sessions_list) + b. 存活 → `openclaw agent --agent zhaoyun` 询问进度 + c. 不存活 → 标记 s1 为 failed,重新分配给其他 Agent 或调整计划 + d. 记录 decision: "赵云超时,重新分配" +4. 追加 Moment: agent_failed + decision_made +``` + +#### 流程 3:用户中途干预 + +``` +场景:执行到 s2 时用户说"改成 MACD 策略" + +1. 用户消息注入庞统 session +2. 庞统: + a. 暂停当前执行(通知张飞停止) + b. 修改 context.json(goal 改为 MACD 策略) + c. 重新规划 plan.json(s2 需要修改) + d. 向用户确认修改方案 + e. 用户确认后继续执行 +3. 追加 Moment: user_steer + plan_adjusted +``` + +### 3.8 与 OpenClaw 的集成点(v2.3 修正) + +> ⚠️ v2.3 关键修正(司马懿第二轮评审反馈): +> - ❌ 废弃自建 Gateway WS Client(不稳定、需维护长连接) +> - ✅ 改用 `openclaw agent` CLI 命令(Gateway 原生、有超时和 fallback、零连接管理) +> - ✅ 全程推送式事件链路,零轮询零 cron + +| v2.0 功能 | 实现方式 | 说明 | +|-----------|---------|------| +| 庞统对话 | 庞统主 session (webchat) | 用户通过 Control Center 对话 | +| 庞统查进展 | curl GET daemon API | 实时查询 SQLite,AI 生成汇报 | +| 庞统操作任务 | curl POST daemon API | 创建/规划/启动/暂停/恢复任务 | +| **Daemon 调度 Agent** | **`openclaw agent` CLI** | daemon 调用 CLI 发消息到 Agent 主 session | +| Agent 调度(复杂) | sessions_spawn + cleanup=delete | 复杂步骤用 sub-agent 隔离 | +| Agent 回报 | curl POST daemon API | 完成/失败/进度上报 | +| Agent 查黑板 | curl GET daemon API | 查询任务上下文/计划/产出物 | +| **Daemon 通知庞统** | **`openclaw agent` CLI** | daemon 调用 CLI 注入消息到庞统 session | +| Agent 上下文管理 | Gateway reset | 定期 reset Agent 主 session 防止膨胀 | +| 知识检索 | wiki-query skill (Agent 侧) | Agent 需要时可调用 | +| 数据持久化 | SQLite (daemon) + 文件系统 (artifacts) | 状态在 SQLite,产出物在文件系统 | +| 执行历史归档 | daemon archiver | 归档后物理删除原始 session 文件 | + +#### 3.8.1 Agent 调度的具体实现 + +```bash +# Daemon 调度 Agent(简单步骤:主 session) +openclaw agent --agent zhangfei-dev \ + --message "执行步骤 s2: 编码均线策略逻辑。上下文见 http://localhost:8080/api/steps/s2" \ + --json + +# Daemon 通知庞统 +openclaw agent --agent pangtong-fujunshi \ + --message "[systemEvent] 步骤 s1 完成,zhaoyun 回报:数据获取成功" \ + --json +``` + +**并行调度**:daemon 用 `subprocess.Popen` 异步启动多个 `openclaw agent` 命令,poll 等待结果。 + +**事件传播链(全程推送,零轮询)**: +``` +Agent 完成任务 + → curl POST http://localhost:8080/api/steps/{id}/complete(回报 daemon) + → daemon 更新 SQLite + 验证产出 + → daemon 调 openclaw agent --agent pangtong-fujunshi(通知庞统) + → 庞统收到消息,决定下一步 + → daemon 调 openclaw agent --agent zhangfei-dev(调度下一个 Agent) +``` + +#### 3.8.2 庞统上下文恢复协议 + +庞统是"无任务状态"的(状态在 daemon SQLite),但需要"推理上下文"。 +庞统每次被 daemon 通知时,第一步总是 `GET /api/tasks/{id}/status` 重建认知。 + +#### 3.8.3 Agent 任务消息模板 + +Daemon 调度 Agent 时发送的标准消息格式: + +``` +[moziplus v2] 任务步骤分配 + +任务: {task_title} +步骤: {step_id} - {step_title} +目标: {step_intent} +预期产出: {step_end_state} + +上下文获取: + 任务状态: curl http://localhost:8080/api/tasks/{task_id} + 步骤详情: curl http://localhost:8080/api/steps/{step_id} + 前序产出: curl http://localhost:8080/api/steps/{prev_step_id}/output + +约束: + 产出物必须写到 artifacts/task-{task_id}/steps/{step_id}/ 目录 + 完成后回报: curl -X POST http://localhost:8080/api/steps/{step_id}/complete + 失败回报: curl -X POST http://localhost:8080/api/steps/{step_id}/fail +``` | + +--- + +### 3.9 共享意识空间物理结构 + +> "可预测骨架 + AI 动态填充" 的物理载体。 +> SQLite 存状态(查询快、事务安全),文件系统存内容(代码/数据/文档,git 可追踪)。 +> 同一信息只存在一个地方,不重复。 + +#### 3.9.1 SQLite 表结构 + +```sql +-- 任务表 +CREATE TABLE tasks ( + id TEXT PRIMARY KEY, -- UUID + title TEXT NOT NULL, + goal TEXT NOT NULL, -- 用户原始需求 + state TEXT NOT NULL DEFAULT 'exploring', + phase INTEGER NOT NULL DEFAULT 1, -- 1=探索, 2=规划, 3=执行, 4=验收 + context_json TEXT, -- {goal, constraints, clarifications, confidence} + plan_json TEXT, -- {steps: [{id, title, intent, agent_hint, depends_on}]} + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + created_by TEXT DEFAULT 'user', -- user | pangtong + token_budget_used INTEGER DEFAULT 0, + token_budget_limit INTEGER DEFAULT 500000 +); + +-- 步骤表 +CREATE TABLE steps ( + id TEXT PRIMARY KEY, -- "{task_id}-s{seq}" + task_id TEXT NOT NULL REFERENCES tasks(id), + seq INTEGER NOT NULL, + title TEXT NOT NULL, + intent TEXT NOT NULL, -- 步骤目标(给 Agent 的指令) + end_state TEXT, -- Agent 应该达到的产出状态 + state TEXT NOT NULL DEFAULT 'pending', + assigned_agent TEXT, -- agent-id 或 null(待分配) + execution_mode TEXT DEFAULT 'sub_agent', -- sub_agent | main_session | human + depends_on TEXT, -- JSON array of step_ids + output_json TEXT, -- {artifacts: [...], confidence, summary} + started_at TEXT, + completed_at TEXT, + retry_count INTEGER DEFAULT 0, + max_retries INTEGER DEFAULT 3 +); + +-- 事件流(Moments) +CREATE TABLE moments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL REFERENCES tasks(id), + step_id TEXT, -- nullable(任务级事件没有 step_id) + type TEXT NOT NULL, -- 事件类型(从 events.yaml 加载) + agent TEXT, -- 触发 Agent + data_json TEXT NOT NULL, -- 事件详情 + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); +CREATE INDEX idx_moments_task ON moments(task_id, created_at); + +-- Agent 状态(心跳) +CREATE TABLE agent_status ( + agent_id TEXT PRIMARY KEY, + status TEXT NOT NULL DEFAULT 'idle', -- idle | busy | offline + current_task_id TEXT, + current_step_id TEXT, + last_heartbeat TEXT, + capabilities TEXT -- JSON array(从 agent-registry.json 加载) +); + +-- AI 决策记录(可追溯) +CREATE TABLE decisions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL REFERENCES tasks(id), + step_id TEXT, + decision_type TEXT NOT NULL, -- agent_selected | plan_adjusted | anomaly_handled | escalated + reasoning TEXT NOT NULL, -- AI 的思考过程 + outcome TEXT NOT NULL, -- 决策结果 + model TEXT, -- 使用的模型 + token_cost INTEGER, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +-- 经验库 +CREATE TABLE experiences ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + source_task_id TEXT NOT NULL, + source_step_id TEXT, + category TEXT NOT NULL, -- pattern | pitfall | best_practice | tool_usage + title TEXT NOT NULL, + content TEXT NOT NULL, -- Markdown 格式 + tags TEXT, -- JSON array + relevance_score REAL DEFAULT 0.5, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); +``` + +#### 3.9.2 文件系统结构 + +``` +sanguo_moziplus_v2/ +└── artifacts/ + └── task-{uuid}/ + ├── context.json # 任务上下文(冗余 SQLite 的 context_json,供 Agent 直接 read) + │ # {goal, constraints, clarifications, confidence, phase_history} + │ + ├── plan.json # 执行计划(冗余 SQLite 的 plan_json,供 Agent 直接 read) + │ # {steps: [{id, title, intent, end_state, assigned_agent, depends_on, state}]} + │ + ├── steps/ # 各步骤产出物 + │ ├── s1/ + │ │ ├── output.json # {artifacts: [{path, type, size}], confidence, summary, duration_s} + │ │ ├── transcript.jsonl # 执行历史归档(从 OpenClaw session transcript 复制) + │ │ ├── hs300_daily.csv # ← 实际产出文件(Agent 直接写到这里) + │ │ └── data_quality.json # ← 数据质量报告 + │ ├── s2/ + │ │ ├── output.json + │ │ ├── transcript.jsonl + │ │ ├── strategy.py # ← 策略代码 + │ │ └── backtest_result.json + │ └── s3/ + │ └── ... + │ + ├── observations/ # Agent 主动观察与建议(v2.4 新增) + │ # Agent 执行中发现的异常、建议、洞察 + │ # 其他 Agent 和庞统可直接 read 获取 + │ ├── zhaoyun-001.md # "数据有缺失值,建议加清洗步骤" + │ ├── zhangfei-001.md # "建议用 MACD 替代均线" + │ └── ... + │ + ├── status.json # 全局状态摘要(v2.4 新增,daemon 实时维护) + │ # {phase, steps: {s1: {state, agent}, ...}, anomalies, observations} + │ # Agent 执行前第一步 read 此文件获取全局视野 + │ + ├── moments.jsonl # 原子事件流(冗余 SQLite moments 表,追加写入) + │ # 每行: {"type":"step_completed","ts":"...","agent":"zhaoyun","data":{...}} + │ + ├── decisions.jsonl # AI 决策记录(冗余 SQLite decisions 表) + │ # 每行: {"type":"agent_selected","ts":"...","reasoning":"...","outcome":"zhangfei"} + │ + └── experience.md # 任务完成后 AI 提取的经验(Markdown) +``` + +**为什么 SQLite 和文件都有?** +- **SQLite**:给 daemon 内部用(查询、事务、索引、复杂条件筛选) +- **文件系统**:给 Agent 用(`read` 工具直接读,Agent 不能访问 daemon 的 SQLite) +- 两者通过 daemon API 同步,SQLite 是权威源,文件是缓存/视图 + +#### 3.9.3 产出物门控(Gating)设计 + +> 参考 OpenAI Codex Cookbook 的 gating check:"Do not advance until required files are present" + +``` +步骤完成回报 POST /api/steps/{id}/complete + │ + ▼ +Daemon validator 做三层检查: + 1. 文件存在性检查 + - output_json 中声明的每个 artifact.path 都必须存在 + - ls -la 验证 + 2. 内容完整性检查 + - 文件大小 > 0(空文件不算) + - 文件大小在合理范围内(非异常值) + - 如果是 JSON/CSV,验证 schema/parsing + 3. 语义检查(可选,消耗 token) + - AI 检查产出物是否与 end_state 描述匹配 + - 只在 confidence < 0.7 时触发 + │ + ▼ +三层全过 → 同一事务内: + - UPDATE steps SET state='completed' + - INSERT INTO moments (type='step_completed') + - INSERT INTO moments (type='validation_passed') +任何一层失败 → 回报 validation_failed,步骤保持 executing +``` + +--- + +### 3.10 Daemon API 接口定义 + +> RESTful + JSON,端口 8080(沿用 v1.0)。 +> 所有请求/响应都记录到 moments 表。 + +#### 3.10.0 API 设计原则(调研审视) + +| 原则 | 来源 | 实现方式 | +|------|------|----------| +| **幂等性** | Wanman JSON-RPC | 所有写操作可安全重试,重复回报不产生副作用 | +| **标准错误码** | Wanman JSON-RPC | 统一错误格式 `{ok: false, error: {code, message}}` | +| **事务保护** | Edict Outbox | 关键写操作(步骤完成)原子化:产出验证 + 状态更新 + 事件记录同一事务 | +| **版本化** | Gstack | API 版本号前缀 `/api/v1/...`,未来升级不破坏兼容性 | +| **Agent 零SDK** | Wanman CLI | Agent 只需 curl,不需要任何 SDK | + +#### 3.10.1 任务生命周期 API + +``` +POST /api/tasks # 创建任务 +GET /api/tasks/{id} # 查询任务状态 +GET /api/tasks/{id}/status # 轻量状态查询(给庞统用) +PUT /api/tasks/{id}/context # 更新任务上下文 +POST /api/tasks/{id}/plan # 提交执行计划 +POST /api/tasks/{id}/start # 启动执行(Phase 3) +POST /api/tasks/{id}/pause # 暂停 +POST /api/tasks/{id}/resume # 恢复 +POST /api/tasks/{id}/cancel # 取消 +POST /api/tasks/{id}/replan # 重新规划(用户改需求或异常调整) +GET /api/tasks/{id}/moments # 查询事件流 +GET /api/tasks/{id}/decisions # 查询决策记录 +``` + +**POST /api/tasks** — 创建任务 +```json +// Request +{ + "title": "均线策略回测", + "goal": "对沪深300做双均线交叉策略回测", + "constraints": ["5年日线数据", "滑点0.2%"], + "metadata": {} // 可选 +} +// Response +{ + "ok": true, + "task_id": "t-uuid-001", + "state": "exploring" +} +``` + +**GET /api/tasks/{id}/status** — 轻量状态查询(庞统用,返回人类可读摘要) +```json +{ + "task": { "title": "均线策略回测", "state": "executing", "phase": 3 }, + "plan": { + "steps": [ + { "id": "s1", "title": "数据获取", "state": "completed", "agent": "zhaoyun" }, + { "id": "s2", "title": "策略编码", "state": "executing", "agent": "zhangfei" } + ], + "completed": 1, "total": 4 + }, + "current_step": { + "id": "s2", "agent": "zhangfei", "started_at": "...", + "progress": null // Agent 可选上报进度 + }, + "recent_moments": [ + { "type": "step_completed", "agent": "zhaoyun", "summary": "数据获取完成" } + ], + "anomalies": [], + "token_budget": { "used": 120000, "limit": 500000 } +} +``` + +#### 3.10.2 步骤执行 API + +``` +POST /api/steps/{id}/assign # 分配 Agent +POST /api/steps/{id}/start # 标记开始执行 +POST /api/steps/{id}/complete # Agent 回报完成 +POST /api/steps/{id}/fail # Agent 回报失败 +POST /api/steps/{id}/block # Agent 回报阻塞 +POST /api/steps/{id}/progress # Agent 上报进度(可选) +POST /api/steps/{id}/escalate # Agent 请求人工介入 +GET /api/steps/{id} # 查询步骤详情 +GET /api/steps/{id}/output # 查询步骤产出 +``` + +**POST /api/steps/{id}/complete** — Agent 回报完成 +```json +// Request +{ + "artifacts": [ + { "path": "artifacts/task-xxx/steps/s1/hs300_daily.csv", "type": "data", "size_bytes": 1048576 } + ], + "confidence": 0.95, + "summary": "获取沪深300 5年日线数据,共1218条记录,含缺失值处理报告" +} +// Response +{ + "ok": true, + "state": "completed", + "validation": { + "files_exist": true, + "sizes_reasonable": true, + "passed": true + } +} +``` + +**POST /api/steps/{id}/fail** — Agent 回报失败 +```json +// Request +{ + "error": "数据源返回403,权限不足", + "retry_suggested": true, + "partial_artifacts": [] +} +``` + +#### 3.10.3 查询 & 管理API + +``` +GET /api/agents # 所有 Agent 状态 +GET /api/agents/{id}/status # 单个 Agent 状态 +POST /api/agents/{id}/heartbeat # Agent 心跳 + +GET /api/experiences # 经验检索(给 AI 用) +POST /api/experiences # 写入经验 + +GET /api/health # Daemon 健康检查 +GET /api/stats # 统计信息(任务数、成功率等) +``` + +**GET /api/experiences** — 经验检索 +```json +// Query: ?tags=data_cleaning,backtest&limit=5 +{ + "experiences": [ + { + "id": 1, + "source_task": "t-uuid-001", + "category": "pitfall", + "title": "数据缺失值必须在策略编码前处理", + "content": "...", + "tags": ["data_cleaning", "backtest"], + "relevance_score": 0.92 + } + ] +} +``` + +#### 3.10.4 AI Native 扩展 API + +``` +POST /api/tasks/{id}/steer # 用户中途改方向(不改 goal) +POST /api/tasks/{id}/takeover/{step_id} # 用户接管某步骤 +POST /api/tasks/{id}/intervene # 用户主动干预 +POST /api/steps/{id}/review # 触发 challenge 审查 +POST /api/tasks/{id}/distill # 触发经验蒸馏 +``` + +**POST /api/tasks/{id}/steer** — 用户改方向 +```json +// Request +{ + "direction": "改用MACD策略", + "reason": "用户要求", + "affected_steps": ["s2"] // 哪些步骤受影响(可选,AI自动判断) +} +// Response +{ + "ok": true, + "action": "replanning", + "paused_steps": ["s2"], + "message": "正在重新规划受影响的步骤..." +} +``` + +--- + +### 3.11 经验沉淀引擎 + +> 每次任务完成后自动触发,提取可复用的经验存入经验库。 + +#### 3.11.1 沉淀流程 + +``` +任务完成 + │ + ▼ +Daemon 检测到任务 state = completed + │ + ▼ +Daemon 通过 Gateway 触发 AI session 做经验蒸馏 + - 输入: task context + plan + 所有 steps 的 transcript.jsonl + moments + - AI 分析: + 1. 哪些决策是好的?(pattern) + 2. 哪些坑踩了?(pitfall) + 3. 有什么可复用的做法?(best_practice) + 4. 工具使用技巧?(tool_usage) + - 输出: 3-5 条经验 + │ + ▼ +Daemon 写入 SQLite experiences 表 + 生成 experience.md + │ + ▼ +下次创建类似任务时,Daemon 自动检索相关经验注入 context +``` + +#### 3.11.2 经验检索时机 + +| 时机 | 触发者 | 检索内容 | +|------|--------|----------| +| Phase 2 规划 | Daemon | 检索与任务 goal 相关的经验,注入 plan 上下文 | +| Phase 3 Agent 选择 | Daemon | 检索与步骤类型相关的经验(如"这类任务哪个 Agent 做得好") | +| Agent 执行前 | Daemon | 注入与步骤相关的经验到 task 描述 | +| 异常处理 | Daemon/庞统 | 检索类似异常的历史处理方式 | + +#### 3.11.3 调研审视:经验沉淀引擎的改进方向 + +基于 wiki 知识管理体系 + Nuwa 五层蒸馏 + Corpus2Skill + A-MEM + MemAgents ICLR 2026 的调研: + +**改进 1:闭环四阶段(当前只有 DISTILL+APPLY)** + +``` +DISCOVER(发现问题): + - 异常即知识源:步骤失败、数据质量异常、Agent 超时都是潜在的待沉淀知识 + - 反向触发:Agent 主动发现好实践时建议"这个做法可以固化" + - 外部注入:用户丢链接/文章,评估适用性 + +DISTILL(蒸馏提取): + - 参考 Nuwa 五层:表象 → 模式 → 决策启发式 → 反模式 → 边界 + - 不是简单的"踩坑记录",而是提炼出可复用的决策规则 + - AI 分析 transcript.jsonl,不只是看结论,看完整决策过程 + +APPLY(应用): + - 规划时:检索与 goal 相关的经验注入 plan 上下文 + - 执行时:检索与步骤相关的经验注入 task 描述 + - 异常时:检索类似异常的历史处理方式 + +IMPROVE(持续改进): + - 新经验可能让旧经验过时(A-MEM 的 Zettelkasten 思路) + - 检测冲突:新经验与旧经验矛盾时,标注差异,等待下次应用时验证 + - 不删除旧经验,标记为 superseded + 关联新经验 +``` + +**改进 2:经验→Skill 转化(参考 Corpus2Skill)** + +高频使用的经验可以自动转化为 Agent Skill: + +``` +经验使用频率 >= 5次 + → 自动转化为 skills/auto-generated/{experience-name}/SKILL.md + → 下次同类任务直接注入 Skill,不需要检索经验库 + → 减少 token 消耗(Skill 注入比检索更精炼) +``` + +**改进 3:经验蒸馏的五层结构(参考 Nuwa)** + +```yaml +experience: + title: "数据清洗应先于策略编码" + layers: + - layer: surface # 表象:发生了什么 + content: "赵云获取数据后有缺失值,张飞直接编码导致回测结果异常" + - layer: pattern # 模式:这类问题的通用模式 + content: "数据质量和策略逻辑是两个独立关注点,应串行处理" + - layer: heuristic # 决策启发式:下次怎么做 + content: "IF 任务涉及数据分析 THEN 第一步必须是数据质量检查" + - layer: anti_pattern # 反模式:绝对不做的事 + content: "绝对不要假设数据是干净的,即使数据源声称已清洗" + - layer: boundary # 边界:什么时候不适用 + content: "如果是纯模拟数据回测,不需要数据清洗步骤" +``` + +### 3.12 中央协调 + Agent 自主(v2.4 核心设计修正) + +> **问题**:v2.3 设计在三轮评审后逐渐妥协了三个 AI native 核心目标: +> 1. Agent 自主协作 → 退化为中央调度 +> 2. 实时共享感知 → 退化为按需查询 +> 3. AI 持续参与 → 退化为完成时通知 +> +> **根因**:遇到技术难点时采纳降级方案,而非在约束下找实现目标的方法。 +> +> **v2.4 修正**:技术栈不变(Daemon + SQLite + `openclaw agent` CLI + 文件系统),变的是**设计理念**—— +> 从“中央控制”变为“中央协调 + Agent 自主”。 +> +> **调研来源**: +> - Edict 朝堂议政:TurnScheduler + phaseMask(Agent 声明在哪些阶段主动发言) +> - Ouroboros:后台意识循环 consciousness.py(任务间主动思考) +> - Network-AI:Blackboard + CRDT(Agent 读黑板、看到变化、自己决定行动) +> - Open Multi-Agent:sharedMemory + delegate_to_agent(Agent 可主动委派) +> - oh-my-claudecode:Team staged pipeline(team-plan → team-prd → team-exec → team-verify → team-fix) +> - Hermes Agent:kanban board + dispatcher(Agent 通过 kanban_* 工具集自主 claim task) +> - Claude Code:Grove(共享项目工作区)、Swarm/Team(hub-spoke 协调) +> - MCP + A2A:MCP 提供上下文共享,A2A 提供 Agent 间直接通信 +> - 学术:Multi-Agent Blackboard System (arXiv:2510.01285) 消除中央协调器先验知识需求 + +#### 3.12.1 三层自主模型 + +``` +┌──────────────────────────────────────────────────────┐ +│ 庞统(中央协调) │ +│ 定方向、定约束、异常干预、持续观察 │ +│ 不微观管理:不指定每一步怎么做 │ +└────────────┬─────────────────────┬───────────────────┘ + │ │ + ┌────────▼────────┐ ┌────────▼────────┐ + │ Agent 自主权 │ │ Agent 自主权 │ + │ 执行方式自主决定 │ │ 执行方式自主决定 │ + │ 可主动发现问题 │ │ 可主动发现问题 │ + │ 可主动建议 │ │ 可主动建议 │ + └────────┬────────┘ └────────┬────────┘ + │ │ + ┌────────▼─────────────────────▼────────┐ + │ 共享意识空间(文件系统) │ + │ status.json → 全局状态实时摘要 │ + │ observations/ → Agent 主动观察 │ + │ moments.jsonl → 原子事件流 │ + │ steps/ → 各步骤产出物 │ + └──────────────────────────────────────┘ +``` + +**与 v2.3 的核心区别**: + +| 维度 | v2.3(妥协版) | v2.4(AI native) | +|------|-------------|----------------| +| 调度模式 | daemon 全权调度 | 庞统定方向,Agent 自主执行 | +| 信息获取 | Agent curl 查 API | Agent 直接 read 共享目录文件 | +| AI 参与 | 步骤完成后通知 | 持续推送执行摘要,庞统主动干预 | +| Agent 主动性 | 无 | observations 目录 + 主动建议机制 | +| 协作方式 | 庞统串行分配 | Agent 可观察其他 Agent 产出并主动调整 | + +#### 3.12.2 Agent 自主执行协议 + +Agent 被调度后,不只是“执行指令”,而是执行一套自主行为协议: + +``` +Agent 收到任务消息后: + +1. 【感知】read artifacts/task-{id}/status.json + → 了解全局:当前 phase、各步骤状态、异常列表 + +2. 【感知】read artifacts/task-{id}/observations/ + → 了解其他 Agent 的观察和建议 + → 特别是前序步骤 Agent 的发现 + +3. 【感知】read artifacts/task-{id}/steps/{前序步骤}/output.json + → 获取前序步骤的具体产出 + +4. 【执行】基于全局信息自主决定执行方式 + → 不需要庞统指定“用什么工具、什么方法” + → 根据任务意图 + 全局上下文自行判断 + +5. 【主动观察】如果执行中发现异常/有价值的发现: + → write artifacts/task-{id}/observations/{agent}-{seq}.md + → 内容:发现的问题、建议的调整、重要的洞察 + → 格式自由(Markdown),由庞统或其他 Agent 读取 + +6. 【回报】完成/失败后回报 daemon API + → POST /api/steps/{id}/complete 或 /fail +``` + +**关键设计**:步骤 1-3(感知)是**强制的**,写入 Agent 的 task-bootstrap Skill。 +这确保每个 Agent 执行前都有全局视野,而不是盲人摸象。 + +#### 3.12.3 Agent 任务消息模板(v2.4 增强) + +调度 Agent 时,消息内容不仅是“执行 X”,还包含: + +```markdown +## 任务:{step_title} + +### 意图 +{step_intent} → 目标状态:{end_state} + +### 全局上下文 +- 任务:{task_title} +- Phase:{current_phase} +- 前序步骤:{completed_steps_summary} +- **请先感知全局**:read artifacts/task-{id}/status.json 和 observations/ + +### 你的自主权 +- 执行方式由你决定(工具、方法、顺序) +- 发现异常请写入 observations/ +- 遇到专业外的问题主动回报,不硬撑 + +### 约束 +- 产出物必须写到 artifacts/task-{id}/steps/{step_id}/ +- 超时 {timeout_minutes} 分钟自动标记 blocked + +### 完成后 +POST /api/steps/{step_id}/complete +body: {artifacts: [...], confidence: 0-1, summary: "...", observations: [...]} +``` + +#### 3.12.4 庞统持续意识机制 + +庞统不是被动的审批节点,而是持续观察的指挥官。 + +**实现方式**:daemon 通过 `openclaw agent` CLI 定期推送执行摘要给庞统。 + +``` +触发时机(不是 cron,是事件驱动): + +1. 步骤完成事件 + → daemon 立即通知庞统 + → 庞统判断:正常→无干预 / 异常→主动发指令 + +2. Agent 写入 observation + → daemon 检测到新文件(inotify/fswatch) + → 推送给庞统:"赵云发现数据异常" + → 庞统决定:加步骤 / 调整方案 / 通知用户 + +3. 定期心跳(每 N 分钟,可配置) + → daemon 推送当前全局摘要 + → 庞统判断是否有异常趋势(如某步骤超时、Agent 连续失败) + → 正常→回"继续" / 异常→主动干预 + +4. AI 质量评估点(Phase 2/3 交界处) + → 庞统审查当前执行计划 vs 实际进展 + → 主动调整步骤、重新分配 Agent、升级问题 +``` + +**Token 消耗控制**(通过质量分级): +- `critical` 任务:每个事件都推送 +- `standard` 任务:步骤完成 + observation 时推送 +- `exploratory` 任务:仅步骤完成时推送 + +#### 3.12.5 文件系统 = 实时共享感知 + +**核心洞察**:文件系统天然就是“实时共享”的——一个 Agent write 了文件,另一个 Agent 立刻可以 read。 +不需要 WebSocket、不需要推送、不需要轮询 API。 + +关键在于让 Agent **知道去读什么**。这就是 `status.json` + `observations/` 的作用: + +- `status.json`:daemon 在每次状态变化后立即更新,Agent read 即可获得全局视野 +- `observations/`:Agent 发现问题时主动写入,其他 Agent read 即可感知 +- `steps/`:前序步骤的产出物就在文件系统里,不需要通过 API 传输 + +**为什么不用 WebSocket/实时推送?** +因为 Agent 的执行是 `openclaw agent` CLI 调用——每次调用是一个独立进程。 +Agent 不需要“订阅”事件流,而是在被调用时通过 read 文件获取全部所需上下文。 +这比实时推送更简单、更可靠(无连接断开问题)。 + +#### 3.12.6 Agent 主动协作场景 + +``` +场景 1:赵云发现数据异常 + → 赵云写入 observations/zhaoyun-001.md + → daemon 检测到 → 通知庞统 + → 庞统判断:需要加清洗步骤 + → 庞统调整计划 → daemon 更新 status.json + plan.json + → 张飞被调度时 read status.json,看到新增的清洗步骤 + → 张飞 read observations/zhaoyun-001.md,了解数据异常详情 + → 张飞自主决定:在策略编码前加入数据清洗逻辑 + +场景 2:张飞主动建议 + → 张飞执行策略编码时,发现更好的技术指标 + → 张飞写入 observations/zhangfei-001.md + → daemon 通知庞统 + → 庞统判断:值得尝试,修改计划允许探索 + → 关羽审核时 read observations/,看到张飞的建议,针对性审查 + +场景 3:关羽主动拦截 + → 关羽审核代码时发现风险 + → 关羽写入 observations/guanyu-001.md(标记为 blocking) + → daemon 立即通知庞统 + → 庞统决定:暂停后续步骤,要求张飞修改 + → 不需要等步骤“完成”才发现问题 +``` + +#### 3.12.7 实现注意事项(司马懿第四轮评审反馈,v2.4 补充) + +> 司马懿判定:v2.4 设计方向正确,可进入实现。以下为实现时必须注意的 5 个细节。 + +**1. Agent 自主的真实边界** + +Agent 在执行开始时获取全局快照(read status.json + observations/),**执行过程中不感知变化**。 +并行执行场景下,如果需要运行时感知,由庞统通过 steer 触发。 +不在文件监控或实时推送上过度承诺。 + +**2. Observation 通知机制** + +不依赖 inotify/fswatch(macOS 可靠性存疑,SMB 挂载不工作)。 +改为:**Agent 写入 observation 后主动 POST `/api/observations` 通知 daemon**,daemon 再通知庞统。 +更简单、更可靠、不依赖文件系统事件。 + +**3. daemon vs 庞统的角色透明化** + +不要把 daemon 的确定性决策包装成"庞统定方向"。 + +| 决策类型 | 执行者 | 性质 | +|---------|-------|------| +| Agent 选择算法 | daemon (selector.py) | 确定性 | +| 状态流转规则 | daemon (orchestrator.py) | 确定性 | +| 超时检测 | daemon (watchdog.py) | 确定性 | +| 产出验证 | daemon (validator.py) | 确定性 + 可选 AI | +| Phase 2 规划 | **庞统 AI** | AI 决策 | +| Phase 3 异常裁决 | **庞统 AI** | AI 决策 | +| 计划调整审批 | **庞统 AI** | AI 决策 | +| 用户意图一致性检查 | **庞统 AI** | AI 决策 | + +**4. 文件系统可靠性细节** + +- `status.json` 更新必须使用 `atomic_write`(tmp → rename),复用 Section 2.1.4 的实现 +- observations 文件命名改为 `{agent}-{timestamp}-{uuid短}.md`,避免命名冲突 +- artifacts 目录在本地文件系统(Mac mini SSD),不写在 SMB 挂载上 + +**5. Observation 写入标准与频率限制** + +task-bootstrap Skill 中必须明确写入标准: +``` +observation 写入条件(必须满足至少一条): +- 发现了可能影响后续步骤的问题 +- 有能提升最终产出质量的建议 +- 遇到了专业外需要上报的问题 +禁止写入:进度报告、正常状态确认、无关细节 +``` + +频率限制:每个步骤每个 Agent 最多 3 个 observation,超过由 daemon 丢弃并记录 warning。 + +**6. 庞统上下文管理** + +每次被触发时,不把历史推理过程带入上下文,只带入当前状态快照。 +类似"每次被唤醒都是全新视角"——避免庞统 session 的上下文膨胀。 + +**7. B1 贯穿性:用户意图一致性检查** + +当用户在执行中途改方向时(steer),庞统不只重新规划,还应先评估影响并帮用户决策: +- "改成周线意味着数据量减少到 1/5,统计显著性可能不够。你确定吗?" +- "增加止损条件会导致策略复杂度上升,回测时间可能翻倍。要继续吗?" + +这是 PRD B1(AI 帮用户想清楚要什么)的全程贯穿,不限于 Phase 1。 +在庞统的 prompt 中加入"用户意图一致性检查"规则。 + +#### 3.12.8 AI native Skill 体系(v2.6 修订) + +> **v1.0 的 Skill 问题**:把命令操作手册写到 Skill 里——"数据获取步骤:1. 连接数据库 2. 执行 SQL 3. 保存 CSV"。 +> 这不是 AI native,这是给 AI 写操作流程。AI 自己会决定怎么操作。 +> +> **v2.0 AI native Skill**:不是告诉 AI 怎么做,而是设定边界——做什么是好的、做什么是错的、遇到什么情况该怎么想。 +> AI 自己决定具体步骤。 + +**三层 Skill 模型**: + +| 层级 | 名称 | 内容 | 类比 | +|------|------|------|------| +| L1 | **Principles**(原则) | 做事的底线和方向 | 宪法 | +| L2 | **Patterns**(模式) | 遇到什么情况应该怎么想 | 判例法 | +| L3 | **Anti-patterns**(反模式) | 绝对不能做的事 | 刑法 | + +**具体 Skill 内容示例**(`data-acquisition/SKILL.md`): + +```markdown +# 数据获取 Skill(赵云) + +## L1 原则 +- 数据获取后必须先检查质量再回报 +- 任何数据源声称已清洗都不能信任 +- 产出物必须包含数据质量报告 + +## L2 模式 +- 如果发现缺失值 < 5%:前值填充,记录在 observation +- 如果发现缺失值 > 5%:标记异常区间,在 observation 中建议后续步骤如何处理 +- 如果发现数据时间范围与任务要求不符:立即回报,不自行裁剪 +- 如果数据源有多个版本:选择最完整的,在 observation 中说明选择理由 + +## L3 反模式 +- 绝不能假设数据是干净的 +- 绝不能默默修复数据问题而不在 observation 中记录 +- 绝不能返回未经检查的数据 +- 绝不能跳过数据质量报告直接标记步骤完成 +``` + +**与 v1.0 的核心区别**: +- v1.0:"连接 jqdatasdk,执行 get_price(),保存到 CSV"(操作手册) +- v2.0:"获取后必须检查质量,缺失值要记录"(行为准则) +- AI 自己决定用什么工具、什么 API、什么格式 + +**v2.0 预设 Skill 目录**: + +``` +skills/ +├── task-bootstrap/SKILL.md # Agent 启动协议(感知→执行→观察) +├── task-report/SKILL.md # Agent 完成报告协议 +├── quality-gate/SKILL.md # 产出物自检协议 +├── orchestration-strategy/SKILL.md # 庞统调度策略(五原则 + 防降级) +├── data-acquisition/SKILL.md # 数据获取行为准则(赵云) +├── strategy-coding/SKILL.md # 策略编码行为准则(张飞) +├── risk-review/SKILL.md # 风控审核行为准则(关羽) +├── infra-management/SKILL.md # 基础设施管理行为准则(姜维) +└── experience-distill/SKILL.md # 经验蒸馏行为准则 +``` + +**Skill 格式**:Markdown(LLM 理解最好、token 最省、自由度最高)。 +每个 Skill 包含完整的 L1 + L2 + L3 三层。 + +**Skill 加载方式**: +- 每个 Agent 的 SOUL.md 中引用自己的专属 Skill +- task-bootstrap 和 quality-gate 通过任务消息模板注入(所有 Agent 通用) +- orchestration-strategy 通过庞统的 SOUL.md 注入 +- 经验蒸馏完成后,相关经验自动注入到后续任务的上下文中(半自动 Skill 更新) + +#### 3.12.9 庞统调度策略 Skill(v2.5 新增) + +> 综合 Microsoft Azure Checker Pattern、Google RouterAgent、oh-my-claudecode Team pipeline、AWS Strands ReWOO。 + +**调度五原则**: + +| # | 原则 | 说明 | 业界来源 | +|---|------|------|---------| +| 1 | **Capability Matching** | 根据 Agent 能力画像匹配任务,不是轮询分配 | Google RouterAgent | +| 2 | **Dependency-Aware Scheduling** | 先调度无依赖步骤(可并行),有依赖的等前置完成 | AWS Strands ReWOO | +| 3 | **Iterative Refinement** | 执行→验证→如有问题→第二轮修正→再验证(设上限) | Microsoft Azure Checker Pattern | +| 4 | **Quality-Gated Advancement** | 当前步骤通过门控后才调度下一步 | oh-my-claudecode verify→fix loop | +| 5 | **Escalation Over Failure** | 失败不放弃:重试→换 Agent→升级到用户 | Hermes per-task retry | + +**Iterative Refinement 具体流程**: + +``` +庞统调度张飞 → 张飞产出 → 庞统/司马懿审核 + → 通过:调度下一步 + → 未通过:调度张飞第二轮(带上审核意见 + 相关 observation) + → 张飞 read observations/ → 自主调整 → 再提交 + → 通过:调度下一步 + → 未通过 + 达到迭代上限(3次):升级到用户 +``` + +**并行步骤的处理**: +并行步骤间不需要实时感知。庞统在两个并行步骤都完成后, +检查 observation 是否有关联。如果有关联,调度受影响的 Agent 做第二轮。 + +#### 3.12.10 Observation 设计方案(v2.5 完善) + +**Observation 生命周期**: + +``` +1. Agent 执行中发现问题 + → 判断是否符合写入标准(影响后续步骤/提升质量/专业外问题) + → 不符合标准就不写(禁止进度报告、正常确认、无关细节) + +2. 写入 observation + → write artifacts/task-{id}/observations/{agent}-{timestamp}-{uuid短}.md + → 唯一强制:第一行必须是 [SEVERITY: blocking/warning/info] + → 内容格式完全自由(不固定 JSON 或 Markdown) + → POST /api/observations 通知 daemon + +3. daemon 收到通知 + → blocking:立即通知庞统 + → warning/info:步骤完成时一起通知 + +4. 庞统处理 + → 评估影响 → 决策(忽略/调整计划/通知后续 Agent/调度第二轮) + +5. 后续 Agent 感知 + → 被调度时 read observations/ → 自主决定是否调整 +``` + +**为什么内容格式不固定**: +固定 JSON 或 Markdown 格式会束缚 AI 的表达能力。 +Agent 根据发现的情况自由选择最合适的表达方式。 +唯一强制 severity 标记是因为 daemon 需要它来决策通知级别。 + +**频率限制**:每个步骤每个 Agent 最多 3 个 observation。 + +**observation 的 pav 执行记录**(v2.5 新增): +Agent 执行过程中的关键决策点用 severity=audit 记录: +- 为什么选这个方法 +- 做了什么取舍 +- 发现了什么 +audit 级别不通知庞统,只记录在文件系统供事后审查。 + +#### 3.12.11 Agent 调度方式(v2.6 终版确认) + +**实验结论**(2026-05-14 两轮 spawn 实验): +- `sessions_spawn` 配置 `allowAgents: ["*"]` 后可以指定其他 agentId +- 跨 Agent spawn 读写文件、结论回传、cleanup:delete 全部正常 +- **spawn 是 v2.0 调度将军的首选方式** + +**Gateway 配置**(已应用): +```json +// ~/.openclaw/openclaw.json → agents.list[id=pangtong-fujunshi] +{ "subagents": { "allowAgents": ["*"] } } +``` + +**v2.0 调度方式(按优先级排序)**: + +| 优先级 | 方式 | 用途 | 特点 | +|--------|------|------|------| +| **首选** | `sessions_spawn(agentId="xxx", cleanup="delete")` | 调度将军执行步骤 | 不走 Gateway、并行、自动清理、结论回传 | +| Fallback | `openclaw agent --agent xxx --local --json` | spawn 不可用时 | `--local` 本地运行,自动 fallback | +| 内部 | `sessions_spawn()` (无 agentId) | 庞统内部复杂分析 | 子 session 继承上下文 | + +**spawn 的关键优势**(vs `openclaw agent` CLI): +- 不依赖 Gateway(Gateway 挂了也能调度) +- 庞统可以通过 `subagents list` 实时监控子 session 状态 +- 完成后自动回传结果,不需要轮询 +- 支持嵌套调度(`maxSpawnDepth: 2`) +- 支持并行(`maxConcurrent: 8`) + +#### 3.12.12 庞统上下文管理(v2.6 新增) + +> **核心问题**:庞统在一个 session 里处理多个任务时,上下文会膨胀。如果“每次唤醒全新视角”, +> 之前的推理过程丢失,决策质量下降。如果保留全部上下文,token 爆炸。 +> **必须找到平衡**。 + +**调研来源**: +- **get-shit-done**:Wave Execution——每个 plan 用独立新鲜上下文,避免 context rot +- **Claude Code**:session linked by parent_session_id + auxiliary-LLM compression +- **学术:Episodic Memory**(arXiv:2502.06975):episodic memory 是长期 Agent 的缺失拼图 +- **学术:Memory as Action**(arXiv:2509.25250):自主上下文策展 + +**方案:三层上下文架构** + +``` +┌───────────────────────────────────────────┐ +│ L1: 永久记忆(跨任务保留) │ +│ MEMORY.md + 经验库 + Skill 体系 │ +│ 每次启动自动注入,不受上下文窗口限制 │ +└───────────────────────────────────────────┘ + +┌───────────────────────────────────────────┐ +│ L2: 任务工作记忆(单任务内保留) │ +│ decisions.jsonl + moments.jsonl + status.json │ +│ 存文件系统,庞统被唤醒时 read 获取 │ +│ → 这就是“上下文恢复协议”的实质 │ +└───────────────────────────────────────────┘ + +┌───────────────────────────────────────────┐ +│ L3: 推理上下文(每次唤醒重建) │ +│ 当前推理过程,对话历史中的近期部分 │ +│ 通过 OpenClaw compaction 自动管理 │ +└───────────────────────────────────────────┘ +``` + +**具体机制**: + +1. **庞统被唤醒时**: + - read `status.json`(当前全局状态) + - read `decisions.jsonl`(之前做过的决策及理由) + - read `moments.jsonl`(最近 10 条事件) + - 这三步重建 L2 工作记忆 + +2. **庞统做决策时**: + - 关键决策写入 `decisions.jsonl`(不只在对话历史里) + - 格式:`{decision, reason, alternatives_rejected, timestamp}` + - 下次被唤醒时能看到自己之前为什么做这个决策 + +3. **防止“忘了”的兜底**: + - 每个 observation 写入时,daemon 同时记录到 `moments.jsonl` + - 庞统被唤醒时看到的 moments 包含所有 Agent 的 observation 摘要 + - 即使庞统“忘了”对话中的推理,通过文件系统能找回来 + +4. **并行步骤完成后的识别**: + - 两个并行步骤都完成后,daemon 生成一个“并行步骤关联分析” + - 检查两个步骤的 observation 是否有关联 + - 如果有关联,写入 moments 并通知庞统 + - 庞统不需要“记住”并行步骤的关系——文件系统替他记着 + +**与之前“全新视角”方案的区别**: +- 之前:每次唤醒清空一切,只看 status.json +- 现在:每次唤醒有 L1(永久)+ L2(文件系统工作记忆)+ L3(对话上下文) +- L2 是关键创新——决策理由和 observation 摘要存在文件里,不依赖对话历史 + +#### 3.12.13 防降级机制(v2.6 新增) + +> **核心问题**:调度策略本身如何防止在执行过程中逐渐妥协? +> 庞统如何保证不为了“完成任务”而降级目标? + +**四个防降级机制**: + +**机制 1:反合理化表(Anti-Rationalization Table)** + +来源:Agent Skills 生命周期实践 + +写入 orchestration-strategy Skill 的 L3(反模式)层: + +``` +庞统自检清单——每个调度决策前必须过: +❌ “这个步骤简化一下” → 问:PRD 目标是否被覆盖? +❌ “时间不够跳过验证” → 没有验证的产出不算完成 +❌ “这个Agent做不到就算了” → 换 Agent 或拆步骤 +❌ “先用简单方案后续优化” → v2.0 就是完整实现 +❌ “用户应该不在意” → 你的判断 ≠ 用户需求 +``` + +**机制 2:目标锚定(Goal Anchoring)** + +每个任务的原始 goal 和 PRD 需求写入 `context.json`,不可修改。 +庞统每次做调度决策时必须对照 goal: +- 决策是否更接近 goal? +- 如果偏离 goal,必须升级到用户,不能自行降级 + +**机制 3:范围缩减检测(Scope Reduction Detection)** + +来源:get-shit-done 实践 + +planner.py 在 Phase 2 生成计划时,每个步骤必须标注覆盖哪些需求。 +daemon 在执行完所有步骤后,反向检查:每个需求是否被至少一个步骤覆盖。 +如果有遗漏,不允许进入 Phase 4,必须 replan。 + +**机制 4:验证前不完成(Verification-Before-Completion)** + +来源:superpowers 实践 + +Agent 声称完成时,validator.py 检查: +1. 产出物文件存在且非空 +2. 产出物与 end_state 描述匹配(AI 语义检查) +3. 没有未处理的 blocking observation +任何一项不通过,步骤状态不变为 completed。 + +--- + +## 4. 已决策(全部) + +| # | 决策 | 结论 | 理由 | +|---|------|------|------| +| 1 | 黑板载体 | **SQLite + 文件系统** | SQLite 存状态(查询快、事务安全),文件系统存产出物(git 可追踪) | +| 2 | 庞统运行方式 | **主 session + daemon API** | 庞统在正常 session 里与用户对话,通过 HTTP API 调度 daemon | +| 3 | Agent 调度 | **`openclaw agent` CLI(简单)+ sessions_spawn(复杂)** | CLI 是 Gateway 原生能力,有超时和 fallback;复杂步骤用 sub-agent 隔离 | +| 4 | 事件触发 | **daemon 内部事件循环** | Agent 回报时触发下一步,不用 cron | +| 5 | 配置化 | **YAML/JSON 配置文件** | 状态/流转/事件/模板全部配置化,代码零硬编码 | +| 6 | Agent 团队 | 复用三国角色 | 角色映射清晰 | +| 7 | v1.0 共存 | 独立 session,并行运行 | v1.0 和 v2.0 互不干扰 | +| 8 | 前端 | 先纯对话(Control Center) | 后续加可视化 | +| 9 | 状态机 | 继承 v1.0 + AI native 扩展 | steer/takeover/intervene/replan 等 | +| 10 | 执行历史 | sub-agent transcript 归档到任务目录 | 可追溯、可学习、可复盘 | +| 11 | Daemon 通知庞统 | `openclaw agent` CLI | 步骤完成/异常/需要裁决时 | +| 12 | 经验沉淀 | 任务完成后自动触发 AI 蒸馏 | + 规划/执行/异常时自动检索 | +| 13 | 实现方式 | **一次性完整实现** | 不做最小集迭代,避免做着做着偏离 | +| 14 | 治理原则 | **质量优先,成本和工期为质量让步,但有硬性上限** | 见 4.1 治理框架 | +| 15 | Agent 调度机制 | **`openclaw agent` CLI** | 司马懿评审:废弃自建 WS Client,改用 CLI | +| 16 | 数据存储 | **SQLite 为唯一权威,文件系统为索引缓存** | 司马懿评审:消除双写一致性问题 | +| 17 | 经验蒸馏 | **先两层(surface+heuristic),验证后扩展** | 司马懿评审:五层过重,YAGNI | +| 18 | 实施策略 | **分批集成(设计一次做完,实现分批验证)** | 司马懿评审:骨架→智能→高级 | +| 19 | Agent 协作模式 | **中央协调 + Agent 自主**(非纯中央调度) | v2.4 修正:庞统定方向约束,Agent 自主执行+主动观察 | +| 20 | 信息共享方式 | **文件系统直读**(非 API 查询) | v2.4 修正:status.json + observations/ 目录,Agent 直接 read | +| 21 | AI 参与方式 | **事件驱动持续参与**(非完成时通知) | v2.4 修正:步骤完成/observation/心跳/质量评估点四类触发 | +| 22 | Agent 主动性 | **observations 目录 + 自主行为协议** | v2.4 新增:Agent 可主动写观察、建议、异常,其他 Agent 可感知 | +| 23 | Skill 体系 | **三层模型(Principles + Patterns + Anti-patterns)** | v2.5 新增:综合 oh-my-claudecode/Hermes/Nuwa/Agent Skills | +| 24 | 调度策略 | **五原则(Capability/Dependency/Iterative/QualityGate/Escalation)** | v2.5 新增:综合 Azure/Google/OMC/AWS | +| 25 | Observation 格式 | **自由格式 + 强制 severity 标记** | v2.5 确认:不固定 JSON/MD,唯一强制 [SEVERITY: xxx] | +| 26 | Agent 调度方式 | **双模式(openclaw agent CLI + sessions_spawn)** | v2.5 确认:CLI 调度将军,spawn 庞统内部分析 | +| 27 | 并行感知方案 | **多轮迭代(非实时感知)** | v2.5 确认:庞统判断是否需第二轮,不依赖运行时感知 | +| 28 | 信息隔离预留 | **agent-registry.json 中 visibility 字段** | v2.6 预留:v2.0 全部 full,v2.1+ 按需开启 | +| 29 | Agent 调度首选 | **sessions_spawn(allowAgents配置已生效)** | v2.6 确认:实验验证跨 Agent spawn 可用 | +| 30 | 庞统上下文管理 | **三层架构(永久记忆+文件工作记忆+对话上下文)** | v2.6 新增:防止“全新视角”丢信息 | +| 31 | 防降级机制 | **四个机制(反合理化+目标锚定+范围缩减+验证前不完成)** | v2.6 新增:确保调度不降级 | +| 32 | Skill 本质 | **行为准则(L1原则+L2模式+L3反模式),非操作手册** | v2.6 明确:告诉 AI 做什么是对的,不告诉怎么做 | +| 33 | 评审模式 | **单任务单评审+汇总后再评审,交叉评审按需** | v2.6 确认 | + +### 4.1 质量/成本/工期治理框架 + +> 核心原则:**质量是第一优先级,成本和工期可以为质量妥协,但不能无限妥协。** +> +> 参考:Wanman TokenBudget(per-agent ceiling + 全局 ceiling)、Network-AI FederatedBudget(双层预算)、Network-AI QualityGate(两层门控:规则+AI)。 + +#### 三层预算体系 + +每个任务创建时分配三重预算,任何一重耗尽都触发干预: + +```yaml +# config/settings.yaml 中的默认预算 +budget_defaults: + token: + per_task_ceiling: 500000 # 单任务总 token 上限 + per_step_ceiling: 100000 # 单步骤 token 上限 + per_agent_ceiling: 200000 # 单 Agent token 上限 + time: + per_task_ceiling: 3600 # 单任务总时间上限(秒) + per_step_ceiling: 600 # 单步骤时间上限(秒) + depth: + max_plan_adjustments: 5 # 计划调整次数上限 + max_retries_per_step: 3 # 单步骤重试次数上限 + max_challenge_rounds: 3 # 挑战循环次数上限 +``` + +**预算耗尽时的行为**: + +| 预算类型 | 耗尽行为 | 谁干预 | +|---------|---------|--------| +| step token 耗尽 | 步骤标记 failed,触发异常处理 | Daemon 自动 | +| step 时间耗尽 | 步骤标记 failed,检查 Agent 存活 | Daemon 自动 | +| task token 耗尽 | 任务暂停,通知庞统 + 用户 | Daemon → 庞统 → 用户 | +| task 时间耗尽 | 任务暂停,通知庞统 + 用户 | Daemon → 庞统 → 用户 | +| 计划调整次数耗尽 | 停止调整,用当前最佳计划继续 | Daemon 自动 | +| 重试次数耗尽 | 步骤标记 failed,升级到庞统 | Daemon → 庞统 | + +#### 质量门控(Quality Gate) + +参考 Network-AI 两层门控: + +``` +Agent 提交产出物 + │ + ▼ +Layer 1: 规则验证(确定性,零成本,毫秒级) + ├── 文件存在性检查 + ├── 文件非空检查 + ├── Schema 校验(JSON/CSV 格式正确) + ├── Placeholder 检测(TODO/FIXME/...) + └── 基础完整性(字段非空、数值范围合理) + │ + ├─ 通过 → 进入 Layer 2 + └─ 失败 → reject(退回 Agent 修复) + │ + ▼ +Layer 2: AI 审查(有成本,秒级,仅当 confidence < 阈值时触发) + ├── 产出物是否与 end_state 描述匹配 + ├── 代码质量(变量命名、异常处理、逻辑正确性) + └── 幻觉检测(声称的结论是否有数据支撑) + │ + ├─ approve → 完成 + ├─ quarantine → 隔离待人工审查(通知庞统 + 用户) + └─ reject → 退回 Agent 修复 +``` + +**成本控制**:Layer 2 不是每次都跑。默认只在以下情况触发: +- Agent 回报 confidence < 0.7 +- 步骤是关键步骤(用户明确指定要审查的) +- 连续多个步骤 Layer 1 都一次通过(抽样审查,防止 Agent 学会绕规则) + +#### 质量提升的分级投入 + +不是所有任务都需要极致质量。根据任务重要性分级投入: + +```yaml +# config/settings.yaml +quality_levels: + critical: # 资金安全、实盘交易 + layer2_threshold: 0.8 + max_retries: 5 + always_ai_review: true + human_approval_required: true + token_budget_multiplier: 2.0 + + standard: # 策略回测、数据分析 + layer2_threshold: 0.7 + max_retries: 3 + always_ai_review: false + human_approval_required: false + token_budget_multiplier: 1.0 + + exploratory: # 探索性研究、快速验证 + layer2_threshold: 0.5 + max_retries: 1 + always_ai_review: false + human_approval_required: false + token_budget_multiplier: 0.5 +``` + +任务创建时,AI 根据内容自动判定 quality_level,用户也可以手动指定。 + +#### 成本回收机制 + +高质量的产出如果被后续任务复用,相当于摊薄了成本: + +- **经验沉淀**:高质量任务的产出自动蒸馏为经验,下次同类任务减少 token 消耗 +- **Skill 转化**:高频经验固化为 Skill,直接注入减少检索成本 +- **模板复用**:高质量的计划结构可以保存为模板,下次直接使用 + +#### 工期控制:何时干预 + +| 场景 | 判断标准 | 干预方式 | +|------|---------|----------| +| 明显死循环 | 同一步骤 retry > max_retries | 强制终止 + 升级 | +| 计划反复调整 | plan_adjustments > max | 锁定当前计划继续 | +| Agent 超时无响应 | step 执行时间 > per_step_ceiling | 检查存活 → 重试或换 Agent | +| 质量长期不达标 | 连续 3 个步骤 AI 审查 reject | 暂停任务 + 通知用户 | +| 成本超预期 | task token > 80% ceiling | 通知庞统评估是否值得继续 | +| 接近 deadline | task 时间 > 80% ceiling | AI 判断:是否降级质量等级以保工期 | + +#### AI 决策结构化 + +每个 AI 决策必须记录完整的推理过程(司马懿评审意见)。 + +```sql +-- decisions 表结构化扩展 +CREATE TABLE decisions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL, + step_id TEXT, + decision_type TEXT NOT NULL, -- agent_selected / plan_adjusted / anomaly_classified / quality_review + + -- 结构化推理(司马懿要求:输入、选项、选择、原因、置信度) + input_summary TEXT NOT NULL, -- AI 看到了什么 + options_considered TEXT NOT NULL, -- 考虑了哪些选项(JSON array) + selected TEXT NOT NULL, -- 选了什么 + reason TEXT NOT NULL, -- 为什么选这个 + confidence REAL NOT NULL, -- 置信度 0.0-1.0 + + model TEXT, + token_cost INTEGER, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); +``` + +#### 经验生命周期 + +经验不是写进去就永远有效: + +```sql +-- experiences 表扩展 +ALTER TABLE experiences ADD COLUMN lifecycle TEXT DEFAULT 'draft'; -- draft / verified / superseded / expired +ALTER TABLE experiences ADD COLUMN verified_at TEXT; +ALTER TABLE experiences ADD COLUMN verified_by TEXT; -- ai / human +ALTER TABLE experiences ADD COLUMN expires_at TEXT; -- TTL,过期自动降权 +ALTER TABLE experiences ADD COLUMN superseded_by INTEGER REFERENCES experiences(id); +``` + +| 生命周期 | 说明 | 检索权重 | +|---------|------|---------| +| draft | AI 自动蒸馏,未验证 | 0.5x | +| verified | 经过实际任务验证(引用 >= 2 次) | 1.0x | +| superseded | 被更好的经验替代 | 0.1x | +| expired | TTL 过期,自动降权 | 0.0x(不检索) | + +#### 异常处理策略表 + +异常分类用确定性逻辑,处理策略用预设决策表(司马懿评审意见): + +```yaml +# config/exceptions.yaml +exceptions: + - type: agent_timeout + detection: "step execution time > per_step_ceiling" + strategy: + - action: check_agent_alive + - action: retry + max: 3 + backoff: exponential + - action: reassign_agent # 换一个 Agent + - action: escalate_to_human + + - type: validation_failed + detection: "quality gate Layer 1 failed" + strategy: + - action: reject_to_agent + max: 3 + - action: escalate_to_human + + - type: ai_judgment_conflict + detection: "two AI decisions contradict" + strategy: + - action: escalate_to_human # AI 冲突必须人工裁决 + + - type: resource_deadlock + detection: "circular dependency detected" + strategy: + - action: break_cycle # 随机释放一个等待 + - action: reassign_agent + - action: escalate_to_human + + - type: budget_exceeded + detection: "any budget ceiling reached" + strategy: + - action: pause_task + - action: notify_pangtong + - action: await_human_decision + + - type: unknown + detection: "unclassified exception" + strategy: + - action: ai_classify # 只有未知异常才让 AI 分类 + - action: apply_strategy # 应用 AI 选择的策略 + - action: escalate_to_human # 兜底 +``` + +--- + +## 5. 实现清单 + +> 用户决策:一次性完整实现,不做最小集迭代。 + +``` +核心基础设施: + [x] Daemon HTTP API (FastAPI + uvicorn + SQLite WAL) + [x] config/ 配置化体系(states.yaml / step-states.yaml / events.yaml / agent-registry.json) + [x] SQLite 表结构(tasks / steps / moments / agent_status / decisions / experiences) + [x] artifacts/ 文件系统结构 + [x] `openclaw agent` CLI 封装(daemon → Agent session 通信) + [x] daemon 启动时补偿归档(检查未归档的 transcript) + [x] observations/ 目录(Agent 主动观察与建议) + [x] status.json 实时全局摘要(daemon 每次事件后更新) + [x] Agent 自主行为协议(task-bootstrap Skill 中注入感知+执行+观察三段式) + [x] 庞统持续意识机制(事件驱动通知 + 定期心跳) + +四相循环: + [x] Phase 1 需求探索(庞统苏格拉底对话) + [x] Phase 2 动态规划(AI 生成 plan + 用户审批) + [x] Phase 3 自主执行(daemon 事件循环 + Agent 调度) + [x] Phase 4 主动汇报(AI 生成报告 + 用户验收) + +Agent 管理: + [x] agent-registry.json 能力画像 + [x] Agent 选择算法(基于能力匹配 + 历史表现) + [x] 主 session 调度(简单步骤) + [x] sub-agent 调度(复杂步骤 + cleanup=delete) + [x] 执行历史归档(transcript → artifacts) + +智能特性: + [x] 幻觉门控(产出验证) + [x] 挑战循环(reviewing → challenge pass/iterate/fail) + [x] 动态计划调整(异常时 replan) + [x] AI 决策记录(可追溯) + [x] 经验沉淀引擎(任务完成后自动蒸馏) + [x] 经验检索(规划/执行/异常时自动注入) + +人工介入: + [x] steer(用户改方向) + [x] takeover(用户接管步骤) + [x] intervene(用户主动干预) + [x] escalated(AI 主动升级) + [x] waiting_human(Agent 请求确认) + +监控 & 运维: + [x] 健康检查(daemon 内部定时 + API) + [x] Token 预算管理 + [x] Agent 心跳 + [x] 与 v1.0 并行运行验证 +``` + +--- + +## 附录:术语表 + +| 术语 | 含义 | +|------|------| +| Blackboard | 共享意识空间,所有 Agent 的唯一信息共享中枢 | +| Control Unit | AI 指挥官(庞统),负责动态规划、Agent 选择、异常处理 | +| Moment | 原子事件,任务执行过程中的最小信息单元 | +| Fidelity | 信息保真度,控制不同 Agent 看到多少信息 | +| propose→validate→commit | 三阶段原子写入,防止并发竞态 | +| Boids | 群体智能规则,让 Agent 自行涌现协作行为 | +| Auftragstaktik | 任务式指挥,只给目标不给步骤 | +| 幻觉门控 | 验证 Agent 产出是否真实存在 | +| Observations | Agent 主动观察与建议,写入共享目录供其他 Agent 和庞统感知 | +| Status.json | 全局状态摘要,daemon 实时维护,Agent 执行前必读 | +| 自主行为协议 | Agent 三段式行为:感知(read 全局) → 执行(自主决定) → 观察(write observations) | +| Ralph Loop | 持久目标跨 turn 保持机制 | + +--- + +## 附录 B:调研参考来源 + +### 共享意识空间物理结构 +- **OpenAI Codex Cookbook** — PM Agent 写 REQUIREMENTS.md / AGENT_TASKS.md,产出物 gating check +- **Wanman Agent Matrix** — `artifact.put` CLI,Agent 通过 bash 与系统交互,per-agent workspace 隔离 +- **Wiki 知识管理体系** — 四层金字塔 L0-L3 + 7 种知识产物类型 + DISCOVER→DISTILL→APPLY→IMPROVE 闭环 + +### Daemon API 设计 +- **Edict (三省六部)** — Transactional Outbox Pattern + Redis Streams 消费者组 + 死信队列 +- **Wanman** — 30+ JSON-RPC 方法 + 标准错误码体系 + Agent 零SDK CLI 模式 +- **Gstack** — 长存活守护进程模型 + 状态文件持久化 + 版本自动重启 +- **Multica** — 任务生命周期状态机 + 分层事件总线(进程内同步 + WebSocket 实时推送) + +### 经验沉淀引擎 +- **Nuwa Skill (女娲)** — 五层蒸馏框架(表象→心智模型→决策启发式→反模式→诚实边界) +- **Corpus2Skill** (arXiv 2604.14572) — 离线蒸馏为可导航的层级 Skill 目录 +- **A-MEM** (arXiv 2502.12110) — Zettelkasten 式记忆,新记忆触发已有记忆的更新 +- **MemAgents** (ICLR 2026 Workshop) — 单次学习 + 上下文感知检索 + 整合为可泛化知识 +- **Hermes** — skill_manage 使用中发现过时立即修复 + +### 中央协调 + Agent 自主(v2.4 新增) +- **Edict 朝堂议政** — TurnScheduler + phaseMask(Agent 声明在哪些阶段主动发言),MessageBus 消息总线 +- **Ouroboros (joi-lab)** — 后台意识循环 consciousness.py,任务间主动思考,宪法驱动 +- **Network-AI** — Blackboard + CRDT,Agent 读黑板、看到变化、自己决定行动 +- **Open Multi-Agent** — sharedMemory + delegate_to_agent,Agent 可主动委派给其他 Agent +- **oh-my-claudecode** — Team staged pipeline(team-plan→team-prd→team-exec→team-verify→team-fix),hub-spoke 协调 +- **Hermes Agent** — Durable SQLite-backed kanban board,Agent 通过 kanban_* 工具集自主 claim task,dispatcher 驱动 +- **Claude Code** — Grove 共享项目工作区、Swarm/Team hub-spoke 协调、sidechain transcript 隔离 +- **Claude Code Sub-Agent Collective** — task-orchestrator 路由枢纽 + 30+ 专业化 Agent + hook TDD 强制 +- **agent-kanban (saltbo)** — Agent-first task board,Mission control for AI workforce +- **AgentsMesh** — PTY sandbox + git worktree 隔离 + ticket-pod binding + 内置 Kanban +- **Operator (untra)** — kanban-shaped git-versioned 软件开发,Agent 自主认领工单 +- **MCP + A2A 协议** — MCP 提供上下文共享,A2A 提供 Agent 间直接通信(2026 H2 成熟) +- **Multi-Agent Blackboard System (arXiv:2510.01285)** — 消除中央协调器对 Agent 先验知识的需求 +- **awesome-agent-orchestrators** — 2026 年活跃的 Agent 编排器汇总(nullclaw, agentsmesh 等) diff --git a/docs/design/deployment-scripts.md b/docs/design/deployment-scripts.md new file mode 100644 index 0000000..8c439e0 --- /dev/null +++ b/docs/design/deployment-scripts.md @@ -0,0 +1,292 @@ +# moziplus v2 运维脚本设计 + +**版本**: 3.0.0 +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-22 +**基于**: deployment-v2.6.md + deployment-v2.6-guide.md(更新版) + +--- + +## 1. 概述 + +moziplus v2 提供一套运维脚本,覆盖部署、状态查看、数据管理、备份和卸载的全生命周期。 + +### 脚本清单 + +| 脚本 | 用途 | 风险等级 | +|------|------|---------| +| `deploy.sh` | 部署/升级 | 低(不覆盖 data/config) | +| `status.sh` | 运行状态查看 | 无(只读) | +| `build-frontend.sh` | 构建前端 | 无 | +| `backup.sh` | 备份数据到 NAS | 无 | +| `reset-data.sh` | 清空运行数据 | 高(删数据) | +| `uninstall.sh` | 完全卸载 | 高(删目录) | + +### 目录约定 + +``` +开发目录: ~/.openclaw/sanguo_projects/sanguo_moziplus_v2/ ← Git 仓库 +安装目录: ~/.sanguo_projects/sanguo_moziplus_v2/ ← PM2 运行 +脚本位置: 开发目录/scripts/ +``` + +--- + +## 2. 环境要求 + +| 依赖 | 版本 | 用途 | +|------|------|------| +| Python | ≥ 3.9 | 后端运行 | +| Node.js | ≥ 20 | 前端构建 | +| PM2 | ≥ 5.0 | 进程管理 | +| rsync | 任意 | 代码同步 | +| curl | 任意 | 健康检查 | +| sqlite3 | 任意 | 数据查询(status -v) | +| NAS SMB | — | 备份目标(backup.sh) | + +--- + +## 3. 场景手册 + +### 场景 1:首次部署 + +**触发**:新机器上第一次安装 moziplus v2 + +```bash +# 从开发目录部署 +cd ~/.openclaw/sanguo_projects/sanguo_moziplus_v2 +bash scripts/deploy.sh + +# 自定义安装目录 +bash scripts/deploy.sh --target=/path/to/install + +# 跳过前端构建(已有 dist/) +bash scripts/deploy.sh --skip-build +``` + +**deploy.sh 执行流程**: +1. 前置检查(Python/PM2/源码目录) +2. 前端构建(npm install → npm run build) +3. 创建安装目录 +4. rsync 同步代码(排除 data/logs/inbox/config/docs/tests) +5. 确保目录结构(data/logs/inbox/config) +6. 首次创建默认配置(不覆盖已有) +7. PM2 start(首次) +8. 前端 + 后端健康检查 +9. 输出结果 + +**排除项**(不部署到生产): +- `docs/` — 设计文档只存开发目录 +- `tests/` — 测试代码不需要在生产环境 +- `scripts/` — 脚本从开发目录执行 +- `config/` — 不覆盖用户配置 +- `data/` `logs/` `inbox/` — 运行时数据 +- `frontend/src/` `node_modules/` — 只需要 dist/ + +### 场景 2:日常升级(代码更新后) + +**触发**:开发目录有代码变更,需要更新生产环境 + +```bash +# 标准升级(自动构建前端 + 重启) +cd ~/.openclaw/sanguo_projects/sanguo_moziplus_v2 +bash scripts/deploy.sh + +# 仅后端改动,跳过前端构建 +bash scripts/deploy.sh --skip-build +``` + +**deploy.sh 自动检测**: +- 安装目录已存在 → PM2 restart(而非 start) +- 不覆盖 `config/` 和 `data/` +- 健康检查确认服务正常 + +### 场景 3:查看运行状态 + +```bash +# 简要状态 +bash scripts/status.sh + +# 详细状态(项目列表、任务数、磁盘占用) +bash scripts/status.sh -v +``` + +**输出示例**: +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + moziplus v2 — Status +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Version: v3.0.0 + Install: ~/.sanguo_projects/sanguo_moziplus_v2 ✅ + PM2: online ✅ + PID: 19591 + Restarts: 18 + Memory: 49.6MB + Health: running ✅ + Ticks: 106 + Projects: 26 + Data size: 23M +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +### 场景 4:前端独立构建 + +**触发**:只改了前端代码,不需要完整部署 + +```bash +bash scripts/build-frontend.sh +# 产物在 src/frontend/dist/ +# 然后用 deploy.sh --skip-build 同步 +``` + +### 场景 5:数据备份 + +```bash +# 在线备份(不停服务,SQLite 可能有不一致快照,日常可接受) +bash scripts/backup.sh + +# 一致性备份(停服务) +bash scripts/backup.sh --stop + +# 指定 NAS 路径 +bash scripts/backup.sh --nas=/Volumes/stock +``` + +**备份策略**: +- 目标:NAS `/Volumes/stock/moziplus-v2-backups/backup-/` +- 内容:data/ + config/ + metadata.json +- 自动保留最近 10 个备份 +- metadata.json 记录时间、版本、是否停服务 + +### 场景 6:数据重置 + +**触发**:测试数据污染、需要干净环境 + +```bash +# 预览(不加 --confirm 只显示会做什么) +bash scripts/reset-data.sh + +# 清空所有数据 +bash scripts/reset-data.sh --confirm + +# 只清指定项目 +bash scripts/reset-data.sh --project=demo --confirm +bash scripts/reset-data.sh --project=demo,e2e-test --confirm +``` + +**安全保护**: +- 必须传 `--confirm`,否则只预览不执行 +- 停服务 → 清数据 → 重启 +- trap EXIT 确保异常时也恢复服务 +- 保留 config/ 配置 + +### 场景 7:完全卸载 + +**触发**:不再使用 moziplus v2 + +```bash +# 预览 +bash scripts/uninstall.sh + +# 完全删除(含数据) +bash scripts/uninstall.sh --confirm + +# 保留数据 +bash scripts/uninstall.sh --confirm --keep-data +``` + +**安全保护**: +- 必须传 `--confirm` +- `--keep-data` 将 data/ 移动到 `~/moziplus-v2-data-backup-/` +- PM2 delete + save + +--- + +## 4. 典型工作流 + +### 日常开发 → 部署循环 + +``` +开发目录修改代码 + ↓ +git commit(sanguo-git-sync 自动推送到 gitee) + ↓ +bash scripts/deploy.sh + ↓ +bash scripts/status.sh(确认正常) + ↓ +继续开发 或 测试验证 +``` + +### 版本发布 + +``` +1. 开发目录完成功能 + 测试 +2. 司马评审通过 +3. 更新 pyproject.toml 版本号 +4. git tag + git push +5. bash scripts/deploy.sh +6. bash scripts/backup.sh --stop(发布前备份) +7. bash scripts/status.sh -v(确认) +``` + +### 故障恢复 + +``` +1. bash scripts/status.sh(确认问题) +2. pm2 logs sanguo-moziplus-v2(查看日志) +3. 如需回滚数据:从 NAS 恢复备份 +4. 如需回滚代码:git revert → bash scripts/deploy.sh +5. 如 v2 不可用:pm2 stop sanguo-moziplus-v2,切回 v1(8082) +``` + +--- + +## 5. 脚本参数速查 + +### deploy.sh +| 参数 | 默认值 | 说明 | +|------|--------|------| +| `--source=DIR` | 开发目录 | 源码目录 | +| `--target=DIR` | `~/.sanguo_projects/sanguo_moziplus_v2` | 安装目标 | +| `--skip-build` | false | 跳过前端构建 | + +### status.sh +| 参数 | 说明 | +|------|------| +| `-v, --verbose` | 显示项目详情、配置信息 | + +### build-frontend.sh +无参数。自动检测 package-lock 变化并安装依赖。 + +### backup.sh +| 参数 | 默认值 | 说明 | +|------|--------|------| +| `--target=DIR` | 安装目录 | 数据源 | +| `--nas=DIR` | `/Volumes/stock` | NAS 挂载点 | +| `--stop` | false | 备份前停服务 | + +### reset-data.sh +| 参数 | 默认值 | 说明 | +|------|--------|------| +| `--target=DIR` | 安装目录 | 目标 | +| `--project=NAME` | 全部 | 指定项目(逗号分隔) | +| `--confirm` | false | **必须传** | + +### uninstall.sh +| 参数 | 默认值 | 说明 | +|------|--------|------| +| `--target=DIR` | 安装目录 | 目标 | +| `--keep-data` | false | 保留数据到备份目录 | +| `--confirm` | false | **必须传** | + +--- + +## 6. 与旧部署文档的关系 + +本文档替代以下旧文档: +- `deployment-v2.6.md` §6 部署流程 → 改用 `deploy.sh` +- `deployment-v2.6-guide.md` 全文 → 改用脚本 + 本文档场景说明 +- `deployment-v2.6.md` §13 运维场景手册 → 改用 `status.sh` + `backup.sh` + `reset-data.sh` + +旧文档保留作为架构参考,不删除。 diff --git a/docs/design/deployment-v2.6-guide.md b/docs/design/deployment-v2.6-guide.md new file mode 100644 index 0000000..6e237ff --- /dev/null +++ b/docs/design/deployment-v2.6-guide.md @@ -0,0 +1,171 @@ +# v2.6 部署指南 + +**版本**: 2.6.0 +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-17 + +--- + +## 架构概览 + +``` +安装目录: ~/.sanguo_projects/sanguo_moziplus_v2/ +├── config/default.yaml # 全局配置 +├── data/ # 运行时数据(项目黑板DB) +├── src/ # Python 源码 +│ ├── main.py # FastAPI 入口 +│ ├── frontend/dist/ # 前端构建产物 +│ ├── api/ # API 路由 +│ ├── blackboard/ # 黑板核心 +│ └── daemon/ # Daemon 模块 +├── tests/ # 单元测试 +├── pyproject.toml # Python 项目配置 +├── ecosystem.config.cjs # PM2 配置 +└── requirements.txt # Python 依赖 + +开发目录: ~/.openclaw/sanguo_projects/sanguo_moziplus_v2/ +└── (同结构,git 仓库) +``` + +## 路径解析策略 + +所有路径通过以下优先级解析: + +1. **环境变量** `BLACKBOARD_ROOT` → 项目数据根目录(最高优先级) +2. **配置文件** `config/default.yaml` 的 `data_root` → 项目数据根目录 +3. **相对路径默认** → `{安装目录}/data/`(`main.py` 用 `__file__` 相对定位) + +### 关键路径 + +| 用途 | 解析方式 | 默认值 | +|------|---------|--------| +| 项目数据 | `BLACKBOARD_ROOT` 环境变量 或 `config.data_root` | `{src/../data/}` | +| 配置文件 | `__file__` 相对定位 | `{src/../config/default.yaml}` | +| 前端静态文件 | `__file__` 相对定位 | `{src/frontend/dist/}` | +| Inbox JSONL | 相对于项目数据目录 | `{项目}/inbox/daemon.jsonl` | +| 日志 | PM2 管理 | stdout | + +## 部署步骤 + +### 1. 同步代码 + +```bash +# 从开发目录同步到安装目录 +rsync -av --exclude='__pycache__' --exclude='.pytest_cache' \ + ~/.openclaw/sanguo_projects/sanguo_moziplus_v2/ \ + ~/.sanguo_projects/sanguo_moziplus_v2/ +``` + +### 2. 安装依赖 + +```bash +cd ~/.sanguo_projects/sanguo_moziplus_v2 +pip3 install -r requirements.txt +``` + +### 3. 配置 + +编辑 `config/default.yaml`,按需调整: + +```yaml +# 数据根目录(可选,不设则用 {安装目录}/data/) +data_root: "~/.sanguo_projects/sanguo_moziplus_v2/data" + +daemon: + tick_interval: 30 + max_global_agents: 5 +``` + +### 4. 前端构建(如果从开发目录 rsync 了 dist/ 可跳过) + +```bash +cd ~/.sanguo_projects/sanguo_moziplus_v2/src/frontend +npm install +npm run build +# 产物在 src/frontend/dist/ +``` + +### 5. PM2 启动 + +```bash +cd ~/.sanguo_projects/sanguo_moziplus_v2 +pm2 start ecosystem.config.cjs +``` + +### 6. 验证 + +```bash +# API +curl http://localhost:8080/api/daemon/status + +# Swagger 文档 +open http://localhost:8080/docs + +# 前端 Dashboard +open http://localhost:8083 +# 或(如果 FastAPI 托管静态文件) +open http://localhost:8080/ +``` + +## PM2 配置 + +`ecosystem.config.cjs`: + +```javascript +module.exports = { + apps: [{ + name: "sanguo-moziplus-v2", + script: "src/main.py", + interpreter: "python3", + cwd: "/Users/chufeng/.sanguo_projects/sanguo_moziplus_v2", + env: { + BLACKBOARD_ROOT: "/Users/chufeng/.sanguo_projects/sanguo_moziplus_v2/data", + }, + max_memory_restart: "500M", + log_date_format: "YYYY-MM-DD HH:mm:ss", + }] +}; +``` + +## NAS/Docker 部署(姜维负责) + +- 数据目录挂载到 NAS `/Volumes/stock/moziplus-v2/` +- Docker 容器运行 FastAPI + 前端 +- 定时 `sqlite3 backup` 黑板 DB 到 NAS + +## 开发 → 部署 同步流程 + +``` +开发目录修改 → git commit → rsync 到安装目录 → pm2 restart → 验证 + ↑ ↓ + └────────────── 回滚(git revert)←─────────────┘ +``` + +## 测试 + +### 单元测试 + +```bash +cd ~/.sanguo_projects/sanguo_moziplus_v2 +python3 -m pytest tests/ -q +``` + +### 端到端测试(司马懿执行) + +```bash +# 1. 创建项目 +python3 src/cli/admin.py create demo "Demo Project" --agents agent1,agent2 + +# 2. 创建任务 +python3 src/cli/blackboard.py create demo --title "Test Task" --type coding + +# 3. 查看任务 +python3 src/cli/blackboard.py read demo + +# 4. API 测试 +curl http://localhost:8080/api/projects +curl http://localhost:8080/api/daemon/status + +# 5. 前端访问 +open http://localhost:8083 +``` diff --git a/docs/design/deployment-v2.6.md b/docs/design/deployment-v2.6.md new file mode 100644 index 0000000..7d4267e --- /dev/null +++ b/docs/design/deployment-v2.6.md @@ -0,0 +1,493 @@ +# v2.6 部署方案设计 + +**版本**: v2.6.2-deploy +**基于**: technical-design-v2.6.md v2.6.2 +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-16 + +--- + +## 1. 部署环境 + +| 项目 | 规格 | +|------|------| +| 主机 | 楚锋的 Mac mini (Apple M4, ARM64, 16GB RAM, macOS) | +| Python | 3.11(moziplus venv) | +| Node.js | v22.22.1 | +| PM2 | 6.0.14 | +| OpenClaw Gateway | 127.0.0.1:18789 | +| NAS | 192.168.2.154(SMB /Volumes/stock/) | +| Windows-Test-Node | 192.168.2.33 | + +### 目录规划 + +| 用途 | 路径 | 说明 | +|------|------|------| +| 开发目录 | `~/.openclaw/sanguo_projects/sanguo_moziplus_v2/` | Git 管理 | +| 安装目录 | `~/.sanguo_projects/sanguo_moziplus_v2/` | PM2 运行 | +| Git 远程 | gitee (origin) + gitea (NAS 192.168.2.154) | 双远程 | + +--- + +## 2. 部署架构 + +``` +PM2 (进程管理器) +├── sanguo-moziplus-v2 (port 8083) ← 🆕 v2 主进程(API + Daemon ticker + 前端) +├── sanguo-moziplus (port 8082) ← v1 保留(v2 验证后下线) +├── sanguo-git-sync ← 不变 +├── sanguo-mail-* (8个) ← v2 验证后逐步下线 +└── sanguo-mozi ← 不变 + +进程内部结构: +sanguo-moziplus-v2 +├── uvicorn (FastAPI) — asyncio event loop +│ ├── API 路由(黑板/Daemon/SSE/项目) +│ ├── 静态文件服务(frontend/dist/) +│ └── SSE 推送端点(/api/events) +├── asyncio background task: Daemon ticker(30s) +├── asyncio background task: Inbox watcher(1s 轮询) +├── Agent spawn: asyncio.create_subprocess_exec(异步非阻塞) +└── ActiveAgentCounter(asyncio.Semaphore) +``` + +**纯 asyncio 单线程。** Daemon ticker、API、SSE、Agent spawn 全部在同一个 asyncio event loop 中,无同步阻塞调用。Full Agent spawn 用 `asyncio.create_subprocess_exec`(不 await 完成),Subagent 用 Gateway 内部 API。不新增 PM2 进程。 + +--- + +## 3. 多项目数据布局 + +``` +~/.sanguo_projects/sanguo_moziplus_v2/ +├── projects/ +│ ├── _registry.yaml ← 全局注册表(YAML,人可读,Git 版本管理) +│ ├── sanguo_quant_live/ +│ │ ├── blackboard.db ← per-project 黑板 +│ │ ├── config/ +│ │ │ └── project.yaml ← per-project 配置覆盖 +│ │ ├── artifacts/ ← per-project 产出物 +│ │ │ └── {task-id}/ +│ │ │ ├── outputs/ +│ │ │ ├── reviews/ +│ │ │ ├── archive/ +│ │ │ └── data/ +│ │ └── experiences/ ← per-project 经验库 +│ │ └── skills/ ← per-project Skill 覆盖/扩展 +│ ├── sanguo_vnpy/ +│ │ └── ... +│ └── _archived/ ← 归档项目(只读) +│ └── old_project/ +│ +├── inbox/ +│ └── daemon.jsonl ← Inbox 事件推送 +│ +├── skills/ ← 全局 Skill 库 +│ ├── builtins/ ← 系统内置 Skill +│ │ ├── blackboard_operations/ +│ │ ├── code_review/ +│ │ └── data_validation/ +│ └── custom/ ← 蒸馏产出/用户创建 Skill +│ └── {skill_id}/ +│ ├── SKILL.md ← 四要素 +│ ├── template.md ← 可选详细模板 +│ └── meta.yaml ← 版本/来源/tag +│ +├── config/ +│ ├── default.yaml ← 全局默认配置 +│ ├── guardrails.yaml +│ ├── template_components.yaml +│ └── review_protocols/ +│ ├── plan_review.yaml +│ ├── output_review.yaml +│ ├── analysis_review.yaml +│ └── final_review.yaml +│ +├── prompt_templates/ ← L2 模板(全局共享) +├── schemas/ ← Schema 校验(全局共享) +└── src/ ← 代码 +``` + +**物理隔离**:每个项目的 blackboard.db 完全独立。一个项目数据库损坏不影响其他项目。 + +--- + +## 4. PM2 配置 + +```javascript +// ecosystem.config.cjs +module.exports = { + apps: [{ + name: 'sanguo-moziplus-v2', + script: '~/.sanguo_projects/sanguo_moziplus_v2/.venv/bin/uvicorn', + args: 'src.main:app --host 0.0.0.0 --port 8083', + cwd: '~/.sanguo_projects/sanguo_moziplus_v2', + interpreter: 'none', + autorestart: true, + max_restarts: 10, + restart_delay: 3000, + watch: false, + env: { + PYTHONPATH: '~/.sanguo_projects/sanguo_moziplus_v2', + BLACKBOARD_ROOT: '~/.sanguo_projects/sanguo_moziplus_v2/projects', + INBOX_PATH: '~/.sanguo_projects/sanguo_moziplus_v2/inbox/daemon.jsonl', + LOG_LEVEL: 'INFO', + }, + out_file: '~/.sanguo_projects/sanguo_moziplus_v2/logs/daemon.out.log', + error_file: '~/.sanguo_projects/sanguo_moziplus_v2/logs/daemon.err.log', + }] +} +``` + +--- + +## 5. 配置体系 + +### 5.1 全局配置(default.yaml) + +```yaml +daemon: + tick_interval: 30 # Tick 间隔(秒) + task_timeout: 600 # 单任务超时(秒,10分钟) + max_global_agents: 5 # 全局最大并发 Agent + max_per_agent: 1 # 每 Agent 最大并发(默认 sequential) + zombie_threshold: 20 # 连续 N tick 无变更 → 告警 + +inbox: + path: "inbox/daemon.jsonl" + watch_interval: 1 # Inbox 轮询间隔(秒) + max_size_bytes: 1048576 # 1MB 触发 truncate + +review: + default_max_rounds: 3 + debate_max_rounds: 5 + confidence_threshold: 0.7 # 低于此值升级庞统 + +experience: + distill_threshold: 5 # 同 tag 积累 N 条触发二级蒸馏 + expire_days: 30 # 30天无引用 → deprecated + +logging: + level: INFO + max_file_size: 10MB +``` + +### 5.2 Per-project 配置覆盖 + +```yaml +# projects/{project_id}/config/project.yaml +project: + name: "量化实战项目" + description: "sanguo_quant_live" + agents: + - pangtong-fujunshi + - simayi-challenger + - zhangfei-dev + - guanyu-dev + - zhaoyun-data + - jiangwei-infra + max_global_agents: 3 # 覆盖全局默认 + context: + nas_path: "/Volumes/stock/" + backtest_service: "http://192.168.2.154:8088" + frameworks: + - "vnpy" + - "sanguo_vnpy" +``` + +--- + +## 6. 部署流程 + +### 6.1 首次部署 + +```bash +# 1. 创建安装目录 +mkdir -p ~/.sanguo_projects/sanguo_moziplus_v2/{projects,inbox,logs,config} + +# 2. 同步代码(从开发目录) +rsync -av --exclude='.venv' --exclude='node_modules' --exclude='.git' \ + ~/.openclaw/sanguo_projects/sanguo_moziplus_v2/ \ + ~/.sanguo_projects/sanguo_moziplus_v2/ + +# 3. 安装 Python 依赖 +cd ~/.sanguo_projects/sanguo_moziplus_v2 +python3 -m venv .venv +.venv/bin/pip install fastapi uvicorn pyyaml + +# 4. 构建前端 +cd src/frontend && npm install && npm run build && cd ../.. + +# 5. 启动 PM2 +pm2 start ecosystem.config.cjs + +# 6. 验证 +curl http://127.0.0.1:8083/api/daemon/status +curl http://127.0.0.1:8083/ # 前端页面 +``` + +### 6.2 日常更新 + +```bash +# 1. 同步代码 +rsync -av --exclude='.venv' --exclude='node_modules' --exclude='.git' \ + ~/.openclaw/sanguo_projects/sanguo_moziplus_v2/ \ + ~/.sanguo_projects/sanguo_moziplus_v2/ + +# 2. 如果前端有变更 +cd ~/.sanguo_projects/sanguo_moziplus_v2/src/frontend && npm run build && cd ../.. + +# 3. 重启 +pm2 restart sanguo-moziplus-v2 + +# 4. 验证 +curl http://127.0.0.1:8083/api/daemon/status +``` + +### 6.3 创建项目 + +```bash +# 通过 CLI 创建 +python3 src/cli/admin.py project create \ + --name "量化实战" \ + --id "sanguo_quant_live" \ + --agents "pangtong-fujunshi,simayi-challenger,zhangfei-dev,guanyu-dev,zhaoyun-data,jiangwei-infra" + +# 自动创建 projects/sanguo_quant_live/ 目录和 blackboard.db +``` + +--- + +## 7. 回滚方案 + +| 场景 | 回滚方式 | +|------|---------| +| v2 启动失败 | `pm2 stop sanguo-moziplus-v2`,v1 (8082) 不受影响 | +| Daemon tick 异常 | API `POST /api/daemon/stop` 停 ticker 不停服务 | +| 单项目数据库损坏 | 删除该项目的 `blackboard.db`,重启自动重建 | +| 全局不可用 | v1 仍在 8082 运行,切换回 v1 | +| 前端构建失败 | 回退 `dist/` 目录到上一版 Git commit | + +**v1 和 v2 完全独立**。v2 出问题直接停掉,v1 继续服务。 + +--- + +## 8. 监控 + +### 8.1 健康端点 + +``` +GET /api/daemon/status +{ + "ticker_running": true, + "last_tick": "2026-05-16T23:00:00", + "tick_interval_seconds": 30, + "projects": { + "sanguo_quant_live": { + "active_agents": 2, + "tasks_summary": {"pending": 3, "working": 1, "review": 1, "done": 15}, + "db_size_bytes": 45056 + } + }, + "inbox_pending": 0, + "active_sessions": 2 +} +``` + +### 8.2 逻辑健康自检 + +Daemon 每 tick 检查:连续 N tick(默认 20,即 10 分钟)无任何任务状态变更 → 写 observation 告警 + 通知用户。 + +PM2 只能检测进程崩溃,逻辑死循环/卡死需要应用层检测。 + +### 8.3 日志 + +```bash +# Tick 日志 +pm2 logs sanguo-moziplus-v2 --lines 100 | grep "Tick" + +# Agent spawn 日志 +pm2 logs sanguo-moziplus-v2 --lines 100 | grep "Spawning" + +# 审查日志 +pm2 logs sanguo-moziplus-v2 --lines 100 | grep "review\|rebuttal" +``` + +### 8.4 SSE 推送监控 + +前端通过 `/api/events` SSE 接收实时事件。推送 4 级: + +| 级别 | 图标 | 场景 | +|------|------|------| +| 🔴 紧急 | 任务失败/系统异常 | 立即推 | +| 🟡 需关注 | 任务超时/审查不通过 | 实时推 | +| 🟢 日常 | 任务完成/日报 | 批量推 | +| 🔵 可选 | Agent 活动日志 | 用户订阅 | + +--- + +## 9. 安全 + +| 项目 | 措施 | +|------|------| +| SQLite 文件权限 | `blackboard.db` owner-only 读写(0600) | +| API 认证 | 内网环境,暂不认证;后续可加 API Key | +| CLI 限制 | blackboard.py 只能本地 exec,不暴露网络 | +| Agent 权限 | SOUL.md 约束 + claim assignee 检查 | +| 项目隔离 | per-project SQLite 物理隔离,一个 WHERE 漏掉也不会跨项目 | +| Session 清理 | fcntl 文件锁保护 sessions.json 编辑 | +| 归档安全 | 项目归档前必须无 working 状态任务 | + +--- + +## 10. 性能预估 + +| 指标 | 预估值 | 理由 | +|------|--------|------| +| Tick 耗时 | <50ms | 单项目 <10 张表几十条记录 | +| Agent spawn 延迟 | 2-5s | openclaw agent 冷启动 | +| 并发 Agent 数 | ≤5(全局) | ActiveAgentCounter 控制 | +| 项目数上限 | ~20 | SQLite 单文件无压力,PM2 不新增进程 | +| Inbox 文件大小 | <200KB | 每秒 truncate,崩溃积压 ~200KB/1000条 | +| blackboard.db 大小 | <10MB/项目 | 每任务 ~50-100KB,含索引和 WAL overhead | +| 前端首屏加载 | <2s | Vite 构建优化 + 本地网络 | + +--- + +## 11. v1 → v2 迁移计划 + +``` +Phase 1: v2 后端验证(v1 不动) + ├── v2 部署到 8083 + ├── CLI 验证全流程 + └── E2E 测试通过 + +Phase 2: v2 前端验证 + ├── 前端构建部署 + ├── Dashboard 功能验证 + └── SSE 推送验证 + +Phase 3: 并行运行 + ├── v1 继续服务(8082) + ├── v2 接受新任务(8083) + └── 对比 v1/v2 行为一致性 + +Phase 4: v1 下线 + ├── pm2 stop sanguo-moziplus + ├── pm2 delete sanguo-moziplus + ├── v2 可选接管 8082 + └── v1 代码和数据库保留只读(历史归档) + +Phase 5: Mail 逐步下线(远期目标,与黑板评论长期共存) + ├── v2 上线后 Mail 和黑板评论并行 + ├── 所有 Agent 的 Skill 更新支持黑板评论后逐步关闭 Mail poller + └── 最终只保留 Mail 作为外部通知通道 +``` + +--- + +## 12. 依赖梳理 + +### 12.1 运行时依赖 + +| 依赖 | 版本要求 | 用途 | 安装方式 | +|------|---------|------|----------| +| Python | ≥ 3.9(推荐 3.11) | Daemon + FastAPI | macOS 系统自带 3.9.6 / venv 用 3.11 | +| Node.js | ≥ 20 | 前端构建 | nvm 管理 | +| PM2 | ≥ 5.0(当前 6.0.14) | 进程管理 | `npm i -g pm2` | +| FastAPI | latest | HTTP API | `pip install fastapi` | +| uvicorn | latest | ASGI server | `pip install uvicorn` | +| PyYAML | latest | YAML 配置读写 | `pip install pyyaml` | +| OpenClaw CLI | ≥ 2026.5.7 | Agent spawn / sessions_spawn | 已安装 | +| Git | ≥ 2.x | 版本管理 | 系统自带 | + +### 12.2 构建时依赖 + +| 依赖 | 用途 | 说明 | +|------|------|------| +| npm / pnpm | 前端构建 | React + Vite + TypeScript | +| rsync | 开发→安装同步 | macOS 自带 | + +### 12.3 外部服务依赖 + +| 服务 | 地址 | 用途 | 容错 | +|------|------|------|------| +| OpenClaw Gateway | 127.0.0.1:18789 | Agent spawn / sessions_spawn | v2 启动时检查,不可用则 Daemon 只运行 tick 不调度 Agent | +| NAS SMB | 192.168.2.154 | 数据存储(跨项目桥接) | 挂载失败时 per-project 功能不受影响 | +| Gitee | gitee.com | Git 远程 | 网络不可用时本地正常运行 | +| Gitea | 192.168.2.154:3000 | Git 备份远程 | 网络不可用时本地正常运行 | + +--- + +## 13. 运维场景手册 + +### 13.1 日常运维 + +| 场景 | 操作 | 频率 | +|------|------|------| +| 查看系统状态 | `curl http://127.0.0.1:8083/api/daemon/status` | 按需 | +| 查看日志 | `pm2 logs sanguo-moziplus-v2 --lines 50` | 按需 | +| 重启服务 | `pm2 restart sanguo-moziplus-v2` | 更新后 | +| 查看活跃 Agent | `curl http://127.0.0.1:8083/api/daemon/sessions` | 按需 | + +### 13.2 数据备份 + +| 数据 | 备份方式 | 频率 | +|------|---------|------| +| blackboard.db | `sqlite3 backup` 到 NAS | 每小时 cron | +| _registry.yaml | Git 管理 | CLI 操作时自动 commit | +| skills/ 目录 | Git 管理 | Skill 变更时 commit | +| 前端 dist/ | Git 管理 | 构建后 commit | +| artifacts/ | 不备份(可重建) | — | + +**备份策略**:Git 只管文本文件(YAML/Markdown/JSON/代码/前端 dist)。`blackboard.db`、`artifacts/`、`experiences/` 加到 `.gitignore`,用独立备份策略: +- `blackboard.db`:每小时 cron 执行 `sqlite3 {db} ".backup '{backup_path}'"` 到 NAS。WAL 模式下直接 git add `.db` 文件会拿到不一致快照(db 和 -wal/-shm 不匹配),必须用 SQLite 官方 backup 命令 +- `_registry.yaml` + `config/` + `skills/` + `dist/`:Git 管理,推送到 gitee + gitea 双远程 + +### 13.3 磁盘满处理 + +```bash +# 1. 检查磁盘占用 +du -sh ~/.sanguo_projects/sanguo_moziplus_v2/projects/*/ + +# 2. 归档不活跃项目 +python3 src/cli/admin.py project archive --id old_project + +# 3. 清理归档项目 artifacts +rm -rf ~/.sanguo_projects/sanguo_moziplus_v2/projects/_archived/old_project/artifacts/ + +# 4. 清理旧日志 +pm2 flush sanguo-moziplus-v2 +``` + +### 13.4 告警升级 + +| 级别 | 触发条件 | 处理方式 | +|------|---------|----------| +| 自动恢复 | 进程崩溃 | PM2 auto restart | +| 应用告警 | 逻辑死循环 | observation 写黑板 + SSE 推送 | +| 人工介入 | DB 损坏 / 逻辑异常 | Dashboard 告警 + 用户介入 | +| 回滚 | v2 不可用 | `pm2 stop sanguo-moziplus-v2`,切回 v1(8082) | + +--- + +## 14. 交付检查清单 + +P1 部署前逐项验证: + +- [ ] 项目创建、git init、远程配置完成 +- [ ] PM2 配置正确,`pm2 start` 成功 +- [ ] 全局注册表 `_registry.yaml` 可读写,项目列表正确 +- [ ] CLI `admin.py project create` 创建项目成功 +- [ ] per-project `blackboard.db` 自动创建且 schema 正确(含 reviews/experiences/comments.comment_type) +- [ ] CLI `blackboard.py read/claim/output/comment/decide/observe/create/review` 全部可用 +- [ ] Daemon tick 30s 循环正常运行 +- [ ] Inbox JSONL watcher 正常消费 + truncate +- [ ] Agent spawn 成功(asyncio.create_subprocess_exec 非阻塞 + sessions_spawn) +- [ ] Session 完成后自动存档 +- [ ] ActiveAgentCounter 并发控制生效 +- [ ] API 端点全部可用(/api/projects/ /api/daemon/ /api/events) +- [ ] SSE 推送正常工作 +- [ ] 前端构建产物可访问(8083 端口) +- [ ] 健康端点返回正确状态 +- [ ] 逻辑健康自检(连续 20 tick 无变更→告警) +- [ ] v1 (8082) 不受 v2 (8083) 影响 diff --git a/docs/design/development-plan-v2.6.md b/docs/design/development-plan-v2.6.md new file mode 100644 index 0000000..2966efc --- /dev/null +++ b/docs/design/development-plan-v2.6.md @@ -0,0 +1,327 @@ +# v2.6 开发计划 + +**版本**: v2.6.0-plan +**基于**: technical-design-v2.6.md v2.6.3 + deployment-v2.6.md v2.6.3 +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-17 +**状态**: 执行中 + +--- + +## 开发模式 + +- **分工**: 庞统编码(F1-F18 全部),司马懿测试+评审 +- **流程**: 写完一个功能 → 单元测试 → 发司马懿评审 → 通过 → 下一个 +- **参考实践**: superpowers(verify-before-complete)、oh-my-claudecode(Execution+Enhancement+Guarantee 三层)、hermes(僵尸检测+幻觉门控) +- **原则**: 每个功能一次做完,不分 phase;不确定的先记录待确认区,按 AI native + 最优实践方向先执行 + +--- + +## 开发顺序(按依赖拓扑) + +``` +Layer 0: F1 骨架 → F2 黑板 +Layer 1: F3 多项目 → F4 CLI → F5 API +Layer 2: F6 Ticker → F7 Inbox → F8 健康检查 +Layer 3: F9 调度器 → F10 Counter → F11 Bootstrap +Layer 4: F12 审查流水线 → F13 Guardrail → F14 反驳权 +Layer 5: F15 经验沉淀 → F16 Skill +Layer 6: F17 SSE+Hook → F18 前端 +``` + +### F1 项目骨架 + 配置体系 + +| 项目 | 内容 | +|------|------| +| **依赖** | 无 | +| **估计** | 1h | +| **产出** | `src/main.py`(FastAPI 空壳)+ `config/default.yaml` + `pyproject.toml` + `ecosystem.config.cjs` + `.gitignore` | +| **完成标准** | `pm2 start` 成功,`/api/daemon/status` 返回 200,`/docs` 自动文档可访问 | +| **测试** | `test_main.py`: 启动、健康端点、CORS | + +### F2 黑板核心(DB + CRUD) + +| 项目 | 内容 | +|------|------| +| **依赖** | F1 | +| **估计** | 3h | +| **产出** | `src/blackboard/{db,models,operations,queries}.py` + `schemas/*.json` | +| **完成标准** | 全部 9 表 CRUD 可用,WAL + busy_timeout + BEGIN IMMEDIATE,schema 校验通过 | +| **测试** | `test_blackboard.py`: create_task / claim / output / comment / decide / observe / review / events 写入 + 状态机转换 + 并发写入 | + +### F3 多项目管理 + +| 项目 | 内容 | +|------|------| +| **依赖** | F1, F2 | +| **估计** | 2h | +| **产出** | `src/blackboard/registry.py` + `src/cli/admin.py`(project create/delete/list) | +| **完成标准** | CLI 创建项目 → per-project DB 自动创建 → `_registry.yaml` 更新 → list 显示正确 | +| **测试** | `test_registry.py`: create/delete/list + YAML 读写 + 并发注册 | + +### F4 CLI 工具 + +| 项目 | 内容 | +|------|------| +| **依赖** | F1, F2 | +| **估计** | 2h | +| **产出** | `src/cli/blackboard.py`(8 个子命令) | +| **完成标准** | read / claim / output / comment / decide / observe / create / review 全部可用 | +| **测试** | `test_cli.py`: 每个子命令 happy path + 参数校验 + 错误处理 | + +### F5 API 层 + +| 项目 | 内容 | +|------|------| +| **依赖** | F1, F2 | +| **估计** | 3h | +| **产出** | `src/api/{blackboard,daemon,project,sse}_routes.py` | +| **完成标准** | 全部 API 端点 curl 可用,Swagger 文档完整,SSE 端点可连接 | +| **测试** | `test_api.py`: 每个端点 CRUD + 状态码 + 参数校验 | + +### F6 Daemon Ticker + +| 项目 | 内容 | +|------|------| +| **依赖** | F2, F3, F5 | +| **估计** | 3h | +| **产出** | `src/daemon/ticker.py` | +| **完成标准** | 30s 循环运行,scan_tasks + 依赖推进 + events 写入正常,asyncio background task 与 FastAPI 共存 | +| **测试** | `test_ticker.py`: tick 循环 + 状态扫描 + 依赖推进 + 多项目轮询 | + +### F7 Inbox JSONL Watcher + +| 项目 | 内容 | +|------|------| +| **依赖** | F6 | +| **估计** | 1.5h | +| **产出** | `src/daemon/inbox.py` | +| **完成标准** | Agent 写 JSONL → 1s 内 Daemon 收到并处理 + truncate | +| **测试** | `test_inbox.py`: 写入+消费+truncate + 并发写入 + 损坏文件恢复 | + +### F8 健康检查 + +| 项目 | 内容 | +|------|------| +| **依赖** | F6 | +| **估计** | 1h | +| **产出** | `src/daemon/health.py` | +| **完成标准** | 连续 20 tick 无变更 → observation 告警写入 | +| **测试** | `test_health.py`: 正常/僵尸/恢复 场景 | + +### F9 Agent 调度器 + +| 项目 | 内容 | +|------|------| +| **依赖** | F6, F7 | +| **估计** | 3h | +| **产出** | `src/daemon/{dispatcher,spawner}.py` | +| **完成标准** | 三级调度(Daemon/Full/Sub)可用,`asyncio.create_subprocess_exec` spawn 不阻塞 event loop | +| **测试** | `test_dispatcher.py`: 三级决策树 + `test_spawner.py`: spawn + session 注册 + 超时 | + +### F10 ActiveAgentCounter + +| 项目 | 内容 | +|------|------| +| **依赖** | F9 | +| **估计** | 1h | +| **产出** | `src/daemon/counter.py` | +| **完成标准** | max_global=5 控制,per-agent 串行,acquire/release 正确 | +| **测试** | `test_counter.py`: 全局上限 + per-agent 串行 + 并发竞争 + release 恢复 | + +### F11 Bootstrap 拼装 + +| 项目 | 内容 | +|------|------| +| **依赖** | F1, F2 | +| **估计** | 2h | +| **产出** | `src/daemon/bootstrap.py` + `prompt_templates/*.md` + `config/template_components.yaml` | +| **完成标准** | `build_bootstrap()` 按 role 拼装 L0-L3 四层,token 占用 < 4096 | +| **测试** | `test_bootstrap.py`: 各 role 拼装 + token 估算 + 缺失组件降级 | + +### F12 审查流水线 + +| 项目 | 内容 | +|------|------| +| **依赖** | F9, F10, F11 | +| **估计** | 3h | +| **产出** | `src/daemon/review_flow.py` + `config/review_protocols/*.yaml` | +| **完成标准** | 4 级分级审查(high/standard/low/research)端到端可用,confidence 计算 + 升级逻辑正确 | +| **测试** | `test_review_flow.py`: 4 级分流 + confidence 阈值 + 升级庞统 | + +### F13 Guardrail 系统 + +| 项目 | 内容 | +|------|------| +| **依赖** | F9, F11 | +| **估计** | 2h | +| **产出** | `src/daemon/guardrail.py` + `config/guardrails.yaml` | +| **完成标准** | L1 assert 自动执行 + L2 subagent 二次确认,配置驱动 | +| **测试** | `test_guardrail.py`: L1 通过/失败 + L2 触发 + 配置加载 + 误杀恢复 | + +### F14 反驳权流程 + +| 项目 | 内容 | +|------|------| +| **依赖** | F12 | +| **估计** | 2h | +| **产出** | 集成在 `review_flow.py` + `prompt_templates/rebuttal.md` | +| **完成标准** | 审查不通过 → 反驳协商 → max_rounds → 庞统裁决 | +| **测试** | `test_rebuttal.py`: 反驳触发 + 轮次计数 + 超轮次升级 + 结果写入 | + +### F15 经验沉淀 + +| 项目 | 内容 | +|------|------| +| **依赖** | F6, F2 | +| **估计** | 2h | +| **产出** | `src/daemon/experience.py` | +| **完成标准** | 任务完成 → 一级蒸馏 → experiences 表写入 → 二级蒸馏触发 → Skill 草稿生成 | +| **测试** | `test_experience.py`: 一级蒸馏 + tag 匹配 + 二级触发 + 过期清理 | + +### F16 Skill 体系 + +| 项目 | 内容 | +|------|------| +| **依赖** | F15 | +| **估计** | 1.5h | +| **产出** | `src/daemon/skill_loader.py` + Skill 目录结构 | +| **完成标准** | Skill 四要素(name/description/triggers/actions)加载 + per-project 覆盖 + 生命周期管理 | +| **测试** | `test_skill_loader.py`: 加载 + 验证 + 覆盖 + 版本管理 | + +### F17 SSE 推送 + Hook 系统 + +| 项目 | 内容 | +|------|------| +| **依赖** | F5, F6 | +| **估计** | 2h | +| **产出** | `src/api/sse_routes.py`(增强)+ `src/daemon/notifier.py` + Hook 触发点 | +| **完成标准** | SSE 4 级推送正常,降级轮询兜底,3 个 Hook 触发点工作 | +| **测试** | `test_sse.py`: 连接+推送+断线重连 + `test_hooks.py`: 触发+过滤 | + +### F18 前端 Dashboard + +| 项目 | 内容 | +|------|------| +| **依赖** | F5, F17 | +| **估计** | 8h | +| **产出** | `src/frontend/` 全部 5 页面 + 组件 | +| **完成标准** | TaskBoard + GlobalMonitor + ArtifactVault + SystemConfig + AIBriefing 全部可用,SSE 实时更新,项目切换器工作 | +| **测试** | `test_frontend.py`(E2E): 页面渲染 + API 集成 + SSE 更新 + 项目切换 | + +--- + +## 测试策略 + +### 单元测试(每个功能自带) + +| 测试文件 | 覆盖模块 | 优先级 | +|----------|---------|--------| +| `test_main.py` | main.py 启动 + 配置 | P0 | +| `test_blackboard.py` | 黑板 CRUD + 并发 + WAL | P0 | +| `test_registry.py` | 多项目管理 | P0 | +| `test_cli.py` | CLI 8 子命令 | P0 | +| `test_api.py` | API 全端点 | P0 | +| `test_ticker.py` | Daemon tick 循环 | P0 | +| `test_inbox.py` | Inbox JSONL | P1 | +| `test_health.py` | 健康检查 | P1 | +| `test_dispatcher.py` | Agent 调度决策 | P0 | +| `test_spawner.py` | Agent spawn | P0 | +| `test_counter.py` | ActiveAgentCounter | P0 | +| `test_bootstrap.py` | Bootstrap 拼装 | P1 | +| `test_review_flow.py` | 审查流水线 | P0 | +| `test_guardrail.py` | Guardrail | P0 | +| `test_rebuttal.py` | 反驳权 | P0 | +| `test_experience.py` | 经验沉淀 | P1 | +| `test_skill_loader.py` | Skill 加载 | P1 | +| `test_sse.py` | SSE 推送 | P1 | +| `test_hooks.py` | Hook 系统 | P1 | +| `test_frontend.py` | 前端 E2E | P1 | + +### 集成测试(F14 完成后) + +端到端链路:用户提需求 → 庞统规划 → 任务写入 → Agent 执行 → Guardrail → 审查 → 反驳 → 完成 → 经验沉淀 + +### 司马懿职责 + +- 每个 F 编码完成后评审代码 + 审查测试覆盖 +- F14 完成后执行集成测试 +- F18 完成后执行 E2E 验收 +- 发现问题写 Mail → 庞统修复 → 复审 + +--- + +## 进度追踪 + +| 序号 | 功能 | 状态 | 测试 | 评审 | +|------|------|------|------|------| +| F1 | 项目骨架+配置 | ✅ 完成 | ✅ 10/10 | ✅ 通过 | +| F2 | 黑板核心 | ✅ 完成 | ✅ 39/39 | ✅ 通过 | +| F3 | 多项目管理 | ✅ 完成 | ✅ 11/11 | ✅ 通过 | +| F4 | CLI 工具 | ✅ 完成 | ✅ 14/14 | ✅ 通过 | +| F5 | API 层 | ✅ 完成 | ✅ 23/23 | ✅ 通过 | +| F6 | Daemon Ticker | ✅ 完成 | ✅ 18/18 | ✅ 通过 | +| F7 | Inbox JSONL | ✅ 完成 | ✅ 16/16 | ✅ #277 | +| F8 | 健康检查 | ✅ 完成 | ✅ 16/16 | ✅ #277 | +| F9 | Agent 调度器 | ✅ 完成 | ✅ 29/29 | ✅ #278 | +| F10 | Counter | ✅ 完成 | ✅ 14/14 | ✅ #278 | +| F11 | Bootstrap | ✅ 完成 | ✅ 22/22 | ✅ #278 | +| F12 | 审查流水线 | ✅ 完成 | ✅ 20/20 | ✅ #278 | +| F13 | Guardrail | ✅ 完成 | (含F12) | ✅ #278 | +| F14 | 反驳权 | ✅ 完成 | (含F12) | ✅ #278 | +| F15 | 经验沉淀 | ✅ 完成 | ✅ 22/22 | ✅ #278 | +| F16 | Skill 体系 | ✅ 完成 | ✅ 24/24 | ✅ #278 | +| F17 | SSE+Hook | ✅ 完成 | ✅ 24/24 | ✅ #278 | +| F18 | 前端 Dashboard | ✅ 完成 | ✅ build | ✅ #279 | +| --- | **v2.8 状态增强 + M3 Checkpoint** | | | | +| v2.8 | 状态增强(paused/escalated/waiting_human + 归档 + 前端改造) | ✅ 设计完成 | ✅ 已完成 | ✅ #278-#280 | +| M3 | Checkpoint 机制 + Artifact 成果物面板 | ✅ 设计完成 | ✅ 已完成 | ✅ #280 | +| --- | **Agent 编排集成** | | | | +| F19 | Ticker 调度集成 | ✅ 设计完成 | ✅ 已完成 | ✅ 评审通过 | +| F20 | Spawner prompt 模板 | ✅ 设计完成 | ✅ 已完成 | ✅ 评审通过 | +| F21 | 审查流水线集成 | ✅ 设计完成 | ✅ 已完成 | ✅ 评审通过 | +| F22 | 前端 API 对接 | ✅ 设计完成 | ✅ 已完成 | ✅ 评审通过 | +| --- | **v3.0 集成 + 安全红线** | | | | +| G1 | HealthChecker → Ticker | ✅ 已完成 | ✅ 33/33 | ✅ #313 | +| G2 | BootstrapBuilder → Spawner | ✅ 已完成 | ✅ 测试通过 | ✅ #313 | +| G3 | InboxWatcher → Ticker | ✅ 已完成 | ✅ 33/33 | ✅ #313 | +| G4 | ExperienceDistiller → Ticker | ✅ 已完成 | ✅ 33/33 | ✅ #313 | +| G5 | 安全红线 Guardrails | ✅ 已完成 | ✅ 测试通过 | ✅ #316 | +| G6 | MorningPanel + ArtifactPanel 前端集成 | ✅ 已完成 | ✅ build | ✅ #313 | + +--- + +## 待确认区 + +开发过程中遇到的设计决策,按 AI native + 最优实践方向先执行,记录于此,用户明早 review。 + +#### 2026-05-17 SQLite 3.51.0 CHECK 约束解析 bug + +**发现**: SQLite 3.51.0 不允许 CHECK 约束作为列定义列表的中间项(带尾随逗号)。 +```sql +-- ❌ 报错:near "foo": syntax error +CREATE TABLE t (status TEXT, CHECK (status IN ('a','b')), foo TEXT) +-- ✅ 正常:inline CHECK +CREATE TABLE t (status TEXT CHECK (status IN ('a','b')), foo TEXT) +``` +**处理**: 所有 CHECK 约束改为内联方式(附在字段定义上)。 +**影响**: 无功能影响,只是 SQL 写法不同。 + +#### 2026-05-17 BUG-1 修复:/api/daemon/status 端点重复注册 + +**发现**: 司马懿评审 F1-F5 时发现 main.py 和 daemon_routes.py 都注册了 `/api/daemon/status`,导致 Swagger 文档 Duplicate Operation ID warning。 +**修复**: 删掉 main.py 中的定义,统一由 daemon_routes.py 提供。同时重构 main.py 将占位 ticker 替换为真实 Ticker 类。 +**影响**: daemon_routes.py 新增 tick_count 字段,manual_tick 端点对接真实 Ticker。 + +--- + +## 参考实践来源 + +| 实践 | 来源项目 | 应用点 | +|------|---------|--------| +| verify-before-complete | superpowers (clawgator) | 每个 F 编码完必须测试通过才算 done | +| Execution+Enhancement+Guarantee | oh-my-claudecode | 三层质量保证:编码→增强→保障 | +| 僵尸检测+reclaim | hermes-agent | health.py 逻辑死循环检测 | +| 幻觉门控 | hermes-agent | Guardrail 产出物验证 | +| YAML frontmatter + preamble-tier | gstack | Skill 加载格式 | +| Wave Execution(新鲜上下文) | get-shit-done | Bootstrap 拼装不累积旧上下文 | diff --git a/docs/design/frontend-principles.md b/docs/design/frontend-principles.md new file mode 100644 index 0000000..5497ec7 --- /dev/null +++ b/docs/design/frontend-principles.md @@ -0,0 +1,40 @@ +# moziplus v2 前端设计原则 + +**创建**: 2026-05-15 +**状态**: 用户确认 + +## 核心原则 + +> **UI 样式和布局参考 v1.0,具体功能设计按 v2.6。** + +| 维度 | 参考谁 | 说明 | +|------|--------|------| +| **样式(CSS/主题/配色)** | v1.0 朝堂古风设计系统 | 深蓝底 + mc-card + m-section + CSS 变量体系 | +| **布局(页面结构/组件位置)** | v1.0 Dashboard | 侧边栏导航 + 主内容区 + 弹窗详情 | +| **组件风格** | v1.0 TaskModal / EdictBoard | 卡片/管道/状态徽章等视觉元素 | +| **功能设计** | v2.6 课题7+9 | 5页精简、推送分级、评论线程、DAG拓扑、AI Briefing | +| **交互逻辑** | v2.6 课题7+9 | 四种交互模式、超时兜底、乐观锁 | + +## 参考文件 + +- **v1.0 前端源码**: `~/.sanguo_projects/sanguo_moziplus/dashboard/` +- **v1.0 设计文档**: `~/.sanguo_projects/sanguo_moziplus/docs/design/dashboard-frontend-design.md` +- **v2.6 课题7+9方案**: `~/.openclaw/sanguo_projects/sanguo_moziplus_v2/docs/design/topic7-interaction-dashboard-proposal.md` +- **Edict 组件参考**: `~/.openclaw/knowledge_base/edict/` + +## 页面对照(v1.0 10Tab → v2.6 5页) + +| v2.6 页面 | v1.0 来源 | 功能变更 | +|-----------|----------|---------| +| 任务看板 | 任务看板 + 编排调度 | 合并,加 DAG 拓扑视图 | +| 全局监控 | 将军总览 + 会话监控 | 合并,加心跳可视化 | +| 产出档案 | 奏折阁 | 保留,加产出物预览 | +| 系统配置 | 模型配置 + 技能配置 | 合并为一个配置页 | +| AI 简报 | 无(新增) | v2.6 新增,AI 自动生成 | + +## 技术栈(延续 v1.0) + +- React 18 + Vite + TypeScript +- Zustand 状态管理 +- Tailwind CSS +- 轮询(Phase 2) → WebSocket(Phase 3) diff --git a/docs/design/frontend-redesign-v2.6.md b/docs/design/frontend-redesign-v2.6.md new file mode 100644 index 0000000..7f1d0b2 --- /dev/null +++ b/docs/design/frontend-redesign-v2.6.md @@ -0,0 +1,213 @@ +# v2.6 前端任务管理改造方案 + +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-17 +**状态**: 待确认 + +--- + +## 一、从 v2.0 定位出发 + +### v2.0 的核心定位(PRD v2.0) + +> **用户提一个方向,AI 帮他梳理成清晰目标,然后自主组织 Agent 团队执行,全程持续指挥、自主协调、主动汇报,人只在关键决策点介入。** + +四条信念: +1. AI 帮人想清楚要什么 +2. 编排层是 AI 指挥官,不是确定性代码 +3. Agent 之间共享意识,不传递消息 +4. 人退到系统的边缘 + +### Dashboard 在 v2.0 中的角色 + +**不是"管理控制台",而是"AI 工作可视化窗口"**。 + +| 传统 Dashboard | AI Native Dashboard | +|---|---| +| 用户主动操作(点按钮、填表单) | AI 主动展示(推送状态、产出、决策请求) | +| 用户监控一切 | AI 只在关键点拉人,其余自主 | +| 表格+表单为主 | 时间线+卡片+对话为主 | +| 用户是驾驶员 | 用户是乘客,偶尔导航 | + +参考的优秀实践: +- **Devin 模式**:全自主执行,Dashboard 是观察窗口 +- **Claude Code App**:多 session 并排 + diff review +- **Linear**:精准通知,不噪音,状态流转极简 +- **Edict**:MiniPipe 管道可视化 + 心跳 + 操作按钮 +- **Hermes Kanban**:任务卡片 + 结果区 + 评论线程 + 事件流 + Nudge 按钮 + +--- + +## 二、当前后端能力盘点 + +### 已有(v2.6 F1-F18 实现) + +| 能力 | API | 数据表 | +|------|-----|--------| +| 多项目管理 | GET/POST /api/projects | _registry.yaml | +| 任务 CRUD | GET/POST /api/projects/{pid}/tasks | tasks | +| 任务认领 | POST /tasks/{id}/claim | tasks.claimed_at | +| 状态流转 | POST /tasks/{id}/status | tasks.status + events | +| 评论/Handoff | GET/POST /tasks/{id}/comments | comments | +| 产出写入 | GET/POST /tasks/{id}/outputs | outputs | +| 决策记录 | POST /tasks/{id}/decisions | decisions | +| 观察/吹哨 | POST /tasks/{id}/observations | observations | +| 评审 | GET/POST /tasks/{id}/reviews | reviews | +| 事件流 | GET /tasks/{id}/events(缺) | events | +| SSE 推送 | GET /api/events | SSEBroker | +| Daemon 状态 | GET /api/daemon/status | 内存 | +| 经验沉淀 | experiences.jsonl + 读取API | experiences | +| Agent 注册 | agents 表 | agents | + +### 缺失但设计中有 + +| 能力 | 设计位置 | 说明 | +|------|---------|------| +| **任务详情聚合 API** | §9.1 `GET /tasks/{id}` 返回关联数据 | 前端需要一次性拿全部关联数据 | +| **任务事件查询 API** | §9.1 `GET /tasks/{id}/events` | Flow Log 时间线 | +| **任务关联经验 API** | §9.1 | 关联经验展示 | +| **Checkpoint 机制** | 课题7 §6 + §11 前端架构 | 三种检查点(验证/决策/执行),后端无实现 | +| **通知推送对接** | §9.3 SSE + §7 异常通知 | SSEBroker 存在,但 ticker 未推送事件 | +| **AI Briefing** | 课题7 §8 + §3.5 | 日报/周报自动生成,完全未实现 | +| **/api/daemon/sessions** | §9.2 | 活跃 Agent session 列表 | + +--- + +## 三、改造方案 + +### 设计原则 + +1. **从 AI Native 出发,不是从后端有什么出发** +2. **前后端联动**——前端需要的数据,后端必须提供;后端已有的数据,前端必须展示 +3. **缺的功能标记为 TODO**,不隐藏也不糊弄 +4. **参考 Edict + Hermes + Claude Code 的优秀实践** + +### 页面设计(5页 + 通知中心) + +#### P1. 任务看板(核心页面) + +**参考**: Edict EdictBoard + Hermes Kanban + +**卡片设计**(v2.0 简化版,无 DAG 节点概念): + +``` +┌─────────────────────────────────────────────┐ +│ task-013 │ +│ 动量因子策略回测 │ +│ │ +│ 🔄 working 👤 agent1 📊 coding │ +│ ⏱ 12min前创建 ⚠️ standard │ +│ │ +│ ┌─ 状态管线 ──────────────────────────────┐ │ +│ │ ●pending → ●claimed → ◉working → ○done │ │ +│ └──────────────────────────────────────────┘ │ +│ │ +│ [⏸ 暂停] [🚫 取消] [📋 详情] │ +└─────────────────────────────────────────────┘ +``` + +数据来源:tasks 表全部字段,映射清晰。 + +#### P2. 任务详情面板(Modal) + +**参考**: Edict TaskModal + Claude Code diff review + Hermes result section + +**8 个区域**(按优先级排列): + +| # | 区域 | 数据来源 | 交互 | 状态 | +|---|------|---------|------|------| +| 1 | **基本信息** | tasks 表 | 只读 | ✅ 后端有 | +| 2 | **状态管线** | tasks.status + VALID_TRANSITIONS | 按钮(基于守卫) | ✅ 后端有 | +| 3 | **产出物** | outputs 表 | 预览/下载 | ✅ 后端有 | +| 4 | **评审记录** | reviews 表 | 只读展示 | ✅ 后端有 | +| 5 | **事件时间线** | events 表 | 时间线展示 | ⚠️ 需补 API | +| 6 | **评论/决策** | comments + decisions 表 | 只读展示 | ✅ 后端有 | +| 7 | **关联经验** | experiences 表 | 只读展示 | ⚠️ 需补 API | +| 8 | **Checkpoint** | ❌ 后端未实现 | 占位("Checkpoint 功能开发中") | ❌ TODO | + +#### P3. 全局监控 + +保留 v1.0 的 MonitorPanel 适配 v2.6 API。新增 Agent 状态展示。 + +#### P4. 系统配置 + +保留 v1.0 的 SettingsPanel + 项目管理。适配 v2.6 的项目 API。 + +#### P5. AI Briefing + +**参考**: v2.0 设计 §8(日报/周报格式) + +当前状态:后端未实现。前端做占位页面,显示"Briefing 功能开发中"。 + +#### P6. 通知中心(Header 铃铛) + +**参考**: v2.0 设计 §3.4 + Linear 通知 + +当前状态:SSE broker 存在但未对接。前端做铃铛 UI + 占位。 + +### 后端改动清单 + +| 优先级 | 改动 | 说明 | 工作量 | +|--------|------|------|--------| +| **P0** | `GET /tasks/{id}?expand=all` | 任务详情聚合(一次返回 comments+outputs+reviews+events+decisions) | 30min | +| **P0** | `GET /tasks/{id}/events` | 事件时间线查询 | 15min | +| **P1** | `GET /tasks/{id}/experiences` | 关联经验查询 | 15min | +| **P1** | ticker 事件推送 SSE | ticker 状态变更 → SSE push | 1h | +| **P2** | Checkpoint 数据模型 + API | 新表 + 交互 API | 设计+编码 3-4h | +| **P2** | AI Briefing 生成 | 聚合数据 + 模板 | 设计+编码 2-3h | + +### 前端改动清单 + +| 优先级 | 改动 | 说明 | 工作量 | +|--------|------|------|--------| +| **P0** | EdictBoard 重写 | v2.0 任务卡片 + 状态管线 + 操作按钮 | 2h | +| **P0** | TaskModal 重写 | 8 区域详情面板 | 3h | +| **P1** | api.ts 补新 API | 对接聚合 API + events + experiences | 30min | +| **P1** | 通知中心占位 | 铃铛 + 下拉面板 | 30min | +| **P1** | AI Briefing 占位 | 页面框架 + "开发中" | 15min | + +### 执行顺序 + +``` +Phase 1: 后端补 API(P0 的 3 个接口) ~1h +Phase 2: 前端核心重写(EdictBoard + TaskModal) ~5h +Phase 3: 前端补完(通知+Briefing占位) ~1h +Phase 4: 联调 + 部署 + 端到端验证 ~1h +总计: ~8h +``` + +### Checkpoint 和 AI Briefing 的处理 + +**v2.8/M3 更新**: Checkpoint 已完成设计(`docs/design/v2.8-state-enhancement.md`),作为 M3 专项开发。 +- 三种 Checkpoint 类型(验证/决策/执行)+ Artifact 成果物面板 +- 依赖 v2.8 新增的 `waiting_human` 状态 +- 前端已有 CheckpointPanel.tsx(350行)和 ArtifactPanel.tsx(185行),扩展对接新 API + +AI Briefing 仍未实现,保持占位。 + +### v2.8 前端易用性改造 + +详见 `docs/design/v2.8-state-enhancement.md` §三,核心改动: + +1. **筛选栏两行布局**(仿 v1.0): + - 第 1 行:项目下拉 + 活跃/归档/全部 切换 + 一键归档 + - 第 2 行:11 个状态筛选 + 搜索框 + +2. **卡片快捷按钮**(最多 3 个):每个状态有专属操作按钮,直接调 API + toast 反馈 + +3. **项目下拉移入筛选栏**:支持"全部任务"跨项目聚合 + +4. **新建军令 title 可选**:后端自动生成(description 前 30 字 + "…") + +--- + +## 四、与优秀实践的对齐 + +| 实践 | 我们借鉴了什么 | +|------|--------------| +| **Edict MiniPipe** | 任务卡片上的状态管线可视化(pending→claimed→working→done) | +| **Hermes Kanban** | 卡片 + 结果区 + 评论线程 + 事件流的组合布局 | +| **Claude Code** | TaskModal 的多 Tab 切换(产出/评审/事件),产出 diff 风格展示 | +| **Linear** | 通知铃铛 + 精准推送(不噪音);状态流转按钮守卫 | +| **Devin** | AI 自主执行的观察窗口理念——Dashboard 不需要密集操作 | +| **OpenClaw Control Center** | WebChat 入口 + Dashboard 入口的双入口对等理念 | diff --git a/docs/design/gateway-watchdog.md b/docs/design/gateway-watchdog.md index 9699c28..959fd17 100644 --- a/docs/design/gateway-watchdog.md +++ b/docs/design/gateway-watchdog.md @@ -1,161 +1,144 @@ # Gateway Watchdog 设计文档 -**版本**: 2.0 +**版本**: 1.0 **作者**: 庞统(副军师)🐦 -**日期**: 2026-06-02 +**日期**: 2026-05-28 **状态**: 已实现 --- ## 1. 问题背景 -### 1.1 历史问题(v1.0 覆盖) +zhipu GLM-5.1 在高峰期返回 **429 限流**("该模型当前访问量过大,请您稍后再试",errorCode=1305),导致: -zhipu GLM-5.1 在高峰期返回 **429 限流**,导致 Gateway 假死。 +1. Gateway 调用失败,`stopReason=error` +2. Session 进入 stalled 状态 +3. stalled 后无法自动恢复,必须手动重启 Gateway +4. 所有 Agent 假死,直到人工介入 -### 1.2 新增问题(v2.0 新增) +### 历史记录 -线上观察发现两种新的致命信号,Gateway 自身无法恢复,必须重启: +- 2026-05-26 09:00 CST — 429 导致假死 +- 2026-05-28 ~10:00 CST — 429 导致假死,持续约 1 小时 +- 累计 9 次 session.stalled 事件 -| 信号 | 日志关键字 | 现象 | -|------|-----------|------| -| **所有 provider 挂了** | `lane task error` + `FailoverError` | 主模型 + fallback 全部失败,所有 Agent 无法工作 | -| **Session 卡死** | `stalled session` + `recovery=none` | Gateway diagnostic 检测到 session 卡死但承认自己恢复不了 | +## 2. 双层防护 -### 1.3 为什么 v1.0 检测不到 +### 2.1 第一层:跨 Provider Fallback(立即生效) -v1.0 扫描的是 **session jsonl**(`~/.openclaw/agents/*/sessions/*.jsonl`),但这些关键字实际出现在 **Gateway 进程日志**(`/tmp/openclaw/openclaw-{日期}.log`)中。两类日志的数据格式完全不同。 +**配置**: `~/.openclaw/openclaw.json` -## 2. 检测规则(v2.0) - -### 2.1 数据源 - -**Gateway 进程日志**:`/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log` - -- JSON lines 格式,每行一条记录 -- 关键字在 `"message"` 字段中(也在 `"1"` 字段中有副本) -- 每天自动轮转,watchdog 只读当天文件 - -### 2.2 三条规则 - -| 规则 | 匹配条件 | 阈值 | 含义 | 严重度 | -|------|---------|------|------|--------| -| **R1** | `message` 含 `lane task error` **且** 含 `FailoverError` | ≥2 次/120s | 所有 provider 都挂了,不重启没救 | 🔴 致命 | -| **R2** | `message` 含 `stalled session` **且** 含 `recovery=none` | ≥3 次/120s | Gateway 承认自己恢复不了 | 🟠 严重 | -| **R3** | `message` 含 `rate_limit` **或** 含 `"429"` | ≥2 次/120s | API 限流 | 🟡 警告 | - -### 2.3 匹配示例 - -**R1 匹配日志行**: ```json -{"_meta":{"logLevelName":"ERROR"},"message":"lane task error: lane=main durationMs=93772 error=\"FailoverError: ⚠️ API rate limit reached. Please try again later.\""} +{ + "model": { + "primary": "zhipu/glm-5.1", + "fallbacks": ["deepseek/deepseek-v4-pro"] + } +} ``` -**R2 匹配日志行**: -```json -{"_meta":{"logLevelName":"WARN"},"message":"stalled session: sessionKey=agent:main:main state=processing age=266s recovery=none"} +- zhipu 429 时自动切 deepseek(不同 provider,不会一起死) +- deepseek-v4-pro 也是免费的,支持 1M 上下文 +- Gateway 原生支持,无需额外代码 + +### 2.2 第二层:Watchdog 自动重启(兜底) + +即使 fallback 也失败(所有 provider 同时挂),watchdog 检测到连续 429 后自动重启 Gateway。 + +## 3. Watchdog 设计 + +### 3.1 检测原理 + +**数据源**: Gateway 的 session jsonl 日志 + +``` +~/.openclaw/agents/{agent-id}/sessions/*.jsonl ``` -**R3 匹配日志行**: +**429 精确特征**: ```json -{"_meta":{"logLevelName":"WARN"},"message":"lane task error: ... error=\"FailoverError: ...\"","providerRuntimeFailureKind":"rate_limit"} +{ + "message": { + "stopReason": "error", + "errorCode": "1305", + "errorMessage": "429 该模型当前访问量过大,请您稍后再试" + } +} ``` -### 2.4 判定逻辑 +**判定条件**(全部满足才算 429): +1. `stopReason === "error"` +2. `errorMessage` 包含 `"429"` 或 `errorCode` 为 `"1305"` + +**不会误判**: +- 正常 stop(`stop=stop`、`stop=toolUse`)不算 +- 其他 error(如 context overflow 的 errorCode 不同)不算 +- `stopReason` 不是 `"error"` 的不算 + +### 3.2 检测流程 ``` 每分钟执行 - │ - ├─ 1. Gateway health check - │ └─ 失败 → 直接重启(可能 Gateway 进程已死) - │ - ├─ 2. 读当天 Gateway 日志,python3 过滤最近 120 秒的行 - │ - ├─ 3. 三条规则分别 grep 计数 - │ - ├─ 4. 任一规则命中阈值 - │ ├─ 在冷却期内 → 只 log 不重启 - │ └─ 不在冷却期 → 重启 Gateway + 记录原因 - │ - └─ 5. 都没命中 → 正常 + │ + ├─ Gateway health check + │ ├─ 失败 → 直接重启(可能 Gateway 进程已死) + │ └─ 成功 → 继续 + │ + ├─ 遍历所有 agent session jsonl + │ ├─ 跳过 trajectory 文件 + │ ├─ 跳过 120 秒内未修改的文件(性能优化) + │ ├─ 跳过 <100 字节的文件 + │ └─ 统计 CHECK_WINDOW(120s) 内的 429 错误数 + │ + ├─ 判断 + │ ├─ 有新 429 → consecutive++ + │ └─ 无新 429 → consecutive=0(重置) + │ + └─ consecutive >= THRESHOLD(3) → 重启 Gateway + 重置计数 ``` -## 3. 防重启风暴 - -- **冷却期**:重启后 5 分钟内再次检测到问题只 log 不重启 -- **状态文件**:`/tmp/gateway-watchdog-state`(JSON) - ```json - {"last_restart_time":"2026-06-02T21:00:00+08:00","last_restart_reason":"R1","cooldown_until":1717333800} - ``` - -## 4. 重启原因记录 - -每次重启自动追加到 `/tmp/gateway-watchdog-restarts.log`(永久文件,用于统计分析): - -```json -{"time":"2026-06-02T21:00:00+0800","reason":"R1","detail":"FailoverError x3","counts":{"r1":3,"r2":0,"r3":1}} -``` - -字段说明: -- `time`:重启时间(ISO 格式) -- `reason`:触发规则(R1/R2/R3/health_fail) -- `detail`:匹配到的关键字摘要 -- `counts`:所有三条规则的实际命中次数(无论哪条触发都记录全量) - -## 5. 参数 +### 3.3 参数 | 参数 | 默认值 | 说明 | |------|--------|------| | CHECK_WINDOW | 120s | 检查最近多少秒的日志 | -| R1_THRESHOLD | 2 | FailoverError 触发阈值 | -| R2_THRESHOLD | 3 | stalled recovery=none 触发阈值 | -| R3_THRESHOLD | 2 | rate_limit/429 触发阈值 | -| COOLDOWN | 300s | 重启后冷却期 | +| THRESHOLD | 3 | 连续检测到多少次 429 才重启 | | 检测频率 | 60s | crontab 每分钟执行 | -## 6. 文件位置 +### 3.4 状态文件 -| 文件 | 路径 | 说明 | -|------|------|------| -| 脚本 | `scripts/gateway-watchdog.sh` | watchdog 主脚本 | -| 本文档 | `docs/design/gateway-watchdog.md` | 设计文档 | -| 运行日志 | `/tmp/gateway-watchdog.log` | crontab 重定向输出 | -| 状态文件 | `/tmp/gateway-watchdog-state` | 冷却期控制 | -| 重启记录 | `/tmp/gateway-watchdog-restarts.log` | 重启原因(永久追加) | -| 锁文件 | `/tmp/gateway-watchdog.lock` | 防并发 | +`/tmp/gateway-watchdog-429-count` — 记录连续 429 检测次数,重启后重置为 0。 -## 7. 部署 +### 3.5 日志 + +`/tmp/gateway-watchdog.log` — 每次执行的检测结果。 + +## 4. 文件位置 + +| 文件 | 路径 | +|------|------| +| 脚本 | `scripts/gateway-watchdog.sh` | +| 本文档 | `docs/design/gateway-watchdog.md` | +| 日志输出 | `/tmp/gateway-watchdog.log` | +| 状态文件 | `/tmp/gateway-watchdog-429-count` | + +## 5. 部署 ```bash # crontab 每分钟执行 (crontab -l 2>/dev/null | grep -v "gateway-watchdog"; \ - echo "* * * * * /Users/chufeng/.openclaw/sanguo_projects/sanguo_moziplus_v2/scripts/gateway-watchdog.sh >> /tmp/gateway-watchdog.log 2>&1") \ + echo "* * * * * /path/to/sanguo_moziplus_v2/scripts/gateway-watchdog.sh >> /tmp/gateway-watchdog.log 2>&1") \ | crontab - ``` -## 8. 运维 +## 6. 运维 ```bash -# 查看运行日志 -tail -f /tmp/gateway-watchdog.log +# 查看日志 +tail -20 /tmp/gateway-watchdog.log -# 查看状态(冷却期等) -cat /tmp/gateway-watchdog-state - -# 查看重启历史 -cat /tmp/gateway-watchdog-restarts.log | python3 -m json.tool - -# 统计重启原因 -grep "reason" /tmp/gateway-watchdog-restarts.log | python3 -c " -import json, sys -from collections import Counter -reasons = Counter() -for line in sys.stdin: - reasons[json.loads(line)['reason']] += 1 -print('重启统计:') -for k,v in reasons.most_common(): - print(f' {k}: {v}次') -" +# 查看当前 429 计数 +cat /tmp/gateway-watchdog-429-count # 手动测试 bash scripts/gateway-watchdog.sh @@ -164,20 +147,8 @@ bash scripts/gateway-watchdog.sh crontab -l | grep -v "gateway-watchdog" | crontab - ``` -## 9. 与 v1.0 的差异 +## 7. 已知局限 -| 维度 | v1.0 | v2.0 | -|------|------|------| -| 数据源 | session jsonl 文件 | Gateway 进程日志 | -| 检测规则 | 仅 429(1 条) | 3 条(FailoverError / stalled / rate_limit) | -| 重启风暴 | 无防护 | 5 分钟冷却期 | -| 原因记录 | 无 | JSON 文件永久追加 | -| 统计能力 | 无 | 结构化数据可分析 | -| 配套脚本 | 独立(gateway_monitor.py 不动) | 不冲突 | - -## 10. 已知局限 - -1. **事后检测** — 检测的是已发生的错误,不是预防 -2. **只读当天日志** — 跨天 0 点时日志轮转,前一分钟的日志在新文件里但文件名不同(暂不处理,影响极小) -3. **无通知** — 重启后没有主动推送通知(可后续接入飞书/邮件) -4. **单机** — 只能在 Gateway 所在机器上运行 +1. **事后检测** — 检测的是已发生的 429,不是预防 +2. **重启治标** — 重启 Gateway 恢复 stalled session,但不解决 zhipu 限流根因 +3. **无通知** — 重启后没有主动通知用户(可后续加飞书/邮件通知) diff --git a/docs/design/mail-fix-design.md b/docs/design/mail-fix-design.md new file mode 100644 index 0000000..09329c7 --- /dev/null +++ b/docs/design/mail-fix-design.md @@ -0,0 +1,225 @@ +# Mail 修复方案设计文档 + +> 版本:v1.0 | 日期:2026-05-22 | 作者:庞统 | 状态:评审中 + +## 1. 背景与问题 + +moziplus v2 的飞鸽传书(Mail)功能当前存在多个 bug 和设计缺陷,导致 v2 Mail 基本不可用。 + +### 已确认的 Bug + +| 编号 | 问题 | 严重程度 | +|------|------|---------| +| M-BUG-1 | ticker 不扫 `_mail` 项目,邮件永远 pending | 🔴 阻塞 | +| M-BUG-2 | `send_mail` 对 inform 类型直接 `status=done`,无投递确认 | 🔴 阻塞 | +| M-BUG-3 | Tab badge 取值错误(用 sseEvents 而非 mail API) | 🟡 体验 | +| M-BUG-4 | `is_read` / `mark_executed` 语义与 Agent 系统不匹配 | 🟡 设计 | +| M-BUG-5 | MailPanel 无发送入口 | 🟡 功能缺失 | + +### 调查发现 + +1. **v1 投递卡死根因**:`isAgentBusy()` 进程死后仍返回 true + Gateway 永远存活(PID 有效)→ webchat 占用 main session lock → poller 永远认为忙碌 +2. **v2 不存在此问题**:spawner 用 `--session-id uuid4()` 创建新 session,不走 main session lock +3. **v2 Mail 和 v1 Sanguo Mail 是两套独立系统**:v2 Mail 存在 `_mail/blackboard.db`,v1 Sanguo Mail 用文件系统 inbox + +## 2. 设计原则 + +1. **Mail = 通信层,Task = 任务层**:Mail 不加 depends_on,复杂工作用 Task +2. **done = Agent 处理完返回**,不是收到即 done +3. **统一用 done 标记完成**,废弃 `is_read` / `mark_executed` +4. **inform 也不直接 done**:统一从 pending 开始,由 Agent 处理后写 done +5. **conversation_id 只做前端聚合**,不加后端依赖机制 + +## 3. Mail vs Task 约束 + +| 场景 | 用 Mail | 用 Task | +|------|---------|---------| +| 通知/同步信息 | ✅ | ❌ | +| 简单请求(< 10 分钟) | ✅ | ❌ | +| 问问题 | ✅ | ❌ | +| 多步骤/有依赖/需追踪 | ❌ | ✅ | +| 需要产出物追踪 | ❌ | ✅ | +| 不确定复杂度 | 先 Mail → 发现复杂 → 转 Task | — | + +Mail 转 Task 的约束: +- 当前 Task 创建入口只有 Dashboard 和庞统的 Control UI +- Agent 不会调 v2 `/api/mail` 发邮件(不知道 API 存在) +- Agent 不会主动创建 Task(只能通过 mozi CLI) +- **结论:Mail 转 Task 需要庞统或用户手动操作,不自动转换** + +## 4. 修复方案 + +### P0-1:ticker 加 `_mail` 虚拟项目扫描 + +**问题**:`_mail` 不在 registry 中,ticker 只扫 registry + `_general`,不扫 `_mail`。 + +**修复**:在 ticker 的 tick 循环中,和 `_general` 一样加 `_mail` 虚拟项目处理。 + +**文件**:`src/daemon/ticker.py` + +**改动**: +```python +# _tick_all_projects 中,_general 之后加 _mail + +# 虚拟项目 _mail:飞鸽传书 +mail_db = Path(self.registry.root) / "_mail" / "blackboard.db" +if mail_db.exists() and "_mail" not in active_project_ids: + try: + pr = await self._tick_project("_mail", { + "id": "_mail", "name": "飞鸽传书", + "status": "active", "source": "virtual", + }) + results["projects"]["_mail"] = pr + except Exception: + logger.exception("Tick %d _mail error", tick_num) + results["projects"]["_mail"] = {"error": str(e)} +``` + +**预估**:~10 行 + +### P0-2:send_mail 统一 pending + +**问题**:`send_mail` 对 `type=inform` 直接设 `status=done`,无投递确认。 + +**修复**:inform 和 text 统一从 `pending` 开始,由 Agent 处理后写 done。 + +**文件**:`src/api/mail_routes.py` + +**改动**: +```python +# 之前: +status="done" if body.get("type") == "inform" else "pending", + +# 之后: +status="pending", +``` + +**预估**:1 行 + +**注意**:inform 类型邮件的 Agent spawn prompt 需要引导 Agent 快速确认(不需要复杂执行),在 spawner 的 spawn message 中根据 task_type 区分。 + +### P1-1:Tab badge + mailUnread 计算逻辑修正 + +**问题**: +- Tab badge 用 `sseEvents`,不是 mail API 的实际未读数 +- `store.ts` 的 `mailUnread` 按 `!m.is_read` 计算,但废弃 is_read 后语义不对 +- failed 的邮件 is_read 仍为 false,会被误计为 unread + +**修复**: + +**文件 1**:`src/frontend/src/store.ts`(mailUnread 计算逻辑) +```typescript +// 之前: +mailUnread: mails.filter(m => !m.is_read).length, + +// 之后:按 status 计算,unread = 需要处理的邮件数 +mailUnread: mails.filter(m => !['done', 'failed', 'cancelled'].includes(m.status)).length, +``` + +**文件 2**:`src/frontend/src/App.tsx`(Tab badge 取值) +```typescript +// 之前: +const unread = ((useStore.getState().sseEvents || []) as any[]) + .filter((n: any) => !n.read).length; + +// 之后: +const unread = useStore.getState().mailUnread || 0; +``` + +**预估**:2 行 + +### P1-2:废弃 is_read / mark_executed,统一用 done + +**问题**: +- `is_read` 在 Agent 系统里无意义(Agent 不需要"已读") +- `mark_executed` 是给人手动标记的,但语义模糊 +- Agent 通过 Task 状态机写 done 后,`is_read` 仍是 false,前端还显示"未读" + +**修复**: + +**后端**:`src/api/mail_routes.py` +- 删除 `mark_read` 和 `mark_executed` 端点(或标记 deprecated) +- `list_mail` 返回时,`status=done` 的邮件 `is_read` 强制为 true +- 不改数据库字段(保持向后兼容),只在 API 层面调整 + +**前端**:`src/frontend/src/components/MailPanel.tsx` +- 去掉"标记已读"和"标记已执行"按钮 +- 改为展示邮件状态:`pending`(未处理)/ `claimed/working`(处理中)/ `done`(已处理)/ `failed`(失败) +- 未处理邮件 = `status != done && status != failed` + +**文件**:`mail_routes.py` + `MailPanel.tsx` + +**预估**:~30 行 + +### P1-3:前端加人工重试按钮(failed → pending) + +**问题**:投递失败的邮件没有重试入口。 + +**修复**:MailPanel 中对 `failed` 状态的邮件显示"重试"按钮。 + +**后端**:`src/api/mail_routes.py` 新增端点或复用 status 更新 +```python +# PATCH /api/mail/{task_id}/status +# {"status": "pending"} → 重置为 pending,ticker 重新调度 +``` + +**前端**:`MailPanel.tsx` 加重试按钮 + +**预估**:~20 行 + +### P2-1:投递失败自动 escalate + +**问题**:Agent 多次失败后无人知道。 + +**修复**:ticker 检测 `_mail` 任务的 `retry_count >= 3` → escalate 给庞统。 + +**文件**:`src/daemon/ticker.py`(_check_timeouts 中已有 escalate 逻辑,_mail 自然享受) + +**预估**:0 行(现有逻辑已覆盖) + +## 5. 邮件状态机 + +``` +pending → claimed → working → done + │ │ + └→ failed ─┘→ pending(人工重试) +``` + +- `pending`:邮件已创建,等待调度 +- `claimed`:ticker 调度,分配给 Agent +- `working`:Agent 正在处理 +- `done`:Agent 处理完成 +- `failed`:处理失败,可人工重试 + +邮件复用 Task 的状态机和状态转换逻辑,不需要独立的状态机。 + +## 6. 不做的事 + +| 项目 | 原因 | +|------|------| +| Mail 加 depends_on | 当前不需要,复杂依赖用 Task | +| Mail Tab 加发送入口 | P2,当前通过 API 发送足够 | +| inform 走简化流程(跳过 working/review) | 先统一走 Task 状态机,确认能跑通再优化 | +| conversation_id 后端依赖 | 纯前端聚合字段,不加后端逻辑 | +| 投递到主 session | Mail 投递到主 Agent session(use_main_session=True),通过 Gateway queue 排队;spawn session 执行 Task 与主 session session lane 隔离,互不干扰 | + +## 7. 改动范围 + +| 文件 | 改动 | 预估行数 | +|------|------|---------| +| `src/daemon/ticker.py` | `_tick_all_projects` 加 `_mail` 虚拟项目 | ~10 行 | +| `src/api/mail_routes.py` | send_mail 统一 pending + 废弃 mark_read/mark_executed + 重试端点 | ~30 行 | +| `src/frontend/src/App.tsx` | Tab badge 改用 mailUnread | ~1 行 | +| `src/frontend/src/components/MailPanel.tsx` | 废弃已读/已执行按钮 + 状态展示 + 重试按钮 | ~40 行 | + +总计约 80 行,4 个文件。 + +## 8. 测试计划 + +| 用例 | 验证方式 | 预期 | +|------|---------|------| +| Mail 投递到 Agent | POST /api/mail → ticker 调度 → Agent spawn → done | 邮件状态 pending → done | +| Tab badge 数字 | 发 3 封邮件,2 封 done | badge 显示 1 | +| inform 类型不直接 done | POST /api/mail {type: inform} | 初始 status=pending | +| 重试按钮 | 邮件 failed → 点重试 → status 回 pending → ticker 重新调度 | 重新执行 | +| escalate | 邮件连续 3 次失败 | 自动 escalate 给庞统 | diff --git a/docs/design/polling-ux-proposal.md b/docs/design/polling-ux-proposal.md new file mode 100644 index 0000000..61b64e1 --- /dev/null +++ b/docs/design/polling-ux-proposal.md @@ -0,0 +1,103 @@ +# 轮询 UX 优化方案 + +> 日期:2026-05-20 +> 作者:庞统 +> 状态:待评审 +> 问题:5 秒全局轮询导致输入框失焦,用户无法在搜索框、新建对话框等输入文字 + +## 一、现状 + +### 当前轮询机制 +- `setInterval` 每 1 秒减 countdown,countdown 归零时 `loadAll()` +- `loadAll()` 每 5 秒执行:`loadLive()` → `loadProjects()` → `loadV2Tasks()` → 可选 `loadAgentConfig()` +- 右上角显示 `⟳ 5s` 倒计时 + +### 问题分析 +1. **输入失焦**:`set({ v2tasks: ... })` 触发 EdictBoard 重渲染,所有受控输入框失焦 +2. **请求浪费**:用户无操作时仍每 5 秒全量刷新 +3. **全部任务模式**:聚合 N 个项目的请求,5 秒一次压力大 +4. **视觉干扰**:倒计时数字不停跳动,分散注意力 + +## 二、方案:智能轮询 + 输入保护 + +### 2.1 输入焦点保护(核心修复) + +**原理**:轮询前检测是否有输入焦点,有则延迟刷新。 + +```typescript +// 判断是否有活跃输入 +function hasActiveInput(): boolean { + const el = document.activeElement; + if (!el) return false; + const tag = (el as HTMLElement).tagName; + return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' + || (el as HTMLElement).isContentEditable; +} +``` + +**轮询逻辑调整**: +- 有输入焦点 → 跳过本次 `loadV2Tasks()`,只刷新轻量数据(ticker) +- 输入结束 2 秒后补刷一次任务列表(用 `blur` 事件 + debounce) +- 倒计时继续走,不暂停计时器本身 + +### 2.2 轮询间隔分级 + +| 场景 | 间隔 | 说明 | +|------|------|------| +| 活跃操作后 | 10 秒 | 用户刚点击/切换 tab | +| 空闲状态 | 30 秒 | 无操作,静默刷新 | +| 全部任务模式 | 30 秒 | 聚合请求多,降低频率 | + +**实现**:记录上次用户操作时间戳,动态调整间隔。 + +### 2.3 视觉优化 + +| 改动 | 说明 | +|------|------| +| 去掉秒级倒计时 | 改为状态指示器:`●` 绿色=正常 `●` 黄色=刷新中 `●` 红色=连接失败 | +| 手动刷新按钮 | 点击立即刷新,重置计时器 | +| 刷新时卡片闪烁 | 用 CSS transition 平滑过渡,不要整块替换 | + +### 2.4 增量刷新(可选优化) + +当前每次轮询全量替换 `v2tasks` 数组。优化为: +- 比对新旧数据,只有状态变化时才更新对应任务 +- 减少不必要的重渲染 + +```typescript +// 简单增量合并 +const merged = newTasks.map(nt => { + const old = oldTasks.find(ot => ot.id === nt.id); + return (old && JSON.stringify(old) === JSON.stringify(nt)) ? old : nt; +}); +``` + +## 三、改动范围 + +| 文件 | 改动 | 行数估计 | +|------|------|---------| +| `store.ts` | 轮询逻辑 + hasActiveInput + 分级间隔 + 增量合并 | ~50 行 | +| `App.tsx` | 倒计时 UI → 状态指示器 + 手动刷新按钮 | ~20 行 | +| `EdictBoard.tsx` | 搜索框添加 onFocus/onBlur 事件(可选) | ~5 行 | + +**总计**:~75 行改动,不涉及后端。 + +## 四、不做的事 + +1. ❌ 不改 WebSocket(当前 HTTP 轮询够用,未来迁移成本不高) +2. ❌ 不改后端 API(轮询是前端优化) +3. ❌ 不做 Service Worker 缓存(过度设计) + +## 五、实施优先级 + +1. **P0**:输入焦点保护(解决失焦问题) +2. **P1**:轮询间隔 10→30 秒分级(减少请求) +3. **P2**:视觉优化(倒计时→状态指示器) +4. **P3**:增量刷新(性能优化,当前数据量不需要) + +## 六、验收标准 + +1. 在搜索框、新建项目对话框输入时,5 秒内不失焦 +2. 空闲 30 秒后自动刷新任务列表 +3. 手动刷新按钮可用 +4. 右上角不再有跳动数字 diff --git a/docs/design/spawner-monitor-design.md b/docs/design/spawner-monitor-design.md new file mode 100644 index 0000000..d73364f --- /dev/null +++ b/docs/design/spawner-monitor-design.md @@ -0,0 +1,774 @@ +# Spawner Monitor 设计文档 + +> 版本:v2.0 | 日期:2026-05-26 | 作者:庞统 | 状态:v2.0 大幅更新(P0 stdout 修复 + spawn 前检查 + counter 调用级 + 假死复活术) + +## 1. 背景与问题 + +当前 `_monitor_process` 只看进程退出码,不读 stdout/stderr,不检查 session 状态,无法区分超时原因。且超时后直接 `proc.kill()`,可能丢失 Agent 执行进度。 + +`openclaw agent` 命令的实际行为: +- Gateway 有内置 timeout(默认 600s),到时间后 Gateway 会中断 Agent,进程自行退出 +- 中断后上下文保留在 session 里,用同一 session-id 再次调用可继续("续杯"机制) +- 执行过程中可能触发 auto-compaction(上下文压缩),compact 完成后自动 retrying prompt +- compact 期间进程不退出,只是执行时间变长 +- 极端情况下进程可能卡住不退出(LLM 卡死、Gateway 异常等) + +## 2. 核心设计原则 + +1. **每次 agent 调用都是独占的**:openclaw 无论成功失败都会返回,最差情况 timeout 返回。谁占用谁持有,进程退出就 release +2. **counter 生命周期是调用级**:spawn 时 acquire,进程退出就 release。不是任务级 +3. **spawn 前检查所有可用信号**:counter + 冷却期 + session state(lock/processing/compact),避免注定失败的 spawn +4. **不主动 kill 进程**:进程可能还在正常执行,kill 会丢失所有进度 +5. **续杯只有 Gateway timeout 才触发**:lock/compact/api_error 等不续杯,等 ticker +6. **escalate 不自动 kill**:超过重试上限后标记 failed + escalate,是否 kill 留给用户决定 + +## 3. Spawn 前检查(拦截无效 spawn) + +spawn_full_agent 启动进程前,依次检查: + +| # | 场景 | 检测方法 | 检测到后方案 | +|---|------|---------|-------------| +| L1 | moziplus 内部并发 | counter.can_acquire() | AgentBusyError → 等 ticker | +| L2 | API 429 冷却期 | counter.is_cooling_down() | AgentBusyError → 等 ticker | +| L3a | main session 被外部占用 | _check_session_state → lock_pid_alive | AgentBusyError → 等 ticker | +| L3b | main session 正在执行 | _check_session_state → status=processing | AgentBusyError → 等 ticker | +| L3c | main session 正在 compact | _check_session_state → recent_compact | AgentBusyError → 等 ticker | + +L1+L3 互补:counter 防 moziplus 内部并发,session state 防外部占用(webchat/Control UI/cron)。 +所有检查失败统一走 AgentBusyError → 任务保持 working → ticker 30 秒后重新调度。 + +## 4. 参数配置 + +```yaml +daemon: + gateway_timeout: 600 # 传给 openclaw agent --timeout 的值 + agent_timeout: 630 # _monitor_process 的等待时间(比 gateway_timeout 长 30s) + max_retries: 3 # 续杯上限(情况 A) + max_monitor_timeouts: 3 # monitor timeout 上限(情况 B) +``` + +## 5. 情况 A:进程在 monitor_timeout 内退出 + +``` +0s 600s 630s +├──────────────────────┤──────────────────┤ +│ Agent 执行中 │ Gateway timeout │ monitor timeout +│ │ 进程退出 │ (情况 B 才到这里) +``` + +进程退出后,读 stdout JSON(`openclaw agent --json` 输出)+ 查任务 DB 状态。 + +### JSON 输出格式 + +`openclaw agent --json` 输出到 stdout 的 JSON 结构: + +```json +{ + "status": "ok", + "summary": "completed", + "result": { + "payloads": [{ "text": "...", "mediaUrl": null }], + "meta": { + "durationMs": 5673, + "executionTrace": { + "runner": "gateway", + "fallbackUsed": false, + "fallbackReason": null + }, + "aborted": false + } + } +} +``` + +### 可用字段 + +| 字段 | 路径 | 取值范围 | 说明 | +|------|------|---------|------| +| `status` | `data.status` | `"ok"` / `"error"` / `"timeout"` | CLI 执行结果 | +| `summary` | `data.summary` | `"completed"` / 错误信息字符串 | 辅助判断 | +| `fallbackUsed` | `data.result.meta.executionTrace.fallbackUsed` | `true` / `false` | 是否 fallback | +| `fallbackReason` | `data.result.meta.executionTrace.fallbackReason` | `"gateway_timeout"` 等 | fallback 原因 | +| `payloads` | `data.result.payloads` | `[{text, mediaUrl}]` 或空数组 | Agent 回复内容 | + +### 分类原则 + +- **优先用 `status`**:`status` 是 Gateway 官方提供的执行结果,比推断准确 +- **不解析 `meta` 的其他字段**:agentMeta、systemPromptReport 等是 OpenClaw 内部信息 +- **stdout 为空 = 进程异常终止**:`openclaw agent` 正常退出一定会输出 JSON + +### A0:stdout 为空(进程异常终止) + +> 注意:A0 在判定顺序上位于 A4 之后。exit=0 + stdout 为空 + task_status=done/review +> 会被 A4/A1 兜住,不会走到 A0。 + +``` +现象: + - 进程退出,exit_code ≠ 0 + - stdout 完全为空(没有 JSON 输出) + - status 解析为 None + +原因:进程被异常终止(被 kill、崩溃等),没有走到 writeRuntimeJson + +处理: + - 记录 outcome = "process_crash" + - counter.release()(wrapped_on_complete 可能没被调用 → ticker T1 兜底) + - 不续杯 + - 任务保持 working → ticker T1 检测 PID 死 → release counter + 推回 pending + - 等 ticker 重新 dispatch +``` + +### A0b:stdout 为空但 exit=0 + +``` +现象: + - 进程退出,exit_code = 0 + - stdout 完全为空(没有 JSON 输出) + - status 解析为 None + +原因:openclaw agent --json 在某些情况下不输出 JSON(已知行为) + +处理: + - 查任务 DB 状态 + - done/review → outcome = "completed"(正常完成) + - 其他 → outcome = "agent_error"(不续杯,等 ticker) +``` + +### A1:status="ok" + summary="completed" + fallbackUsed=false + +``` +现象: + - stdout JSON status = "ok" + - summary = "completed" + - executionTrace.fallbackUsed = false + - 任务 DB status = done 或 review + +原因:Agent 正常完成 + +处理: + - counter.release()(由 wrapped_on_complete 保证) + - 记录 outcome = "completed" + - 无需其他操作 +``` + +### A2/A3:status="timeout" + +``` +现象: + - stdout JSON status = "timeout" + +原因:Gateway timeout,Agent 被中断 + +处理: + - counter.release() + - 续杯次数 +1 + - 超过上限(3) → ❌ failed + escalate + - 未超限 → 🔄 通过 spawn_full_agent 续杯 + - 续杯 message:提示 Agent 检查历史继续未完成工作 +``` + +### A4:status="ok" + 任务 DB status=failed + +``` +现象: + - stdout JSON status = "ok" + - 但任务 DB status = failed + +原因:Agent 自己判断无法完成,主动标了 failed + +处理: + - counter.release() + - 记录 outcome = "agent_failed" + - 尊重 Agent 的判断,不续杯 +``` + +### A5/A6:status="ok" + fallbackUsed=true + +``` +现象: + - stdout JSON status = "ok" + - executionTrace.fallbackUsed = true + - executionTrace.runner = "embedded" + +原因:Gateway 端超时/错误,CLI fallback 到本地 embedded 执行 + +处理: + - 查任务 DB 状态 + - done/review → release counter → 结束(fallback 成功完成了) + - working/claimed → release counter → 标 failed + escalate + - 记录 outcome = "fallback_timeout",附带 warning +``` + +### A7-A12:status="error" + +``` +现象: + - stdout JSON status = "error" + - summary 含错误信息 + +原因:各类错误(认证/连接/API/compact/lock/未知) + +处理: + - counter.release() + - 不续杯 + - 记录 outcome = "api_error" / "gateway_unreachable" / "auth_failed" 等 + - 等 ticker 重新调度 +``` + +### A 兜底:status 未知值 + +``` +现象: + - stdout 有 JSON 但 status 不是 ok/error/timeout + +原因:未预期的状态值 + +处理: + - counter.release() + - 不续杯 + - 记录 outcome = "unknown_status" + - 等 ticker 重新调度 +``` +## 6. 情况 B:monitor_timeout 到了进程还没退出 + +``` +0s 600s 630s +├──────────────────────┤──────────────────┤ +│ Agent 执行中 │ Gateway timeout │ monitor timeout 触发 +│ │ (可能没触发) │ 进程还没退出 +``` + +### B1:lock PID 已死 + sessions.json status=running + +``` +现象: + - monitor_timeout 触发,进程没退出 + - lock 文件中的 PID 已不存在(os.kill(pid, 0) 抛 ProcessLookupError) + - sessions.json 中 status = "running" + +原因:Gateway 异常退出/崩溃,没有清理 lock 和 session 状态 + 子进程可能已经变成孤儿进程 + +处理(v2.0:假死复活术): + 1. 尝试复活: + a. 修改 sessions.json,把对应 session 的 status 从 running 改为 idle + b. release counter + c. ticker 下次 dispatch 时重新投递任务给 agent + 2. 如果同一任务连续假死 ≥ 2 次: + - ❌ failed + escalate + - 记录 outcome = "session_stuck" + - escalate 消息中包含:PID、session key、诊断信息、假死次数 + 3. 不 kill(让用户决定) +``` + +### B2:lock PID 存活 + sessions.json status=running + stderr 有 compact 关键字 + +``` +现象: + - monitor_timeout 触发,进程没退出 + - lock PID 仍然存活 + - sessions.json status = "running" + - 已读的 stderr 含 "compaction" / "context-overflow" + +原因:compact 正在进行中或 compact 后 retrying prompt 仍在执行 + compact 本身可能耗时很长(最长记录 15 分钟) + +处理: + - monitor_timeout_count +1 + - 未超限(< 3) → 不 release counter → 再启动一轮 _monitor_process 继续等 + - 超限(≥ 3,累计 31.5 分钟) → ❌ failed + escalate → counter.release(),不 kill + - 记录 outcome = "compact_hanging" +``` + +### B3:lock PID 存活 + sessions.json status=running + 无 compact 关键字 + +``` +现象: + - monitor_timeout 触发,进程没退出 + - lock PID 存活 + - sessions.json status = "running" + - 无 compact 相关关键字 + +原因(两种可能): + a) LLM 推理极慢/卡死(无输出) + b) 长任务正在执行(Agent 有在输出,但整体时间超过预期) + +处理: + - monitor_timeout_count +1 + - 未超限(< 3) → 不 release counter → 再启动一轮 _monitor_process 继续等 + - 超限(≥ 3) → ❌ failed + escalate → counter.release(),不 kill + - 记录 outcome = "process_hanging" + +区别 a 和 b: + - 无法在 monitor 层面精确区分 + - escalate 消息中列出两种可能,让用户判断 +``` + +### B4:lock PID 存活 + sessions.json status≠running(如 idle) + +``` +现象: + - monitor_timeout 触发,进程没退出 + - lock PID 存活 + - sessions.json status = "idle" 或其他非 running 状态 + +原因:session 状态已被其他操作改变(如 /reset、daily reset), + 但子进程还在运行(可能是 Gateway 正在清理或延迟退出) + +处理: + - 再等 60s(给 Gateway 清理时间) + - 如果进程仍未退出 → 按 B3 处理 + - 记录 outcome = "session_state_mismatch" +``` + +## 7. 续杯机制 + +### 续杯触发条件 + +进程退出 + 任务 API 状态不是终态(done/failed/cancelled)。 + +### Session 策略 + +| 任务类型 | Session 策略 | 说明 | +|---------|-------------|------| +| `_mail` 项目 | **主 Agent session**(不带 `--session-id`) | Mail 投递到主 session | +| 普通任务 | 新 session(`--session-id uuid4`) | 未来可动态选择主/sub | + +实现:`spawn_full_agent(use_main_session=True)` → 不传 `--session-id`,dispatcher 根据 `project_id == "_mail"` 判断。 + +⚠️ **已知问题**:Mail 用 main session 和 webchat/Control UI 共享同一 session。当 webchat 占用时,spawn 会等 session lock → timeout。通过 spawn 前 L3 session state 检查提前拦截。 + +### 续杯 message + +```python +RETRY_PROMPT = """你收到一个续杯提醒。你的任务在执行过程中被中断了。 + +## 任务信息 + +- 项目: {project_id} +- 任务ID: {task_id} +- 标题: {title} +- 续杯次数: 第 {retry_count} 次(上限 {max_retries} 次) + +请检查 session 历史中你之前做了什么,然后继续未完成的工作。 + +## 操作指令 + +### 查看任务当前状态 +```bash +curl http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}?expand=all +``` + +### 如果已经完成,标记 review +```bash +curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/status \ + -H 'Content-Type: application/json' \ + -d '{{"status": "review", "agent": "{agent_id}"}}' +``` + +### 写入产出(如果之前没写) +```bash +curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/outputs \ + -H 'Content-Type: application/json' \ + -d '{{"agent": "{agent_id}", "type": "<类型>", "title": "<标题>", "content": "<内容>", "summary": "<摘要>"}}' +``` + +### 如果无法解决,标记失败 +```bash +curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/status \ + -H 'Content-Type: application/json' \ + -d '{{"status": "failed", "agent": "{agent_id}", "detail": "<失败原因>"}}' +``` + +{fallback_hint}""" +``` + +### 续杯 spawn + +```python +# 续杯时复用 session_id +session_id = task.detail.get("retry_session_id") or original_session_id + +await self.spawner.spawn_full_agent( + agent_id=agent_id, + message=RETRY_PROMPT.format(...), + session_id=session_id, # 复用! + task_id=task.id, + on_complete=on_complete, +) +``` + +## 8. 计数器设计 + +| 计数器 | 用途 | 上限 | 超限处理 | +|--------|------|------|---------| +| `retry_count` | 续杯次数(A2/A3/A10/A12) | 3 | failed + escalate | +| `connect_retry_count` | 连接失败次数(A8) | 3 | failed + escalate | +| `api_retry_count` | API 错误次数(A9) | 3 | failed + escalate | +| `lock_retry_count` | Lock 冲突次数(A11) | 3 | ticker 下个 tick 重试 | +| `monitor_timeout_count` | monitor timeout 次数(B2/B3) | 3 | failed + escalate | + +存储在 `task_attempts.metadata` JSON 中。 + +### counter 生命周期(v2.0:调用级) + +``` +spawn_full_agent 内部 acquire + │ + ├─ 进程退出 → wrapped_on_complete → counter.release() + │ ├─ A1/A4 完成 → 结束 + │ ├─ A2/A3 timeout → _do_retry 手动 release → spawn_full_agent 重新 acquire + │ └─ A7-A12 → release → 等 ticker + │ + ├─ monitor timeout(B)→ counter 不 release(进程还在跑) + │ └─ B1 假死 → 手动 release + 复活 + │ + └─ 进程崩溃/PM2 重启 → ticker _check_timeouts 检测 → 手动 release +``` + +counter 生命周期 = 调用级:spawn 时 acquire,进程退出时 release。 +只有情况 B(进程不退出)counter 保持占用。 + +wrapped_on_complete 保证 release(try/finally),即使业务回调异常也不泄漏。 + +## 9. escalate 消息格式 + +``` +⚠️ Agent {agent_id} 任务 {task_id} 执行异常 + +类型: {outcome} +累计时间: {elapsed} +PID: {pid}({存活/已死}) +Session: {session_key} +续杯次数: {retry_count}/{max_retries} + +诊断信息: +- sessions.json status: {status} +- lock PID: {lock_pid} +- 最后 stderr: {stderr_tail} +- compaction checkpoints: {recent_checkpoints} + +建议操作: +1. 查看日志: pm2 logs sanguo-moziplus-v2 +2. 检查 session: openclaw sessions --agent {agent_id} --json +3. 继续执行: 如果任务在正常执行,手动 reset 状态 +4. 终止进程: kill {pid}(强制终止,会丢失进度) + +请决定如何处理。 +``` + +## 10. 改动范围 + +| 文件 | 改动 | 预估行数 | +|------|------|---------| +| `src/daemon/spawner.py` | `_monitor_process` 重写(情况 A/B 全部分支) | ~150 行 | +| `src/daemon/spawner.py` | `spawn_full_agent` 加 `--timeout` + session_id 复用 | ~15 行 | +| `src/daemon/spawner.py` | 新增辅助方法:`_get_task_status`、`_classify_exit`、`_read_sessions_json`、`_check_lock_pid` | ~80 行 | +| `src/daemon/spawner.py` | 新增 `RETRY_PROMPT` 模板 | ~20 行 | +| `src/daemon/ticker.py` | `_check_timeouts`:暂时性失败(A8/A9/A11)不改状态,等 ticker 自然重试 | ~15 行 | +| `config/guardrails.yaml` | 无需改动 | — | +| `config/default.yaml` | 新增 `gateway_timeout`、`max_retries`、`max_monitor_timeouts` | ~3 行 | + +注:`task_attempts` 表已有 `metadata` 列(TEXT 类型),无需改 db.py/models.py。 + +总计约 280 行,3 个文件。 + +## 11. 测试计划 + +| 用例 | 模拟方式 | 验证 | +|------|---------|------| +| A1 正常完成 | E2E 已有 | 任务 done | +| A2 Gateway timeout + 续杯 | 手动设 gateway_timeout=60s,任务需要 90s | 续杯 1 次后完成 | +| A3 连 working 都没写 | 模拟 Agent 第一步就超时 | 续杯后从步骤 1 开始 | +| A4 Agent 自己 failed | Agent 输出 `status: failed` | 不续杯 | +| A5 fallback 成功 | 模拟 Gateway timeout(很难模拟) | 查任务状态决定 | +| A7 认证失败 | 改错 token | 不续杯,escalate | +| A8 Gateway 不可达 | 停 Gateway | 重试 3 次后 escalate | +| B1 假死 | kill Gateway 但保留子进程 | escalate | +| B2 compact 卡住 | 主 session 长对话触发 compact | 等待或 escalate | +| B3 进程不退出 | 模拟长时间无输出 | 等 3 轮后 escalate | +| 续杯上限 | 设 max_retries=1,任务永远完不成 | 第 2 次续杯后 failed | + +--- + +## 12. v2.8.1 补丁:Crash 健壮性(2026-05-31) + +> 作者:庞统 | 触发:R1 E2E 中 simayi 连续 crash 3 次无熔断 + +### 12.1 问题发现 + +R1 E2E 测试中,simayi-challenger 在 review 阶段连续 crash 3 次(exit=1,空 stdout): + +``` +18:32:20 Spawned (broadcast), session=None, pid=13844 + status=done, recent_compact=False ← compact 检测通过(实际 Gateway 在做 compact) + Gateway 加载 10MB main session → 触发 auto-compaction → 耗时 ~3 分钟 +18:35:47 Crash (exit=1), task_status=review + → on_complete: "review agent crashed, NOT marking done" +18:35:48 PM2 restart → ticker 重启 → re-dispatch review to simayi + recent_routing 未拦截(crash 时 routing_decision 未持久化) +18:36:51 Crash again (exit=1) +...(共 3 次 crash) +``` + +**根因**:main session 历史 10MB,Gateway 触发 compact,compact 耗时导致进程异常退出。 + +### 12.2 暴露的三个问题 + +#### P1:Compact 检测失效 + +现有检测逻辑: +```python +for cp in main_session.get("compactionCheckpoints", []): + if (now_ms - cp.get("createdAt", 0)) < 300_000: + result["recent_compact"] = True +``` + +**失效原因**: +- Gateway 的 compact 不写入 `compactionCheckpoints`(只在 session reset 时写入,且只保留 3 条) +- 实际 `compactionCount` 已达 68 次,但 checkpoints 最新一条是 10 天前 +- 因此 `recent_compact` 始终为 False + +#### P2:current_agent 不回退 + +review dispatch 时设置 `current_agent = simayi-challenger`,crash 后不回退。 +导致 `exclude_current=True` 时,simayi 被排除,但无其他 review agent 可用 → 任务卡住。 + +#### P3:Crash 无分级防护 + +- crash 后无 cooldown(cooldown 只在 api_error/429 时设置) +- review 状态无超时回收(`_check_timeouts` 只查 claimed/working) +- 无 crash 计数上限,同一 task 可无限重试 +- v2.7.2 设计文档曾讨论 crash_count 熔断,结论是"❌ 不采纳,崩溃可能是任务问题" + → 部分正确,但缺乏更精细的分级处理 + +### 12.3 修复方案 + +#### Fix-1:Compact 检测改用 session jsonl 末尾扫描 + +**原理**:Gateway 完成一次 compact 后,会在 session jsonl 文件末尾追加一条 `type=compaction` 记录。 + +```python +def _check_recent_compaction(agent_id: str, window_seconds: int = 300) -> bool: + """读 session jsonl 末尾,检查是否有 window_seconds 内的 compaction 记录""" + sessions_path = Path.home() / ".openclaw" / "agents" / agent_id / "sessions" / "sessions.json" + if not sessions_path.exists(): + return False + try: + with open(sessions_path) as f: + sessions = json.load(f) + main = sessions.get(f"agent:{agent_id}:main", {}) + session_file = main.get("sessionFile", "") + if not session_file or not Path(session_file).exists(): + return False + # 读末尾 50 行,查找最近的 compaction + from datetime import datetime, timezone + now = datetime.now(timezone.utc) + with open(session_file, "rb") as sf: + # seek to last 50KB + sf.seek(0, 2) + size = sf.tell() + sf.seek(max(0, size - 51200)) + tail = sf.read().decode("utf-8", errors="replace") + for line in reversed(tail.splitlines()): + if not line.strip(): + continue + try: + obj = json.loads(line) + except: + continue + if obj.get("type") == "compaction": + ts = obj.get("timestamp", "") + if ts: + ct = datetime.fromisoformat(ts.replace("Z", "+00:00")) + if (now - ct).total_seconds() < window_seconds: + return True + return False + except Exception: + return False +``` + +**改动范围**:`spawner.py` `_check_session_state` 方法,替换 `compactionCheckpoints` 检测逻辑。 + +**同时保留** `_compact_waits` 的 monitor compact 等待逻辑不变。 + +**边界处理**(review WARN 补充): +- jsonl 不存在 → 返回 False(无 compact = 安全) +- json.loads 失败 → 跳过该行(compact 未完成 = 不判定为 compacting) +- 只在 agent 非空闲时(status 不是 done/idle)才扫描,减少不必要 I/O +- 检测到 compacting 后同时设 cooldown(和 Fix-3a 共用),避免 ticker 反复 dispatch → AgentBusyError 循环 + +#### Fix-2:current_agent 回退 + +**原理**:crash 后在 `_task_on_complete` 中回退 `current_agent` 到 `assignee`。 + +```python +# dispatcher.py _task_on_complete 中 +if _is_review: + if outcome in ("completed", "session_revived"): + _dispatcher._mark_task_status(_task_db, _task_id, "done") + else: + logger.warning("Task %s: review agent %s (%s), NOT marking done", _task_id, aid, outcome) + # Fix-2: crash 后回退 current_agent,避免 exclude_current 卡死 + conn = get_connection(_task_db) + try: + conn.execute( + "UPDATE tasks SET current_agent = " + "(SELECT assignee FROM tasks WHERE id=?) " + "WHERE id=? AND current_agent=?", + (_task_id, _task_id, aid) + ) + conn.commit() + finally: + conn.close() +``` + +**改动范围**:`dispatcher.py` `_task_on_complete` 回调,~6 行。 + +**精确化条件**(review WARN 回应): +```python +ROLLBACK_OUTCOMES = {"crashed", "compact_failed", "process_crash", "session_stuck", "compact_hanging"} +``` +只对明确的异常 outcome 回退,skipped/cancelled 不回退。 + +**assignee 排除问题**:回退后 assignee 可能仍在 exclude 列表。但 exclude_current 的逻辑是在 `_dispatch_reviews` 中基于 `task.current_agent` 构建,回退后 current_agent=assignee(原始执行者),exclude 会排除的是 review agent(已 crash 的那个),不是 assignee。所以无冲突。 + +#### Fix-3:Crash 分级防护 + +**设计原则**:不是"禁用 agent"(v2.7.2 已否决),而是"给 agent 恢复时间 + 限制单 task 重试次数"。 + +##### 3a. Crash 后设 cooldown + +```python +# spawner.py _handle_exit else 分支(crash 走这里) +if outcome in ("crashed", "compact_failed", "process_crash", "agent_error"): + if self.counter: + self.counter.set_cooldown(agent_id, seconds=300) # 5 分钟冷却 + logger.info("Crash cooldown set for %s: 300s", agent_id) +``` + +**改动范围**:`spawner.py` `_handle_exit` else 分支,~4 行。 + +**理由**:crash 后 agent 的 session 可能处于异常状态(timeout/compacting), +5 分钟冷却给 Gateway/agent 恢复时间。如果 compact 正在进行,5 分钟后一般已完成。 + +**优化**(review INFO 采纳):cooldown 固定 5 分钟足够,不引入轮询复杂度。 +compact 场景通过 Fix-1 的 compact 检测 + AgentBusyError 覆盖,cooldown 是额外保险。 + +##### 3b. Review 超时回收 + +在 `_check_timeouts` 中增加 review 状态的超时检查: + +```python +# ticker.py _check_timeouts 末尾增加 +# review 超时 → 推回 pending(让 ticker 重新 dispatch review) +review_tasks = queries.tasks_by_status("review") +for task in review_tasks: + # 检查是否有活跃的 review agent(current_agent 对应的进程是否存活) + current = task.current_agent # 需要确认 task model 有此字段 + if current: + session_info = self.spawner.get_session_by_agent(current) if self.spawner else None + pid = session_info.get("pid") if session_info else None + if pid and self._is_pid_alive(pid): + continue # review agent 还在跑,不回收 + # 无活跃进程 → 检查超时 + updated = task.updated_at + if updated: + elapsed = (now - datetime.fromisoformat(updated)).total_seconds() / 60.0 + if elapsed > self.review_timeout_minutes: # 默认 15 分钟 + conn = get_connection(db_path) + try: + self._transition_status( + conn, task.id, "pending", + agent="daemon", + detail={"reason": "review_timeout", "elapsed_minutes": round(elapsed, 1)}, + ) + # 回退 current_agent + conn.execute( + "UPDATE tasks SET current_agent = NULL WHERE id=?", (task.id,) + ) + conn.commit() + logger.warning("Review timeout: %s (%.1fm), pushed back to pending", + task.id, elapsed) + finally: + conn.close() +``` + +**改动范围**:`ticker.py` `_check_timeouts`,~25 行。 + +**配置**:新增 `review_timeout_minutes` 参数,默认 15 分钟。 + +**"无活跃进程"定义**(review WARN 回应):双层判断: +1. `spawner.get_session_by_agent(current_agent)` → 检查内存中是否有 running session +2. 如果有 → 检查 PID 存活 → PID 活着则不回收 +3. 如果无内存记录 → 无活跃进程 → 可回收 + +**推回原子性**(review WARN 回应):推回前先 recheck 当前状态, +如果已是 pending/working/done/failed 则不推回(可能被其他操作改变了)。 + +**Fix-3b/3c 交互**(review INFO 回应):超时推回 pending → ticker 重新 dispatch → +如果又 crash,这个 crash 会通过 `_record_attempt` 记录到 task_attempts, +自然被 `_check_crash_limit` 计数。无需特殊处理。 + +##### 3c. Crash 计数 + 上限 escalate + +利用现有 `task_attempts` 表,在 dispatch 前查该 task 的最近 crash 次数: + +```python +# dispatcher.py dispatch() 入口增加 +def _check_crash_limit(self, task_id: str, db_path: Path, limit: int = 3) -> bool: + """检查 task 最近 30 分钟内的 crash 次数是否超限""" + try: + conn = get_connection(db_path) + try: + row = conn.execute( + "SELECT COUNT(*) as cnt FROM task_attempts " + "WHERE task_id=? AND outcome='crashed' " + "AND created_at > datetime('now', '-30 minutes')", + (task_id,) + ).fetchone() + return (row["cnt"] if row else 0) >= limit + finally: + conn.close() + except Exception: + return False +``` + +在 `_dispatch_reviews` 中调用: +```python +if self._check_crash_limit(task.id, db_path, limit=3): + # 超限 → 标 failed + escalate + conn = get_connection(db_path) + try: + self._transition_status( + conn, task.id, "failed", + agent="daemon", + detail={"reason": "review_crash_limit", "task_id": task.id}, + ) + finally: + conn.close() + logger.error("Task %s: 3 crashes in 30min, marking failed", task.id) + continue +``` + +**改动范围**:`dispatcher.py` 新增 ~15 行方法 + `_dispatch_reviews` 调用点 ~8 行。 + +### 12.4 改动总览 + +| 文件 | 改动 | 行数 | +|------|------|------| +| `spawner.py` | Fix-1 compact 检测替换 + Fix-3a crash cooldown | ~30 行 | +| `dispatcher.py` | Fix-2 current_agent 回退 + Fix-3c crash 计数 | ~25 行 | +| `ticker.py` | Fix-3b review 超时回收 | ~25 行 | +| **总计** | | **~80 行** | + +### 12.5 测试计划 + +| 用例 | 模拟方式 | 验证 | +|------|---------|------| +| Compact 检测 | 手动在 session jsonl 末尾写 compaction 记录 | recent_compact=True → AgentBusyError | +| current_agent 回退 | review agent crash → 检查 DB current_agent = assignee | ✅ 回退 | +| Crash cooldown | review agent crash → 检查 counter.is_cooling_down() | ✅ 5 分钟冷却 | +| Review 超时 | review 状态 15 分钟无活跃进程 → 检查推回 pending | ✅ 回收 | +| Crash 计数上限 | 同一 task 连续 crash 3 次 → 检查标 failed | ✅ escalate | +| E2E 回归 | 重新跑 v3.0 E2E 测试 | 全流程通过 | diff --git a/docs/design/technical-design-v2.6.md b/docs/design/technical-design-v2.6.md new file mode 100644 index 0000000..e5ca997 --- /dev/null +++ b/docs/design/technical-design-v2.6.md @@ -0,0 +1,998 @@ +# v2.6 技术方案设计 + +**版本**: v2.6.2-tech +**基于**: architecture-v2.6.md (v2.6.11) +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-16 + +--- + +## 1. 总体技术架构 + +### 1.1 全局架构图 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 用户层 │ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ Agent 对话入口 │ │ Dashboard (8083) │ │ +│ │ (OpenClaw Chat) │ │ React + Vite │ │ +│ └────────┬─────────┘ └────────┬─────────┘ │ +│ │ │ │ +│ 自然语言对话 REST API + SSE │ +│ │ │ │ +└───────────┼───────────────────────┼────────────────────────────────────────┘ + ▼ ▼ +┌───────────────────────────────┐ ┌──────────────────────────────────────┐ +│ Agent 执行环境(独立进程) │ │ v2 主进程(PM2: sanguo-moziplus-v2) │ +│ │ │ asyncio event loop │ +│ ┌─────────────────────────┐ │ │ │ +│ │ L0 铁律 (OpenClaw Hook) │ │ │ ┌─────────────────────────────────┐ │ +│ │ L1 角色 (SOUL/IDENTITY) │ │ │ │ API 层 (FastAPI + uvicorn) │ │ +│ │ L2 引擎注入 (bootstrap) │ │ │ │ ┌──────┐ ┌──────┐ ┌────────┐ │ │ +│ │ L3 被动参考 (Skill) │ │ │ │ │黑板API│ │项目API│ │SSE推送 │ │ │ +│ └─────────────────────────┘ │ │ │ └──┬───┘ └──┬───┘ └───┬────┘ │ │ +│ │ │ └─────┼────────┼─────────┼───────┘ │ +│ Agent → 黑板 的交互方式: │ │ │ │ │ │ +│ ① CLI (blackboard.py) ───────┼──┼──→ 直接操作黑板 DB │ +│ ② Inbox JSONL ──────────────┼──┼──→ 秒级事件推送给 Daemon │ +│ │ │ │ +│ │ │ ┌─────────────────────────────────┐ │ +│ │ │ │ Daemon 引擎层(同一 event loop)│ │ +│ │ │ │ │ │ +│ │ │ │ ticker (30s scan) │ │ +│ │ │ │ ┌──────────┐ ┌──────────────┐ │ │ +│ │ │ │ │审查流水线 │ │ 依赖推进 │ │ │ +│ │ │ │ │(4级分级) │ │ 反驳权/蒸馏 │ │ │ +│ │ │ │ └──────────┘ └──────────────┘ │ │ +│ │ │ │ ┌──────────┐ ┌──────────────┐ │ │ +│ │ │ │ │Agent调度器│ │Bootstrap拼装 │ │ │ +│ ← asyncio.create_subprocess │ │ │ └────┬─────┘ └──────────────┘ │ │ +│ _exec (spawn 不等返回) │←─┼──┼───────┘ │ │ +│ │ │ │ ┌──────────────────────────────┐│ │ +│ │ │ │ │ ActiveAgentCounter (并发控制) ││ │ +│ │ │ │ └──────────────────────────────┘│ │ +│ │ │ └─────────────────────────────────┘ │ +│ │ │ │ +│ │ │ ┌─────────────────────────────────┐ │ +│ │ │ │ 黑板数据层 (per-project SQLite) │ │ +│ │ │ │ ┌─────┐ ┌──────┐ ┌──────┐ │ │ +│ │ │ │ │tasks│ │outputs│ │reviews│ │ │ +│ │ │ │ └─────┘ └──────┘ └──────┘ │ │ +│ │ │ │ ┌──────────┐ ┌───────────┐ │ │ +│ │ │ │ │comments │ │experiences│ │ │ +│ │ │ │ │decisions │ │events │ │ │ +│ │ │ │ │observations│ │ │ │ +│ │ │ │ └──────────┘ └───────────┘ │ │ +│ │ │ └─────────────────────────────────┘ │ +└───────────────────────────────┘ └──────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────────────────────┐ +│ 配置 & 知识层(文件系统) │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │_registry │ │guardrails │ │review_ │ │prompt_ │ │ +│ │.yaml │ │.yaml │ │protocols/ │ │templates/ │ │ +│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │schemas/ │ │skills/ │ │project.yaml│ (per-project 覆盖) │ +│ │(JSON) │ │(builtin+ │ │ │ │ +│ │ │ │ custom) │ │ │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +└───────────────────────────────────────────────────────────────────────────┘ +``` + +### 1.2 多用户并发架构 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 多用户并发入口 │ +│ │ +│ 用户A ──→ OpenClaw Chat ──→ 庞统 Agent ──→ 项目X 任务A1, A2 │ +│ 用户B ──→ OpenClaw Chat ──→ 庞统 Agent ──→ 项目Y 任务B1 │ +│ 用户C ──→ Dashboard ─────→ REST API ────→ 项目X 任务C1 │ +│ │ +│ 所有用户的请求汇聚到同一个 Daemon ticker(30s 扫描全部项目) │ +│ 并发由 ActiveAgentCounter 控制(max_global=5, max_per_agent=1) │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**多用户设计要点**: + +| 维度 | 机制 | 说明 | +|------|------|------| +| 项目隔离 | per-project blackboard.db | 不同项目的任务、产出、评论完全隔离 | +| 并发控制 | ActiveAgentCounter | 全局 max_global=5 同时最多 5 个 Agent,per-agent max=1 串行执行 | +| 资源竞争 | Agent 池排队 | 张飞正在执行任务A,任务B 排队等张飞释放 | +| tick 调度 | 30s 扫全部项目 | 每轮 tick 按 project_id 轮询,每项目独立 tick | +| 事件驱动 | 共享 Inbox JSONL | 任何项目的 Agent 完成都秒级触发 Daemon | +| 前端隔离 | URL path `/api/projects/{pid}/` | Dashboard 切换项目,SSE 按 pid 过滤 | + +**当前规模定位**:单机部署(Mac mini M4, 16GB),3-5 个活跃项目,同时 ≤5 个 Agent 并发。超出此规模需要架构演进(见 §15.3)。 + +### 1.3 核心数据流 + +``` + ┌──────────┐ + │ 用户需求 │ + └────┬─────┘ + │ + 庞统规划+拆解 + │ + ┌────▼─────┐ + │ 任务写入 │ + │ 黑板 │ + └────┬─────┘ + │ + ┌──────────┼──────────┐ + ▼ ▼ ▼ + pending in_review blocked + │ │ │ + │ │ ┌────┘ + │ │ ▼ + 调度Agent 审查流水线 依赖满足 + (受Counter (分级处理) │ + 控制)│ │ → pending + │ │ + ▼ │ + ┌──────────────┐ │ + │ Agent 执行 │ │ + │ (独立进程) │ │ + └───┬──────┬───┘ │ + │ │ │ + 写产出 写评论 │ + │ │ │ + ▼ │ │ + Guardrail │ │ + 检查 ───────┤ │ + │ │ │ + ┌────┘ │ │ + ▼ │ │ + 通过 → 触发审查 │ │ + 失败 → 回 working │ │ + │ │ │ + │ ┌─────┘ │ + │ ▼ │ + │ 通过 → done ───────┤──→ 一级蒸馏 → 经验注入 + │ │ │ + │ 不通过 → 反驳权 │ + │ │ ↕ 协商 │ + │ │ max_rounds │ + │ │ ↓ 超轮次 │ + │ 庞统裁决 │ + │ │ │ + │ ├→ done/revision│ + │ └───────────────┘ + │ + └──→ 完成后检查:下游 blocked 任务是否可解锁 +``` + +**关键循环路径**(非直线流程): +1. **Guardrail 失败循环**:产出写完 → Guardrail 检查 → 失败 → 回 working → Agent 重新执行 +2. **审查-反驳循环**:审查不通过 → 反驳权协商(≤ max_rounds 轮)→ 超轮次庞统裁决 +3. **依赖解锁循环**:任务完成 → 检查下游 blocked 任务 → 依赖满足 → 下游变 pending → 调度 +4. **经验闭环**:任务完成 → 一级蒸馏 → 经验注入 → 下次 build_bootstrap 复用 + +### 1.4 交互路径详解 + +| 路径 | 方式 | 说明 | +|------|------|------| +| 用户 → Agent | OpenClaw Chat | 自然语言对话,Agent 自带身份和工具 | +| 用户 → Dashboard | 浏览器访问 8083 | REST API 查黑板、SSE 收推送 | +| Agent → 黑板 | CLI `blackboard.py` | read/claim/output/comment/decide/observe/create/review | +| Agent → Daemon | 写 Inbox JSONL | 秒级事件推送,触发 Daemon 提前 tick | +| Agent → Agent | 黑板评论 | comments 表(handoff/rebuttal/debate),替代 Mail | +| Daemon → Agent | `asyncio.create_subprocess_exec` | spawn 不等返回,下次 tick 检查产出 | +| Daemon → Dashboard | SSE `/api/events` | 任务状态变更、审查结果、通知推送 | +| Dashboard → Daemon | REST API | 查状态、手动 tick、项目管理 | + +### 1.5 组件职责矩阵 + +| 层 | 组件 | 职责 | 技术 | +|----|------|------|------| +| **用户层** | Agent 对话 | 自然语言交互入口 | OpenClaw Chat | +| | Dashboard | 可视化监控/配置/Checkpoint | React + Vite (8083) | +| **API 层** | FastAPI | REST API + SSE + 静态文件 | uvicorn | +| **引擎层** | Daemon ticker | 30s 状态扫描 + 事件驱动 | asyncio background task | +| | 调度器 | 三级调度 (Daemon/Full/Sub) | DISPATCH_RULES 配置表 | +| | 审查流水线 | 分级审查 + 反驳权 + 辩论 | review_flow.py | +| | Bootstrap 拼装 | 四层上下文 L0-L3 | prompt_templates + YAML | +| | Inbox watcher | JSONL 秒级事件推送 | asyncio 1s 轮询 | +| | 健康自检 | 逻辑死循环检测 | 连续 N tick 无变更告警 | +| **Agent 层** | Full Agent | 有身份角色的任务执行 | openclaw agent CLI | +| | Subagent | 无身份一次性检查 | sessions_spawn API | +| **数据层** | 黑板 DB | 任务/评论/产出/审查/经验 | SQLite per-project | +| | Inbox | 跨进程事件队列 | JSONL + truncate | +| | _registry.yaml | 项目注册表 | YAML (人可读) | +| **配置层** | guardrails.yaml | L1/L2 检查规则 | YAML | +| | review_protocols/ | 审查协议 | YAML | +| | skills/ | 三层载体 (Memory/Skill/Rule) | SKILL.md + meta.yaml | +| | project.yaml | per-project 配置覆盖 | YAML | + +--- + +## 2. 技术栈与选型权衡 + +### 2.1 技术栈总览 + +| 组件 | 选型 | 版本 | +|------|------|------| +| Daemon 进程管理 | PM2 | 6.0.14 | +| Daemon 框架 | Python + asyncio | 3.9+ | +| 黑板存储 | SQLite (per-project) | 3.51+ | +| HTTP API | FastAPI + uvicorn | 独立安装 | +| Agent 调度 | openclaw CLI / sessions_spawn | 2026.5.7 | +| 前端 | React + Vite + TypeScript | 复用 v1.0 | +| 实时推送 | SSE | FastAPI 内置 | +| 配置 | YAML (PyYAML) | — | + +### 2.2 选型权衡分析 + +**每个关键技术选型都遵循"不加重型新依赖"原则,基于已有环境。** + +| 决策 | 选择 | 备选 | 选择理由 | 备选缺陷 | +|------|------|------|---------|---------| +| HTTP 框架 | **FastAPI** | Starlette / Litestar / Flask | 2025-2026 AI Agent 编排系统事实标准(Agno AgentOS、OpenAI Agents SDK 均用 FastAPI);内置 OpenAPI + SSE + async;团队已有 v1 经验 | Starlette 需自己拼 Auth/SSE/DI;Litestar 太新生态小;Flask 不原生 async | +| 黑板存储 | **SQLite** | PostgreSQL / JSON 文件 | per-project 物理隔离(单项目故障不影响其他);零运维(无 DB server);WAL 模式足够应对单机并发;团队已有 WAL+busy_timeout 经验 | PostgreSQL 需额外 DB server 运维,对单机场景过重;JSON 文件无事务、无并发保护 | +| 实时推送 | **SSE** | WebSocket / 轮询 | 单向推送足够(Daemon→Dashboard);FastAPI 原生支持;自动重连;降级轮询简单 | WebSocket 双向能力多余,增加复杂度;纯轮询延迟高 | +| 并发模型 | **纯 asyncio 单线程** | threading / multiprocessing | 与 FastAPI event loop 共存;Agent spawn 用 create_subprocess_exec 非阻塞;无锁竞争问题 | threading 引入 GIL + 锁复杂度;multiprocessing 进程间通信代价大 | +| 配置格式 | **YAML** | TOML / JSON / Python | guardrails/review_protocols 需要人可读可编辑;YAML 支持 multiline string(prompt 模板);紧急运维可直接编辑 | TOML multiline 不便;JSON 无注释;Python 有安全风险 | +| 进程管理 | **PM2** | systemd / supervisord | 已有 10 个 PM2 进程运行;log management + auto restart + 开机自启;ecosystem.config.cjs 统一管理 | systemd 需 root 权限;supisord 不如 PM2 对 Node.js 友好 | +| Agent spawn | **asyncio.create_subprocess_exec** | subprocess.Popen / sessions_spawn | Full Agent 需要独立进程+独立 session;async 版本不阻塞 event loop;下次 tick 检查产出而非等待返回 | Popen 同步阻塞 event loop;sessions_spawn 仅适用无身份 Subagent | + +--- + +## 3. 项目结构 + +``` +sanguo_moziplus_v2/ # 项目根目录 +├── src/ +│ ├── main.py # FastAPI 入口 + Daemon ticker 启动 +│ │ +│ ├── blackboard/ # 黑板核心模块 +│ │ ├── __init__.py +│ │ ├── db.py # SQLite 连接管理(per-project) +│ │ ├── models.py # 数据模型 +│ │ ├── operations.py # 写操作(task/comment/output/decision/observation/review) +│ │ └── queries.py # 读操作(L1/L2/L3 分层) +│ │ +│ ├── daemon/ # Daemon 核心模块 +│ │ ├── __init__.py +│ │ ├── ticker.py # Tick 循环主逻辑(30s) +│ │ ├── dispatcher.py # Agent 调度判据(Full/Subagent/Daemon 三级) +│ │ ├── spawner.py # Agent spawn + session 管理 +│ │ ├── inbox.py # Inbox JSONL watcher + truncate +│ │ ├── guardrail.py # L1 assert 执行 + L2 subagent 触发 +│ │ ├── review_flow.py # 审查流水线(单审/反驳权/辩论) +│ │ ├── experience.py # 一级蒸馏触发 + 经验注入 +│ │ ├── bootstrap.py # build_bootstrap() L1/L2/L3 消息拼装 +│ │ ├── health.py # 健康检查 + 逻辑死循环检测 +│ │ ├── counter.py # ActiveAgentCounter 异步计数器 +│ │ └── notifier.py # @mention 解析 + Inbox 事件注入 +│ │ +│ ├── api/ # HTTP API +│ │ ├── blackboard_routes.py # 黑板 CRUD API +│ │ ├── daemon_routes.py # Daemon 控制 API +│ │ ├── sse_routes.py # SSE 推送端点 +│ │ └── project_routes.py # 多项目管理 API +│ │ +│ ├── cli/ # CLI 工具 +│ │ ├── blackboard.py # Agent 黑板操作 CLI +│ │ └── admin.py # 管理员控制 CLI +│ │ +│ └── frontend/ # 前端(React + Vite) +│ ├── src/ +│ │ ├── pages/ # 5 页面 +│ │ │ ├── TaskBoard.tsx # 任务看板(核心) +│ │ │ ├── GlobalMonitor.tsx # 全局监控 +│ │ │ ├── ArtifactVault.tsx # 产出档案 +│ │ │ ├── SystemConfig.tsx # 系统配置 +│ │ │ └── AIBriefing.tsx # AI Briefing +│ │ ├── components/ +│ │ │ ├── CheckpointPanel.tsx # Checkpoint 交互 +│ │ │ ├── NotificationCenter.tsx # 推送通知中心 +│ │ │ └── ProjectSwitcher.tsx # 项目切换器 +│ │ ├── store.ts # Zustand 状态管理 +│ │ └── api.ts # API 层 +│ └── dist/ # 构建产物(8083 直接服务) +│ +├── config/ +│ ├── default.yaml # 全局默认配置 +│ ├── guardrails.yaml # Guardrail 规则(per task_type) +│ ├── template_components.yaml # 模板组件库(7组件 + custom) +│ └── review_protocols/ # 审查协议 +│ ├── plan_review.yaml +│ ├── output_review.yaml +│ ├── analysis_review.yaml +│ └── final_review.yaml +│ +├── prompt_templates/ # L2 引擎注入模板 +│ ├── executor.md # 执行者操作规范 +│ ├── reviewer.md # 审查者操作规范 +│ ├── planner.md # 庞统规划操作规范 +│ ├── adjudicator.md # 庞统裁决操作规范 +│ ├── rebuttal.md # 反驳权操作规范 +│ └── plan_checker.md # Plan Checker 验证规范 +│ +├── schemas/ # L1 Schema 校验 +│ ├── handoff.schema.json +│ ├── output.schema.json +│ ├── decide.schema.json +│ └── observe.schema.json +│ +├── projects/ # 多项目目录(课题11) +│ └── {project_id}/ +│ ├── blackboard.db # per-project SQLite +│ ├── config/ # per-project 配置覆盖 +│ │ └── project.yaml +│ ├── artifacts/ # per-project 产出物 +│ ├── experiences/ # per-project 经验库 +│ └── skills/ # per-project Skill 覆盖/扩展 +│ +├── inbox/ # Inbox JSONL +│ └── daemon.jsonl # 跨进程事件推送 +│ +├── skills/ # 全局 Skill 库 +│ ├── builtins/ # 系统内置 Skill +│ │ ├── blackboard_operations/ +│ │ ├── code_review/ +│ │ └── data_validation/ +│ └── custom/ # 蒸馏产出/用户创建 +│ └── {skill_id}/ +│ ├── SKILL.md # 四要素 +│ ├── template.md # 可选详细模板 +│ └── meta.yaml # 版本/来源/tag +│ +├── tests/ +│ ├── unit/ +│ └── e2e/ +│ +├── pyproject.toml +├── ecosystem.config.cjs # PM2 配置 +└── docs/ # 设计文档 +``` + +--- + +## 4. 数据库设计 + +### 4.1 数据库架构(per-project) + +每个项目独立 SQLite 数据库,物理隔离。路径:`projects/{project_id}/blackboard.db`。 + +全局注册表:`projects/_registry.yaml`(YAML 文件,人可读可 Git 版本管理,紧急运维可直接编辑)。tick 热路径用内存 dict 缓存,_registry.yaml 只在 CLI 操作时读写(非热路径)。 + +### 4.2 表结构 + +完整 Schema 见 `architecture-v2.6.md §3.2`,此处列出核心表: + +| 表 | 用途 | 来源课题 | +|----|------|---------| +| `tasks` | 任务主表(状态机) | 课题1 | +| `comments` | 评论/Handoff/辩论论点(含 comment_type) | 课题2/3 | +| `outputs` | 产出物(含 attempt_number) | 课题2 | +| `decisions` | 决策记录 | 课题1 | +| `observations` | 观察/吹哨(severity 分级) | 课题1 | +| `events` | 事件流(审计追踪) | 课题2 | +| `reviews` | 评审结果(含 confidence/rebuttal_status/debate_round) | 课题3 | +| `experiences` | 经验沉淀(含 tags) | 课题6 | +| `experience_tags` | 经验标签关联表 | 课题6 | +| `checkpoints` | Checkpoint 人工确认(v2.8/M3) | M3 | + +### 4.3 关键约束 + +- WAL 模式 + `busy_timeout=5000` + `PRAGMA foreign_keys=ON` +- 所有写操作走 `BEGIN IMMEDIATE` 串行化 +- comments 表 `comment_type` CHECK 约束区分 8 种类型 + +--- + +## 5. Daemon 核心架构 + +### 5.1 Tick 循环(30s) + +```python +async def tick(project_id: str): + conn = get_connection(project_id) + try: + # 1. 逻辑健康自检(连续 N tick 无变更 → 告警) + health_check(conn) + + # 2. 处理 Inbox JSONL(即时事件) + process_inbox(conn) + + # 3. 扫描黑板状态 + tasks = scan_tasks(conn) + + # 4. 审查流水线处理 + review_flow(conn, tasks) + + # 5. 依赖推进(已完成任务解锁下游) + advance_dependencies(conn, tasks) + + # 6. 反驳权进度检测(催促通知) + check_rebuttal_progress(conn) + + # 7. Agent 调度 + dispatch_pending(conn, tasks) + + # 8. 一级蒸馏触发 + check_distillation(conn) + + finally: + conn.close() +``` + +**Tick + Inbox 双层事件架构**: +- Tick 30s 兜底扫描(Pull) +- Inbox JSONL 秒级推送加速(Push) +- Inbox 文件用 truncate(清空不删除),避免并发写入时文件不存在 + +### 5.2 Agent 调度判据(三级决策树) + +详见 `topic3-challenge-review-proposal.md §5.4`。 + +```python +def dispatch(task, action_type, project_config): + registered_agents = project_config.get("agents", []) + + # Level 1: 纯机械检查 → Daemon 直接执行 + if action_type in ("L1_guardrail", "format_check", "file_exists_check"): + return execute_locally(task) + + # Level 2: 有名字的角色 → Full Agent (asyncio.create_subprocess_exec) + if task.assignee in registered_agents: + if action_type == "adjudication": + return spawn_full_agent(task.assignee, new_session=True) + return spawn_full_agent(task.assignee) + + # Level 3: 无名字的一次性任务 → Subagent (sessions_spawn) + if DISPATCH_RULES.get(action_type) == "subagent": + return spawn_subagent(task_description=action_type) + + # Level 4: 未知 action_type → 庞统裁决 + return spawn_full_agent("pangtong-fujunshi", new_session=True) +``` + +**spawn 方式**: +- Full Agent:`asyncio.create_subprocess_exec`(异步非阻塞),不 await 完成,下次 tick 检查产出 +- Subagent:`sessions_spawn`(Gateway API),等返回 + +### 5.3 线程模型:纯 asyncio 单线程 + +整个 Daemon 运行在单个 asyncio event loop 中(与 FastAPI 共享)。 + +**关键设计决策**:Full Agent spawn 不用 `subprocess.Popen`(同步,会短暂阻塞 event loop),改用 `asyncio.create_subprocess_exec`(异步非阻塞)。这样 API 请求处理、SSE 推送、Daemon tick 三者互不阻塞。 + +```python +async def spawn_full_agent(agent_id: str, message: str, new_session: bool = False) -> str: + """异步非阻塞 spawn,全程 asyncio""" + session_id = str(uuid.uuid4()) + cmd = ["openclaw", "agent", "--agent", agent_id, + "--session-id", session_id, "--message", message, "--json"] + + proc = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + # 不 await proc.wait(),让子进程独立运行 + # 下次 tick 通过产出物检查完成状态 + _register_session(agent_id, session_id, proc.pid) + return session_id +``` + +**Subagent spawn** 通过 OpenClaw Gateway 内部 API(`sessions_spawn`),天然异步。 + +### 5.4 ActiveAgentCounter(课题11) + +纯 asyncio 实现(`asyncio.Semaphore`),与线程模型一致: + """异步计数器,控制并发""" + def __init__(self, max_global=5, max_per_agent=1): + self._global = asyncio.Semaphore(max_global) + self._per_agent = {} # agent_id → Semaphore(max_per_agent) + + async def acquire(self, agent_id: str) -> bool: + if self._global.locked(): + return False + agent_sem = self._per_agent.get(agent_id) + if agent_sem and agent_sem.locked(): + return False + await self._global.acquire() + if agent_id not in self._per_agent: + self._per_agent[agent_id] = asyncio.Semaphore(self._max_per_agent) + await self._per_agent[agent_id].acquire() + return True + + def release(self, agent_id: str): + self._per_agent[agent_id].release() + self._global.release() +``` + +### 5.5 build_bootstrap() 四层上下文拼装 + +详见 `topic4-decomposition-skill-proposal.md D4-7`。 + +``` +L0 铁律层(~500 tokens) → Hook 注入,不占 bootstrap +L1 角色层(~2000 tokens) → SOUL.md / IDENTITY.md(Agent 自带) +L2 引擎注入层(~1500 tokens)→ prompt_templates 按 role 拼装 + ① 操作规范(executor.md / reviewer.md / planner.md) + ② 项目背景(project_context.yaml) + ③ 任务上下文(黑板数据) + ④ 前序信息(depends_on 产出摘要) + ⑤ Guardrail 规则(guardrails.yaml,仅执行者) + ⑥ 审查协议(review_protocols/,仅审查者) + ⑦ 经验注入(experiences 表按 tag 匹配) +L3 被动参考层(按需加载)→ Skill description 四要素 +``` + +L2 按角色精确注入:执行者注入⑤⑥,审查者注入⑥,庞统注入⑥。 + +--- + +## 6. 审查流水线 + +### 6.1 分级审查 + +| 风险等级 | 流水线 | 方案审查 | 产出审查 | 模式 | max_rounds | +|---------|--------|---------|---------|------|-----------| +| **high** | 三阶段 | ✅ 对抗辩论 | ✅ 对抗辩论 + Guardrail | debate | 5 | +| **standard** | 二阶段 | ✅ 单审 | ✅ 单审 + Guardrail | single_reviewer | 3 | +| **low** | 一阶段 | ❌ | ⚡ Guardrail 自动 | auto | 0 | +| **research** | 一阶段 | ❌ | ✅ 庞统确认 | single_reviewer | 2 | + +### 6.2 Guardrail 执行 + +```yaml +# config/guardrails.yaml 示例 +task_types: + coding: + output_review: + required: true + layers: + L1: # Daemon 直接执行 + - assert: "len(glob('tests/test_*.py')) > 0" + message: "缺少测试文件" + - assert: "import subprocess; subprocess.run(['python', '-m', 'py_compile', output_path], check=True)" + message: "Python 语法检查失败" + L2: # Subagent 执行 + prompt: "检查代码是否遵循项目规范,重点关注:命名、异常处理、资源释放" + deploy: + output_review: + required: true + layers: + L1: + - assert: "os.path.exists('deploy_log.md')" + message: "缺少部署日志" + data: + output_review: + required: false # low 风险,Guardrail 自动 +``` + +### 6.3 反驳权流控 + +审查者 verdict=needs_revision(有 critical/major)→ spawn 原执行者反驳 → 协商轮次 ≤ max_rounds → 不设超时,有催促通知 → 超轮次升级庞统。 + +详见 `topic3-challenge-review-proposal.md §5.3`。 + +### 6.4 评审详情 Schema + +评审结果写入 `{task_id}/reviews/{review_id}.json`,包含: +- `issues[]`(status/severity/category/location+context/suggestion) +- `evidence[]`(file_content/command_output) +- `positives[]`、`meta` +- category 含:correctness/security/performance/style/scope_deviation/**architecture/robustness** + +详见 `topic3-challenge-review-proposal.md §5.2`。 + +--- + +## 7. 经验沉淀 + +### 7.1 两级蒸馏 + +| 级别 | 触发时机 | 产出 | 存储 | +|------|---------|------|------| +| 一级(实时) | 任务完成后 | 经验条目(key findings/lessons/tags) | experiences 表 | +| 二级(周期) | 同 tag 积累 N 条 | Skill 草稿(draft→active→deprecated) | skills/ 目录 | + +### 7.2 经验注入 + +build_bootstrap() 按 tag 检索 experiences 表,格式化后注入 L2 上下文。 + +详见 `topic6-experience-loop-proposal.md`。 + +--- + +## 8. Skill 体系 + +### 8.1 Skill 三层载体 + +| 载体 | 自由度 | 生命周期 | 产出者 | 存储 | +|------|--------|---------|--------|------| +| **Memory** | 高(自然语言) | 短期(30天过期) | Agent 自动沉淀 | experiences 表 | +| **Skill** | 中(模板+约束) | 中期(版本管理) | 二级蒸馏 / 人工编写 | skills/ 目录 | +| **Rule** | 低(脚本/断言) | 长期(代码级稳定) | 人工提炼 | guardrails.yaml | + +信息压缩方向:Memory → Skill → Rule(自由度递减,可靠性递增)。 + +### 8.2 Skill 目录结构 + +``` +sanguo_moziplus_v2/ +├── skills/ ← 全局 Skill 库 +│ ├── builtins/ ← 系统内置 Skill +│ │ ├── blackboard_operations/ ← 黑板操作规范 +│ │ ├── code_review/ ← 代码审查标准 +│ │ └── data_validation/ ← 数据验证 +│ └── custom/ ← 用户/蒸馏产出 Skill +│ └── {skill_id}/n │ ├── SKILL.md ← 四要素:description/triggers/actions/constraints + │ ├── template.md ← 可选:详细模板 + │ └── meta.yaml ← 版本、来源、tag + │ +├── projects/{pid}/ + │ └── skills/ ← per-project Skill 覆盖/扩展 + │ └── {skill_id}/ + │ └── project_override.md +``` + +### 8.3 Skill 四要素(SKILL.md 格式) + +```markdown +# {skill_name} + +## Description +一句话描述能力。 + +## Triggers +自动触发条件(任务类型/关键词/产出类型)。 + +## Actions +执行步骤和产出要求。 + +## Constraints +硬性约束(必须满足/禁止做)。 +``` + +### 8.4 Skill 生命周期 + +``` +draft(蒸馏产出/人工创建) + → active(验证通过,build_bootstrap 可引用) + → deprecated(30天无引用/被更好的替代) + → deleted(清理) +``` + +- **draft → active**:二级蒸馏自动标记,或人工在 Dashboard 审批 +- **active → deprecated**:`expire_days` 无引用自动降级 +- **active → deprecated → deleted**:有新版本替代时 + +### 8.5 Skill 在 Daemon 中的角色 + +```python +# daemon/bootstrap.py 中 Skill 加载逻辑 + +def load_skills(task, role, project_config) -> list[str]: + """L3 被动参考层:按需加载 Skill description""" + skills = [] + + # 1. 按 task_type 匹配 triggers + builtin_skills = scan_skills("skills/builtins/", task.task_type) + + # 2. per-project 覆盖 + project_skills = scan_skills(f"projects/{task.project_id}/skills/", task.task_type) + + # 3. 经验匹配(experiences 表按 tag 检索,注入 L2) + # → 这部分在 build_bootstrap() 的 L2 ⑦ 中处理 + + # 4. 只返回 description(四要素摘要),不加载完整 SKILL.md + return [s.description for s in builtin_skills + project_skills if s.status == 'active'] +``` + +**四层加载**(来源:课题4 D4-6): + +| 层 | 加载时机 | 内容 | 占用 | +|----|---------|------|------| +| Agent 固化 | OpenClaw agent 配置 | SOUL.md/IDENTITY.md/TOOLS.md | L1(Agent 自带) | +| OpenClaw 注册 | agent 启动时 | OpenClaw skills/ 目录 | L3(按需) | +| moziplus 注入 | build_bootstrap() | prompt_templates + guardrails + review_protocols | L2(精确注入) | +| 按需检索 | Agent 执行中主动查询 | Skill SKILL.md 全文 | L3(Agent 自己读) | + +### 8.6 template_components.yaml + +模板组件库,build_bootstrap() L2 拼装时引用: + +```yaml +components: + - id: executor_ops + file: prompt_templates/executor.md + inject_to: [executor] + + - id: reviewer_ops + file: prompt_templates/reviewer.md + inject_to: [reviewer] + + - id: planner_ops + file: prompt_templates/planner.md + inject_to: [planner] + + - id: guardrails + file: config/guardrails.yaml + inject_to: [executor] + condition: task_type in guardrails + + - id: review_protocol + file: config/review_protocols/{task_type}_review.yaml + inject_to: [reviewer, planner] + condition: review required + + - id: experience_injection + source: experiences_table + inject_to: [executor, reviewer] + condition: matching_tags > 0 + + - id: custom + file: null # per-project 自定义 + inject_to: [] +``` + +--- + +## 9. API 设计 + +### 9.1 黑板 API + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/api/projects/{pid}/tasks` | 任务列表(支持过滤) | +| GET | `/api/projects/{pid}/tasks/{id}` | 任务详情 | +| POST | `/api/projects/{pid}/tasks` | 创建任务 | +| POST | `/api/projects/{pid}/tasks/{id}/claim` | 认领 | +| PATCH | `/api/projects/{pid}/tasks/{id}/status` | 更新状态 | +| POST | `/api/projects/{pid}/tasks/{id}/comments` | 添加评论 | +| POST | `/api/projects/{pid}/tasks/{id}/outputs` | 写入产出 | +| POST | `/api/projects/{pid}/tasks/{id}/decisions` | 记录决策 | +| POST | `/api/projects/{pid}/tasks/{id}/observations` | 添加观察 | +| POST | `/api/projects/{pid}/tasks/{id}/reviews` | 提交评审 | +| POST | `/api/projects/{pid}/tasks/{id}/archive` | 归档/取消归档(v2.8) | +| POST | `/api/projects/{pid}/tasks/archive-done` | 一键归档(v2.8) | +| GET | `/api/projects/{pid}/tasks/{id}/checkpoints` | Checkpoint 列表(M3) | +| POST | `/api/projects/{pid}/tasks/{id}/checkpoints` | 创建 Checkpoint(M3) | +| POST | `/api/projects/{pid}/tasks/{id}/checkpoints/{cid}/approve` | Checkpoint 通过(M3) | +| POST | `/api/projects/{pid}/tasks/{id}/checkpoints/{cid}/reject` | Checkpoint 驳回(M3) | + +### 9.2 Daemon 控制 API + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/api/daemon/tick` | 手动触发 tick | +| GET | `/api/daemon/status` | Daemon 状态 | +| GET | `/api/daemon/sessions` | 活跃 session | + +### 9.3 SSE 推送 + +``` +GET /api/events?project={pid} +→ SSE 流,事件类型: + task_created / task_status_changed / review_submitted / rebuttal_added + notification(4级:🔴🟡🟢🔵) +``` + +### 9.4 多项目 API + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/api/projects` | 项目列表 | +| POST | `/api/projects` | 创建项目 | +| GET | `/api/projects/{pid}` | 项目详情 | +| DELETE | `/api/projects/{pid}` | 归档项目(安全流程) | + +--- + +## 10. Hook 系统 + +双重 Hook 架构: + +| Hook | 所属 | 触发时机 | 用途 | +|------|------|---------|------| +| `agent_turn_prepare` | OpenClaw Plugin | Agent 接收消息前 | 注入铁律(L0) | +| `agent_turn_complete` | OpenClaw Plugin | Agent 完成后 | 触发 lint/test(工具链集成点) | +| `pre_task_claim` | moziplus HookRegistry | Agent claim 前 | 权限检查 | +| `post_output_write` | moziplus HookRegistry | 产出写入后 | 触发 Guardrail 检查 | +| `post_review_submit` | moziplus HookRegistry | 评审提交后 | 触发反驳权流程 | + +--- + +## 11. 前端架构 + +### 11.1 技术选型 + +复用 v1.0 前端框架(React + Vite + TypeScript),重设计页面结构。 + +### 11.2 页面结构(5页) + +| 页面 | 内容 | 来源 | +|------|------|------| +| **任务看板** | 任务卡片、Checkpoint 交互、状态流转 | 课题7+9 | +| **全局监控** | Agent 状态、系统健康、资源使用 | 课题9 | +| **产出档案** | 产出物预览/下载、评审详情 | 课题9 | +| **系统配置** | 项目管理、Agent 配置、Guardrail 编辑 | 课题9 | +| **AI Briefing** | 日报/周报自动生成 | 课题9 | + +### 11.3 实时推送 + +SSE 端点 `/api/events`,前端 EventSource 监听。4 级推送(🔴🟡🟢🔵)。降级:SSE 不可用时 30s 轮询。 + +### 11.4 构建部署 + +```bash +cd src/frontend && npm run build +# 产物在 src/frontend/dist/ +# FastAPI 直接托管静态文件(port 8083) +``` + +详见 `topic9-dashboard-design.md`(课题9 Dashboard 前后端完整设计方案)。 + +--- + +## 12. 测试策略 + +### 12.1 单元测试 + +| 测试文件 | 覆盖 | +|---------|------| +| test_blackboard.py | operations 全函数(claim CAS、状态机、comment_type、review CRUD) | +| test_daemon_tick.py | tick 流程(Inbox 处理、依赖推进、反驳权进度检测) | +| test_dispatcher.py | 三级调度判据 + fallback | +| test_guardrail.py | L1 assert 执行 + L2 prompt 触发 | +| test_review_flow.py | 审查流水线(分级审查、反驳权、升级) | +| test_experience.py | 一级蒸馏 + tag 检索 | +| test_bootstrap.py | 四层上下文拼装(按 role 精确注入) | +| test_counter.py | ActiveAgentCounter 并发控制 | + +### 12.2 集成测试 + +- 创建任务 → 调度执行 → Guardrail → 审查 → 反驳 → 完成 闭环 +- 多项目并发:两个项目同时 tick,互不影响 +- 竞态测试:两个 Agent 同时 claim + 同时写 inbox + +### 12.3 测试数据库 + +per-project 测试用 `:memory:` SQLite。全局注册表测试用临时文件。 + +--- + +## 13. 实现顺序 + +| Phase | 内容 | 估计时间 | +|-------|------|---------| +| **P1** | 黑板 + Daemon + CLI + API + 多项目 | ~20-25h(含调试 buffer) | +| **P2** | 审查流水线 + Guardrail + 反驳权 + build_bootstrap | ~12h | +| **P3** | 前端重设计 + SSE 推送 + AI Briefing | ~10h | +| **P4** | 经验沉淀 + Skill 进化 + 工具链集成 | ~8h | + +--- + +## 14. 容量估算 + +### 14.1 存储容量 + +| 组件 | 单项目估算 | 说明 | +|------|-----------|------| +| blackboard.db | 2-10 MB / 100 任务 | tasks + comments(5-20行/任务) + outputs + reviews + events(5-10行/任务) + experiences,含索引和 WAL overhead | +| artifacts/ | 10-50 MB / 100 任务 | 代码文件、分析报告、数据文件 | +| experiences/ | < 1 MB | 经验条目轻量,文本为主 | +| Inbox JSONL | < 100 KB | 持续 truncate,不累积 | +| _registry.yaml | < 10 KB | 项目元信息,轻量 | + +**总磁盘预估(10 项目,1000 任务)**:~1-2 GB,完全在 Mac mini 容量范围内。 + +### 14.2 内存占用 + +| 组件 | 占用估算 | 说明 | +|------|---------|------| +| Daemon + FastAPI | 50-100 MB | Python 进程基础 + asyncio event loop | +| 每个 Agent spawn | 100-300 MB | openclaw agent CLI 进程,执行期间临时占用 | +| SQLite 连接池 | < 10 MB | per-project 连接,用完释放 | +| 前端 dist 静态服务 | < 5 MB | 内存映射 | + +**峰值内存(max_global=5,5 个 Agent 同时执行)**:~1-1.5 GB,16GB Mac mini 无压力。 + +### 14.3 并发能力 + +| 维度 | 设计上限 | 瓶颈分析 | +|------|---------|----------| +| 同时活跃项目 | 无硬限(10+) | tick 30s 扫全部项目,每项目 tick 耗时 < 100ms,10 项目 < 1s | +| 并发 Agent | 5 (max_global) | ActiveAgentCounter 控制,超出排队 | +| SSE 连接 | 10-20 | 单机场景足够,FastAPI SSE 无硬限 | +| Inbox 吞吐 | 200+ 写/秒 | truncate 模式,实测无丢失 | +| API QPS | 100+ | FastAPI 单进程足够,无 LB 瓶颈 | + +### 14.4 性能约束 + +| 场景 | 延迟 | 说明 | +|------|------|------| +| 用户提需求 → 任务写入黑板 | < 5s | Agent 对话 + CLI 写入 | +| 任务 pending → Agent 开始执行 | 30s 内 | 最坏等一轮 tick;Inbox 触发可秒级 | +| Agent 完成 → 下游任务 pending | 30s 内 | tick 扫描依赖推进 | +| SSE 事件推送延迟 | < 1s | event loop 内直接推送 | +| 审查 → 反驳权启动 | 30s 内 | tick 扫描 review 状态 | + +--- + +## 15. 失败模式与可靠性 + +### 15.1 进程级故障 + +| 故障 | 检测 | 恢复 | +|------|------|------| +| Daemon 进程崩溃 | PM2 auto restart | PM2 5s 内自动拉起,tick 恢复扫描 | +| Agent spawn 子进程僵死 | health.py 僵尸检测 | 连续 20 tick 无变更 → observation 告警 + reclaim 释放 semaphore | +| Agent spawn 失败(CLI 错误) | spawner 异常捕获 | 标记任务 failed,写入 events 表,不阻塞其他任务 | +| FastAPI 进程异常 | PM2 restart | 与 Daemon 同进程,PM2 统一管理 | + +### 15.2 数据级故障 + +| 故障 | 检测 | 恢复 | +|------|------|------| +| SQLite 写入冲突 | busy_timeout=5s 等待 + WAL 模式 | WAL 允许并发读写;超时后 raise,tick 下次重试 | +| SQLite DB 损坏 | 连接时 PRAGMA integrity_check | per-project 隔离,单 DB 损坏不影响其他项目;从最新 Git 备份恢复 | +| Inbox JSONL 损坏 | JSON 解析异常 | truncate 清空重置,Agent 重发事件 | +| _registry.yaml 损坏 | YAML 解析异常 | Git 版本管理,`git checkout` 恢复 | + +### 15.3 逻辑级故障 + +| 故障 | 检测 | 恢复 | +|------|------|------| +| Agent 无限循环 | 连续 20 tick 无状态变更 | observation 告警 + 通知用户 + reclaim | +| 反驳权无限协商 | max_rounds 兜底 | 超轮次自动升级庞统裁决 | +| 任务卡在 blocked | tick 依赖推进检查 | 每轮 tick 重算依赖,不依赖缓存 | +| Guardrail 误杀 | L1 assert 可能误判 | L2 subagent 二次确认;用户可在 Dashboard 手动 override | + +### 15.4 恢复优先级 + +``` +P0: Daemon 进程 → PM2 auto restart +P1: 数据完整性 → SQLite WAL + Git 备份 +P2: 任务进度 → tick 全量扫描自动恢复 +P3: 经验数据 → experiences 表 + skills/ 目录 Git 版本管理 +``` + +--- + +## 16. 演进路径 + +### 16.1 已知限制与扩展点 + +| 当前限制 | 触发条件 | 演进方向 | +|---------|---------|--------| +| SQLite 单机 | 项目 > 20 或 DB > 100MB | 迁移 PostgreSQL(per-project schema),代码层只改 db.py | +| max_global=5 | Agent 并发不够 | 调整 ActiveAgentCounter 参数;引入优先级队列 | +| 单机部署 | 需要多节点 | Agent spawn 改为远程调度(Windows-Test-Node 已可远程执行) | +| SSE 单进程 | SSE 连接 > 50 | 引入 Redis pub/sub + 多 worker | +| 无用户认证 | 多用户协作需求 | 引入 API token + per-project 权限(project.yaml 扩展) | + +### 16.2 接口抽象层(为演进预留) + +关键接口已做抽象,替换底层不影响上层逻辑: + +| 接口 | 当前实现 | 替换时影响范围 | +|------|---------|--------------| +| `blackboard.db.py` | SQLite | 只改 db.py,operations/queries 不变 | +| `daemon.spawner.py` | asyncio.create_subprocess_exec | 只改 spawner.py,dispatcher 不变 | +| `daemon.counter.py` | asyncio.Semaphore | 只改 counter.py,dispatcher 不变 | +| `api.sse_routes.py` | FastAPI SSE | 只改 sse_routes.py,前端不变 | + +--- + +## 17. 风险 + +| 风险 | 概率 | 影响 | 缓解 | +|------|------|------|------| +| per-project SQLite 并发 tick | 中 | 中 | WAL + busy_timeout + per-project asyncio.Lock | +| Agent spawn 子进程回收 | 中 | 中 | health.py 僵尸检测 + reclaim | +| Inbox JSONL 并发写入 | 低 | 低 | truncate(不删除),实测 200 并发 0 丢失 | +| 前端 v1→v2 迁移 | 低 | 低 | v1(8082) 和 v2(8083) 独立运行,渐进替换 | +| Daemon 逻辑死循环 | 低 | 中 | 连续 20 tick 无变更 → observation 告警 + 通知用户 | +| 反驳权无限协商 | 低 | 低 | max_rounds 兜底 + 超轮次升级庞统 | +| 上下文窗口膨胀 | 中 | 中 | L0-L3 四层分级 + 产出写入黑板不累积 | +| 经验注入噪声 | 低 | 低 | 只注入 matching tag 的经验;N 条阈值可控 | +| Guardrail 误杀 | 低 | 中 | L1+L2 双层验证;用户可 Dashboard override | diff --git a/docs/design/test-plan-v2.6.md b/docs/design/test-plan-v2.6.md new file mode 100644 index 0000000..7e7cb26 --- /dev/null +++ b/docs/design/test-plan-v2.6.md @@ -0,0 +1,305 @@ +# v2.6 测试计划 + +**版本**: v2.6.0-test-plan +**作者**: 司马懿(质量总监)🗡️ +**日期**: 2026-05-17 +**状态**: F1-F5 已评审,F6-F18 待编码 + +--- + +## 一、测试基础设施 + +### 1.1 当前配置 + +- **框架**: pytest(已有 `tests/` 目录,97 个用例全部通过) +- **Fixture**: `tmp_path`(SQLite 临时数据库)、`TestClient`(FastAPI) +- **Mock 策略**: 目前无 mock,全部用真实 SQLite 内存/临时文件 +- **运行方式**: `python3 -m pytest tests/ -q` + +### 1.2 测试分层 + +| 层级 | 覆盖范围 | 何时执行 | +|------|---------|---------| +| **单元测试** | 每个 F 的内部逻辑 | 每写完一个 F | +| **集成测试** | F1-F14 端到端链路 | F14 完成后 | +| **E2E 验收** | 完整系统含前端 | F18 完成后 | + +### 1.3 测试规范 + +- 每个 F 必须有对应的 `test_*.py`,测试文件与模块同名 +- 测试类按功能分组:`class TestTaskCRUD`、`class TestTransitions` 等 +- P0 测试必须有,P1 测试建议有但不强阻塞 +- 测试必须可重复运行(不依赖外部状态、不依赖时序) +- 新增测试必须在 `tests/` 目录下,不放在 `src/` 内 + +--- + +## 二、F1-F5 单元测试评审(已完成) + +### 现有覆盖情况 + +| F | 测试文件 | 用例数 | P0 覆盖 | 评审 | +|---|---------|--------|---------|------| +| F1 骨架 | `test_main.py` | 10/10 | ✅ 启动、健康端点、配置、Swagger | 通过 | +| F2 黑板 | `test_blackboard.py` | 39/39 | ✅ CRUD、状态机、并发、WAL、事件 | 通过(附 2 条意见) | +| F3 多项目 | `test_registry.py` | 11/11 | ✅ 创建/删除/归档/持久化/黑板的联动 | 通过 | +| F4 CLI | `test_cli.py` | 14/14 | ✅ 8 子命令 + admin + 错误处理 | 通过 | +| F5 API | `test_api.py` | 23/23 | ✅ 全端点 CRUD + 状态码 + SSE 连接 | 通过(附 1 条意见) | + +**总计**: 97/97 通过,质量合格。 + +### 评审意见(F1-F5) + +#### BUG-1 [P1] `/api/daemon/status` 端点重复注册 +- **位置**: `main.py` 第 99-111 行直接定义了 `daemon_status` 端点,同时 `daemon_routes.py` 也定义了同名路由 +- **现象**: pytest 输出 `Duplicate Operation ID daemon_status` warning +- **修复**: 删除 `main.py` 中的 `/api/daemon/status` 端点定义,统一由 `daemon_routes.py` 提供 +- **影响**: 不影响功能,但 Swagger 文档会混乱 + +#### OBS-1 [P2] `operations.py` 的 `_conn()` 每次操作都新建连接 +- **位置**: `operations.py` 全文,每次方法调用 `self._conn()` → `sqlite3.connect()` +- **现状**: 单次操作用完即关,连接不池化 +- **评估**: v2.6 阶段可接受(SQLite WAL + busy_timeout 足够),但如果后续 F6 ticker 30s 轮询 + 多项目并发,建议 F6 阶段引入连接池或 `per-thread` 连接复用 +- **行动**: 不阻塞当前进度,F6 编码时再评估性能 + +#### OBS-2 [P2] `queries.py` 的 `blocked_tasks_with_deps()` 和 `pending_dispatchable()` 存在 SQL 注入风险 +- **位置**: `queries.py` 第 52 行和第 71 行,使用 `f-string` 拼接 SQL +- **现象**: `f"SELECT ... WHERE id IN ({','.join('?' * len(deps))})"` — 虽然 `deps` 来自数据库的 `depends_on` 字段(不是用户输入),且用了参数化占位符,但 `f-string` 拼接 SQL 是坏习惯 +- **评估**: 当前实际安全(因为值都来自参数化 `?`),但风格不好 +- **行动**: 不阻塞,但后续统一改为纯参数化写法 + +--- + +## 三、F6-F18 测试计划(逐模块) + +### F6 Daemon Ticker — `test_ticker.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | tick 循环正常运行 | ✅ | 30s 间隔(测试中加速),不阻塞 event loop | +| T2 | scan_tasks 检测 pending | ✅ | pending → 触发调度 | +| T3 | 依赖推进 | ✅ | blocked → pending(依赖 done 后) | +| T4 | events 写入 | ✅ | 每次 tick 写 `daemon_tick` 事件 | +| T5 | 多项目轮询 | ✅ | tick 遍历所有 active 项目 | +| T6 | tick 异常不中断 | P1 | 单次 tick 抛异常 → 下次 tick 继续 | +| T7 | 手动 tick 端点 | P1 | POST /api/daemon/tick 触发即时 tick | + +### F7 Inbox JSONL Watcher — `test_inbox.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 写入+消费 | ✅ | Agent 写 JSONL → 1s 内 Daemon 读取并处理 | +| T2 | truncate | ✅ | 消费后清空文件 | +| T3 | 并发写入 | ✅ | 多 Agent 同时写不同行 | +| T4 | 损坏行恢复 | P1 | 非法 JSON 行跳过不崩溃 | +| T5 | 空文件处理 | P1 | 空文件不触发处理 | + +### F8 健康检查 — `test_health.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 正常场景 | ✅ | 有变更 → 无告警 | +| T2 | 僵尸检测 | ✅ | 连续 N tick 无变更 → observation 写入 | +| T3 | 恢复场景 | ✅ | 僵尸后有变更 → 告警解除 | +| T4 | 多项目独立检测 | P1 | 项目 A 僵尸不影响项目 B | + +### F9 Agent 调度器 — `test_dispatcher.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 三级决策树 | ✅ | Daemon task → Full task → Sub task 正确分流 | +| T2 | 调度不阻塞 | ✅ | spawn 用 `asyncio.create_subprocess_exec` | +| T3 | 队列满拒绝 | ✅ | active agent 数达上限 → 不调度 | +| T4 | 任务优先级排序 | P1 | 高优先级先调度 | + +### F9 Agent Spawner — `test_spawner.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | spawn 成功 | ✅ | 子进程启动 + session 注册 | +| T2 | 超时处理 | ✅ | 超时后 kill + task_attempt 记录 | +| T3 | spawn 失败 | ✅ | 命令不存在 → task_attempt 记录 spawn_failed | +| T4 | session 清理 | P1 | 子进程结束后 session 归档 | + +### F10 ActiveAgentCounter — `test_counter.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 全局上限 | ✅ | max_global=5 时第 6 个 acquire 失败 | +| T2 | per-agent 串行 | ✅ | 同一 agent 第二个 acquire 排队 | +| T3 | release 恢复 | ✅ | release 后等待中的 acquire 成功 | +| T4 | 并发竞争 | P1 | 多 agent 同时 acquire 不超限 | + +### F11 Bootstrap 拼装 — `test_bootstrap.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 各 role 拼装 | ✅ | 不同 role 得到不同 L0-L3 四层 | +| T2 | token 估算 | ✅ | 拼装结果 < 4096 tokens | +| T3 | 缺失组件降级 | P1 | 某层文件不存在 → 跳过不崩溃 | +| T4 | 模板变量替换 | P1 | `{{task_id}}` 等占位符正确替换 | + +### F12 审查流水线 — `test_review_flow.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 4 级分流 | ✅ | high/standard/low/research 正确路由 | +| T2 | confidence 计算 | ✅ | 分值在 0-1 区间,阈值逻辑正确 | +| T3 | 升级到庞统 | ✅ | confidence < 阈值 → 升级 | +| T4 | 审查通过 → done | ✅ | verdict=approved → 状态变 done | +| T5 | 审查拒绝 → 重新执行 | P1 | verdict=rejected → 回到 working | + +### F13 Guardrail — `test_guardrail.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | L1 assert 通过 | ✅ | 合规产出 → 放行 | +| T2 | L1 assert 失败 | ✅ | 不合规产出 → 拦截 | +| T3 | L2 subagent 触发 | ✅ | 高风险 → 二次确认 | +| T4 | 配置加载 | P1 | `guardrails.yaml` 正确解析 | +| T5 | 误杀恢复 | P1 | L2 确认通过 → 放行 | + +### F14 反驳权 — `test_rebuttal.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 反驳触发 | ✅ | review rejected → 反驳协商开始 | +| T2 | 轮次计数 | ✅ | round 递增正确 | +| T3 | 超轮次升级 | ✅ | 超过 max_rounds → 庞统裁决 | +| T4 | 结果写入 | ✅ | 最终裁决写入 reviews 表 | + +### F15 经验沉淀 — `test_experience.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 一级蒸馏 | ✅ | 任务完成 → experiences 表写入 | +| T2 | tag 匹配 | ✅ | 相似 tag 的经验可被查询到 | +| T3 | 二级触发 | P1 | N 条同类经验 → Skill 草稿生成 | +| T4 | 过期清理 | P1 | deprecated 经验不被查询 | + +### F16 Skill 体系 — `test_skill_loader.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 加载 + 验证 | ✅ | 四要素完整才加载 | +| T2 | per-project 覆盖 | ✅ | 项目级 Skill 覆盖全局 Skill | +| T3 | 缺要素拒绝 | P1 | 缺少 name/description/triggers/actions → 拒绝 | +| T4 | 版本管理 | P1 | 同名 Skill 版本号正确比较 | + +### F17 SSE + Hook — `test_sse.py` / `test_hooks.py` + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | SSE 连接 | ✅ | 客户端连接 → 收到初始事件 | +| T2 | SSE 4 级推送 | ✅ | task/agent/daemon/system 四级 | +| T3 | 断线重连 | P1 | 断线后重连 → 收到后续事件 | +| T4 | 降级轮询 | P1 | SSE 不可用 → 轮询兜底 | +| T5 | Hook 触发 | ✅ | 3 个触发点正确执行 | +| T6 | Hook 过滤 | P1 | 条件不满足 → 不触发 | + +### F18 前端 Dashboard — `test_frontend.py`(E2E) + +| # | 测试用例 | P0 | 验证点 | +|---|---------|-----|-------| +| T1 | 页面渲染 | ✅ | 5 个页面均可加载 | +| T2 | API 集成 | ✅ | 页面操作 → API 调用正确 | +| T3 | SSE 实时更新 | ✅ | 后端事件 → 前端 UI 更新 | +| T4 | 项目切换 | ✅ | 切换项目 → 数据刷新 | + +--- + +## 四、集成测试计划(F14 完成后) + +### 端到端链路测试 + +``` +用户提需求 → 庞统规划 → 任务写入黑板 → Agent 执行 → +Guardrail 检查 → 审查流水线 → 反驳协商 → 完成 → 经验沉淀 +``` + +| # | 场景 | 关键验证 | +|---|------|---------| +| I1 | 正常流程(happy path) | 全链路无阻断,最终 done | +| I2 | 依赖链 | A→B→C,A done 后 B 自动 pending,B done 后 C 自动 pending | +| I3 | 审查拒绝 → 反驳 → 通过 | review rejected → rebuttal → verdict 翻转 | +| I4 | 审查拒绝 → 反驳 → 超轮次 → 庞统裁决 | max_rounds 后升级 | +| I5 | Guardrail 拦截 → L2 确认 → 放行 | L1 fail → L2 confirm → proceed | +| I6 | Agent 僵尸检测 | 无 heartbeat → observation → reclaim | +| I7 | 多项目并行 | 项目 A/B 同时运行,互不干扰 | +| I8 | 失败重试 | failed → pending(retry_count +1)→ 再次执行 | + +### 集成测试基础设施 + +```python +# conftest.py 需要的 fixture +@pytest.fixture +def full_stack(tmp_path): + """完整系统:FastAPI + Ticker + Dispatcher + Spawner(mock)""" + ... + +@pytest.fixture +def mock_spawner(): + """Mock spawner,模拟 Agent 执行""" + ... +``` + +--- + +## 五、E2E 验收场景(F18 完成后) + +| # | 场景 | 验收标准 | +|---|------|---------| +| E1 | 创建项目 → 提交任务 → 查看任务板 | Dashboard 实时更新 | +| E2 | 全局监控页 | 所有项目状态汇总、活跃 Agent 数、最近事件 | +| E3 | 产物仓库页 | 任务产出物可浏览、可下载 | +| E4 | 系统配置页 | 运行时配置可查看、Skill 列表可管理 | +| E5 | AI 简报页 | 项目级 AI 总结可生成 | +| E6 | SSE 实时 | 所有页面操作后 SSE 实时反映 | + +--- + +## 六、测试通过标准 + +### 每个 F 的单元测试通过标准 + +1. **全部 P0 用例通过**(硬性要求) +2. **P1 用例通过率 ≥ 80%**(可协商) +3. **无 crash / 无未捕获异常** +4. **测试可重复运行**(跑 3 次结果一致) + +### 集成测试通过标准 + +1. **I1-I8 全部场景通过** +2. **无死锁、无资源泄漏** +3. **并发测试无数据竞争** + +### E2E 验收标准 + +1. **E1-E6 全部场景通过** +2. **前端无 console error** +3. **SSE 延迟 < 2s** + +--- + +## 七、进度追踪 + +| F | 测试计划 | 用例设计 | 编码 | 执行 | 评审 | +|---|---------|---------|------|------|------| +| F1 | ✅ | ✅ 10 | ✅ | ✅ 10/10 | ✅ 通过 | +| F2 | ✅ | ✅ 39 | ✅ | ✅ 39/39 | ✅ 通过(附 2 条意见) | +| F3 | ✅ | ✅ 11 | ✅ | ✅ 11/11 | ✅ 通过 | +| F4 | ✅ | ✅ 14 | ✅ | ✅ 14/14 | ✅ 通过 | +| F5 | ✅ | ✅ 23 | ✅ | ✅ 23/23 | ✅ 通过(附 1 条意见) | +| F6 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F7 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F8 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F9 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F10 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F11 | ✅ 本文档 | ⬜ 本文档 | ⬜ | — | — | +| F12 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F13 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F14 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F15 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F16 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F17 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | +| F18 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | diff --git a/docs/design/toolchain-proposal.md b/docs/design/toolchain-proposal.md new file mode 100644 index 0000000..cb188a7 --- /dev/null +++ b/docs/design/toolchain-proposal.md @@ -0,0 +1,77 @@ +# 工具链课题:DevOps 工具链集成 + +> **状态**: 信息收集阶段,未正式立项设计 +> **作者**: 庞统(副军师)🐦 +> **日期**: 2026-05-16 +> **定位**: 所有工具链能力最终要集成到 moziplus v2.0 中,不是独立系统 + +--- + +## 1. 背景 + +moziplus v2.0 编排的不只是 AI Agent 的任务流转,还包括完整的开发生命周期。Agent 执行编码任务后,lint/test/build/deploy 应该自动串联;Agent 发现 bug 后应该能自动创建 Issue 追踪。工具链是 v2.0 从"任务流转工具"升级为"AI Native DevOps 平台"的关键拼图。 + +## 2. 需求来源 + +| 来源 | 内容 | 状态 | +|------|------|------| +| PRD C10 | lint/test/build 自动触发,按需调用,结果驱动流转 | ❌ 未设计 | +| 评审 D10 | 工具链自动化 Phase 规划 | ❌ 未覆盖 | +| 评审 D13 | 需求→设计→编码→测试→部署→运维 全生命周期阶段映射 | ❌ 未覆盖 | +| 评审 F8 | lint/test/build/deploy 自动化 | ❌ 未覆盖 | +| MEMORY.md | gitee Issue 管理机制:选 repo、定流程、谁维护、标签体系 | ❌ 未做 | +| T4-9 | Skill 测试框架(结构测试+干跑测试+场景测试) | ➡️ 移入本课题 | + +## 3. 工具链分类(初步) + +| 分类 | 具体内容 | 当前状态 | 备注 | +|------|---------|---------|------| +| **代码管理** | Git 仓库(gitee + gitea)、分支策略、PR 流程 | ✅ 有基础 | gitee Issue 管理机制待建 | +| **问题管理** | Issue/Bug/需求追踪、标签体系 | ❌ 无 | 与黑板 tasks 表的关系待定 | +| **CI/CD** | lint/test/build 自动触发、结果驱动流转 | ❌ 无 | PRD C10 | +| **测试工具** | 单元测试、集成测试、Skill 测试框架 | ❌ 无 | T4-9 移入 | +| **开发环境** | Mac mini 本地开发环境 | ✅ 有 | 多 Agent 并行改代码隔离待设计 | +| **测试/Staging 环境** | 独立验证环境 | ❌ 无 | 待定 | +| **沙箱环境** | Agent 执行命令时的安全隔离 | ⚠️ 部分 | AgentsMesh PTY sandbox 参考 | +| **运维工具** | PM2 进程管理、Daemon 健康自检、日志 | ⚠️ 部分 | Daemon 健康自检已在 architecture-v2.6 | + +## 4. 调研参考 + +| 项目 | 关键实践 | 可借鉴点 | +|------|---------|---------| +| AgentsMesh | PTY sandbox + git worktree 隔离 + ticket-pod binding | Agent 执行隔离、代码并行修改 | +| Aider | verification_commands(lint/test 自动验证) | Guardrail 验证脚本层已在 architecture-v2.6 | +| nuwa-skill | 5 阶段蒸馏 + 质量验证 | Skill 测试框架参考 | +| Claude Code | 98.4% Infrastructure,上下文管理为主 | 工具链服务于 Agent,不是 Agent 服务于工具链 | + +## 5. 待讨论的关键问题 + +1. **CI/CD 走什么**:本地跑(Mac mini)?接 GitHub Actions?接 gitee CI?还是 moziplus Daemon 自己调度? +2. **测试环境怎么搭**:和开发环境同一台机器?NAS Docker?Windows-Test-Node(192.168.2.33)? +3. **沙箱到什么程度**:Agent 执行命令时的安全隔离?文件系统级隔离(worktree)?还是进程级隔离? +4. **Issue 管理用 gitee 还是自己做**:黑板 tasks 表已能追踪任务,还需要外部 Issue 系统吗?两者怎么打通? +5. **与 moziplus v2.0 集成方式**:工具链是 v2.0 内置能力还是外部集成?Daemon 怎么触发 lint/test/build?Guardrail 和工具链的关系? +6. **全生命周期阶段映射**:需求→设计→编码→测试→部署→运维,每个阶段对应哪些工具链能力?Daemon 怎么感知当前阶段? +7. **结果驱动流转**:lint/test/build 结果怎么写回黑板?怎么影响任务状态?(如 test 失败 → 自动标记 needs_revision) + +## 6. 与 v2.0 架构的集成点 + +``` +moziplus v2.0 Daemon + │ + ├── Guardrail(课题3)──→ 工具链触发点 + │ ├── L1 机械检查 ──→ 文件存在/格式校验(Daemon 直接执行) + │ ├── L2 AI 检查 ──→ lint/test 自动触发 + 结果评估 + │ └── L3 Agent 审查 ──→ 代码审查 + Issue 创建 + │ + ├── Hook(双重 Hook)──→ 工具链触发时机 + │ ├── agent_turn_prepare ──→ 注入工具链上下文 + │ └── agent_turn_complete ──→ 触发 lint/test/build + │ + ├── reviews 表 ──→ 工具链结果存储 + │ └── review_type: "guardrail" ──→ lint/test/build 结果 + │ + └── Dashboard(课题9)──→ 工具链可视化 + ├── 任务看板 ──→ CI/CD 状态展示 + └── 系统配置 ──→ 工具链配置管理 +``` diff --git a/docs/design/topic11-multi-project-proposal.md b/docs/design/topic11-multi-project-proposal.md new file mode 100644 index 0000000..d291568 --- /dev/null +++ b/docs/design/topic11-multi-project-proposal.md @@ -0,0 +1,685 @@ +# 课题11 设计方案:用户级多项目支持 + +> **日期**: 2026-05-16 +> **作者**: 庞统(副军师)🐦 +> **状态**: v2(并发调度模型重设计,待评审) +> **前置**: 课题1-4、课题6 已完成设计 +> **变更**: v2 新增 §5.4 并发调度模型(per-project 线程 + 全局资源信号量),替代原串行 tick + +--- + +## 1. 核心问题 + +用户同时有多个工作域(量化策略A + 平台开发 + 数据研究),需要项目级隔离——不同项目的任务、配置、产出互不干扰,但共享同一套 Agent 团队和 Daemon 基础设施。 + +## 2. 需要隔离什么 + +| 隔离项 | 原因 | 隔离方式 | +|--------|------|---------| +| 黑板数据(tasks/comments/outputs/decisions/observations/events/agents/task_attempts/reviews/experiences/experience_tags) | 不同项目的任务不能混在一起 | 独立 SQLite 文件 | +| 配置(guardrails.yaml / prompt_templates / project_context.yaml) | 不同项目可能有不同的审查规则、上下文 | 项目级 config/ 目录,覆盖全局默认 | +| 产出文件 | 不同项目的代码/数据物理隔离 | 项目级 outputs/ 目录 | +| Agent session | 同一 Agent 参与不同项目时上下文不串 | OpenClaw `--session-id` 已有隔离 | +| Daemon 连接 | 不同数据库连接不能混淆 | 连接池 project_id → Connection 映射 | + +**不需要隔离的**: +- Agent 注册表(agents.yaml)—— 同一套 Agent 团队服务所有项目 +- Daemon 进程 —— 单进程管理所有项目 +- Schema 定义 —— 所有项目共享同一套表结构 +- 全局 prompt_templates —— 项目级覆盖,不是替换 + +## 3. 方案选择 + +### 3.1 三个方案对比 + +| 方案 | 做法 | 优点 | 缺点 | +|------|------|------|------| +| **A. 多实例** | 每个项目独立 Daemon + 独立 SQLite + 独立端口 | 完全隔离、互不影响 | 资源开销翻倍、管理复杂、Agent 重复注册 | +| **B. 单实例多命名空间** | 一个 SQLite,所有表加 `project_id` | 零额外资源 | 每个查询带 WHERE、单文件性能上限、删除项目危险 | +| **C. 单 Daemon 多数据库** | 一个 Daemon,每个项目一个 SQLite 文件 | 物理隔离数据、共享 Daemon | Daemon 需管理多连接 | + +### 3.2 选择方案 C + +理由: +1. **方案 A 不适合**——Mac mini 资源有限,每多一个项目就多一套 Daemon + PM2 进程 + 端口。6 个 Agent 跑在 OpenClaw 上已经固定开销,不需要重复。 +2. **方案 B 不够安全**——`WHERE project_id = ?` 容易漏,SQLite 单文件多项目并发有 WAL 锁瓶颈,删除项目 = 跨所有表 DELETE。 +3. **方案 C 是最优点**——数据物理隔离(每个 `.db` 文件独立),但共享 Daemon 进程和 Agent 注册表。Daemon 切换项目只是切换 SQLite 连接,无额外资源开销。 + +**优秀实践验证**: +- Wanman:Per-Agent Worktree + $HOME 严格隔离 → 验证"物理隔离比逻辑隔离可靠" +- ClawTeam:Git Worktree 隔离 + fcntl 文件锁 → 验证"共享进程 + 独立存储"模式可行 +- Cline:Kanban + Worktree → 验证"多任务并行 + 物理隔离"是主流 +- Hermes:单 Dispatcher + 单 SQLite → **Hermes 是单项目设计**,我们没有"多项目用单数据库"的先例 + +## 4. 目录结构 + +``` +~/.sanguo_projects/moziplus_v2/ +├── daemon.py # 单 Daemon 进程 +├── daemon.yaml # Daemon 全局配置(端口、tick 间隔等) +├── projects/ +│ ├── _registry.yaml # 项目注册表(所有项目的元数据) +│ ├── quant-momentum/ # 项目 1 +│ │ ├── blackboard.db # 独立 SQLite +│ │ ├── config/ +│ │ │ ├── project.yaml # 项目元信息(名称、描述、创建时间) +│ │ │ ├── guardrails.yaml # 项目级审查规则(覆盖全局默认) +│ │ │ ├── project_context.yaml # 项目背景知识(注入 L2) +│ │ │ └── prompt_overrides/ # 可选:覆盖默认 prompt 模板 +│ │ └── outputs/ # 项目产出目录 +│ ├── quant-pairs/ # 项目 2 +│ │ ├── blackboard.db +│ │ ├── config/ +│ │ └── outputs/ +│ └── moziplus-dev/ # 项目 3(自身开发) +│ ├── blackboard.db +│ ├── config/ +│ └── outputs/ +└── shared/ + ├── prompt_templates/ # 全局默认模板 + ├── schemas/ # 全局 Schema + └── agents.yaml # 全局 Agent 注册表 +``` + +### 4.1 项目注册表(_registry.yaml) + +```yaml +# projects/_registry.yaml +default_project: quant-momentum +projects: + quant-momentum: + display_name: "动量因子策略" + description: "基于动量因子的量化策略研发" + created_at: "2026-05-16T10:00:00Z" + status: active # active / archived + agents: [zhangfei-dev, zhaoyun-data, guanyu-dev] # 该项目可用的 Agent + quant-pairs: + display_name: "配对交易策略" + description: "统计套利配对交易研究" + created_at: "2026-05-16T10:00:00Z" + status: active + agents: [zhangfei-dev, zhaoyun-data] + moziplus-dev: + display_name: "平台开发" + description: "moziplus 自身开发" + created_at: "2026-05-16T10:00:00Z" + status: active + agents: [zhangfei-dev, simayi-challenger] +``` + +## 5. Daemon 变更 + +### 5.1 多连接池 + +```python +# daemon 内部 +class ProjectManager: + def __init__(self, projects_dir: Path): + self.projects_dir = projects_dir + self._connections: dict[str, sqlite3.Connection] = {} + self._configs: dict[str, ProjectConfig] = {} + self._load_registry() + + def get_connection(self, project_id: str) -> sqlite3.Connection: + if project_id not in self._connections: + db_path = self.projects_dir / project_id / "blackboard.db" + self._connections[project_id] = sqlite_connect(db_path) + return self._connections[project_id] + + def get_config(self, project_id: str) -> ProjectConfig: + if project_id not in self._configs: + config_path = self.projects_dir / project_id / "config" / "project.yaml" + self._configs[project_id] = ProjectConfig.load(config_path) + return self._configs[project_id] + + def load_guardrails(self, project_id: str) -> dict: + """项目级 guardrails.yaml 覆盖全局默认""" + global_guardrails = load_yaml("shared/guardrails.yaml") + project_guardrails_path = self.projects_dir / project_id / "config" / "guardrails.yaml" + if project_guardrails_path.exists(): + project_guardrails = load_yaml(project_guardrails_path) + return deep_merge(global_guardrails, project_guardrails) # 项目级覆盖 + return global_guardrails +``` + +### 5.2 ~~Tick 逻辑变更~~(已废弃,见 §5.4) + +> **原设计**:Daemon 主循环串行遍历所有项目 tick。每个项目 tick 完再 tick 下一个。 +> **问题**:所有项目/任务一起排队,项目 A 的长任务阻塞项目 B。 +> **新设计**:见 §5.4 per-project 并发调度。 + +### 5.3 Daemon 逻辑健康自检 + 线程存活监控(v2 扩展) + +```python +# §14 风险缓解:连续 N tick 无状态变更则告警 +STALE_TICK_THRESHOLD = 20 + +class DaemonHealth: + def __init__(self, project_id: str): + self.project_id = project_id + self._idle_ticks = 0 + + def record_idle(self): + self._idle_ticks += 1 + + def record_change(self): + self._idle_ticks = 0 + + def is_stale(self) -> bool: + return self._idle_ticks >= STALE_TICK_THRESHOLD +``` + +**线程存活监控**(见 §5.4.4 `Daemon._check_slot_health()`): +- Daemon 主线程每 60s 检查所有 ProjectSlot 线程是否存活 +- 线程死亡 → 记录日志 + 自动重启 +- 连续重启 3 次失败 → 告警(通过 Sanguo Mail 通知用户) + +**计数器超时兜底**: +- 如果 Agent 完成回调丢失(进程被杀、网络断),`ActiveAgentCounter` 不会归零 +- `_check_working_tasks()` 中,working 任务超过 `task_timeout`(默认 10 分钟)视为完成 +- 视为完成时主动 `decrement()`,防止计数器泄漏 + +### 5.5 Daemon 崩溃恢复(v2 新增) + +**设计原则**:状态全在 SQLite,Daemon 无状态。重启 = 重新加载所有项目 + 所有任务状态,继续执行。 + +**恢复流程(保守策略)**: + +``` +PM2 检测 Daemon 挂了 → 重启 Daemon + │ + ├── 读取 _registry.yaml → 恢复项目列表 + ├── 遍历每个 active 项目 → 打开 SQLite 连接 + ├── 扫描所有 working 任务 → 标记为 failed(原因: "Daemon restart, agent process lost") + ├── 启动 ProjectSlot 线程 + └── 后续 pending 任务正常分配 +``` + +**为什么不重新执行**: +1. Daemon 崩溃是不正常事件,Agent 子进程状态不可预测 +2. output.json 可能写了一半,重新执行比恢复更安全 +3. 用户手动 retry 比自动重新执行更可控 +4. task_attempts 表记录完整,不丢信息 + +**关键设计**: +1. **SQLite 是真相来源**——所有任务状态、产出记录都在 `.db` 文件里,Daemon 内存无状态 +2. **SQLite WAL 保护数据完整性**——崩溃时未提交的事务自动回滚 +3. **ActiveAgentCounter / DaemonHealth 重启后归零**——不需要持久化 +4. **task_attempts 的 attempt_index 递增**——retry 不覆盖历史 + +**不需要额外存储**: +- 不需要 checkpoint 文件——SQLite 就是 checkpoint +- 不需要 recovery log——task_attempts 表已经记录所有尝试 +- 不需要 recovery log——`task_attempts` 表已记录每次尝试 +- 不需要状态快照——每次 tick 从 SQLite 实时读取 + +**唯一注意**:`ActiveAgentCounter` 重启后从零开始——但 `_tick()` 会重新扫描 working 任务并重新计数,所以没问题。 + +### 5.4 并发调度模型(v2 新增) + +#### 5.4.1 问题 + +原设计中 Daemon 主循环串行 tick 所有项目: + +``` +Tick → Project A(30s)→ Project B(等A完成)→ Project C(等B完成) +``` + +问题: +1. **项目间互相阻塞**——Project A 有一个长任务在执行,Project B 的独立任务必须等 +2. **响应延迟**——3 个项目 tick 一次可能要 90s+,Project C 要等 60s 才被检查 +3. **不符合业界实践**——调研 7 个项目(Hermes/open-multi-agent/Wanman/Google ADK/Microsoft AutoGen/AgentScope/GSD),没有一个用全局串行排队 + +#### 5.4.2 业界并发模型调研 + +| 项目 | 并发模型 | 核心机制 | +|------|---------|---------| +| **open-multi-agent** | AgentPool + Semaphore | 全局 `maxConcurrency=5`,per-agent 互斥锁,`Promise.allSettled` 并行执行独立任务 | +| **Wanman** | per-agent 进程 | 每个 Agent 独立进程+独立 runLoop,Supervisor 通过消息总线协调 | +| **Google ADK** | asyncio.TaskGroup | `ParallelAgent` 用 `TaskGroup` 并行执行子 Agent | +| **Microsoft AutoGen** | Pregel Superstep | 每个 superstep 内所有激活 Executor 并行执行 | +| **Hermes** | 单线程 tick + flock | **单项目设计**,tick 内只有几个 cron job,不需要并发 | + +**关键发现**:open-multi-agent 的 `AgentPool + Semaphore + per-agent Lock` 是最成熟、最可借鉴的模型。 + +#### 5.4.3 设计:per-project 线程 + 全局资源信号量 + +``` +Daemon 主进程(轻量路由器 + 资源管控) +│ +├── 全局 LLM Semaphore(max_concurrent=3) +├── per-agent Lock(张飞不能同时在两个项目里跑) +│ +├── ProjectSlot A(独立线程) +│ └── 自己的 SQLite 连接 +│ └── 自己的 tick 循环(30s) +│ └── spawn Agent 时:acquire agent_lock → acquire llm_semaphore +│ +├── ProjectSlot B(独立线程) +│ └── (同上) +│ +└── ProjectSlot C(独立线程) + └── (同上) +``` + +**三层资源控制**: + +| 层级 | 控制对象 | 机制 | 原因 | +|------|---------|------|------| +| L1: 项目隔离 | SQLite 连接 | per-project 独立连接 | 数据物理隔离,无竞争 | +| L2: Agent 互斥 | 同一 Agent 不能并行 | `threading.Lock` per-agent | Agent session 不是线程安全的,张飞同一时刻只能服务一个任务 | +| L3: 全局资源 | LLM API 调用并发 | `threading.Semaphore(max_concurrent)` | 防止 API 限流、控制成本 | + +#### 5.4.4 核心代码 + +```python +import threading +import time +from pathlib import Path + + +class ActiveAgentCounter: + """线程安全的 Agent 活跃任务计数器。""" + + def __init__(self, max_global: int = 5): + self._counts: dict[str, int] = {} # agent_id → 活跃任务数 + self._total = 0 # 全局活跃总数 + self._max_global = max_global + self._lock = threading.Lock() + + def can_acquire(self, agent_id: str, max_per_agent: int = 1) -> bool: + """检查是否可以分配(非阻塞)。""" + with self._lock: + if self._total >= self._max_global: + return False + return self._counts.get(agent_id, 0) < max_per_agent + + def increment(self, agent_id: str): + with self._lock: + self._counts[agent_id] = self._counts.get(agent_id, 0) + 1 + self._total += 1 + + def decrement(self, agent_id: str): + with self._lock: + if self._counts.get(agent_id, 0) > 0: + self._counts[agent_id] -= 1 + self._total -= 1 + + +class Daemon: + """单进程 Daemon,per-project 线程并发。""" + + def __init__(self, config: DaemonConfig): + self.config = config + self.agent_counter = ActiveAgentCounter(max_global=config.max_global_active) # 默认 5 + self.slots: dict[str, ProjectSlot] = {} + self._slot_threads: dict[str, threading.Thread] = {} # 线程存活监控 + self._shutdown = threading.Event() + + def start(self): + """启动所有 active 项目的独立线程。""" + registry = load_registry() + for project_id, meta in registry["projects"].items(): + if meta["status"] != "active": + continue + self._start_slot(project_id, meta) + + # 主线程:监控 + 等待 shutdown + while not self._shutdown.is_set(): + self._check_slot_health() + self._shutdown.wait(60) # 每 60s 检查一次线程存活 + + def _start_slot(self, project_id: str, meta: dict): + slot = ProjectSlot( + project_id=project_id, + config=meta, + agent_counter=self.agent_counter, + tick_interval=self.config.tick_interval, + shutdown_event=self._shutdown, + ) + self.slots[project_id] = slot + t = threading.Thread(target=slot.run_loop, name=f"project-{project_id}", daemon=True) + t.start() + self._slot_threads[project_id] = t + + def _check_slot_health(self): + """检查所有 ProjectSlot 线程是否存活,死亡则重启。""" + for project_id, thread in list(self._slot_threads.items()): + if not thread.is_alive(): + logger.warning(f"ProjectSlot {project_id} thread died, restarting...") + meta = self.slots[project_id].config + self._start_slot(project_id, meta) + + def shutdown(self): + self._shutdown.set() + + +class ProjectSlot: + """单项目的独立 tick 循环。""" + + def __init__(self, project_id, config, agent_counter, + tick_interval=30, shutdown_event=None): + self.project_id = project_id + self.conn = sqlite_connect(Path(f"projects/{project_id}/blackboard.db")) + self.config = config + self.agent_counter = agent_counter # 共享:全局 Agent 活跃计数器 + self.tick_interval = tick_interval + self.shutdown = shutdown_event or threading.Event() + self.health = DaemonHealth(project_id) + + def run_loop(self): + """独立线程的主循环。""" + while not self.shutdown.is_set(): + try: + self._tick() + except Exception as e: + logger.error(f"[{self.project_id}] tick failed: {e}") + self.shutdown.wait(self.tick_interval) + + def _tick(self): + """单次 tick:找 pending 任务,尝试分配。""" + # 先检查已完成的 Agent,释放计数器 + completed = self._check_working_tasks() # 返回已完成的 agent_id 列表 + for agent_id in completed: + self.agent_counter.decrement(agent_id) + + pending = find_pending(self.conn) + if not pending: + self.health.record_idle() + return + + for task in pending: + agent_id = task["assignee"] + max_per_agent = self.config.get("max_active_per_agent", 1) + + # 检查全局 + per-agent 并发上限 + if not self.agent_counter.can_acquire(agent_id, max_per_agent): + logger.info(f"[{self.project_id}] {agent_id} at capacity, skip task {task['id']}") + continue + + # sequential 模式:检查该 Agent 在本项目是否有 working 任务 + if self.config.get("agent_parallelism") != "parallel": + if has_working_task(self.conn, agent_id): + continue + + # 生成 session_id(§5.4.6 命名规则) + session_id = self._get_session_id(agent_id, task['id']) + + # spawn Agent(异步,不阻塞) + spawn_agent( + project_id=self.project_id, + task=task, + session_id=session_id, + ) + self.agent_counter.increment(agent_id) # +1 + self.health.record_change() + + def _get_session_id(self, agent_id: str, task_id: str) -> str: + """§5.4.6 session 命名规则。""" + if self.config.get("agent_parallelism") != "parallel": + # sequential:同一项目同一 Agent 复用 session(保持上下文连续性) + return f"agent:{agent_id}:project:{self.project_id}" + else: + # parallel:每个任务独立 session + return f"agent:{agent_id}:project:{self.project_id}:task:{task_id}" +``` + +#### 5.4.5 资源控制模型(v2 修订:异步计数器替代同步信号量) + +**原设计问题**:spawn_agent() 不是瞬时操作——Agent 执行涉及 LLM API 调用(可能多轮工具调用),如果在 spawn 完成后才释放锁/信号量,并发退化回串行;如果在 spawn 启动后立即释放,信号量没有真正限流。 + +**修订方案**(采纳司马懿评审建议): + +| 资源 | 原方案 | 修订方案 | 原因 | +|------|--------|---------|------| +| Agent 互斥 | `threading.Lock` | 移除。改为 per-project session 命名 + `_check_working_tasks()` | 同一 Agent 可用不同 session-id 安全服务不同项目 | +| LLM 并发 | `threading.Semaphore` | `ActiveAgentCounter`(异步计数器) | spawn 是异步的,同步信号量无法精确限流 | +| 项目隔离 | per-project SQLite | 不变 | | + +**新时序**: + +``` +ProjectSlot._tick() + │ + ├── 检查 ActiveAgentCounter[agent_id] < max_active? + │ └── 否 → 跳过,下个 tick 再检查 + ├── 检查 agent_parallelism == "sequential" 且该 Agent 有 working 任务? + │ └── 是 → 跳过 + ├── spawn_agent(project_id, task) + │ ├── session_id = "agent:{agent_id}:project:{project_id}:task:{task_id}" + │ └── ActiveAgentCounter.increment(agent_id) # +1 + │ + └── Agent 完成回调(下次 tick 检测到 output 或 webhook) + └── ActiveAgentCounter.decrement(agent_id) # -1 +``` + +**关键变化**: +1. 不再使用 `threading.Lock` 和 `threading.Semaphore`——它们是同步原语,不适合异步 spawn 场景 +2. 改用 `ActiveAgentCounter`(线程安全计数器),spawn 时 +1,Agent 完成回调时 -1 +3. `_tick()` 分配前检查计数器,超过阈值就跳过 +4. Agent session 按 `agent:{agent_id}:project:{project_id}:task:{task_id}` 命名,项目+任务级天然隔离 + +#### 5.4.6 Agent 并行策略 + Session 隔离 + +**并行策略配置**: + +```yaml +# _registry.yaml 中可配置 +projects: + quant-momentum: + agent_parallelism: sequential # 同一 Agent 同一时刻只跑一个任务(默认) + max_active_per_agent: 1 # sequential 的显式写法 + quant-pairs: + agent_parallelism: parallel # 同一 Agent 可同时跑多个任务 + max_active_per_agent: 2 # 最多 2 个并行 +``` + +**Session 命名规则**: + +``` +格式:agent:{agent_id}:project:{project_id}:task:{task_id} +示例:agent:zhangfei-dev:project:quant-momentum:task:task-001 +``` + +- 每个任务独立 session,任务间上下文不串 +- 同一 Agent 在不同项目用不同 session,项目间上下文不串 +- `sequential` 模式:同一项目同一 Agent 只有一个活跃 session(新的任务复用或新开) +- `parallel` 模式:每个任务独立 session + +**sequential 模式下的 session 复用**: + +```python +def _get_session_id(self, agent_id: str, task_id: str) -> str: + project_config = self.config + if project_config.get("agent_parallelism") != "parallel": + # sequential:同一项目同一 Agent 复用 session(保持上下文连续性) + return f"agent:{agent_id}:project:{self.project_id}" + else: + # parallel:每个任务独立 session + return f"agent:{agent_id}:project:{self.project_id}:task:{task_id}" +``` + +#### 5.4.7 并发安全保证 + +| 并发场景 | 风险 | 保护机制 | +|---------|------|---------| +| 两个项目同时写同一个 SQLite | 数据损坏 | 每个项目独立 `.db` 文件,不存在此场景 | +| 两个项目同时分配同一个 Agent | Agent 资源争抢 | `ActiveAgentCounter` + `max_active_per_agent` 限制 | +| LLM API 并发超限 | API 限流/超限 | `ActiveAgentCounter` 全局计数,`_tick()` 分配前检查 | +| ProjectSlot 线程异常退出 | 项目 tick 停止 | try/except 包裹 + Daemon 监控线程存活(§5.4.8) | +| Daemon 主进程崩溃 | 所有项目停止 | PM2 自动重启 + SQLite WAL 保护数据完整性 | +| Agent 完成回调丢失 | 计数器不归零 | 超时兜底:working 任务超过 `task_timeout` 视为完成,计数器 -1 | +| _registry.yaml 并发写入 | 数据损坏 | _registry.yaml 只在 CLI 操作时读写(非 tick 热路径),tick 状态用内存 dict | + +### 6.1 项目管理命令 + +```bash +# 创建项目 +python3 blackboard.py project create --name quant-momentum --display-name "动量因子策略" --agents zhangfei-dev,zhaoyun-data,guanyu-dev + +# 列出项目 +python3 blackboard.py project list + +# 切换默认项目 +python3 blackboard.py project default quant-momentum + +# 归档项目(不删除数据,只停 tick) +python3 blackboard.py project archive quant-pairs + +# 删除项目(删除数据,需确认) +python3 blackboard.py project delete quant-pairs --confirm +``` + +### 6.2 所有操作指定项目 + +```bash +# 方式1:命令行参数 +python3 blackboard.py read --project quant-momentum --task task-001 + +# 方式2:环境变量(设置后所有命令默认用此项目) +export MOZIPLUS_PROJECT=quant-momentum +python3 blackboard.py read --task task-001 + +# 方式3:默认项目(_registry.yaml 中 default_project) +# 不指定 --project 也不设环境变量时,使用 default_project +python3 blackboard.py read --task task-001 +``` + +### 6.3 Agent 使用的项目解析优先级 + +``` +1. --project 参数(显式指定) +2. MOZIPLUS_PROJECT 环境变量 +3. _registry.yaml 中的 default_project +4. 如果只有一个 active 项目,自动使用 +5. 都没有 → 报错"请指定项目" +``` + +## 7. L2 上下文注入变更 + +### 7.1 Agent spawn 时注入项目上下文 + +L2 prompt_template 三段式注入增加项目段: + +``` +═══ 项目上下文 ═══ +项目: quant-momentum(动量因子策略) +背景: +可用 Agent: 张飞(编码)、赵云(数据)、关羽(风控) +═══ 任务上下文 ═══ +(原有内容不变) +``` + +### 7.2 project_context.yaml 示例 + +```yaml +# projects/quant-momentum/config/project_context.yaml +description: "基于动量因子的量化策略研发" +domain: "量化交易" +data_sources: + - "NAS /Volumes/stock/ A股日线数据" + - "NAS /Volumes/stock/minute_kline/ 分钟线数据" +code_repo: "~/.openclaw/sanguo_projects/sanguo_quant_live/" +key_constraints: + - "所有策略必须通过回测验证才能上实盘" + - "止损逻辑必须经过关羽风控审查" +``` + +## 8. 跨项目协作 + +### 8.1 默认禁止跨项目 + +Agent 不能跨项目读写黑板。这是安全边界——不同项目的数据、配置、产出互不干扰。 + +### 8.2 跨项目数据共享 + +如果项目 A 需要项目 B 的产出(如"moziplus-dev 需要赵云的数据"),通过文件系统共享: + +```bash +# 项目 A 中,Agent 把产出写到 NAS 共享路径 +# 项目 B 中,Agent 从 NAS 共享路径读取 +``` + +不需要特殊的跨项目协议——**NAS 路径就是跨项目的桥梁**,和当前团队的工作方式一致。 + +### 8.3 跨项目任务请求(可选扩展) + +如果未来需要 Agent 主动发起跨项目请求: + +``` +项目 A 黑板 → 创建 cross_project_request 类型任务 +→ Daemon 检测到 → 在项目 B 黑板创建对应任务 +→ 项目 B Agent 完成 → 产出写入 NAS +→ Daemon 检测项目 B 完成 → 更新项目 A 任务状态 +``` + +当前不实现,预留设计空间。 + +### 8.4 项目归档/删除时正在运行的任务(v2 新增) + +**归档(archive)**: +1. 检查是否有 working 状态的任务 +2. 有 → 将这些任务标记为 cancelled,等待 Agent 完成回调(超时兜底 5 分钟) +3. 无 → 立即停止该项目的 ProjectSlot 线程 +4. 将 `_registry.yaml` 中状态改为 `archived` + +**删除(delete)**: +1. 必须先 archive +2. 必须无 working 任务(归档时已处理) +3. `--confirm` 确认 +4. `rm -rf projects/{project_id}/` +5. 从 `_registry.yaml` 移除条目 + +**禁止直接删除 active 项目**——必须先归档。 + +## 9. 与其他课题的关系 + +| 课题 | 关系 | 说明 | +|------|------|------| +| 课题1(三层执行) | 无冲突 | Agent spawn 时多传一个 project_id,L2 注入多加项目上下文 | +| 课题2(事件驱动) | 微调 | Inbox JSONL 增加 project_id 字段,Daemon 路由到正确连接 | +| 课题3(挑战/评审) | 项目级配置 | guardrails.yaml 项目级覆盖 | +| 课题4(拆解+上下文) | 项目级配置 | project_context.yaml 注入 L2 | +| 课题6(经验沉淀) | 项目级经验 | 每个项目独立的 experiences 表,经验不跨项目污染 | +| 课题7+9(交互+Dashboard) | 多项目视图 | Dashboard 需要项目切换/多项目概览 | +| Worktree 隔离 | 正交 | Worktree 解决"同项目内多 Agent 并行改代码",课题11 解决"不同项目数据隔离" | + +## 10. 黑板 Schema 变更 + +**不增加 project_id 字段**——每个项目有独立数据库,表结构不变。 + +唯一新增:`_registry.yaml` 项目注册表。 + +## 11. 开发清单 + +| # | 任务 | 依赖 | +|---|------|------| +| 1 | 项目目录结构 + _registry.yaml + project.yaml Schema | 无 | +| 2 | ActiveAgentCounter(线程安全计数器 + 全局/per-agent 双重限制) | 无 | +| 3 | ProjectSlot(独立线程 tick + SQLite 独立连接 + spawn 前检查计数器) | 1, 2 | +| 4 | Daemon 主循环(启动状态恢复 + ProjectSlot 线程监控/重启) | 3 | +| 4a | 启动时幽灵 working 任务处理(扫描+标记 failed) | 4 | +| 5 | CLI project create/list/default/archive/delete 命令 | 1 | +| 6 | CLI 所有操作增加 --project 参数 + 优先级解析 | 1, 3 | +| 7 | L2 prompt_template 注入 project_context + session_id 命名规则 | 3, 6 | +| 8 | Daemon 逻辑健康自检(按项目追踪 + 计数器超时兜底) | 4 | +| 9 | 项目归档/删除安全流程(working 任务处理) | 4, 5 | +| 10 | Dashboard 项目切换 + 多项目概览 | 课题9 | + +--- + +## 附录:方案 B 的详细反驳 + +有人可能觉得方案 B(加 project_id 字段)更简单。但实际上: + +1. **安全面**:方案 B 靠 `WHERE project_id = ?` 逻辑隔离。一个漏掉的 WHERE = 数据泄漏。方案 C 靠物理文件隔离,漏不掉。 + +2. **性能面**:方案 B 所有项目共享一个 SQLite 文件。10 个项目各 10 个任务 = 100 个任务在一个 `.db` 里。WAL 写入是串行的,多项目并发 tick 会互相等待。方案 C 每个项目独立文件,互不影响。 + +3. **运维面**:方案 B 删除项目 = `DELETE FROM tasks WHERE project_id = ?` + 8 张表都要删。方案 C = `rm -rf projects/xxx/`。备份/恢复同理。 + +4. **配置面**:方案 B 的 guardrails.yaml 要设计"全局默认 + 项目覆盖"的合并逻辑。方案 C 每个项目独立 config/ 目录,天然隔离,覆盖逻辑更清晰。 + +5. **经验沉淀面**:方案 B 的经验混在一个 experiences 表里。如果项目 A 的"pytest 参数经验"污染到项目 B(B 可能不做 Python),反而有害。方案 C 每个项目独立经验库。 + +**唯一方案 B 更优的场景**:跨项目统计分析("所有项目的平均完成时间")。但这个需求可以后期通过注册表元数据实现,不需要把所有数据放一个库里。 diff --git a/docs/design/topic3-challenge-review-proposal.md b/docs/design/topic3-challenge-review-proposal.md new file mode 100644 index 0000000..2c5f0bd --- /dev/null +++ b/docs/design/topic3-challenge-review-proposal.md @@ -0,0 +1,906 @@ +# 课题3 设计方案:挑战/评审体系 + 质量门控结构化 + +**作者**: 庞统士元 +**日期**: 2026-05-15 +**状态**: 方案待确认 +**前置**: 课题1(AI决策者参与)、课题2(事件驱动+上下文传递)已结题 + +--- + +## 1. 设计目标 + +课题1已定义了"谁来判断"(双轨制:Daemon系统保护 + Coordinator AI决策)和"判断什么"(must_haves三件套、幻觉门控、Scope Guard)。课题2定义了"怎么触发判断"(Tick+Inbox事件驱动、Handoff Comment无缝接手)。 + +**课题3要解决的问题是:判断的结果怎么记录、怎么协商、怎么流转。** + +具体来说: +1. 评审结果怎么结构化存储(不是散落在 comments 里的自然语言) +2. 挑战/评审的协商流程怎么设计(几轮、升级条件、对抗模式) +3. 评审体系和现有状态机(pending→working→review→done)怎么对齐 + +## 2. v1.0 三层体系的问题 + +| 问题 | v1.0 做法 | 根因 | +|------|----------|------| +| PAV自律形同虚设 | Plan→Act→Verify 靠 Skill 文本约束 | Skill 是"菜单"不是"触发器",Agent 可忽略 | +| 每个节点都审过重 | 司马懿审所有节点 | 没有分级,简单任务和复杂任务同一个审查标准 | +| 方案阶段没审查 | 只审产出不审方案 | 编码前的设计方案没有独立审查 | +| 评审结果不结构化 | 司马懿写自然语言评论 | 无法机器判断"通过还是没通过",只能人读 | +| 司马懿角色重叠 | 节点审一次 + 终审一次 | 重复劳动 | + +## 3. 设计决策 + +### D3-1:评审体系由三层改为"分级 + 流水线" + +> **参考实践**: +> - **superpowers**:串行双审(spec reviewer → code quality reviewer),先审方案再审代码 +> - **TradingAgents**:多空对抗辩论(Bull vs Bear → Research Manager 裁决) +> - **OpenAI Agent SDK**:Guardrail 是 tripwire(检测到违规立即中断,不是建议) +> - **Hermes v0.13**:Comment 的 metadata JSON 承载结构化数据(changed_files / tests_run / diff_path) +> - **v1.0 PRD**:review_result JSON 结构(verdict + issues + round + max_rounds) +> - **GitHub PR Review**:APPROVE / REQUEST_CHANGES / COMMENT 三种明确 verdict + +**核心思路**:不搞三层并行,而是每个任务按风险等级走不同的审查流水线。 + +| 任务风险等级 | 流水线 | 方案审查 | 产出审查 | 审查模式 | 最大轮次 | +|------------|--------|---------|---------|---------|---------| +| **high**(部署/策略/资金) | 三阶段 | ✅ 独立审查 | ✅ 独立审查 + Guardrail | 对抗辩论 | 5 | +| **standard**(编码/设计) | 二阶段 | ✅ 方案过挑战 | ✅ 产出过挑战 | 单审 | 3 | +| **low**(文档/格式化/搬运) | 一阶段 | ❌ 跳过 | ⚡ Guardrail 自动检查 | 自动 | 0 | +| **research**(调研/分析) | 一阶段 | ❌ 跳过 | ✅ 庞统确认方向 | 单审 | 2 | + +**和课题1的分级审查矩阵对齐**:课题1的 §9.5 已定义了四级风险等级和审查深度,课题3 补充的是每个等级内部的具体流程。 + +### D3-2:评审三阶段流水线(high/standard 任务) + +> **参考实践**: +> - **superpowers**:implementer → spec reviewer(必须通过) → code quality reviewer +> - **TradingAgents**:多空辩论 + Manager 裁决,用 `add_conditional_edges` 实现 +> - **OpenAI Agent SDK**:OutputGuardrail 是并行运行的轻量 AI Agent,不是 if-else 规则 + +**阶段 1:方案审查(Plan Review)**——只对 high/standard 任务 + +``` +执行者提交方案(scope_declaration)到黑板 decisions 表 + ↓ +挑战者审查方案 + ├── high:对抗辩论(正方 vs 反方 → 庞统裁决) + └── standard:单审(指定挑战者,默认司马懿) + ↓ +通过 → 进入执行阶段 +未通过 → 协商修改(最多 max_rounds 轮) + └── 超轮次 → 升级用户 +``` + +方案审查和课题1的 Scope Guard 关系: +- Scope Guard(L2 sub 异步检查)是吹哨人——发现偏离写 observation +- 方案审查(L3 Agent 正式审查)是正式评审——通过/打回有 verdict +- 两者互补:Scope Guard 是过程中的软检查,方案审查是阶段门 + +**阶段 2:执行 + Guardrail 自动检查**——所有任务 + +``` +执行者按方案执行 + ↓ +每次写 output 时,Daemon 自动触发 Guardrail + ├── L1 机械检查(文件存在、JSON 格式、字段非空)→ 不通过直接打回 + ├── L2 轻量 AI 检查(Schema 校验、artifacts 路径验证)→ 不通过写 observation + └── L3 安全红线(tripwire:如直接操作生产环境)→ 立即中断 + ↓ +Guardrail 通过 → 进入产出审查 +Guardrail 不通过 → 自动打回(tripwire 直接 block) +``` + +**阶段 3:产出审查(Output Review)**——high/standard 任务 + +``` +执行者提交产出到黑板 outputs 表 + ↓ +挑战者审查产出(质量/完整性/与方案一致性) + ↓ +评审结果结构化记录到 reviews 表 + ↓ +通过 → status: review → done +未通过 → status: review → working(打回重做) + └── 协商在 comments 表讨论 + └── 超轮次 → 升级用户 +``` + +### D3-3:评审结果结构化存储(reviews 表) + +> **参考实践**: +> - **Hermes**:Comment 的 metadata JSON 承载结构化数据 +> - **SWE-agent**:Trajectory 线性追加,不修改历史 +> - **v1.0 PRD**:review_result JSON(verdict + issues + round) +> - **GitHub PR Review**:APPROVE / REQUEST_CHANGES / COMMENT 三种 verdict +> - **Control Center**:artifacts 按 stage 组织,支持阶段回退 + +**设计原则**: +1. **追加写入不修改历史**(SWE-agent 模式)——每轮评审是新记录 +2. **黑板索引 + 文件详情**(课题2 原则)——黑板存摘要,文件存详情 +3. **结构化 verdict**(GitHub PR Review 模式)——枚举值,不是自然语言 +4. **关联产出物**(Control Center 模式)——review 关联到具体 output + +```sql +-- ===== 评审结果表 ===== +CREATE TABLE IF NOT EXISTS reviews ( + id TEXT PRIMARY KEY, -- rev-001 + task_id TEXT NOT NULL, + output_id TEXT, -- 关联产出物(outputs 表) + reviewer TEXT NOT NULL, -- 审查者 agent id 或 'system' + review_type TEXT NOT NULL, + CHECK (review_type IN ('plan_review', 'output_review', 'guardrail', 'final_review')), + + -- 评审结论(必须是枚举) + verdict TEXT NOT NULL, + CHECK (verdict IN ('approved', 'rejected', 'needs_revision')), + + -- 信心度(可选) + confidence REAL, -- 0.0-1.0 + + -- 协商轮次 + round INTEGER NOT NULL DEFAULT 1, + max_rounds INTEGER NOT NULL DEFAULT 3, + consensus_reached BOOLEAN DEFAULT FALSE, + + -- 摘要(黑板索引) + summary TEXT NOT NULL, -- 一句话结论(人可读) + + -- 详情路径 + detail_path TEXT, -- 完整评审报告文件 + + created_at TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (task_id) REFERENCES tasks(id), + FOREIGN KEY (output_id) REFERENCES outputs(id) +); + +CREATE INDEX IF NOT EXISTS idx_reviews_task ON reviews(task_id); +CREATE INDEX IF NOT EXISTS idx_reviews_output ON reviews(output_id); +``` + +**评审详情文件**(detail_path 指向): + +```json +{ + "review_id": "rev-001", + "task_id": "task-001", + "output_id": "out-001", + "reviewer": "simayi-challenger", + "review_type": "output_review", + "verdict": "needs_revision", + "confidence": 0.65, + "round": 1, + "max_rounds": 3, + "summary": "代码逻辑正确但缺少错误处理和单元测试", + "issues": [ + { + "id": "iss-001", + "severity": "critical", + "category": "correctness", + "location": "src/trading.py:42", + "description": "未处理网络超时异常", + "suggestion": "添加 try-except 包装,设置 30s 超时" + } + ], + "positives": ["代码结构清晰,职责分离做得好"], + "metadata": { + "files_reviewed": ["src/trading.py", "src/utils.py"], + "total_lines_reviewed": 150 + } +} +``` + +### D3-4:Guardrail 检查结果也入 reviews 表 + +> **参考实践**: +> - **OpenAI Agent SDK**:Guardrail 的 tripwire_triggered 是结构化输出 +> - **Hermes 幻觉门控**:系统验证产出是否真实存在 + +Guardrail 自动检查结果存入 reviews 表(`reviewer='system'`, `review_type='guardrail'`): + +```json +{ + "review_id": "rev-auto-001", + "reviewer": "system", + "review_type": "guardrail", + "verdict": "rejected", + "summary": "Guardrail 检查失败:产出文件不存在", + "issues": [ + { + "severity": "blocking", + "description": "产出文件不存在: output.md" + } + ] +} +``` + +**和课题1的 Guardrail 吹哨人机制对齐**: +- L1 机械检查失败 → 直接入 reviews 表(verdict=rejected)+ 打回 +- L2 轻量 AI 检查发现问题 → 写 observation(课题1设计)+ 入 reviews 表(verdict=needs_revision) +- L3 tripwire → 直接入 reviews 表(verdict=rejected)+ block 任务 + +### D3-5:对抗辩论模式(high 风险任务) + +> **参考实践**: +> - **TradingAgents**:Bull Researcher vs Bear Researcher → Research Manager 裁决 +> - **MetaGPT**:Engineer Agent 隐式 supervisor,按 SOP 产出 +> - **oh-my-claudecode Critic**:Critic 被禁止 Write/Edit 工具,只能读和评论 + +**high 风险任务的方案审查和产出审查都走对抗模式**: + +``` +方案审查(对抗模式): + 正方(执行者):写 scope_declaration,论证方案可行性 + 反方(挑战者池中的一人):找方案漏洞、风险、遗漏 + ↓ + 庞统裁决:综合双方观点,做最终判断 + ├── 认可正方 → 方案通过 + ├── 认可反方 → 方案打回 + └── 综合意见 → 要求修改后重新辩论 +``` + +**挑战者池**:不是固定司马懿一个人,而是按任务类型选择: +- 编码任务 → 司马懿 +- 风控任务 → 关羽 +- 数据任务 → 赵云(数据质量视角) +- 部署任务 → 姜维 + +庞统在黑板上创建任务时指定 reviewer(`assigned_by` 字段已有)。 + +### D3-6:协商流程与状态机对齐 + +> **参考实践**: +> - **superpowers**:implementer 报告四种状态(task_completed / task_completed_with_notes / task_blocked / task_failed) +> - **Hermes**:block with reason 前缀约定(review-required: / blocked-by:) +> - **v1.0 PRD**:三轮协商上限,超轮次升级用户 + +**现有状态机**:`pending → claimed → working → review → done` + +**和评审流水线的对齐**: + +``` +pending → claimed → working + ↓ + [方案审查](high/standard) + ├── approved → 继续 working + └── needs_revision → 继续 working(修改方案) + └── 超轮次 → 升级用户 + ↓ + [执行 + Guardrail] + ├── guardrail rejected → back to working(自动打回) + └── guardrail passed → 继续 + ↓ + 写 output → status: review + ↓ + [产出审查](high/standard) + ├── approved → status: done + ├── needs_revision → status: working(协商在 comments) + └── rejected → status: working(打回重做) + └── 超轮次 → 升级用户 + +low 风险任务:working → [Guardrail 自动检查] → done(跳过 review 状态) +research 任务:working → [庞统确认] → review → done +``` + +**新状态机扩展**(在现有 8 状态基础上,不增加新状态): + +| 状态转换 | 触发条件 | 对应阶段 | +|---------|---------|---------| +| working → working | 方案审查 needs_revision | 阶段 1 | +| working → review | 写 output + Guardrail 通过 | 阶段 2→3 | +| working → working | Guardrail rejected(自动打回) | 阶段 2 | +| review → done | 产出审查 approved | 阶段 3 | +| review → working | 产出审查 needs_revision | 阶段 3 | +| review → blocked | 超轮次升级 | 阶段 3 | + +### D3-7:comments 表和 reviews 表的职责分离 + +| 表 | 职责 | 内容类型 | verdict | +|---|------|---------|---------| +| **comments** | 讨论、@mention、协商过程 | 自然语言 | ❌ 无 | +| **reviews** | 正式评审结论 | 结构化 JSON | ✅ 必须有 | + +评审协商过程("这里有个问题,建议这样改")写在 comments。 +评审结论("通过" / "不通过,原因见 issues")写在 reviews。 + +两者关联:reviews.detail_path 指向的完整报告可以引用 comments 中的讨论。 + +### D3-8:声明式 Guardrail 配置(YAML) + +> **参考实践**: +> - **OpenAI Agent SDK**:`@output_guardrail` 装饰器声明检查函数 +> - **v1.0 M4 Guard**:entry/exit guard + skill 化检查逻辑 +> - **SonarQube / CodeClimate**:rule_id 关联可查规则文档 + +```yaml +# guardrails.yaml +# +# L1 check 用 assert 字段(Python 表达式,Daemon eval 执行) +# L2 check 用 prompt 字段(传给 subagent 的检查指令) + +task_types: + coding: + output_guardrails: + - name: file_exists + assert: "len(output.get('files', [])) > 0" + severity: blocking + layer: L1 + - name: json_valid + assert: "output.get('json_schema_valid', False) == True" + severity: blocking + layer: L1 + - name: artifacts_exist + assert: "all(os.path.exists(p) for p in output.get('artifacts_paths', []))" + severity: blocking + layer: L1 + - name: scope_alignment + prompt: "Compare the agent's scope_declaration against task truths. Check: is every truth covered? Are there deviations not declared?" + severity: warning + layer: L2 + output_review: + required: true + mode: single_reviewer + max_rounds: 3 + + deploy: + plan_review: + required: true + mode: debate + max_rounds: 5 + output_guardrails: + - name: no_direct_production + assert: "output.get('target_env') != 'production'" + severity: tripwire + layer: L1 + - name: rollback_plan_exists + assert: "output.get('rollback_plan') is not None" + severity: blocking + layer: L1 + output_review: + required: true + mode: debate + max_rounds: 5 + + data: + output_guardrails: + - name: format_check + assert: "output.get('format') in ['csv', 'parquet', 'json']" + severity: blocking + layer: L1 + output_review: + required: false + + research: + output_review: + required: true + reviewer: "pangtong-fujunshi" # 庞统确认方向 + mode: single_reviewer + max_rounds: 2 +``` + +## 4. 评审记录与 Handoff Comment 的关系 + +> **对齐课题2设计** + +Handoff Comment(课题2)是 Agent 结束时写的交接信息。评审记录(课题3)是挑战者写的审查结论。 + +两者在黑板上的关系: + +``` +Agent A 完成产出 + → 写 Handoff Comment(comments 表,type=handoff) + → 写 output(outputs 表) + → 通知 Daemon(inbox JSONL) + ↓ +Daemon 触发 Guardrail 自动检查 + → 结果写入 reviews 表(reviewer='system', review_type='guardrail') + → 通过 → 进入产出审查 + → 不通过 → 打回 + ↓ +Daemon spawn 挑战者 + → 挑战者读黑板:L1 含 Agent A 的 Handoff Comment + output summary + → 挑战者做审查 + → 写 review(reviews 表,verdict + summary + detail_path) + → 写 comment(comments 表,协商讨论) + → 通过 → status: done + → 不通过 → status: working + Agent A 被 spawn 重做 +``` + +下一个 Agent(如果需要)的 L1 消息中包含: +1. Agent A 的 Handoff Comment(最近3条评论之一) +2. 挑战者的 review summary(reviews 表最新一条) +3. 协商过程的 comments(L2 按需读取) + +### D3-9:审查协议注册表(Review Protocol Registry) + +> **参考实践**: +> - **superpowers**:三个独立 prompt 文件——`implementer-prompt.md`、`spec-reviewer-prompt.md`、`code-quality-reviewer-prompt.md`,每个角色有专属模板,不是笼统的"请审查" +> - **oh-my-claudecode Critic**:Investigation Protocol 分 Phase 执行(预判→验证→多视角→缺口分析→自审),不同 artifact type 自动切换视角(代码→安全/新人/运维,方案→执行者/利益相关者/怀疑论者) +> - **superpowers spec-reviewer**:prompt 注入对抗性指令——"DO NOT trust the report. Read the actual code.""Compare to requirements line by line." + +**问题**:审查者不知道自己该审什么,容易陷入局部审查(编码规范、编译通过),漏掉本质问题(需求一致性、语义正确性)。或者被挑战后一律照改,不加思考。 + +**根因**:审查指令是笼统的"请审查",审查者靠"自觉"决定审什么。Skill 是软引导,可看可不看。 + +**方案**:审查协议是代码注入的,不是靠 Agent 自己去找的。Daemon spawn 审查者时,根据任务类型 + 审查类型动态加载协议模板,注入到 bootstrap 消息中。 + +``` +review_protocols/ +├── plan_review.yaml # 方案审查协议 +├── output_review.yaml # 产出审查协议 +├── guardrail_l2.yaml # L2 轻量AI检查协议 +└── analysis_review.yaml # 分析/调研审查协议 +``` + +每个协议文件定义四个维度: + +**维度1:审查维度(审什么)** + +```yaml +# review_protocols/output_review.yaml(示例片段) +dimensions: + - id: requirement_traceability + description: "每个 must_have truth 是否被覆盖" + weight: critical + method: "逐条比对 truths → 产出代码/文件" + + - id: scope_alignment + description: "产出是否与 scope_declaration 一致" + weight: critical + method: "对比 decisions.scope_declaration vs 实际产出" + + - id: correctness + description: "逻辑是否正确" + weight: major + method: "追踪执行路径,验证关键逻辑" + + - id: completeness + description: "是否有遗漏" + weight: major + method: "缺口分析:什么缺失了?什么假设未被验证?" + + - id: constraint_compliance + description: "是否遵守约束" + weight: major + method: "逐条检查 tasks.constraints" +``` + +**维度2:审查方法(怎么审)**——参考 oh-my-claudecode Critic 的 Investigation Protocol + +```yaml +investigation_protocol: + phases: + - name: pre_commitment + instruction: > + 先不读产出,凭领域经验预测3-5个最可能出问题的点。 + 写下预测。然后逐个验证。这迫使主动搜索而非被动阅读。 + + - name: verification + instruction: > + 读实际产出(不是报告),逐条验证每个 truth。 + 提取所有文件引用、函数名、API 调用,逐一对照源码验证。 + 模拟执行每个步骤,不只读文字描述。 + + - name: multi_perspective + instruction: > + 从不同角色看这份产出: + - 安全视角:什么信任边界被跨越?什么输入没校验? + - 新人视角:不熟悉代码的人能理解吗?什么上下文被假设但没说明? + - 运维视角:规模化后怎样?依赖失败时怎样?爆炸半径多大? + + - name: gap_analysis + instruction: > + 不只看"什么有问题",还看"什么缺失了"。 + 问:什么会破坏这个?什么边界情况没处理?什么假设可能是错的? + + - name: self_audit + instruction: > + 给自己的每个 finding 打 confidence(HIGH/MEDIUM/LOW)。 + LOW confidence → 降级为 Open Question。 + "作者能立刻反驳吗?"如果能 → 降级。 + "这是真正的缺陷还是风格偏好?"如果是偏好 → 降级。 +``` + +不同审查类型用不同的 multi_perspective 视角集(参考 Critic 的代码视角 vs 方案视角分离): + +| 审查类型 | 多视角集合 | +|---------|----------| +| `output_review`(代码产出) | 安全 / 新人 / 运维 | +| `plan_review`(方案) | 执行者 / 利益相关者 / 怀疑论者 | +| `analysis_review`(调研分析) | 领域专家 / 实践者 / 反方辩手 | +| `guardrail_l2`(轻量检查) | 无多视角(单一维度快速检查) | + +**维度3:对抗性指令(防止走过场)**——参考 superpowers spec-reviewer 的"DO NOT trust"指令 + +```yaml +adversarial_instructions: + - "DO NOT trust the implementer's report. Read the actual code/files." + - "If something looks correct on the surface, verify it works in context." + - "Not just what's wrong — also check what's MISSING." + - "Accept findings only with evidence (file:line or specific quote). No evidence = opinion." + - "If you find 1 CRITICAL or 3+ MAJOR issues, escalate to adversarial mode: actively hunt for more problems, challenge every design decision, expand scope to adjacent code." + - "Also check for EXTRA/UNNEEDED work that wasn't requested." +``` + +**维度4:输出格式** + +```yaml +output_schema: "schemas/review-output.schema.json" +verdict_options: ["approved", "rejected", "needs_revision"] + +required_fields: + - verdict + - summary + - issues[] (each with severity, description, evidence) + - confidence (0.0-1.0) +``` + +**Daemon 拼接逻辑**: + +```python +def build_reviewer_bootstrap(task, review_type): + # 1. 加载协议模板 + protocol = load_protocol(f"review_protocols/{review_type}.yaml") + + # 2. 注入任务上下文( truths, constraints, scope_declaration) + protocol.inject_context(task) + + # 3. 拼接成 L1 bootstrap 消息 + # 审查者收到的消息 = 角色定义 + 审查协议 + 任务上下文 + 必读材料 + return format_reviewer_bootstrap(protocol, task) +``` + +**防止"一律照改"——反驳权(Rebuttal Phase)**: + +审查不是单向的。但不是每次都触发反驳——有跳过条件: + +**跳过条件**(不需 spawn 反驳): +- 审查者 verdict=approved → 直接 done,跳过 rebuttal +- 审查者 verdict=needs_revision,但 issues 全是 minor severity → 执行者自然在 comments 接受并修改,不 spawn 反驳 + +**触发条件**(spawn 反驳): +- 审查者 verdict=needs_revision,且 issues 中有 critical 或 major severity +- 审查者 verdict=rejected + +``` +审查者提交 review + ↓ +verdict=approved → 直接 done(跳过 rebuttal) +verdict=needs_revision 且只有 minor → 执行者直接修改(跳过 rebuttal) +verdict=needs_revision 且有 critical/major → spawn 反驳 + ↓ +Daemon spawn 原执行者,注入反驳指令: + "你收到了一份审查意见。对每个 issue,你必须明确表态: + ACCEPT(接受并修改)/ REJECT(拒绝,说明为什么)/ PARTIAL(部分接受) + 不允许全部接受不加思考。" + ↓ +执行者 response 写入 comments 表 + ↓ +如果是 REJECT → Daemon spawn 审查者看 response → 继续协商 +如果全部 ACCEPT → 修改后重新提交 → 审查者 re-review +``` + +### D3-10:OpenClaw 集成——Full Agent vs Subagent vs Daemon 直接执行 + +> **参考实践**: +> - **superpowers**:Implementer / Spec Reviewer / Code Quality Reviewer 都是 Task tool dispatch 的 subagent,但各自有独立的 prompt 模板和模型选择 +> - **oh-my-claudecode**:Critic 被禁止 Write/Edit 工具(disallowedTools),是角色隔离而非进程隔离 +> - **open-multi-agent**:TaskQueue 维护 agent pool,scheduler 按 capability-match 分配任务 + +**问题**:什么任务需要完整的 Agent 身份(SOUL/IDENTITY/MEMORY),什么任务只需要无身份的 AI Worker? + +**判据:五个问题** + +| 问题 | 走 Full Agent | 走 Subagent | Daemon 直接执行 | +|------|-------------|-----------|--------------| +| 需要独立身份/人格吗? | ✅ 司马懿的"质量守门人" | ❌ | ❌ | +| 需要 Agent 专属工具吗? | ✅ 关羽的风控工具 | ❌ 通用 exec/read 就够 | 不需要 AI | +| 任务复杂度 | 编码、审查、调研、决策 | 单一检查、快速评估 | 格式校验、文件存在检查 | +| 需要写黑板吗? | ✅ 写 review/output/decision | ❌ 只返回 pass/fail | 只改状态 | +| 需要多轮交互吗? | ✅ 协商、反驳、辩论 | ❌ 一次性的 | 一次性的 | + +**对应到我们的场景**: + +| 场景 | 走什么 | OpenClaw API | 理由 | +|------|--------|-------------|------| +| 张飞编码 | Full Agent | `openclaw agent --agent zhangfei-dev` | 需要身份、编码工具 | +| 司马懿产出审查 | Full Agent | `openclaw agent --agent simayi-challenger` | 需要质量守门人角色、多轮协商 | +| 执行者反驳审查 | Full Agent(原 Agent) | `openclaw agent --agent <原执行者>` | 需要原执行者的身份和上下文 | +| 庞统任务规划 | Full Agent | `openclaw agent --agent pangtong-fujunshi` | 需要副军师角色、决策能力 | +| 庞统冲突裁决 | Full Agent(隔离 session) | `openclaw agent --agent pangtong-fujunshi --session-id ` | 避免主 session 上下文膨胀 | +| 赵云数据下载 | Full Agent | `openclaw agent --agent zhaoyun-data` | 需要数据工具、NAS 操作 | +| 姜维部署 | Full Agent | `openclaw agent --agent jiangwei-infra` | 需要 Docker/PM2 工具 | +| L2 Guardrail AI 检查 | Subagent | `sessions_spawn(task=...)` | 单一检查、不需要身份 | +| Scope Guard 异步检查 | Subagent | `sessions_spawn(task=...)` | 轻量、一次性 | +| L1 机械校验(文件存在/JSON格式) | **不走 AI** | Daemon 直接执行 | 纯机械操作,不需要 AI | + +**简化规则**:黑板上有名字的角色(庞统/司马懿/张飞/关羽/赵云/姜维)走 Full Agent。没有名字的一次性检查走 Subagent。纯机械检查 Daemon 自己做。 + +**OpenClaw spawn 具体实现**: + +```python +def spawn_agent(agent_id, task_id, mandate): + """Spawn Full Agent(有身份、有专属工具)""" + session_id = f"moziplus-{task_id}-{agent_id}-{uuid4().hex[:8]}" + bootstrap_msg = format_mandate_message(mandate) + + subprocess.run([ + "openclaw", "agent", + "--agent", agent_id, + "--session-id", session_id, + "--message", bootstrap_msg + ], capture_output=True, text=True) + + # 记录到黑板 events 表 + blackboard.execute( + "INSERT INTO events (task_id, event_type, data) VALUES (?, 'agent_spawned', ?)", + task_id, json.dumps({"session_id": session_id, "agent_id": agent_id}) + ) + +def spawn_subagent(task_id, task_description, context=None): + """Spawn Subagent(无身份、通用工具、一次性)""" + # 通过 OpenClaw API spawn 轻量 worker + # 返回结果直接处理,不写入黑板 reviews 表 + # 适用于 L2 Guardrail、Scope Guard 等轻量检查 + pass +``` + +**庞统主 session 的隔离策略**: + +庞统主 session 做轻量调度(L1 构建、状态检查、黑板 tick 处理)。复杂的任务拆解和裁决 spawn 一个 pangtong-fujunshi 的隔离 session,避免主 session 上下文膨胀(课题2 D2-6:不需要 Auto-compact,但庞统是唯一可能有累积的 session)。 + +--- + +## 5. 遗留 TODO + +| # | 待解决事项 | 归属 | 说明 | +|---|----------|------|------| +| ~~T3-1~~ | ~~reviews 表的 CLI 命令~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-2~~ | ~~Guardrail YAML 解析 + 执行引擎~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-3~~ | ~~对抗辩论模式的具体黑板协议~~ | ~~Phase 3~~ | ✅ 方案已定(见 §5.1),仅在用户明确要求时启用 | +| ~~T3-4~~ | ~~挑战者池的选择策略~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-5~~ | ~~confidence 低于阈值自动升级~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-6~~ | ~~评审详情文件的 Schema 定义~~ | ~~Phase 2~~ | ✅ 方案已定(见 §5.2),待司马懿评审确认 | +| ~~T3-7~~ | ~~low 风险任务 Guardrail 自动通过~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-8~~ | ~~Review Protocol 模板文件编写~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T3-9~~ | ~~反驳权 Daemon 流控~~ | ~~Phase 2~~ | ✅ 方案已定(见 §5.3),轮次上限兜底不设超时 | +| ~~T3-10~~ | ~~Agent 调度判据~~ | ~~Phase 2~~ | ✅ 方案已定(见 §5.4),配置表驱动非 AI 判断 | + +### 5.1 对抗辩论黑板协议(T3-3 方案) + +> **触发条件**:仅在用户明确在任务意图中包含"辩论"等字样时启用。大多数任务走标准挑战机制(单审 + 反驳权)足够。 + +**调研来源**:TradingAgents(Bull vs Bear → Manager 裁决)、学术界 MAD(critic/defender/judge 三角色、稳定性检测早停)、claude-goal(none/adversarial/double 三级)。 + +**三角色 × 最多 5 轮 × 2 种结束条件**: + +- **正方(Proponent)**= 执行者 +- **反方(Opponent)**= 挑战者池按任务类型选择 +- **裁决者(Judge)**= 庞统(隔离 session) + +**黑板交互格式**: + +每个参与者用 comments 表写入,按类型区分: +- `comment_type = "debate_argument"` → 正方/反方论点 +- `comment_type = "debate_rebuttal"` → 反驳 +- `comment_type = "debate_judgment"` → 庞统裁决 + +每条 comment 的结构: +```json +{ + "role": "proponent | opponent | judge", + "round": 1-5, + "claims": [ + {"point": "...", "evidence": "文件路径/行号/数据", "severity": "critical|major|minor"} + ], + "concession": ["我接受的观点..."], + "stance": "hold | yield | partial" +} +``` + +**流程**: + +``` +Round 1: 正方写 scope_declaration + arguments → 反方读后写反驳 +Round 2+: 正方回应反驳 → 反方回应 +每轮结束后 Daemon 检查结束条件 +``` + +**结束条件**(任一满足即停): + +1. **达成共识**:一方 stance=yield 或双方 stance=partial +2. **轮次耗尽**:达到 max_rounds(默认 5) +3. **稳定性检测**:连续 2 轮无新论点(claims 无变化)→ 提前终止 + +达成共识 → 按共识结论执行。轮次耗尽/稳定性 → 庞统裁决。 + +庞统裁决输出:写入 reviews 表(verdict + summary + detail_path),verdict = approved | rejected | needs_revision。 + +**reviews 表变更**:新增 `debate_context` JSON 字段(存辩论参与者和轮次数)。 + +### 5.2 评审详情文件 Schema(T3-6 方案) + +> **待司马懿评审确认。** + +**调研来源**:SARIF(Static Analysis Results Interchange Format)result 结构、GitHub PR Review、Hermes comment metadata JSON。 + +**Schema**(`{task_id}/reviews/{review_id}.json`): + +```json +{ + "$schema": "review-detail-v1", + "review_id": "rev-001", + "task_id": "task-001", + "reviewer": "simayi-challenger", + "review_type": "output_review", + "verdict": "approved", + "confidence": 0.85, + "round": 1, + + "summary": "一句话结论", + + "issues": [ + { + "id": "iss-1", + "status": "open|accepted|rejected|deferred", + "severity": "critical|major|minor|info", + "category": "correctness|security|performance|style|scope_deviation|architecture|robustness", + "location": { + "file": "src/strategy.py", + "line_start": 42, + "line_end": 55, + "context": "def calculate_signal(df):\n ..." + }, + "title": "简短描述", + "description": "详细说明", + "evidence": "代码片段或引用", + "suggestion": "修复建议", + "remediation_effort": "low|medium|high" + } + ], + + "evidence": [ + { + "type": "file_content", + "path": "src/strategy.py", + "relevant_lines": [42, 43, 44] + }, + { + "type": "command_output", + "command": "pytest tests/ -x", + "exit_code": 1, + "output": "失败信息..." + } + ], + + "positives": ["做得好的地方"], + + "meta": { + "review_protocol": "output_review", + "files_reviewed": 3, + "model_used": "zhipu/glm-5.1" + } +} +``` + +**SARIF 借鉴**:issues 数组结构(severity + location + message)是 SARIF result 核心字段,但我们简化了——不需要 SARIF 的完整 taxonomy 和 rule metadata,我们的场景是 Agent 审查而非静态分析工具。 + +### 5.3 反驳权 Daemon 流控(T3-9 方案) + +**本质问题**:审查者和执行者意见不一致时,Daemon 怎么管理协商过程。 + +**调研来源**:TradingAgents(Bull/Bear → Manager 裁决)、claude-goal(double 审计)、superpowers(verify→fix loop)。 + +**触发条件**(不是所有 review 都触发): + +- review verdict=needs_revision,且 issues 中有 critical/major severity → 触发反驳 +- review verdict=rejected → 触发反驳 +- review verdict=approved → 不触发,直接 done +- review verdict=needs_revision,但只有 minor → 不触发,执行者直接改 + +**流程**: + +``` +审查者提交 review(verdict=needs_revision/rejected) + ↓ +Daemon tick 检测到 review → 检查 issues severity → 判定需要反驳 + ↓ +Daemon spawn 原执行者,注入: + "你收到了一份审查意见。对每个 issue,你必须表态: + ACCEPT / REJECT(说明理由) / PARTIAL(说明接受哪些) + 不允许全部接受不加思考。" + ↓ +执行者写入 comments(comment_type="rebuttal") + ↓ +Daemon 检查 rebuttal 结果: + │ + ├── 全部 ACCEPT + │ → 执行者修改 → 重新提交 → 审查者 re-review + │ + └── 有 REJECT/PARTIAL + → Daemon spawn 审查者看 rebuttal + ↓ + 审查者写入 comments(comment_type="rebuttal_response") + ↓ + Daemon 检查: + ├── 双方达成一致 → 执行者按协商结果修改 → re-review + ├── 审查者认可执行者反驳 → 执行者修改 → re-review + └── 仍有分歧 + → round++ + → round < max_rounds → 回到执行者继续协商 + → round >= max_rounds → 升级庞统裁决 +``` + +**唯一的兜底机制**:协商轮次达到 max_rounds 仍未一致 → 升级庞统裁决。庞统裁决也达不成结论 → 升级用户。 + +**不设超时**——Agent 可能被长任务 block,超时不能等同于"放弃反驳"。轮次上限已经是自然的终止条件。 + +**进度检测**(催促通知,非阻塞):如果同一轮次超过 `task_timeout`(10 分钟)没有任何新 comment 写入 → Daemon 通过 Inbox 发一个催促通知给当前应回应的 Agent。不是强制终止,只是提醒。 + +**reviews 表新增字段**: +- `rebuttal_status`: "pending" | "completed" | "escalated" +- `debate_round`: INTEGER(当前协商轮次,默认 1) + +**和 T3-3 对抗辩论的区别**:反驳权是审查后的协商机制(大多数任务),对抗辩论是正式辩论(仅用户明确要求时)。 + +### 5.4 Agent 调度判据(T3-10 方案) + +**调研来源**:Phil Schmid 四种子 Agent 模式、Addy Osmani Code Agent Orchestra、oh-my-opencode 三层调度。 + +**现有设计已完整**(D3-10 五问题判据 + 场景映射 + 简化规则),补充的是 **Daemon 自动调度逻辑**。 + +**三级决策树(配置表驱动,非 AI 判断)**: + +```python +DISPATCH_RULES = { + "L1_guardrail": "daemon", # Daemon eval guardrails.yaml assert + "L2_ai_check": "subagent", # sessions_spawn 单一检查 + "scope_guard": "subagent", # 轻量异步检查 + "execute": "full_agent", # 走 registered agent + "review": "full_agent", # 走 registered agent + "rebuttal": "full_agent", # 走原执行者 + "debate_proponent": "full_agent", # 走原执行者 + "debate_opponent": "full_agent", # 走挑战者 + "debate_judge": "full_agent", # 庞统隔离 session + "plan_decomposition": "full_agent", # 庞统 +} + +def dispatch(task, action_type, project_config): + # 从项目配置动态读取可用 Agent 列表,不硬编码 + registered_agents = project_config.get("agents", []) + + # Level 1: 纯机械检查 → Daemon 直接执行 + if action_type in ("L1_guardrail", "format_check", "file_exists_check"): + return execute_locally(task) + + # Level 2: 有名字的角色 → Full Agent + if task.assignee in registered_agents: + if action_type == "adjudication": + return spawn_full_agent(task.assignee, new_session=True) + return spawn_full_agent(task.assignee) + + # Level 3: 无名字的一次性任务 → Subagent + if DISPATCH_RULES.get(action_type) == "subagent": + return spawn_subagent(task_description=action_type) + + # Level 4: 未知 action_type → 庞统裁决 + return spawn_full_agent("pangtong-fujunshi", new_session=True) +``` + +**简化规则**:黑板上有名字的角色(庞统/司马懿/张飞/关羽/赵云/姜维)走 Full Agent。没有名字的一次性检查走 Subagent。纯机械检查 Daemon 自己做。未知类型交给庞统裁决。 + +**spawn 方式**(和课题11异步计数器模型一致): +- **Full Agent**:`subprocess.Popen`(非阻塞)启动 `openclaw agent` CLI,不等返回,下次 tick 检查产出 +- **Subagent**:`sessions_spawn`(Gateway 内部 API),等返回 + +## 6. 和现有设计的对齐检查 + +| 已有设计 | 课题3 补充 | 一致性 | +|---------|-----------|--------| +| §9.5 分级审查矩阵(课题1) | D3-1 分级流水线(补充每级内部流程) | ✅ 四级风险对应四级流水线 | +| §4.7 Guardrail 吹哨人(课题1) | D3-4 Guardrail 结果入 reviews 表 | ✅ 吹哨后写 observation + 入 reviews | +| §3.7 Schema 校验(课题2) | D3-8 Guardrail YAML 和 Schema 对齐 | ✅ L1 检查用 Schema,YAML 声明规则 | +| §4.2 Tick+Inbox(课题2) | 评审触发也走 inbox 通知 | ✅ 挑战者 review 写完后通知 Daemon | +| §5.1 Handoff Comment(课题2) | §4 评审与 Handoff 关系 | ✅ Handoff 是执行者写的,review 是挑战者写的 | +| §3.2 SQLite Schema | reviews 表新增 | ✅ 独立新表,不修改现有表 | diff --git a/docs/design/topic4-decomposition-skill-proposal.md b/docs/design/topic4-decomposition-skill-proposal.md new file mode 100644 index 0000000..0edb28c --- /dev/null +++ b/docs/design/topic4-decomposition-skill-proposal.md @@ -0,0 +1,638 @@ +# 课题4 设计方案:智能拆解机制 + Skill 体系工程落地 + +**作者**: 庞统士元 +**日期**: 2026-05-15 +**状态**: 方案待确认 +**前置**: 课题1/2/3 已结题 +**调研报告**: `docs/research/v2.6-research-04-decomposition-skills.md` + +--- + +## Part A:智能拆解机制 + +### 1. 设计目标 + +v1.0 的拆解是"关键词匹配 → 固定 DAG 模板",问题是: +- 模板太死板(5个固定DAG),无法根据需求动态调整 +- 关键词匹配粗糙,复杂需求容易匹配错模板 +- 庞统拆解时靠 LLM 理解,没有结构化引导 + +课题4要解决:**庞统收到用户需求后,如何把它变成黑板上的任务(含依赖关系、角色分配、风险等级)。** + +### 2. 和课题1/2/3的关系 + +课题1-3 已经定义了"任务在黑板上长什么样"(tasks 表 schema、状态机、reviews 表、Guardrail 配置)。课题4定义的是"任务怎么被创建出来"。 + +``` +课题4(创建) → 课题2(触发执行) → 课题3(审查) → 课题1(失败处理) +``` + +### 3. 设计决策 + +#### D4-1:模板组件库替代固定 DAG + +> **参考实践**: +> - **MetaGPT**:SOP 自动链 + `_watch` 声明式订阅,不是固定 DAG +> - **get-shit-done**:discuss→plan→execute→verify→ship 五阶段,但每阶段内部灵活 +> - **open-multi-agent**:Goal → DAG 自动分解,按 complexity 决定粒度 +> - **v1.0**:5 个固定模板(full_role_development / circular_review_test / multi_stage_review / data_acquisition / multi_edge_discussion) + +**方案**:不是 5 个完整 DAG,而是**模板组件库(Phase Components)**。庞统按需组合。 + +```yaml +# template_components.yaml +components: + research: + trigger: "需要调研/分析" + mandatory: false + steps: + - id: lit_search + name: "资料调研" + agent: zhaoyun-data + output: "research_report.md" + - id: analysis + name: "分析总结" + agent: pangtong-fujunshi + output: "analysis.md" + + design: + trigger: "需要架构/设计" + mandatory: false + steps: + - id: arch_design + name: "架构设计" + agent: pangtong-fujunshi + output: "design.md" + - id: design_review + name: "设计评审" + agent: simayi-challenger + review_type: plan_review + + coding: + trigger: "需要编码/实现" + mandatory: false + steps: + - id: implement + name: "编码实现" + agent: zhangfei-dev + output: "code/" + - id: code_review + name: "代码评审" + agent: simayi-challenger + review_type: output_review + + data: + trigger: "需要数据获取/处理" + mandatory: false + steps: + - id: data_acquire + name: "数据获取" + agent: zhaoyun-data + output: "data/" + - id: data_validate + name: "数据验证" + agent: zhaoyun-data + + deploy: + trigger: "需要部署" + mandatory: false + steps: + - id: deploy + name: "部署" + agent: jiangwei-infra + output: "deploy_log.md" + - id: deploy_verify + name: "部署验证" + agent: jiangwei-infra + + risk_control: + trigger: "涉及实盘/资金/策略" + mandatory: true # 触发条件满足时强制包含 + steps: + - id: risk_check + name: "风控检查" + agent: guanyu-dev + + review: + trigger: "always" + mandatory: true # 铁律 IR-1 + steps: + - id: final_review + name: "最终审查" + agent: simayi-challenger + review_type: final_review + + custom: + trigger: "none" # 不自动触发,庞统主动选择 + description: "庞统自由定义的 phase,结构与标准组件相同" + steps: [] # 空的,庞统运行时填充 +``` + +**custom 组件接口**:与标准组件同 schema(steps 列表,每个 step 有 id/name/agent/output/review_type)。Plan Checker 对 custom 组件的验证和标准组件相同——检查覆盖性、依赖性、粒度、铁律。唯一区别是 custom 不从库中引用,而是庞统在 Step 2 中自由定义。 +``` + +**庞统的拆解流程**: + +``` +Step 1:需求结构化 + - 分析用户输入,提取需求列表 + - 每个需求标注类型(数据/编码/分析/部署/风控) + - 评估复杂度(简单/中等/复杂) + - 隐含需求挖掘(如用户说"回测"→ 隐含需要数据获取) + - 产出:structured_requirements → 写入黑板 decisions 表 + +Step 2:组件选择 + 组合 + - 根据需求类型,从组件库选择需要的 phases + - 检查 mandatory 组件是否被遗漏 + - 组合 phases,排列依赖关系 + - AI 增强:增删调整(如"这次数据已有,跳过 data_acquire") + - 产出:draft_plan → 写入黑板 decisions 表 + +Step 3:Plan Checker 验证 + - 独立验证(不是庞统自己验证自己) + - 检查项: + - 覆盖性:每个需求是否被至少一个 step 覆盖 + - 依赖性:step 依赖是否合理(无环、无遗漏前置) + - 粒度性:step 是否过大/过小 + - 铁律:IR-1(每任务至少2 Agent)是否满足 + - 风险:risk_level 是否合理,高风险任务是否有对抗审查 + - 不通过 → 回 Step 2 调整(最多 2 轮) + - 2 轮不通过 → 庞统带着 Plan Checker issues 创建任务,标注 risk_level=high,由审查者在 plan_review 阶段进一步验证。不升级用户(庞统能处理) + - 通过 → 进入 Step 4 + +Step 4:任务创建 + - 将 plan 转为黑板 tasks + - 每个 step → 一个 task(含 truths / artifacts / constraints / risk_level / assigned_to) + - 依赖关系 → tasks 的 depends_on 字段 + - 庞统在黑板上写 scope_declaration + - 产出:tasks 写入黑板 → Daemon 检测 → 开始执行 +``` + +#### D4-2:需求结构化的输出格式 + +庞统分析用户需求后,输出结构化需求到黑板: + +```json +{ + "original_input": "实现一个双均线策略的回测", + "requirements": [ + { + "id": "req-001", + "description": "获取标的分钟线数据", + "type": "data", + "acceptance_criteria": "数据文件存在于 NAS 指定路径", + "complexity": "simple", + "implicit": true + }, + { + "id": "req-002", + "description": "编写双均线策略逻辑", + "type": "coding", + "acceptance_criteria": "策略代码通过单元测试", + "complexity": "medium", + "implicit": false + }, + { + "id": "req-003", + "description": "回测并生成报告", + "type": "analysis", + "acceptance_criteria": "回测报告包含收益曲线和关键指标", + "complexity": "medium", + "implicit": false + } + ], + "inferred_components": ["data", "coding", "analysis"], + "risk_level": "standard", + "estimated_tasks": 3 +} +``` + +#### D4-3:Plan Checker 独立验证 + +> **参考实践**: +> - **superpowers**:plan-document-reviewer subagent 独立验证方案 +> - **oh-my-claudecode Critic**:预判→验证→自审五阶段 +> - **get-shit-done**:Scope Reduction Detection(完成后反向检查覆盖) + +Plan Checker 是一个 Subagent(不需要 Agent 身份),Daemon spawn 后执行: + +```yaml +# review_protocols/plan_check.yaml +name: plan_check +description: "验证庞统的拆解方案是否合理" + +dimensions: + - id: coverage + description: "每个需求是否被至少一个 task 覆盖" + weight: critical + method: "需求 ID → task ID 映射矩阵,检查是否有遗漏" + + - id: dependency + description: "依赖是否合理" + weight: critical + method: "拓扑排序检查无环,前置是否完整" + + - id: granularity + description: "粒度是否合适" + weight: major + method: "检查每个 task 的 truths 是否是一个 Agent 一次 spawn 能完成的" + + - id: iron_rules + description: "铁律是否满足" + weight: critical + method: "IR-1: 至少 2 Agent 参与;IR-2: 每个 task 有 truths" + + - id: risk_alignment + description: "风险等级是否合理" + weight: major + method: "涉及资金/部署 → high;纯分析 → research" + +output_schema: "schemas/plan-check-output.schema.json" +verdict_options: ["approved", "needs_revision"] +``` + +#### D4-4:复杂度驱动的粒度控制 + +> **参考实践**: +> - **open-multi-agent**:按 complexity 决定是否拆分子任务 +> - **wanman**:Token 预算控制执行深度 + +| 复杂度 | 判断标准 | 拆解策略 | truths 定义 | +|--------|---------|---------|------------| +| **simple** | 单一文件、单一操作、< 50 行 | 1 个 task | 1-2 条 truths | +| **medium** | 多文件、有依赖、需要验证 | 1-3 个 task + 依赖 | 每个 2-3 条 truths | +| **complex** | 跨模块、多角色、需要调研 | 多 phase 组件组合 | 每个 3-5 条 truths | + +**粒度自检规则**(庞统的 Skill 中定义): +- 一个 task 的 truths > 5 条 → 拆分 +- 一个 task 需要跨 3+ 角色 → 拆分 +- 一个 task 预计 > 30 分钟 → 拆分 + +#### D4-5:用户确认点 + +> **参考实践**: +> - **get-shit-done**:discuss phase 是必须的用户确认步骤 +> - **v2.0 PRD**:Phase 1 需求探索(苏格拉底对话) + +不是每步都问用户,但在关键节点确认: + +| 检查点 | 触发条件 | 用户看到什么 | +|--------|---------|------------| +| 需求确认 | Step 1 完成后 | 结构化需求列表 + 隐含需求 | +| 方案确认 | Step 2 完成后(complex 任务) | 任务列表 + 依赖关系 + 预估时间 | + +simple 任务跳过方案确认,直接创建。complex 任务必须确认。 + +**用户干预三种模式**(来源:2026 human-in-the-loop 综述 + OpenAI Agents SDK): + +| 模式 | 机制 | 适用 | +|------|------|------| +| 审批式 | Agent 暂停等用户确认 | high 任务方案确认、部署前 | +| 通知式 | 推送消息,用户可回复可不回复 | standard 任务产出审查后 | +| 中断式 | Agent 主动问用户 | 歧义需求、争议仲裁 | + +**当前落地**:通过庞统 session(webchat/Signal)。**未来**:dashboard Checkpoint 面板。 + +**干预点由 risk_level 动态决定**:low→无干预、standard→通知式、high→审批式、用户随时可中断。 + +--- + + +## Part B:Skill 体系工程落地 + +### 4. 设计目标 + +三个课题产出了大量"Agent 需要知道怎么做"的内容(28 项 Skill TODO),需要一个系统的 Skill 体系来承载。 + +### 5. 核心洞察:被动触发不可靠 + +> **问题**:我们写了好多个 Skill,但除了用户明文指定"用 PAV""用 delegation",几乎没见哪个 Skill 被主动触发过。 +> +> **根因**:Agent 不知道自己不知道什么。OpenClaw 的 Skill 列表注入 system prompt 后,Agent 需要自己判断需要哪个——但 Agent 根本不知道"我应该先读一个 Skill 来学怎么做"。 +> +> **优秀实践验证**:superpowers / GSD / open-multi-agent 没有一个是靠被动 Skill 触发的——全部是编排器构造好 prompt 直接注入。 + +**结论**:moziplus 的关键操作规范不走被动 Skill 发现,走引擎注入。 + +### 5.1 铁律定义(L0 内容) + +铁律是**所有 Agent 共享的**强制规则,不可定制。角色追加的规则放在 L1(SOUL.md)。 + +| 编号 | 铁律 | 来源 | +|------|------|------| +| IR-1 | 每个任务至少 2 个 Agent 参与(做 + 挑战) | v1.0 PRD | +| IR-2 | 每个 task 必须有 truths(可观测行为标准) | 课题1 must_haves | +| IR-3 | 结论必须有证据(file:line 或具体引用) | oh-my-claudecode Critic | +| IR-4 | 语义含糊先确认,不擅自执行 | SOUL.md 底线 | +| IR-5 | 改代码先改文档(需求→设计→测试→部署) | v1.0 实践 | + +**Token 估算**:5 条铁律,每条 ~30 token(编号+一句话),总计 ~150 token + XML 包装 ~50 token = **~200 token**。加上角色追加(L1 SOUL.md 已计入),L0 预算 ~200-300 token,远低于 ~500 上限。 + +### 6. 设计决策 + +#### D4-6:四层上下文架构 + +> **参考实践**: +> - **superpowers**:每个 subagent 有独立 prompt 模板(implementer-prompt.md / spec-reviewer-prompt.md),模板里写死操作步骤 +> - **open-multi-agent**:`buildTaskPrompt()` 拼 task title + description + 依赖产出 + messages +> - **GSD**:每个 executor 拿到 PLAN.md + PROJECT.md + STATE.md + CONTEXT.md,按 context window 大小自适应 +> - **OpenClaw**:Hook `agent_turn_prepare` 可注入 prependContext/appendContext + +共同模式:**操作规范(模板)+ 项目背景(固定)+ 任务上下文(动态)**,编排器拼装,不靠 Agent 自己找。 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Layer 0: 铁律(Iron Rules) │ +│ 机制: OpenClaw Hook agent_turn_prepare │ +│ 保证: 每轮强制注入 | Token: ~500 │ +│ 内容: IR-1~IR-5(每个 Agent 可定制) │ +├─────────────────────────────────────────────────────────────┤ +│ Layer 1: 角色(Agent Identity) │ +│ 机制: SOUL.md + IDENTITY.md + AGENTS.md │ +│ 保证: 每轮强制 | Token: ~2000 │ +│ 内容: 角色定义 + 行为准则 + 记忆 │ +│ 变化频率: 极少(换平台不变) │ +├─────────────────────────────────────────────────────────────┤ +│ Layer 2: 操作规范 + 任务上下文(引擎注入) │ +│ 机制: Daemon spawn 时拼装 bootstrap 消息 │ +│ 保证: 每次 spawn 强制注入 | Token: ~1000-2000 │ +│ 三段式: │ +│ ① 角色操作规范模板(executor.md / reviewer.md / planner.md)│ +│ → 固定部分,写死关键操作步骤(scope/output/handoff等) │ +│ → 变化频率: 很少(moziplus 平台变了才改) │ +│ ② 项目背景(项目路径/框架/NAS/回测服务) │ +│ → 半固定部分,按项目配置 │ +│ ③ 任务上下文(truths/artifacts/constraints + depends_on产出)│ +│ → 动态部分,每次 spawn 不同 │ +├─────────────────────────────────────────────────────────────┤ +│ Layer 3: 参考信息(OpenClaw Skill 被动发现) │ +│ 机制: OpenClaw Skill 列表注入 system prompt │ +│ 保证: ❌ 不保证触发(锦上添花) │ +│ 内容: 具体操作方法(回测流程/数据获取/部署步骤等) │ +│ 优化: description 中加触发场景+示例对话+触发词(见D4-8) │ +└─────────────────────────────────────────────────────────────┘ +``` + +**张飞编码时靠什么**: +- L0+L1:身份和纪律(每轮都有) +- L2 ① executor.md:"读黑板→写scope→执行→写output→写Handoff"(引擎注入,不会忘) +- L2 ③ 任务上下文:"这次要做什么、truths 是什么、前序产出是什么"(引擎注入) +- L3 quant-backtest Skill:具体回测代码怎么写(自己判断要不要读) + +#### D4-7:L2 引擎注入的拼装原则 + +> **参考实践**: +> - **superpowers**:implementer/spec-reviewer/code-quality-reviewer 三个独立 prompt 模板,只包含该角色需要的信息 +> - **open-multi-agent**:`buildTaskPrompt()` 只注入依赖链上的产出(default-deny),不是全量 +> - **GSD**:按 context window 大小自适应(200K 只给任务级,1M 给全量) + +**原则:按角色和任务阶段精确注入,不统一拼装,不多不少。** + +| Agent 角色 | 注入内容 | 不注入 | +|-----------|---------|--------| +| **执行者** | executor.md + Guardrail 配置 + 任务上下文 | Review Protocol | +| **审查者** | reviewer.md + Review Protocol + required_reading | Guardrail 配置 | +| **庞统(规划)** | planner.md + template_components + Plan Checker | Review Protocol | +| **庞统(裁决)** | adjudicator.md + 正方 comment + 反方 comment | Guardrail 配置 | +| **反驳者** | rebuttal.md + 审查者 review + ACCEPT/REJECT 指令 | Review Protocol | +| **Plan Checker** | plan_checker.md + 需求列表 + 拆解方案 | 全部角色模板 | + +**拼装逻辑**(Daemon 代码): +```python +def build_bootstrap(agent_id, role, task): + parts = [] + + # ① 角色操作规范模板 + template = load_template(f"prompt_templates/{role}.md") + parts.append(template) + + # ② 项目背景(半固定) + parts.append(format_project_context(task)) + + # ③ 任务上下文(动态) + parts.append(format_task_context(task)) + + # 按角色追加(只注入该角色需要的) + if role == "executor": + parts.append(format_guardrails(task.task_type)) + elif role == "reviewer": + parts.append(format_review_protocol(task.review_type)) + parts.append(format_required_reading(task)) + elif role == "planner": + parts.append(format_template_components()) + elif role == "rebuttal": + parts.append(format_review_to_respond(task)) + + return "\n\n".join(parts) +``` + +#### D4-8:L3 Skill 被动触发的优化写法 + +> **参考实践**: +> - **superpowers code-reviewer**:description 里嵌入 `` 标签,用对话示例教 Agent 触发场景 +> - **wiki-query**:description 里加 Chinese/English triggers 列表 +> - **gstack plan-design-review**:加 `Proactively suggest when...` 指令 +> - **SkillRouter 论文**(2026):Agent 自主搜索(agentic)比纯关键词/纯语义准确率最高 + +即使 L3 不保证触发,也要优化写法提高命中率。**moziplus 的 Skill 全部按以下规范写 description**: + +```yaml +--- +name: quant-backtest +description: > + 标准化量化回测技能。 + + When to use: 当需要回测量化策略、验证策略历史表现、测试策略参数时。 + + Chinese triggers: 回测、backtest、策略测试、历史表现验证、参数优化。 + English triggers: backtest, strategy test, historical performance, parameter optimization. + + Proactively suggest when: 用户提到策略实现完成后,主动建议运行回测验证。 + + Example: user says "帮我测一下双均线策略" → use quant-backtest skill. +--- +``` + +**description 四要素**: +1. **功能描述**:这个 Skill 干什么 +2. **触发场景**(When to use):什么情况下该用 +3. **触发词**(Chinese/English triggers):中英文关键词 +4. **主动建议**(Proactively suggest):Agent 看到相关场景时主动推荐 + +#### D4-9:Skill 诚实边界 + +> **参考实践**:nuwa-skill Agentic Protocol 要求每个 Skill 声明自己做不到什么 + +每个 Skill 必须包含 `## 诚实边界` section: + +```markdown +## 诚实边界 +- 本 Skill 不覆盖实盘交易逻辑(实盘请参考 live-trading Skill) +- 性能问题需要实际测试数据,纯代码审查无法确认 +- 复杂策略可能需要拆分为多个 task +``` + +#### D4-10:moziplus 目录结构 + +``` +sanguo_moziplus/ + prompt_templates/ ← L2 ① 角色操作规范(Daemon 读取拼装) + executor.md ← 执行者操作步骤(读黑板→scope→执行→output→handoff) + reviewer.md ← 审查者操作步骤(读Protocol→审查→写review) + planner.md ← 庞统拆解步骤(需求结构化→组件选择→PlanChecker) + adjudicator.md ← 庞统裁决步骤(正方反方→判断→写决策) + rebuttal.md ← 反驳步骤(读review→ACCEPT/REJECT/PARTIAL) + plan_checker.md ← Plan Checker 步骤(覆盖性→依赖→粒度→铁律) + + review_protocols/ ← L2 审查协议(Daemon 构造 reviewer bootstrap 时读取) + plan_review.yaml ← 方案审查协议 + output_review.yaml ← 产出审查协议 + guardrail_l2.yaml ← L2 轻量AI检查协议 + analysis_review.yaml ← 分析审查协议 + plan_check.yaml ← Plan Checker 协议 + + schemas/ ← CLI 层 Schema 校验(blackboard.py 读取) + handoff.schema.json ← Handoff 格式校验 + output.schema.json ← Output 格式校验 + decide.schema.json ← Decision 格式校验 + observe.schema.json ← Observation 格式校验 + review-output.schema.json ← Review 格式校验 + + template_components.yaml ← 模板组件库(庞统拆解时参考) + guardrails.yaml ← 声明式 Guardrail(Daemon 执行检查) + project_context.yaml ← L2 ② 项目背景(NAS路径/回测服务/框架) +``` + +**各目录作用**: + +| 目录/文件 | 谁读 | 作用 | 变化频率 | +|----------|------|------|----------| +| `prompt_templates/` | Daemon | 按 Agent 角色拼装 bootstrap 消息 | 很少变 | +| `review_protocols/` | Daemon | 构造审查者的 bootstrap 消息 | 偶尔变 | +| `schemas/` | blackboard.py CLI | 写入时校验数据格式 | 偶尔变 | +| `template_components.yaml` | 庞统(通过 L2 注入) | 拆解需求时选择组件 | 偶尔变 | +| `guardrails.yaml` | Daemon | 自动检查产出 | 偶尔变 | +| `project_context.yaml` | Daemon | 拼装项目背景信息 | 换项目时变 | + +**注意**:Agent 不直接读这些文件——全部由 Daemon 读取后拼装到 bootstrap 消息中注入。 + +#### D4-11:L2 引擎注入示例(张飞编码场景) + +Daemon spawn 张飞时拼装的完整 bootstrap 消息: + +``` +═══ 操作规范(executor.md 模板)═══ + +你是任务执行者。按以下步骤工作: + +## 1. 理解任务 +- 读黑板任务详情(blackboard.py read --task {task_id}) +- 理解 truths(可观测行为标准)、artifacts(必须产出)、constraints(约束) + +## 2. 声明范围 +- 写 scope_declaration(blackboard.py decide --task {task_id} --decider zhangfei-dev) +- 格式:{"decision": "我计划做什么", "rationale": "为什么", "artifacts": ["路径"]} + +## 3. 执行工作 +- 按你的专业能力完成任务 +- 发现风险 → blackboard.py observe --task {task_id} --severity warning +- 需要协作 → blackboard.py comment --task {task_id} @mention + +## 4. 提交产出 +- blackboard.py output --task {task_id} --summary "一句话" --content-path "文件路径" + +## 5. 写交接 +- blackboard.py comment --task {task_id} --author zhangfei-dev \ + --handoff '{"completed":"...", "artifacts":["..."]}' + +## 6. 通知引擎 +- 写 inbox 文件通知 Daemon 完成 + +═══ 项目背景(project_context.yaml)═══ + +项目:sanguo_quant_live +框架:vnpy +NAS 数据:/Volumes/stock/ +回测服务:http://192.168.2.154:8088 +编码规范:类型注解必须、禁止 any、match 现有风格 + +═══ 任务上下文(黑板动态读取)═══ + +任务 ID: task-001 +任务标题: 实现双均线策略 +truths: ["策略代码通过单元测试", "回测结果包含收益曲线"] +artifacts: ["task-001/strategy.py", "task-001/backtest_report.pdf"] +constraints: ["使用 vnpy 框架", "不超过 500 行"] +risk_level: standard + +═══ 前序信息(depends_on 产出)═══ + +[赵云 Handoff] 完成了分钟线数据下载,产出 task-000/data/ +数据路径: /Volumes/stock/min_data/IF888.csv + +═══ 可选参考 ═══ + +如果需要具体操作方法,用 read 加载: +- 编码:~/.openclaw/skills/coding-implementation/SKILL.md +- 回测:~/.openclaw/skills/quant-backtest/SKILL.md +``` + +**对比审查者(司马懿)收到的 bootstrap**: + +``` +═══ 操作规范(reviewer.md 模板)═══ + +你是质量审查者。按 Review Protocol 执行审查: + +## 审查步骤 +1. 预判:先不读产出,预测 3-5 个最可能出问题的点 +2. 验证:读实际产出(不是报告),逐条验证每个 truth +3. 多视角:从安全/新人/运维三个角度看产出 +4. 缺口分析:不只看"什么有问题",还看"什么缺失了" +5. 自审:给每个 finding 打 confidence,低 confidence 降级 + +## 写 Review +- blackboard.py review --task {task_id} --verdict [approved|rejected|needs_revision] ... + +[... 对抗性指令 ...] + +═══ Review Protocol(output_review.yaml)═══ + +[审查维度、方法、输出 Schema 等详细协议内容] + +═══ 任务上下文 ═══ +[同上] + +═══ Required Reading ═══ +- 执行者 scope_declaration: ... +- 产出物: task-001/strategy.py +- must_haves truths: [...] +``` + +--- + +## 7. 和现有设计的对齐检查 + +| 已有设计 | 课题4 补充 | 一致性 | +|---------|-----------|--------| +| §3.2 tasks 表 schema | D4-1 模板组件输出 → tasks 写入 | ✅ truths/artifacts/constraints/risk_level/depends_on 对齐 | +| §9.2 分级审查矩阵 | D4-4 复杂度→粒度控制 | ✅ risk_level 在创建时标注 | +| §4.2 Tick+Inbox | D4-1 Step 4 创建任务后 Daemon tick 检测 | ✅ 创建是事件源 | +| §9.4 Review Protocol Registry | D4-10 review_protocols/ 目录 | ✅ Daemon 构造审查者 bootstrap 时读取 | +| §9.9 Agent vs Subagent | D4-3 Plan Checker 走 Subagent | ✅ 无身份一次性检查 | +| Skill TODO 28项 | D4-10 prompt_templates/ 承载关键操作 | ✅ S-01~S-28 的关键操作写死在模板中 | + +## 8. 遗留 TODO + +| # | 待解决事项 | 归属 | 说明 | +|---|----------|------|------| +| ~~T4-1~~ | ~~template_components.yaml 完整定义~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T4-2~~ | ~~Plan Checker 的 plan_check.yaml 实现~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T4-3~~ | ~~prompt_templates/ 编写~~ | ~~Phase 1~~ | ✅ 方案已定,开发实现 | +| ~~T4-4~~ | ~~project_context.yaml 定义~~ | ~~Phase 1~~ | ✅ 方案已定,开发实现 | +| ~~T4-5~~ | ~~Daemon build_bootstrap() 拼装逻辑~~ | ~~Phase 1~~ | ✅ 方案已定,开发实现 | +| ~~T4-6~~ | ~~铁律 Hook 实现(L0)~~ | ~~Phase 1~~ | ✅ 方案已定,开发实现 | +| ~~T4-7~~ | ~~现有 Skill description 优化(加四要素)~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T4-8~~ | ~~Skill 诚实边界补充~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T4-9~~ | ~~Skill 测试框架~~ | ~~Phase 3~~ | ➡️ 移至工具链课题(开发工具链的一部分,不属于拆解机制) | +| ~~T4-10~~ | ~~Skill 进化/蒸馏机制~~ | ~~Phase 3~~ | ➡️ 合并进课题6(经验→Skill的进化是经验沉淀闭环,课题6已有两级蒸馏+Skill生命周期) | diff --git a/docs/design/topic4-skill-checklist-draft.md b/docs/design/topic4-skill-checklist-draft.md new file mode 100644 index 0000000..c1a5951 --- /dev/null +++ b/docs/design/topic4-skill-checklist-draft.md @@ -0,0 +1,96 @@ +# 课题4 Skill 设计清单(草稿) + +**背景**:三个课题(1/2/3)产出了27项设计决策,其中大量内容需要 Agent 在 Skill 层面"知道怎么做"。当前 TODO 只列了4项(T1-5/6/7 + T2-8),远不够。 + +## 一、按角色分类的 Skill 需求 + +### 1. 所有 Agent 通用 Skill + +| # | Skill 内容 | 来源 | 说明 | +|---|-----------|------|------| +| S-01 | blackboard.py CLI 使用手册 | 课题1 §5.2 | 所有黑板操作的 CLI 命令、参数、Schema 校验说明 | +| S-02 | 读黑板 L1 消息 → 判断是否需要 L2/L3 | 课题2 §4.4 | Agent 收到 L1 后自主决定信息是否足够,不够就主动调 L2/L3 | +| S-03 | 写 Handoff Comment | 课题2 §5.1 | 结束前必须写结构化交接(强烈建议,不强制) | +| S-04 | 读 Handoff Comment | 课题2 §5.1 | 收到上一个 Agent 的 Handoff 后如何利用 | +| S-05 | 写 observation 的时机和格式 | 课题1 §4.7 | 工作中发现风险时主动写 observation + severity | +| S-06 | 写 decision 的时机和格式 | 课题1 §9.4 | 每个关键决策必须记录,哪怕是自己的决策 | +| S-07 | 写 output 的 Schema 约束 | 课题2 §3.7 | --output 必须有 summary + content_path,CLI 校验 | +| S-08 | 收到 Guardrail 打回时的处理 | 课题3 §9.3 | L1 机械失败→修改重提交;L2 语义问题→评估是否合理 | +| S-09 | @mention 的使用规范 | 课题1 §5.2 | 需要协作时 @mention + 附带上下文 | + +### 2. 执行者 Agent(张飞/关羽/赵云/姜维) + +| # | Skill 内容 | 来源 | 说明 | +|---|-----------|------|------| +| S-10 | claim 任务后写 scope_declaration | 课题1 §4.7 | "我计划做什么、产出什么",格式和内容要求 | +| S-11 | scope_declaration 的格式 | 课题1 T1-5 | truths 字段格式、声明方式 | +| S-12 | 收到 review needs_revision 时的处理 | 课题3 §9.5 | 读 issues → 逐条 ACCEPT/REJECT/PARTIAL,写 comment 回应 | +| S-13 | 收到 Guardrail reject 的处理 | 课题3 §9.8 | 理解 assert 失败原因,修改重提交 | +| S-14 | must_haves 三件套理解 | 课题1 §9 | truths/artifacts/constraints 的含义和自检方法 | + +### 3. 审查者 Agent(司马懿 + 挑战者池) + +| # | Skill 内容 | 来源 | 说明 | +|---|-----------|------|------| +| S-15 | 收到 Review Protocol 后的审查流程 | 课题3 §9.4 | 五阶段 Investigation Protocol 的执行方法 | +| S-16 | 多视角审查方法 | 课题3 §9.4 | 代码视角(安全/新人/运维)、方案视角(执行者/利益相关者/怀疑论者) | +| S-17 | 写 review 的 Schema 约束 | 课题3 §9.6 | verdict 必须是枚举,issues 必须有 evidence | +| S-18 | 信心度自评方法 | 课题3 §9.6 | confidence 打分标准,< 0.7 意味着什么 | +| S-19 | 收到反驳(REJECT)后的处理 | 课题3 §9.5 | 评估执行者的反驳是否合理,决定坚持还是让步 | +| S-20 | plan_review 协议 | 课题3 §9.4 | 假设提取+评级、pre-mortem、依赖审计、歧义扫描 | +| S-21 | output_review 协议 | 课题3 §9.4 | 需求追踪、scope 对齐、正确性验证、缺口分析 | +| S-22 | analysis_review 协议 | 课题3 §9.4 | 逻辑跳跃、无支撑结论、数据来源可靠性 | + +### 4. 协调者 Agent(庞统) + +| # | Skill 内容 | 来源 | 说明 | +|---|-----------|------|------| +| S-23 | 创建任务时的 truths/artifacts/constraints 定义 | 课题1 §9 | 用户视角的可观测行为、必须存在的文件、继承的约束 | +| S-24 | 风险等级自动判断 | 课题3 §9.2 | task_type→risk_level 的映射规则 | +| S-25 | 挑战者选择策略 | 课题3 §9.10 | 按任务类型选挑战者(编码→司马懿、风控→关羽、数据→赵云、部署→姜维) | +| S-26 | 对抗辩论裁决方法 | 课题3 §9.10 | 综合正方反方观点做最终判断 | +| S-27 | escalated 任务的用户沟通 | 课题3 §9.7 | 超轮次升级时如何向用户呈现争议和选项 | +| S-28 | confidence 低时的升级判断 | 课题3 T3-5 | confidence < 0.7 时升级策略 | +| S-29 | 任务拆解方法 | 课题1 §5.1 | 复杂任务如何拆成子任务,依赖如何声明 | +| S-30 | L1 消息构建逻辑 | 课题2 §4.4 | 给 Agent spawn 时的 bootstrap 消息拼装 | + +## 二、按阶段分类 + +### Phase 1 必须有(Agent 能基本工作) + +- S-01 blackboard.py CLI 使用手册 +- S-03 写 Handoff Comment +- S-10 claim 后写 scope_declaration +- S-14 must_haves 理解 +- S-23 创建任务(庞统) +- S-24 风险等级判断(庞统) +- S-30 L1 消息构建(庞统/Daemon) + +### Phase 2 必须有(审查体系生效) + +- S-02 L2/L3 按需读取 +- S-04 读 Handoff Comment +- S-05/06 observation/decision 写入 +- S-07 output Schema 约束 +- S-12 收到 review 的处理(反驳权) +- S-15~22 审查者全套 Skill +- S-25 挑战者选择(庞统) +- S-26 对抗辩论裁决(庞统) + +### Phase 3 可选(高级功能) + +- S-16 多视角审查深化 +- S-20~22 三种审查协议细化 +- S-27 escalated 用户沟通 +- S-28 confidence 升级策略 +- S-29 任务拆解方法深化 + +## 三、和现有 TODO 的对齐 + +课题4 的 Skill 设计需要覆盖以上 30 项。现有的 T1-5/6/7 和 T2-8 只是其中4项。 + +建议:课题4 的正式设计把这 30 项整理成 Skill 体系文档,明确: +1. 哪些是 Skill 文件(Agent 读的 Markdown) +2. 哪些是 Protocol 文件(Daemon 注入的 YAML) +3. 哪些是 CLI 帮助信息(blackboard.py --help) +4. 三者的关系和优先级 diff --git a/docs/design/topic6-experience-loop-proposal.md b/docs/design/topic6-experience-loop-proposal.md new file mode 100644 index 0000000..3557218 --- /dev/null +++ b/docs/design/topic6-experience-loop-proposal.md @@ -0,0 +1,337 @@ +# 课题6:经验沉淀闭环设计方案 + +**版本**: v1.0 +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-15 +**评审**: 待司马懿评审 +**状态**: 待评审 + +--- + +## 1. 设计目标 + +moziplus 的 Agent 在工作中产出了大量经验(决策、错误修正、最佳实践),但当前没有机制沉淀这些经验,导致每次任务都在"重新发明轮子"。本课题设计一个闭环系统,让经验自动采集、蒸馏、验证、应用、进化。 + +## 2. 核心洞察 + +> **问题**:v1.0 运行两个月,张飞修了 7 次类似的 bug,赵云踩了 3 次相同的数据格式问题。每次都是"重新发现",没有变成组织记忆。 +> +> **根因**:经验只存在于 Agent 的 session 上下文中,session 结束就消失了。MEMORY.md 是手动维护的,不够系统化。 +> +> **优秀实践**: +> - **Claude Code Memory**:四类记忆法,但只记录不蒸馏 +> - **Hermes**:闭环学习(记忆→Skill→搜索→改进),最完整 +> - **nuwa-skill**:五阶段蒸馏管线,蒸馏的是思维方式不是行为模仿 +> - **GSD**:plan template 可复用,Spec-driven 经验 + +**结论**:需要 DISCOVER → DISTILL → VERIFY → APPLY → IMPROVE 五阶段闭环,但必须实用——不搞复杂的 AI 蒸馏管线,从最简单的开始。 + +## 3. 设计决策 + +### D6-1:经验的三种载体 + +> **参考实践**: +> - **Claude Code**:MEMORY.md(Markdown,按 user/feedback/project/reference 分类) +> - **Hermes**:SKILL.md(步骤化菜谱)+ FTS5 索引 +> - **nuwa-skill**:SKILL.md(心智操作系统:心智模型 + 决策启发式 + 反模式 + 诚实边界) +> - **共享意识研究**:Memory → Skills → Rules 三层压缩 + +经验不是一种东西,有三种形态,载体不同: + +| 载体 | 内容 | 格式 | 生命周期 | 例子 | +|------|------|------|---------|------| +| **Rule(规则)** | 确定性的操作规范 | guardrails.yaml / prompt_templates/ | 长期稳定 | "所有 output 必须通过 JSON Schema 校验" | +| **Skill(技能)** | 半确定性的操作方法 | SKILL.md(Markdown) | 中期演进 | "如何正确下载分钟线数据" | +| **Memory(记忆)** | 非确定性的经验片段 | experiences 表 / MEMORY.md | 短期,可蒸馏为 Skill/Rule | "vnpy 的 load_bar 方法在 Python 3.12 有兼容问题" | + +**进化方向**:Memory → Skill → Rule。频繁出现的 Memory 被蒸馏为 Skill,高频引用的 Skill 可固化到 prompt_templates(Rule)。 + +``` +Memory(发现)→ Skill(方法)→ Rule(铁律) + ↓ 蒸馏 ↓ 固化 ↓ 强制 +非确定性 半确定性 确定性 +短命 中等 长命 +``` + +### D6-2:DISCOVER(信息发现) + +> **参考实践**: +> - **Hermes**:Agent-curated Memory + 任务执行过程自动记录 +> - **GSD**:verify phase 的产出作为经验来源 + +**信息源**(按价值排序): + +| 信息源 | 采集方式 | 价值 | 难度 | +|-------|---------|------|------| +| 错误与修正 | Guardrail 拦截 + Retry 记录 + 审查打回 | ⭐⭐⭐⭐⭐ | 低 | +| Comments(Handoff + 讨论) | 黑板 comments 表,含"为什么这么决策"的上下文 | ⭐⭐⭐⭐⭐ | 低(已有) | +| 黑板 decisions | 自动记录 Agent 决策过程和理由 | ⭐⭐⭐⭐ | 低(已有) | +| 审查记录 | reviews 表的 findings + verdict | ⭐⭐⭐⭐ | 低(已有) | +| Agent observations | 黑板 observations(风险、协作、异常) | ⭐⭐⭐ | 低(已有) | +| 节点 output | 任务完成后的产出文件 | ⭐⭐⭐ | 低(已有) | +| 反驳记录 | ACCEPT/REJECT/PARTIAL 的理由 | ⭐⭐⭐ | 中(Phase 2) | + +> **Comments 的价值说明**:执行者的 Handoff comment 包含"我做了什么+为什么",审查者的 comment 包含"问题在哪里+建议怎么改",讨论过程的 comment 包含"为什么选 A 不选 B"。这些是蒸馏经验的关键上下文,价值不低于 decisions。 + +**采集机制**:不新增采集逻辑。所有信息源都已在课题1-4的设计中存在(黑板 decisions、reviews 表、observations、Guardrail 记录)。DISCOVER 阶段只是**读取已有数据**。 + +### D6-3:DISTILL(蒸馏)——两级蒸馏 + +> **参考实践**: +> - **nuwa-skill**:五阶段蒸馏(发现→采集→提取→验证→精炼),蒸馏的是思维方式 +> - **Hermes**:复杂任务完成 → 自动创建 SKILL.md +> - **GSD**:plan template 复用 + +不做 nuwa-skill 那样复杂的 AI 蒸馏管线。**两级蒸馏,从简单开始**: + +#### 一级蒸馏(实时,每个任务完成后自动) + +**触发**:任务状态变为 completed 或 failed 时。 + +**执行者**:庞统(在创建下一个任务前,花 ~30s 蒸馏上一个任务的经验)。 + +**过程**: +1. 读黑板上该任务的所有 comments(Handoff + 讨论)、decisions、observations、reviews +2. 提取 1-3 条关键经验("什么有效""什么无效""下次注意什么") +3. 写入黑板 experiences 表 + +**产出格式**: +```json +{ + "experience_id": "exp-001", + "source": "task_completion", + "task_id": "task-006", + "tags": ["vnpy", "strategy-coding", "error-handling"], + "summary": "vnpy 的 load_bar() 在 Python 3.12 下需要显式指定 end=None,否则只返回最后一根 bar", + "category": "pitfall", + "confidence": 0.9 +} +``` + +**Token 成本**:~500 token/任务(读黑板数据 + 生成摘要)。 + +#### 二级蒸馏(周期性,按需触发) + +**触发条件**(满足任一): +- 同类 experience 累积 ≥ 5 条(按 tag 聚合) +- 庞统发现重复问题出现 3 次 +- 用户要求"把最近的经验整理一下" + +**执行者**:庞统(Subagent 模式,独立 session)。 + +**过程**: +1. 聚合同 tag 的所有 experiences +2. 提取模式:"遇到 X 情况,应该 Y"(决策启发式) +3. 提取反模式:"遇到 X 情况,千万不要 Z"(避坑指南) +4. 标注适用范围(诚实边界) +5. 生成 SKILL.md 草稿 + +**产出**:SKILL.md 写入 `skills/` 目录(L3 被动参考层)。 + +**蒸馏格式**(简化版 nuwa-skill): +```markdown +# Skill: vnpy 分钟线数据加载 + +## 决策启发式 +- 加载分钟线数据时,优先用 load_bar(end=None) 显式指定全量 +- 数据文件超过 1GB 时,用 chunksize 分批读取 + +## 反模式 +- ❌ 不指定 end 参数 → 只返回最后一根 bar +- ❌ 全量读取大文件 → 内存溢出 + +## 诚实边界 +- 仅适用于 vnpy 的数据加载 API +- 不覆盖数据清洗逻辑 +``` + +### D6-4:VERIFY(验证) + +> **参考实践**: +> - **nuwa-skill**:3 个已知问题测试 + 1 个未知问题测试 + 双 Agent 精炼 +> - **Hermes**:幻觉门控——验证产出是否真实存在 + +**一级蒸馏(Memory)的验证**:轻量级。 +- 庞统自己 review(confidence 打分) +- 不需要独立验证环节 + +**二级蒸馏(Skill)的验证**:标准验证。 + +| 验证步骤 | 方法 | 通过标准 | +|---------|------|---------| +| 格式验证 | JSON Schema / Markdown 结构检查 | 结构完整 | +| 内容验证 | 庞统 review | 逻辑合理,无幻觉 | +| 实用性验证 | 标记为 "draft",下次任务引用后根据效果升级 | 被引用 ≥ 2 次且采纳率 > 50% | + +**Skill 生命周期**: +``` +draft → active → deprecated + ↑ │ + └── improved +``` + +- draft → active:被引用 ≥ 2 次且采纳率 > 50% +- active → deprecated:30 天无引用 或 采纳率 < 20% +- active → improved:发现更好的做法时更新 + +### D6-5:APPLY(应用)——经验的反哺机制 + +> **参考实践**: +> - **Claude Code Memory**:每次 session 启动自动加载 +> - **Hermes**:FTS5 跨 session 搜索 + LLM 摘要 + periodic nudge +> - **课题4 D4-6**:L2 引擎注入 + L3 被动参考 + +经验的反哺走课题4定义的四层架构: + +| 载体 | 反哺方式 | 层次 | 机制 | +|------|---------|------|------| +| **Rule** | 写入 guardrails.yaml / prompt_templates/ | L2 引擎注入 | Daemon 自动执行 | +| **Skill** | 写入 skills/ 目录 | L3 被动参考 | Agent 按需加载 | +| **Memory** | 写入 experiences 表 | L2 引擎注入(相关 Memory) | Daemon 按任务 tag 注入 | + +**Memory 的注入逻辑**(Daemon build_bootstrap 时): +```python +def get_relevant_experiences(task_tags, limit=3): + """按任务 tag 检索相关经验,注入 bootstrap 消息""" + experiences = query_experiences(tags=task_tags, status="active", limit=limit) + if experiences: + return format_as_context(experiences) # "注意:之前遇到过类似问题..." + return None +``` + +**注入位置**:在 L2 bootstrap 消息末尾,作为"历史经验提醒": +``` +═══ 历史经验提醒 ═══ +[exp-003] vnpy 分钟线加载:需要显式指定 end=None +[exp-007] 回测报告:确保包含收益曲线和最大回撤 +``` + +**Token 成本**:每条经验 ~50 token,最多 3 条 = ~150 token。 + +### D6-6:IMPROVE(进化) + +> **参考实践**: +> - **Hermes**:Skill 使用时自动改进 +> - **nuwa-skill**:Darwin.skill 八维评估 + 棘轮机制(只进不退) + +**进化机制**: + +| 机制 | 触发 | 执行者 | +|------|------|--------| +| **使用追踪** | Agent 在 Handoff 中标注"参考了 exp-003" | 执行者 | +| **效果对比** | 月度统计:采纳经验 vs 未采纳的任务成功率 | 庞统 | +| **淘汰** | 30 天无引用 → 标记 deprecated | Daemon 定期清理 | +| **更新** | 发现经验过时 → 庞统更新内容 | 庞统 | + +**棘轮原则**:经验只能改进不能退化。如果更新后效果更差,回退到上一个版本。 + +### D6-7:experiences 表设计 + +```sql +CREATE TABLE experiences ( + experience_id TEXT PRIMARY KEY, + source TEXT NOT NULL, -- task_completion / error_correction / review_finding / manual + task_id TEXT, -- 来源任务(FK → tasks) + summary TEXT NOT NULL, -- 经验摘要(200字以内) + category TEXT NOT NULL, -- pitfall / best_practice / pattern / anti_pattern + confidence REAL DEFAULT 0.8, -- 0-1 + status TEXT DEFAULT 'active', -- draft / active / deprecated + skill_id TEXT, -- 关联的 Skill(二级蒸馏后填入) + usage_count INTEGER DEFAULT 0, -- 被引用次数 + last_used_at TEXT, -- 最后引用时间 + created_at TEXT NOT NULL, + created_by TEXT NOT NULL, -- 庞统 / 用户 + updated_at TEXT, + deprecated_reason TEXT +); + +-- tags 多对多关联表(替代 JSON array,走 B-tree 索引) +CREATE TABLE experience_tags ( + experience_id TEXT NOT NULL REFERENCES experiences(experience_id), + tag TEXT NOT NULL, + PRIMARY KEY (experience_id, tag) +); +CREATE INDEX idx_exptags_tag ON experience_tags(tag); +``` + +**查询示例**(D6-5 注入逻辑): +```sql +SELECT DISTINCT e.* FROM experiences e +JOIN experience_tags et ON e.experience_id = et.experience_id +WHERE et.tag IN ('vnpy', 'strategy-coding') + AND e.status = 'active' +LIMIT 3; +``` + +### D6-8:和课题4四层架构的关系 + +``` +L0 铁律 ──── 最稳定的 Rule(IR-1~IR-5) + ↑ 极少固化(需要用户确认) + +L2 引擎注入 ─ prompt_templates/ + 相关 Memory + ↑ 庞统手动更新模板 + ↑ Daemon 自动注入相关 experience + +L3 被动参考 ─ skills/ 目录(二级蒸馏产出) + ↑ Agent 按需加载 + +黑板 experiences 表 ─ 原始 Memory + ↑ 一级蒸馏写入 + ↑ Daemon 按任务 tag 检索注入 +``` + +**进化路径**: +``` +experiences 表(Memory) + ↓ 累积 ≥ 5 条同类 +skills/ 目录(Skill) + ↓ 引用 ≥ 2 次且采纳 > 50% +prompt_templates/(Rule/L2 注入) + ↓ 用户确认 +guardrails.yaml / IR 铁律(L0) +``` + +### D6-9:实施优先级 + +| 优先级 | 内容 | Phase | 理由 | +|--------|------|-------|------| +| P0 | experiences 表创建 | Phase 1 | 基础设施 | +| P0 | 一级蒸馏(庞统任务完成后自动提取) | Phase 1 | 最小闭环 | +| P1 | Memory 注入 build_bootstrap() | Phase 2 | 经验反哺 | +| P1 | 使用追踪(Handoff 标注引用) | Phase 2 | 效果度量 | +| P2 | 二级蒸馏(Skill 生成) | Phase 3 | 需要足够多的 Memory 积累 | +| P2 | Skill 生命周期管理 | Phase 3 | draft/active/deprecated | +| P3 | 效果对比 + 棘轮机制 | Phase 4 | 需要长期数据 | + +**最小闭环(Phase 1)**:experiences 表 + 一级蒸馏 + Dashboard 可查看 = 从 0 到 1。 + +--- + +## 4. 和现有设计的对齐检查 + +| 已有设计 | 课题6 补充 | 一致性 | +|---------|-----------|--------| +| §3.2 SQLite Schema | D6-7 experiences 表加入黑板的 6 张表 | ✅ 同一数据库 | +| §4.4 Agent Spawn 上下文 | D6-5 Memory 注入 build_bootstrap() | ✅ 在 L2 末尾追加 | +| §4.7 Guardrail 体系 | D6-1 Rule 载体 = guardrails.yaml | ✅ 经验固化的终点 | +| §5.1 Agent 工作流程 | D6-2 信息源 = 黑板已有数据 | ✅ 不新增采集逻辑 | +| §9.4 Review Protocol | D6-2 审查记录作为经验源 | ✅ reviews 表数据 | +| 课题4 D4-6 四层架构 | D6-8 经验载体与四层架构对齐 | ✅ Memory→Skill→Rule 路径清晰 | +| 课题4 D4-10 目录结构 | skills/ 目录承载二级蒸馏产出 | ✅ L3 被动参考层 | +| MEMORY.md | D6-1 Memory 载体包含但超越 MEMORY.md | ✅ 系统化的 experiences 表替代手动维护 | + +## 5. 遗留 TODO + +| # | 待解决事项 | 归属 | 说明 | +|---|----------|------|------| +| ~~T6-1~~ | ~~experiences 表 tags 索引实现~~ | ~~Phase 1~~ | ✅ 已解决:使用 experience_tags 关联表,B-tree 索引 | +| ~~T6-2~~ | ~~一级蒸馏 prompt 模板~~ | ~~Phase 1~~ | ✅ 方案已定,开发实现 | +| ~~T6-3~~ | ~~build_bootstrap() 经验注入逻辑~~ | ~~Phase 2~~ | ✅ 方案已定,开发实现 | +| ~~T6-4~~ | ~~二级蒸馏触发条件实现~~ | ~~Phase 3~~ | ✅ 方案已定,开发实现 | +| ~~T6-5~~ | ~~Skill draft → active 的自动化验证~~ | ~~Phase 3~~ | ✅ 方案已定,开发实现 | +| ~~T6-6~~ | ~~经验淘汰定时任务~~ | ~~Phase 3~~ | ✅ 方案已定,开发实现 | +| ~~T6-7~~ | ~~效果对比报表(Dashboard 展示)~~ | ~~Phase 4~~ | ✅ 方案已定,开发实现 | +| ~~T4-10~~ | ~~Skill 进化/蒸馏机制~~ | ~~Phase 3~~ | ✅ 合并进课题6,已被两级蒸馏+Skill生命周期覆盖 | + +> **课题6结题**:全部方案已定,无设计遗留。 diff --git a/docs/design/topic7-9-interaction-dashboard-proposal.md b/docs/design/topic7-9-interaction-dashboard-proposal.md new file mode 100644 index 0000000..c2e6e1c --- /dev/null +++ b/docs/design/topic7-9-interaction-dashboard-proposal.md @@ -0,0 +1,247 @@ +# 课题7+9 设计方案:AI Native 人机交互 + Dashboard + +> **日期**: 2026-05-16 +> **作者**: 庞统(副军师)🐦 +> **状态**: 设计完成,可结题 +> **前置**: 课题1-4、课题6、课题11 已完成设计 + +--- + +## 一、课题7:AI Native 人机交互 + +### 1. 核心问题 + +v1.0 的交互是"用户点按钮触发操作"——传统 Web 应用。v2.0 要实现 AI Native:**AI 主动推送、用户自然语言对话、关键决策拉人、其余自主完成**。 + +### 2. 四种交互模式 + +| 模式 | 描述 | 场景 | 人的参与密度 | +|------|------|------|------------| +| **沉浸观察** | 用户看 Dashboard 实时状态,不干预 | 任务自主执行中 | 低 | +| **轻触确认** | AI 做完关键步骤,推一个确认卡片给用户 | Checkpoint 验证、风控审批 | 中 | +| **即时对话** | 用户主动找 AI 聊(或 AI 主动找人聊) | 需求变更、异常处理、方向调整 | 高 | +| **被动通知** | AI 完成后推送结果摘要 | 任务完成、日报、异常告警 | 低 | + +### 3. 推送级别分级 + +| 级别 | 含义 | 推送方式 | +|------|------|---------| +| 🔴 Critical | 必须立即处理 | 即时推送(webchat/Signal/Telegram) | +| 🟡 Warning | 需要关注,可稍后 | Dashboard 高亮 + 可选推送 | +| 🟢 Info | 一般进展通知 | Dashboard 记录 | +| 🔵 Silent | 仅记录 | 日志/历史 | + +### 4. 三层信息架构 + +| 层级 | 名称 | 内容 | 场景 | +|------|------|------|------| +| L1 | 一眼全局 | 活跃项目数、进行中任务数、最近告警 | 扫一眼知道系统在干什么 | +| L2 | 看板视图 | 任务卡片列表、状态、负责人、进度 | 日常浏览和操作 | +| L3 | 详情面板 | 单任务完整上下文(对话、产出、评审、经验) | 深入了解某个任务 | + +### 5. 双入口对等 + +- **Agent 对话**(webchat/Signal)= 主交互入口 +- **Dashboard**(Web UI)= 可视化入口 +- 共享同一套黑板数据,操作等效 +- 可以在 Dashboard 点按钮,也可以直接跟庞统说"暂停任务 xxx" + +### 6. Checkpoint 交互规范 + +三种 Checkpoint(来自 M3 设计,课题3 挑战/评审体系): + +| Checkpoint 类型 | 颜色 | 交互 | 场景 | +|----------------|------|------|------| +| 🔍 验证 Checkpoint | 蓝色 #6a9eff | 自动核验 + 人工核验 + 双按钮审批 | 产出物御览确认 | +| 🎯 决策 Checkpoint | 紫色 #818cf8 | 三方案并排对比 + 利弊展示 + 推荐标签 + 御批备注 | 方向选择 | +| 🔧 执行 Checkpoint | 橙色 #f59e0b | 分步打勾 + 命令行高亮 + 进度条 + 验证确认 | 分步执行确认 | + +### 7. 异常通知触发条件 + +| 触发条件 | 推送级别 | 通知内容 | +|---------|---------|---------| +| Agent 执行失败 | 🔴 | 任务ID + Agent + 错误摘要 + retry 按钮 | +| 任务超时(超过 eta) | 🟡 | 任务ID + 已耗时 + 预估剩余 | +| 重试次数达到上限 | 🔴 | 任务ID + 累计失败次数 + 建议操作 | +| Daemon 健康异常 | 🔴 | 异常类型 + 影响范围 | +| 任务完成 | 🟢 | 任务ID + 产出摘要 + 产出链接 | +| 经验提取完成 | 🔵 | 经验标题 + 关联任务数 | + +### 8. AI Briefing 格式 + +**日报**(每天 22:00 自动生成,🟢 级别推送): +``` +📊 今日战报 (2026-05-16) +━━━━━━━━━━━━━━━━━━━━ +📋 完成任务:3 个(动量因子策略、数据清洗、代码审查) +🔄 进行中:2 个(配对交易研究、风控规则开发) +⚠️ 需关注:1 个(task-012 超时,预计 30min) +💰 今日 Token 消耗:127K(预算 200K,剩余 36.5%) +🧠 新增经验:2 条(pytest 参数优化、SQLite 并发保护) +``` + +**周报**(每周日 22:00,🟢 级别): +- 本周完成任务数/平均耗时 +- Agent 利用率(谁最忙谁最闲) +- Token 消耗趋势 +- 经验沉淀汇总 +- 下周建议 + +--- + +## 二、课题9:Dashboard 设计 + +### 1. 核心原则 + +- **v1.0 已有的 11 个 Tab 页全部保留** +- **只有"任务看板"需要重新设计和实现**(配合 v2.0 黑板架构) +- 其他 10 个 Tab 功能基本不变,适配新的 Daemon API 即可 + +### 2. v1.0 已有 Tab 页(保留,不重新设计) + +| Tab | 组件 | 功能 | v2.0 变更 | +|-----|------|------|----------| +| 🏛️ 军议大厅 | `CourtDiscussion` | Agent 间对话/讨论 | 适配新 API | +| 🔌 编排调度 | `MonitorPanel` | Daemon/编排状态监控 | 适配新 API | +| 🤺 将军总览 | `OfficialPanel` | Agent 列表/状态/配置 | 适配新 API | +| 🤖 模型配置 | `ModelConfig` | LLM 模型选择/参数 | 适配新 API | +| 🎯 技能配置 | `SkillsConfig` | Skill 注册/启用/禁用 | 适配新 API | +| 🔭 传令巡哨 | `SessionsPanel` | Agent session 列表/状态 | 适配新 API | +| 💰 花费总览 | `UsagePanel` | Token 消耗/预算/趋势 | 适配新 API | +| 📜 奏折阁 | `MemorialPanel` | 已完成/已取消任务归档 | 适配新 API | +| 📋 任务模板 | `TemplatePanel` | 任务模板管理 | 适配新 API | +| ⚙️ 系统设置 | `SettingsPanel` | Daemon 配置/项目管理 | 适配新 API + 课题11 项目管理 | + +### 3. 需要重新设计的 Tab + +#### 📜 任务看板(EdictBoard)— 重新设计 + 重新实现 + +**v1.0 现状**:基于 moziplus v1 DAG 架构的任务列表,状态是 v1 的状态机(pending/planning/executing/completed/failed/cancelled)。 + +**v2.0 重设计**: + +##### 3.1 任务卡片 + +``` +┌─────────────────────────────────────────────┐ +│ 📜 task-013 动量因子策略回测 │ +│ 项目: quant-momentum | 状态: executing 🔄 │ +│ 负责: 张飞⚔️ | 进度: ████░░ 67% │ +│ ⏱️ 已耗时 12min | 📊 3/5 节点完成 │ +│ ⚠️ 1个告警 │ +│ ─────────────────────────────────────────── │ +│ [暂停] [取消] [查看详情] │ +└─────────────────────────────────────────────┘ +``` + +##### 3.2 任务详情面板(TaskModal v2) + +点击任务卡片展开详情,包含: + +| 区域 | 内容 | +|------|------| +| **基本信息** | 标题、需求描述、项目、创建时间 | +| **状态流转** | 状态按钮(基于课题3状态机守卫 ACTION_GUARDS) | +| **执行图** | 任务拆解后的节点图(节点状态 + Agent + 产出) | +| **Checkpoint** | 三种 Checkpoint 面板(验证/决策/执行) | +| **Flow Log** | 执行日志流(时间线格式) | +| **产出物** | 产出文件预览/下载(来自 ArtifactPanel) | +| **评审记录** | 评审意见 + 评审结果(来自 reviews 表) | +| **关联经验** | 该任务沉淀的经验(来自 experiences 表) | + +##### 3.3 项目切换器(配合课题11) + +Header 区域新增项目下拉: +``` +三国量化 · 编排台 | [▼ 动量因子策略] | ✅ 同步正常 | 3 个任务 +``` + +- 下拉列出所有 active 项目 +- 切换后任务看板只显示当前项目的任务 +- "全部项目"选项显示所有项目的任务(带项目标签) + +##### 3.4 推送通知中心 + +Header 区域新增通知铃铛: +``` +🔔 (3) → 点击展开通知面板 +``` + +通知面板: +- 按推送级别分组(🔴 > 🟡 > 🟢 > 🔵) +- 每条通知:时间 + 级别图标 + 内容摘要 + 关联任务链接 +- 🔴 通知可展开操作按钮(确认/忽略/查看详情) +- 支持标记已读/全部已读 + +##### 3.5 AI Briefing 页面(新增 Tab) + +新增第 12 个 Tab:**📊 战报简报** + +- 日报/周报自动生成(§7.8 定义的格式) +- 趋势图表(完成任务数、Token 消耗、Agent 利用率) +- 经验沉淀摘要 +- 下周建议(AI 生成) + +### 4. 技术栈(不变) + +- React + Vite + TypeScript + Zustand +- CSS 变量体系:`--bg`, `--fg`, `--muted`, `--acc`, `--line`, `--panel`, `--panel2` +- API 层:`api.ts`,对接 Daemon HTTP API +- 构建产物:`dashboard/dist/`,uvicorn 8082 端口直接服务 +- UI 参考:OpenClaw Edict UI + Control Center + +### 5. 实时推送机制 + +**选择 SSE(Server-Sent Events)**: + +- Daemon 端新增 `/events` SSE 端点 +- 前端 `EventSource` 监听,收到事件后更新 Zustand store +- 事件类型:`task.status_changed`、`checkpoint.created`、`notification.pushed`、`agent.state_changed` +- 降级方案:SSE 连接失败时退回轮询(当前 v1.0 已有的 5s 轮询) + +理由: +- 比 WebSocket 简单(单向推送够用) +- 比 pure 轮询实时性好 +- HTTP 兼容性好(代理/防火墙不拦) + +### 6. 开发清单 + +| # | 任务 | 优先级 | 说明 | +|---|------|--------|------| +| 1 | Daemon `/events` SSE 端点 | P0 | 实时推送基础 | +| 2 | 任务看板 v2(EdictBoard 重写) | P0 | 核心页面 | +| 3 | TaskModal v2(详情面板) | P0 | 含 Checkpoint/Artifact/Review 集成 | +| 4 | 项目切换器 | P1 | 课题11 前端对接 | +| 5 | 推送通知中心 | P1 | Header 铃铛 + 通知面板 | +| 6 | AI Briefing 页面 | P2 | 日报/周报自动生成 | +| 7 | 其他 10 个 Tab 适配新 API | P1 | 主要是 api.ts 对接 | +| 8 | SSE 降级轮询 | P1 | 连接失败时自动回退 | + +--- + +## 三、与其他课题的关系 + +| 课题 | 关系 | +|------|------| +| 课题1-2(执行模型+事件驱动) | Dashboard 的数据来源 + SSE 事件源 | +| 课题3(挑战/评审) | Checkpoint 面板的交互 + 状态流转按钮 | +| 课题4(拆解+上下文) | TaskModal 中的执行图展示 | +| 课题6(经验沉淀) | AI Briefing 的内容来源 + TaskModal 关联经验 | +| 课题11(多项目) | 项目切换器 + 任务卡片项目标签 | +| M3(Checkpoint+Artifact) | Checkpoint 三种组件 + ArtifactPanel 集成 | + +--- + +## 四、结题说明 + +课题7+9 的设计决策在 v2.6.6 已确定(四种交互模式、推送分级、信息架构、5页结构),本次方案文档补充了: + +- ✅ Checkpoint 交互规范(三种类型 + 颜色 + 交互方式) +- ✅ 异常通知触发条件和内容模板 +- ✅ AI Briefing 格式(日报/周报模板) +- ✅ v1.0 已有 11 个 Tab 页完整清单和保留策略 +- ✅ 任务看板 v2 重新设计(卡片/详情/项目切换/通知中心) +- ✅ 实时推送机制选型(SSE + 降级轮询) +- ✅ 开发清单(8 项,按优先级排序) + +**剩余工作属于编码范畴**,设计层面可结题。 diff --git a/docs/design/topic7-interaction-dashboard-proposal.md b/docs/design/topic7-interaction-dashboard-proposal.md new file mode 100644 index 0000000..9006f06 --- /dev/null +++ b/docs/design/topic7-interaction-dashboard-proposal.md @@ -0,0 +1,456 @@ +# 课题7+9:AI Native 人机交互 + Dashboard 设计方案 + +**版本**: v1.0 +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-15 +**评审**: 待司马懿评审 +**状态**: 待评审 + +--- + +## Part A:AI Native 人机交互(课题7) + +### 1. 设计目标 + +定义 moziplus 中人与 AI Agent 的交互范式——不是给 AI 团队做 ERP 界面,而是让 AI 主动、精准、不扰民地和用户协作。 + +### 2. 核心洞察 + +> **传统模式**:用户主动查 Dashboard、主动问进度、主动分配任务。 +> **AI Native 模式**:AI 主动推送关键信息,用户只在需要决策时轻触确认。 + +参考六种 AI Native 交互范式(调研报告 §3.2): + +| # | 范式 | 代表 | moziplus 适用度 | +|---|------|------|----------------| +| 1 | REPL 对话 | Claude Code CLI | ⭐⭐⭐ WebChat 已有 | +| 2 | Canvas/Artifacts | ChatGPT Canvas | ⭐⭐⭐⭐ 产出物交互 | +| 3 | 沉浸式观察 | Devin | ⭐⭐⭐ 长任务监控 | +| 4 | Command Palette | Raycast | ⭐⭐⭐⭐ 快捷操作 | +| 5 | Contextual Inline | Cursor | ⭐⭐ 不适用(非IDE) | +| 6 | Event-Driven Notification | Linear | ⭐⭐⭐⭐⭐ 核心模式 | + +**结论**:moziplus 的核心交互范式是 **Event-Driven Notification + 轻触确认**,辅助以 WebChat 深度对话和 Dashboard 全局观察。 + +### 3. 设计决策 + +#### D7-1:四种交互模式 + +| 模式 | 触发方式 | 用户注意力 | AI 主动性 | 适用场景 | +|------|---------|-----------|----------|---------| +| **沉浸观察** (Dashboard) | 用户主动打开 | 高 | 低 | 全局监控、任务详情 | +| **轻触确认** (Approval) | AI 主动推送 | 中 | 高 | 方案审批、风控告警 | +| **即时对话** (WebChat) | 双向触发 | 中高 | 中 | 复杂指令、探索性需求 | +| **被动通知** (Notify) | AI 主动推送 | 低 | 高 | 任务完成、阶段转换 | + +> **参考实践**: +> - **Cursor**:4 种交互粒度(Tab/Cmd+K/Cmd+L/Cmd+I),从被动到主动覆盖所有场景 +> - **Devin**:沉浸式观察,用户只在关键节点介入 +> - **Linear**:精准通知,不噪音 + +#### D7-2:推送级别分级 + +| 推送级别 | 示例 | 默认行为 | 用户可配置 | +|---------|------|---------|----------| +| 🔴 **必须确认** | 方案审批、风控告警、预算超限 | 始终推送 + 阻塞等待 | ✗ 不可关闭 | +| 🟡 **重要进展** | 任务完成、阶段转换、产出交付 | 默认推送 | 可降级为摘要 | +| 🟢 **一般信息** | Agent 启动、中间步骤、心跳正常 | 默认不推送 | 可开启 | +| 🔵 **定时摘要** | 每日工作汇总、Token 消耗报告 | 默认开启 | 频率可调 | + +**推送频率控制**: +- 任务执行中:每 5 分钟一次进展摘要(不是每步都推) +- 任务完成/阻塞/异常:立即推送 +- 正常心跳:不推送,只在 Dashboard 上更新 + +> **参考实践**: +> - **Linear**:关键变更推送 + 精准通知,不噪音 +> - **课题4 D4-5**:审批式/通知式/中断式三种确认模式,按 risk_level 动态决定 + +#### D7-3:推送渠道 + +| 渠道 | 推送级别 | 交互深度 | 延迟 | +|------|---------|---------|------| +| **WebChat** | 🔴🟡 | 深(可对话) | 实时 | +| **Dashboard 通知** | 🟡🟢 | 浅(点击查看) | 实时 | +| **Signal/Telegram** | 🟡🔵 | 极浅(只读) | 实时 | +| **邮件** | 🔵 | 极浅(只读) | 批量 | + +**当前落地**:WebChat(主) + Signal(辅)。Dashboard 通知随 M2 前端一起上线。 + +#### D7-4:用户决策点设计 + +用户不是全程驾驶,而是在关键节点参与: + +``` +用户参与密度: +Phase 1 需求探索 ──── 高(苏格拉底对话) +Phase 2 动态规划 ──── 中(方案确认,可跳过 simple 任务) +Phase 3 自主执行 ──── 低(只收通知,不干预) +Phase 4 验收汇报 ──── 中(审查产出,确认交付) +``` + +**🔴 级别统一超时策略**(适用于所有 🔴必须确认 的决策点) + +> 核心原则:用户不在时,系统宁可暂停也不自动推进高风险操作。 + +| 时间段 | 行为 | +|--------|------| +| 0-1h | 正常等待,无额外动作 | +| 1-4h | 每 30min 重发提醒(频率可配,通过 WebChat + Signal 双渠道) | +| 4h 后 | 任务标记 `escalated` + **暂停**(不继续执行,不自动 reject),通知渠道升级 | +| 用户回来 | 手动恢复(approve/reject/cancel),移除 escalated 标记 | + +**具体决策点**: + +| 决策点 | 触发条件 | 交互模式 | 超时兜底(覆盖上面的通用策略) | +|--------|---------|---------|---------| +| 需求确认 | 庞统完成需求结构化后 | 即时对话 | 30min 后提醒,4h 后暂停 | +| 方案审批 | complex 任务规划完成后 | 轻触确认 | 同通用策略(含 risk_level=high) | +| 风控告警 | 关羽触发风控规则 | 必须确认 | 同通用策略:4h 后 escalated+暂停,保护资金安全 | +| 产出验收 | 任务全部完成后 | 沉浸观察 | 24h 自动确认(仅 risk_level=low,其余走通用策略) | +| 异常升级 | Agent 无法自主解决 | 即时对话 | 同通用策略 | + +> **参考实践**: +> - **v2.0 PRD**:人的参与密度从高到低 +> - **GSD discuss phase**:必须确认步骤 +> - **课题4 D4-5**:三种确认模式 + +#### D7-5:Agent 主动汇报规范 + +Agent 不是沉默执行器——在关键节点主动向用户汇报: + +| 汇报时机 | 谁汇报 | 内容 | 渠道 | +|---------|--------|------|------| +| 任务开始 | 庞统 | "已创建任务 T-006,5个节点,预估30分钟" | WebChat | +| 阶段转换 | 庞统 | "T-006 编码完成,进入审查阶段" | 通知 | +| 遇到阻塞 | 执行者 | "发现 XXX 问题,建议 YYY 方案" | 即时对话 | +| 任务完成 | 庞统 | "T-006 完成!年化收益15.3%,最大回撤8.2%" | WebChat + 通知 | +| 定时摘要 | 庞统 | "今日完成 3 个任务,Token 消耗 ¥12.5" | 通知/邮件 | + +**汇报格式**(简洁、可操作): +``` +✅ T-006 双均线策略 — 完成 + 执行:张飞→司马懿→赵云 + 产出:策略代码 + 回测报告 + 关键指标:年化15.3% / 回撤8.2% / 夏普1.8 + [查看详情] [查看产出] +``` + +#### D7-6:多 Agent 协作可视化 + +用户观察多 Agent 协作过程的三层视图(调研报告 §4.4): + +| 层次 | 展示什么 | 类比 | 用户操作 | +|------|---------|------|---------| +| **全局层** | DAG 拓扑 + 节点状态 | GitHub Actions | 点击节点 | +| **任务层** | 管道进度 + Agent 头像 | Edict MiniPipe | 审批/暂停 | +| **Agent 层** | 当前操作 + 产出物预览 | Devin 工作屏 | 评论/指导 | + +--- + +## Part B:AI Native Dashboard 设计(课题9) + +### 4. 设计目标 + +Dashboard 不是监控面板,是**人 Agent 协作界面**——用户在这里观察、确认、指导 AI 团队的工作。 + +### 5. 设计决策 + +#### D9-1:Dashboard 定位 + +> **参考实践**: +> - **Hermes Kanban**:Dashboard 是协作界面,不是展示墙。Comment Thread 是人 Agent 讨论的最佳实践 +> - **Edict**:看板 + 管道 + 操作按钮 + 皇帝发言 = 完整协作 +> - **Linear**:极简 + 键盘优先,不信息过载 + +**三不做**: +1. ❌ 不做第二个 Grafana(纯监控面板) +2. ❌ 不做第二个 Slack(闲聊频道) +3. ❌ 不做第二个 Jira(重到没人用) + +**定位**:轻量协作界面——展示关键状态 + 快速操作入口 + 深入查看能力。 + +#### D9-2:三层信息架构 + +``` +L1: 一眼看到(顶部栏,永远可见) +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ 全局健康 │ │ 活跃任务 │ │ 待审批 │ │ Token │ +│ 🟢 正常 │ │ 3 │ │ 1 ⚠️ │ │ ¥12.5 │ +└──────────┘ └──────────┘ └──────────┘ └──────────┘ + +L2: 任务看板(主体区域) +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ T-003 策略回测│ │ T-004 数据采集│ │ T-005 风控 │ +│ ⚙️ 执行中 │ │ 📋 规划中 │ │ ✅ 完成 │ +│ ████░░ 张飞 │ │ ░░░░░░ 赵云 │ │ █████ 赵云 │ +│ 3/5 节点完成 │ │ 等待审批 │ │ 产出已交付 │ +└─────────────┘ └─────────────┘ └─────────────┘ + +L3: 任务详情(点击卡片展开) +- 管道进度:规划→审查→执行→交付 +- 产出物:代码 diff / Markdown / 图表 +- 评论线程:Agent 和用户的讨论 +- 操作历史:所有状态变更记录 +``` + +#### D9-3:页面结构 + +**延续 v1.0 的 Tab 结构**,但大幅精简为 5 个核心页面: + +| 页面 | 展示内容 | 核心交互 | 优先级 | +|------|---------|---------|--------| +| **任务看板** | 活跃任务卡片 + 管道进度 | 审批/暂停/取消、点击查看详情 | P1 | +| **全局监控** | DAG 拓扑 + Agent 状态 + 心跳 | 点击节点/Agent、Token 统计 | P1 | +| **产出档案** | 已完成任务 + 产出物 | 预览/下载/复制 | P2 | +| **系统配置** | 模型/技能/Guardrail 配置 | 编辑/保存 | P2 | +| **AI Briefing** | 今日摘要 + 待处理 + 风险预警 | 一键操作 | P3 | + +**砍掉的 v1.0 页面**(合并或降级): +- "将军总览" → 合并到全局监控的 Agent 状态面板 +- "军议大厅" → 合并到任务详情的评论线程(黑板 comment 已支持) +- "奏折阁" → 合并到产出档案 +- "会话监控" → 合并到全局监控 +- "编排调度" → 合并到任务看板(DAG 拓扑) + +> **理由**:5 个页面覆盖所有场景。v1.0 的 10 Tab 有冗余(编排调度 vs 任务看板、奏折阁 vs 产出档案)。参考 Linear 的极简哲学——少即是多。 + +#### D9-4:任务看板设计 + +**任务卡片**(L2 主体): + +``` +┌─────────────────────────────────────┐ +│ T-006 双均线交叉策略 │ +│ 📋 规划中 ─── 🟡 risk: standard │ +│ │ +│ MiniPipe: █████░░░░░░ 2/5 节点 │ +│ 张飞 🟢 编码中 → 司马懿 ⏳ 等待审查 │ +│ │ +│ 预估 30min | 已用 15min | Token ¥2.3 │ +│ │ +│ [⏸暂停] [📋详情] [💬评论] │ +└─────────────────────────────────────┘ +``` + +**卡片上的操作按钮**(按任务状态动态显示): + +| 状态 | 可用操作 | 说明 | +|------|---------|------| +| pending | 取消、编辑 | 规划前可改 | +| planned | 审批、驳回 | 等用户确认方案 | +| executing | 暂停、查看详情 | 执行中可暂停 | +| reviewing | 查看详情 | 审查中只观察 | +| completed | 查看产出、重新执行 | 完成后可复查 | +| failed | 重试、取消、查看详情 | 失败后可重试 | + +> **参考实践**: +> - **Edict TaskModal**:叫停/恢复/取消统一入口 +> - **Hermes Kanban**:7 状态完整状态机 + 按钮动态显示 +> - **课题3 §9.7**:状态机守卫(ACTION_GUARDS),前端按钮与后端对齐 + +#### D9-5:全局监控设计 + +**DAG 拓扑视图**: + +``` + T-006 双均线策略 + ┌──────┐ ┌──────┐ ┌──────┐ + │ 赵云 │────→│ 张飞 │────→│ 司马懿│ + │ 数据 │ ✅ │ 编码 │ ⚙️ │ 审查 │ ⏳ + └──────┘ └──────┘ └──────┘ + │ + ▼ + ┌──────┐ + │ 关羽 │ + │ 风控 │ ⏳ + └──────┘ +``` + +- 颜色:✅ 完成(绿) / ⚙️ 执行中(蓝动画) / ⏳ 等待(灰) / ❌ 失败(红) +- 点击节点 → 滑出该节点的详情面板 +- 连线 = depends_on 依赖关系 + +**Agent 状态面板**: + +``` +┌────────────────────────────────────┐ +│ 张飞 翼德 🟢 │ +│ 当前: T-006 节点2 编码中 │ +│ 心跳: 30s前 | Session: 45min │ +│ 今日: 完成 2 任务 | Token ¥5.2 │ +└────────────────────────────────────┘ +``` + +> **参考实践**: +> - **GitHub Actions**:DAG 拓扑 + 点击节点看日志 +> - **Hermes Kanban**:心跳可视化 + 僵尸检测 +> - **课题2 §4.5**:续杯与心跳机制 + +#### D9-6:AI Briefing(AI 晨报) + +> **参考实践**: +> - **v2.0 PRD Phase 4**:主动汇报,不等人查 +> - **superpowers**:每次 session 开始时自动 summary + +位置:Dashboard 首页顶部卡片。 + +**内容**(AI 自动生成,不是固定模板): + +``` +┌─────────────────────────────────────────┐ +│ 🌅 今日简报 — 2026-05-16 │ +│ │ +│ 📊 活跃任务: 3 | 待审批: 1 | 今日完成: 2 │ +│ │ +│ ⚠️ 需要你处理: │ +│ • T-006 方案审批(等待 2h) │ +│ │ +│ 📈 今日进展: │ +│ • T-004 数据采集完成(赵云) │ +│ • T-005 风控检查通过(关羽) │ +│ │ +│ 💰 Token 消耗: ¥12.5 / 今日预算 ¥50 │ +│ │ +│ [审批 T-006] [查看全部] │ +└─────────────────────────────────────────┘ +``` + +**生成时机**: +- 用户打开 Dashboard 时自动刷新 +- 每日定时推送(可配置) +- 关键事件后更新 + +#### D9-7:Command Palette(⌘K) + +> **参考实践**: +> - **Raycast**:全局快捷键 + 模糊搜索 + 即用即走 +> - **cmdk(shadcn/ui)**:成熟的 React command palette 组件 + +**支持的命令**: + +| 类别 | 示例 | +|------|------| +| 任务操作 | "暂停任务 3" "查看 T-006 详情" "创建新任务" | +| 状态查询 | "张飞在干什么" "Token 用了多少" "有多少待审批" | +| 导航 | "打开产出档案" "跳转到 T-005" | +| 配置 | "切换模型" "查看技能列表" | + +**实现**:Phase 2,使用 cmdk 组件。 + +#### D9-8:评论线程 + +> **参考实践**: +> - **Hermes Kanban**:per-task comment thread,人和 Agent 都可评论 +> - **黑板 comment 机制**(课题1 §5.2):已设计结构化 comment + @mention + +每个任务有独立评论线程,展示在 L3 任务详情中: + +``` +┌─────────────────────────────────────────┐ +│ 💬 T-006 讨论线程 │ +│ │ +│ [庞统] 任务已创建,5个节点,预估30min │ +│ [张飞] ## Handoff: 编码完成 ✅ │ +│ 产出: strategy.py │ +│ [司马懿] 🔍 发现2个问题... │ +│ verdict: needs_revision │ +│ [张飞] REJECT: 问题1已修复,问题2不同意 │ +│ [你] → 输入评论... │ +└─────────────────────────────────────────┘ +``` + +**用户可以直接在评论线程中发言**,发言会被 Agent 看到(通过黑板 comment + inbox 通知)。 + +#### D9-9:技术方案 + +| 方面 | 方案 | 理由 | +|------|------|------| +| **前端框架** | React 18 + Vite + Zustand + Tailwind | 延续 v1.0 + Edict 已验证 | +| **实时通信** | M2: HTTP 轮询(5s) → M3: WebSocket | 渐进升级,延续 v1.0 策略 | +| **DAG 可视化** | React Flow | GitHub Actions 式 DAG,成熟方案 | +| **Command Palette** | cmdk(shadcn/ui) | 成熟的 command palette 组件 | +| **图表** | Recharts | 轻量级,已有使用经验 | +| **后端 API** | FastAPI + SQLite(黑板数据库) | 延续 v1.0 技术栈 | +| **UI 风格** | 深色主题(延续 Edict CSS 变量体系) | 已有完整设计系统 | + +> **注意**:v1.0 前端源码在 `~/.sanguo_projects/sanguo_moziplus/dashboard/`(React + Vite + TypeScript),可直接在此基础上重构。 + +#### D9-10:API 设计 + +Dashboard 后端 API 直接读取黑板数据库: + +| 端点 | 方法 | 用途 | +|------|------|------| +| `/api/tasks` | GET | 任务列表(支持 state/agent 过滤) | +| `/api/tasks/{id}` | GET | 任务详情(含 comments、reviews) | +| `/api/tasks/{id}/action` | POST | 操作(pause/resume/cancel/approve/reject) | +| `/api/agents` | GET | Agent 状态列表 | +| `/api/stats` | GET | 全局统计(活跃/完成/Token) | +| `/api/briefing` | GET | AI Briefing 内容 | +| `/api/comments/{task_id}` | GET/POST | 评论线程读写 | +| `/ws/events` | WebSocket | 实时事件推送(M3) | + +**并发与权限保护**: + +1. **来源标识**:action 端点必须带 `source` 字段(`dashboard` / `daemon` / `cli`),Daemon 处理时检查来源合法性 + ```json + POST /api/tasks/T-006/action + { "action": "approve", "source": "dashboard", "reason": "方案可行" } + ``` + +2. **乐观锁**:action 请求必须带 `expected_version` 字段,Daemon 校验当前 version 是否匹配,不匹配则拒绝(409 Conflict) + ```json + { "action": "approve", "source": "dashboard", "expected_version": 3 } + ``` + → Dashboard 前端在读取任务详情时获取 version,提交时带回,防止 Dashboard 和 Daemon 并发冲突。 + +3. **用户身份**:Dashboard 评论的 `author` 统一为 `"user"`,与 Agent 的 `"pangtong-fujunshi"` / `"zhangfei-dev"` 等区分 + +**关键设计**: +- 所有数据来自黑板数据库(单一数据源) +- Dashboard 不维护独立状态,是黑板数据的只读视图 + 操作入口 +- 操作端点(approve/reject)通过来源标识 + 乐观锁安全地调用 Daemon API + +--- + +## 6. 和现有设计的对齐检查 + +| 已有设计 | 课题7+9 补充 | 一致性 | +|---------|-------------|--------| +| §4.2 Tick+Inbox 事件驱动 | D7-2 推送级别 → inbox 事件驱动通知 | ✅ Inbox 是推送机制的基础 | +| §5.1 Agent spawn 流程 | D7-5 主动汇报 → spawn 后庞统通知用户 | ✅ 庞统作为协调者汇报 | +| §3.4 原子操作 | D9-10 API action 端点 → 调用原子操作 | ✅ Dashboard 操作走黑板原子操作 | +| §3.5 评论线程 | D9-8 Dashboard 评论 → 黑板 comment | ✅ 单一数据源 | +| §9.7 状态机守卫 | D9-4 卡片按钮 → ACTION_GUARDS 对齐 | ✅ 前后端一致 | +| §4.5 续杯与心跳 | D9-5 Agent 心跳状态 → Dashboard 展示 | ✅ 心跳数据可视化 | +| v1.0 10 Tab 设计 | D9-3 精简为 5 页 | ✅ 合并冗余,保留核心 | +| Edict Dashboard | D9-2 三层信息 + D9-4 卡片 + D9-5 DAG | ✅ 直接复用模式 | + +## 7. Phase 落地规划 + +| Phase | 人机交互 | Dashboard | +|-------|---------|-----------| +| **Phase 1** | WebChat 推送(已有)+ 🔴必须确认 | 无前端(CLI + WebChat 够用) | +| **Phase 2** | 🟡通知 + 🟢信息 + ⌘K | 任务看板 + 全局监控 + 评论线程 | +| **Phase 3** | 🔵定时摘要 + AI Briefing | 产出档案 + 系统配置 + WebSocket | +| **Phase 4** | Canvas 产出物交互 | 全功能 + 优化 | + +## 8. 遗留 TODO + +> **注意**:新版 `topic7-9-interaction-dashboard-proposal.md` 已完成设计,以下遗留全部由新版覆盖或为开发实现。 + +| # | 待解决事项 | 归属 | 说明 | +|---|----------|------|------| +| ~~T7-1~~ | ~~推送级别配置 UI~~ | ~~Phase 2~~ | ✅ 新版已设计推送分级,用户偏好配置为开发实现 | +| ~~T7-2~~ | ~~超时兜底机制~~ | ~~Phase 2~~ | ✅ 新版已设计超时推送(🟡级别),具体党底在课题3解决 | +| ~~T7-3~~ | ~~WebChat 推送格式标准化~~ | ~~Phase 1~~ | ✅ 开发实现 | +| ~~T9-1~~ | ~~React Flow DAG 组件~~ | ~~Phase 2~~ | ✅ 开发实现 | +| ~~T9-2~~ | ~~cmdk Command Palette~~ | ~~Phase 2~~ | ✅ 开发实现 | +| ~~T9-3~~ | ~~AI Briefing~~ | ~~Phase 3~~ | ✅ 新版已设计日报/周报格式 | +| ~~T9-4~~ | ~~Dashboard 评论→黑板~~ | ~~Phase 2~~ | ✅ 开发实现 | +| ~~T9-5~~ | ~~WebSocket 实时推送~~ | ~~Phase 3~~ | ✅ 新版选 SSE + 降级轮询替代 | +| ~~T9-6~~ | ~~v1.0 前端重构~~ | ~~Phase 2~~ | ✅ 新版已设计 5 页面结构 | +| ~~T9-7~~ | ~~产出物预览 Canvas~~ | ~~Phase 4~~ | ✅ 开发实现 | + +> **⏳ 待用户确认**:课题7+9设计方案待用户进一步探讨后结题。 diff --git a/docs/design/topic9-dashboard-design.md b/docs/design/topic9-dashboard-design.md new file mode 100644 index 0000000..dbdc37b --- /dev/null +++ b/docs/design/topic9-dashboard-design.md @@ -0,0 +1,466 @@ +# 课题9:Dashboard 前后端完整设计方案 + +**版本**: v2.0(评审修订版) +**作者**: 庞统(副军师)🐦 +**日期**: 2026-05-17 +**状态**: 待司马懿确认 +**评审记录**: Mail #278(司马懿 P0+P1 评审)→ 已全部修复 → Mail #279(验证通过) + +--- + +## 一、核心定位 + +### 1.1 Dashboard 不是管理控制台,是 AI 工作可视化窗口 + +| 传统 Dashboard | AI Native Dashboard(v2.0) | +|---|---| +| 用户主动操作(点按钮、填表单) | AI 主动展示(推送状态、产出、决策请求) | +| 用户监控一切 | AI 只在关键点拉人,其余自主 | +| 表格+表单为主 | 时间线+卡片+对话为主 | +| 用户是驾驶员 | 用户是乘客,偶尔导航 | + +### 1.2 参考的优秀实践 + +| 实践 | 借鉴点 | +|------|--------| +| **Edict MiniPipe** | 任务卡片上的状态管线可视化 | +| **Hermes Kanban** | 卡片 + 结果区 + 评论线程 + 事件流 + Nudge 按钮 | +| **Claude Code App** | 多 Tab 切换详情面板(产出/评审/事件) | +| **Linear** | 精准通知不噪音;状态流转按钮守卫 | +| **Devin** | AI 自主执行的观察窗口理念 | +| **OpenClaw Control Center** | 双入口对等(对话 + Dashboard) | + +--- + +## 二、数据模型 + +### 2.1 V2Task 类型(对齐后端 tasks 表) + +前端核心类型,字段完全对齐后端 `src/blackboard/db.py` 的 `tasks` 表: + +```typescript +interface V2Task { + // 基础字段(直接对应 tasks 表列) + id: string; + title: string; + description: string; + status: TaskStatus; // 8 状态枚举,见 §2.2 + assignee: string | null; + assigned_by: string | null; + depends_on: string | null; // 依赖任务 ID + parent_task: string | null; // 父任务 ID + priority: number; // 1-10 整数(P0 修复:前端曾用 string,已改 number) + task_type: string; + created_at: string; + updated_at: string; + claimed_at: string | null; + completed_at: string | null; + started_at: string | null; + deadline: string | null; + retry_count: number; + max_retries: number; + risk_level: 'low' | 'standard' | 'high' | 'critical'; + escalated: number; // 0/1 整数(与后端 INTEGER 一致) + + // 聚合字段(后端 summary API 或前端多次查询) + comments_count: number; + outputs_count: number; + review_status: 'none' | 'pending' | 'approved' | 'rejected' | 'rebuttal'; + latest_event: string | null; + project_id: string; +} +``` + +### 2.2 状态机(8 状态,对齐后端 VALID_STATUSES) + +``` +pending ──→ claimed ──→ working ──→ review ──→ done + │ │ │ │ + │ │ ├→ blocked ├→ failed + │ │ │ │ + └→ cancelled←┴───────────┴───────────┘ +``` + +**线性管线**(5 步):`pending → claimed → working → review → done` + +**旁路状态**:`failed`(标记在 working 位置)、`blocked`(标记在 working 位置) + +**终态**:`done`、`cancelled` + +### 2.3 状态流转守卫(对齐后端 VALID_TRANSITIONS) + +```typescript +const VALID_TRANSITIONS: Record = { + pending: ['claimed', 'cancelled'], + claimed: ['working', 'pending', 'cancelled'], + working: ['review', 'blocked', 'failed', 'cancelled'], + review: ['done', 'pending', 'failed', 'cancelled'], + blocked: ['pending', 'cancelled'], + failed: ['pending'], + done: [], + cancelled: [], +}; +``` + +与后端 `src/blackboard/db.py` 的 `VALID_TRANSITIONS` 完全一致。 + +### 2.4 Priority 映射 + +后端 `priority` 是 INTEGER(1-10),前端映射: + +```typescript +const PRIORITY_META: Record = { + 1: { color: '#6b7280', label: '低' }, + 2: { color: '#6b7280', label: '低' }, + 3: { color: '#3b82f6', label: '中' }, + 4: { color: '#3b82f6', label: '中' }, + 5: { color: '#f59e0b', label: '高' }, + 6: { color: '#f59e0b', label: '高' }, + 7: { color: '#ff5270', label: '紧急' }, + 8-10: { color: '#ff5270', label: '紧急' }, +}; +``` + +--- + +## 三、前端页面设计 + +### 3.1 页面结构 + +| 页面 | 组件 | 优先级 | 状态 | +|------|------|--------|------| +| **📜 任务看板** | EdictBoard | P0 | ✅ Mock UI 已完成,待对接 API | +| **🔍 任务详情** | TaskModal | P0 | ✅ Mock UI 已完成,待对接 API | +| **🏛️ 朝堂议政** | CourtDiscussion | P1 | 迁移自 v1.0 | +| **🔌 编排调度** | MonitorPanel | P1 | 迁移自 v1.0 | +| **🤺 将军总览** | OfficialPanel | P1 | 迁移自 v1.0 | +| **🔔 通知中心** | NotificationCenter | P1 | 占位(Header 铃铛) | +| **📊 AI Briefing** | AIBriefing | P2 | 占位页面 | +| **⚙️ 系统设置** | SettingsPanel | P1 | 迁移自 v1.0 | + +### 3.2 任务看板(EdictBoard) + +#### 顶部统计(4 格) + +| 格 | 内容 | 数据来源 | +|---|------|---------| +| 活跃任务 | working + claimed + review 数 | tasks 表 count | +| 已完成 | done 数 | tasks 表 count | +| 失败/阻塞 | failed + blocked 数 | tasks 表 count | +| 审查中 | review 数 | tasks 表 count | + +#### 状态管线可视化(5 步线性) + +``` +📋待认领 → 👤已认领 → ⚔️执行中 → 🔍审查中 → ✅已完成 +``` + +- 管线上游步骤显示 ✓(绿色),当前步骤高亮(蓝色),下游步骤灰显 +- `failed` 状态:在 working 位置显示 ✗(红色) +- `blocked` 状态:在 working 位置显示 🚧(黄色) +- `cancelled` 状态:管线全部灰显 + +#### 筛选栏 + +8 个状态筛选按钮(all/pending/claimed/working/review/done/failed/blocked)+ 搜索框。 + +#### 任务卡片 + +每张卡片展示: +- **状态管线**(5 步 MiniPipe) +- **ID + 标题** +- **标签行**:状态标签 + 负责人(agent emoji)+ 优先级 + 升级标记 + 依赖标记 +- **元信息行**:更新时间 + 产出数 + 评论数 + 审查状态图标 +- **最新事件**:单行文本截断 +- **底部**:风险等级 + 重试次数 + 截止时间 + 详情按钮 + +### 3.3 任务详情面板(TaskModal) + +点击任务卡片弹出 Modal,4 Tab 布局: + +#### Tab 1: 📋 总览 + +| 区域 | 内容 | 数据来源 | +|------|------|---------| +| 需求描述 | task.description | tasks 表 | +| 状态操作 | 基于 VALID_TRANSITIONS 的操作按钮 | §2.3 守卫 | +| 任务信息 | 类型/优先级/风险/重试/时间/分配人(8 字段网格) | tasks 表 | +| 事件时间线 | 逆序事件列表(图标 + 描述 + 时间 + agent) | events 表 | +| 评论/交接 | handoff/progress/review/rebuttal/debate/observation 6 种类型 | comments 表 | +| 决策记录 | 决策人 + 决策类型 + 理由 | decisions 表 | +| Checkpoint | 占位(虚线框 + "v2.7 提供") | ❌ 后端未实现 | + +#### Tab 2: 📦 产出 + +产出物列表:agent + 内容 + 类型标签 + 尝试次数 + 时间。数据来源:outputs 表。 + +#### Tab 3: 🔍 审查 + +审查意见:评审人 + verdict(APPROVE/REJECT)+ 置信度 + 风险等级 + 辩论轮次 + 摘要。数据来源:reviews 表。 + +#### Tab 4: 🧠 经验 + +关联经验:标题 + 摘要 + 标签。左侧彩色边框区分 best_practice(绿)/ pitfall(红)/ environment(蓝)。数据来源:experiences 表。 + +### 3.4 通知中心(Header 铃铛) + +``` +🔔 (3) → 点击展开通知面板 +``` + +- 按 4 级分组(🔴 Critical > 🟡 Warning > 🟢 Info > 🔵 Silent) +- 每条:时间 + 级别图标 + 内容摘要 + 关联任务链接 +- 🔴 可展开操作按钮 +- 支持标记已读/全部已读 + +**当前状态**:SSE broker 已实现(F17),ticker 未推送事件。前端做铃铛 UI + 占位。 + +### 3.5 AI Briefing + +日报/周报自动生成。格式: + +``` +📊 今日战报 (2026-05-17) +━━━━━━━━━━━━━━━━━━━━ +📋 完成任务:3 个 +🔄 进行中:2 个 +⚠️ 需关注:1 个 +💰 Token 消耗:127K +🧠 新增经验:2 条 +``` + +**当前状态**:后端未实现。前端做占位页面。 + +--- + +## 四、后端 API 设计 + +### 4.1 已有 API(F5 实现) + +| 端点 | 方法 | 说明 | +|------|------|------| +| `/api/projects/{pid}/tasks` | GET | 任务列表(支持 status 筛选) | +| `/api/projects/{pid}/tasks` | POST | 创建任务 | +| `/api/projects/{pid}/tasks/{tid}` | GET | 单任务基本信息 | +| `/api/projects/{pid}/tasks/{tid}` | PATCH | 更新任务 | +| `/api/projects/{pid}/tasks/{tid}/status` | POST | 状态流转 | +| `/api/projects/{pid}/tasks/{tid}/claim` | POST | 认领任务 | +| `/api/projects/{pid}/tasks/{tid}/comments` | GET/POST | 评论 CRUD | +| `/api/projects/{pid}/tasks/{tid}/outputs` | GET/POST | 产出 CRUD | +| `/api/projects/{pid}/tasks/{tid}/decisions` | GET/POST | 决策 CRUD | +| `/api/projects/{pid}/tasks/{tid}/observations` | GET/POST | 观察 CRUD | +| `/api/projects/{pid}/tasks/{tid}/reviews` | GET/POST | 审查 CRUD | +| `/api/events` | GET (SSE) | SSE 事件流 | +| `/api/daemon/status` | GET | Daemon 状态 | +| `/api/projects` | GET/POST | 项目管理 | + +### 4.2 需新增的 API + +#### API-1: `GET /api/projects/{pid}/tasks/{tid}?expand=comments,outputs,reviews,events,decisions` + +**优先级**: P0 +**说明**: 任务详情聚合,一次返回全部关联数据,避免前端多次请求。 +**实现方案**: 在 `blackboard_routes.py` 的 `get_task` 端点增加 `expand` 查询参数。 + +```python +@router.get("/tasks/{task_id}") +async def get_task( + task_id: str, + expand: str = "", # 逗号分隔: "comments,outputs,reviews,events,decisions" + db_path: Path = Depends(get_db_path), +): + task = queries.get_task(db_path, task_id) + if not task: + raise HTTPException(404, "Task not found") + + result = dict(task) + + if expand: + for rel in expand.split(","): + rel = rel.strip() + if rel == "comments": + result["comments"] = queries.list_comments(db_path, task_id) + elif rel == "outputs": + result["outputs"] = queries.list_outputs(db_path, task_id) + elif rel == "reviews": + result["reviews"] = queries.list_reviews(db_path, task_id) + # 聚合 review_status + if result["reviews"]: + latest = result["reviews"][-1] + result["review_status"] = "approved" if latest["verdict"] == "APPROVE" else "rejected" + else: + result["review_status"] = "none" + elif rel == "events": + result["events"] = queries.list_events(db_path, task_id) + # 聚合 latest_event + if result["events"]: + result["latest_event"] = result["events"][-1]["detail"] + elif rel == "decisions": + result["decisions"] = queries.list_decisions(db_path, task_id) + + # 聚合计数 + result["comments_count"] = queries.count_comments(db_path, task_id) + result["outputs_count"] = queries.count_outputs(db_path, task_id) + + return result +``` + +**工作量**: 30min(queries.py 已有基础方法,只需组合) + +#### API-2: `GET /api/projects/{pid}/tasks/{tid}/events` + +**优先级**: P0 +**说明**: 任务事件时间线查询。 +**实现方案**: 新增路由端点。 + +```python +@router.get("/tasks/{task_id}/events") +async def get_task_events( + task_id: str, + limit: int = 50, + offset: int = 0, + db_path: Path = Depends(get_db_path), +): + return queries.list_events(db_path, task_id, limit=limit, offset=offset) +``` + +**前置**: `queries.list_events()` 已存在(events 表 CRUD 已实现)。 +**工作量**: 15min + +#### API-3: `GET /api/projects/{pid}/tasks/{tid}/experiences` + +**优先级**: P1 +**说明**: 查询与任务关联的经验(通过 task_id 检索 experiences.jsonl)。 +**实现方案**: 新增路由端点 + queries 方法。 + +```python +@router.get("/tasks/{task_id}/experiences") +async def get_task_experiences( + task_id: str, + project_id: str, +): + # 从 experiences.jsonl 读取,过滤 source_task == task_id + experiences = experience_store.list_by_task(project_id, task_id) + return experiences +``` + +**工作量**: 30min(需在 experience_store 增加 `list_by_task` 方法) + +### 4.3 已有 API 但需增强 + +| 端点 | 增强 | 说明 | +|------|------|------| +| `GET /api/projects/{pid}/tasks` | 增加 `counts` 返回 | 任务列表 API 返回各状态计数,供统计卡片使用 | +| `GET /api/events` (SSE) | ticker 事件推送 | ticker 状态变更时自动通过 SSE 推送(F17 SSEBroker 已实现,只需在 ticker 中调用 publish) | + +### 4.4 完整路由表 + +``` +/api/projects GET/POST +/api/projects/{pid} GET/DELETE +/api/projects/{pid}/tasks GET/POST +/api/projects/{pid}/tasks/{tid} GET/PATCH/DELETE +/api/projects/{pid}/tasks/{tid}/status POST +/api/projects/{pid}/tasks/{tid}/claim POST +/api/projects/{pid}/tasks/{tid}/comments GET/POST +/api/projects/{pid}/tasks/{tid}/outputs GET/POST +/api/projects/{pid}/tasks/{tid}/decisions GET/POST +/api/projects/{pid}/tasks/{tid}/observations GET/POST +/api/projects/{pid}/tasks/{tid}/reviews GET/POST +/api/projects/{pid}/tasks/{tid}/events GET ← 新增 API-2 +/api/projects/{pid}/tasks/{tid}/experiences GET ← 新增 API-3 +/api/events GET (SSE) +/api/daemon/status GET +``` + +--- + +## 五、实现方案 + +### 5.1 后端改动清单 + +| # | 改动 | 文件 | 优先级 | 工作量 | +|---|------|------|--------|--------| +| B1 | `get_task` 增加 `expand` 参数 | `blackboard_routes.py` + `queries.py` | P0 | 30min | +| B2 | 新增 `GET /tasks/{tid}/events` 路由 | `blackboard_routes.py` | P0 | 15min | +| B3 | 新增 `GET /tasks/{tid}/experiences` 路由 | `blackboard_routes.py` + `experience_store.py` | P1 | 30min | +| B4 | 任务列表 API 返回状态计数 | `blackboard_routes.py` | P1 | 15min | +| B5 | ticker 状态变更推送 SSE | `ticker.py` | P1 | 1h | + +### 5.2 前端改动清单 + +| # | 改动 | 文件 | 优先级 | 工作量 | +|---|------|------|--------|--------| +| F1 | EdictBoard Mock → 对接真实 API | `EdictBoard.tsx` | P0 | 1h | +| F2 | TaskModal Mock → 对接真实 API | `TaskModal.tsx` | P0 | 1.5h | +| F3 | api.ts 补新 API 方法 | `api.ts` | P0 | 30min | +| F4 | store.ts 补状态字段 | `store.ts` | P0 | 15min | +| F5 | SSE EventSource 替代轮询 | `App.tsx` + `store.ts` | P1 | 1h | +| F6 | 通知中心铃铛占位 | `NotificationCenter.tsx` + `App.tsx` | P1 | 30min | +| F7 | AI Briefing 占位页面 | `AIBriefing.tsx` | P2 | 15min | +| F8 | 其余 v1.0 Tab 适配 | 各 Panel 组件 | P1 | 2h | + +### 5.3 执行顺序 + +``` +Phase 1: 后端补 API(B1-B3) ~1.5h +Phase 2: 前端核心对接(F1-F4) ~3h +Phase 3: SSE + 通知占位(B5+F5+F6) ~2.5h +Phase 4: 其余 Tab 适配(F8) ~2h +Phase 5: 联调 + E2E 验证 ~1h +总计: ~10h +``` + +### 5.4 不在本轮范围 + +| 功能 | 原因 | 计划 | +|------|------|------| +| Checkpoint(验证/决策/执行) | 后端无数据模型,需新设计 | v2.7 专项 | +| AI Briefing 生成 | 后端聚合逻辑未设计 | v2.7 专项 | +| Artifact 文件预览/下载 | 后端无文件存储机制 | v2.7 评估 | +| 朝堂议政迁移 | 需要 CourtDiscussion 适配 v2.6 API | v2.7 | + +--- + +## 六、UI 组件文件结构 + +``` +src/frontend/src/ +├── App.tsx # 主布局 + Header + Tab 路由 +├── api.ts # API 层(所有后端调用) +├── store.ts # Zustand 全局状态 +├── types.ts # V2Task 等类型定义(从 EdictBoard 提取) +└── components/ + ├── EdictBoard.tsx # 任务看板(卡片 + 筛选 + 搜索) + ├── TaskModal.tsx # 任务详情(4 Tab) + ├── NotificationCenter.tsx # 通知铃铛(占位) + ├── CourtDiscussion.tsx # 朝堂议政(迁移自 v1.0) + ├── MonitorPanel.tsx # 编排调度(迁移自 v1.0) + ├── OfficialPanel.tsx # 将军总览(迁移自 v1.0) + ├── ModelConfig.tsx # 模型配置(迁移自 v1.0) + ├── SkillsConfig.tsx # 技能配置(迁移自 v1.0) + ├── SessionsPanel.tsx # 传令巡哨(迁移自 v1.0) + ├── MemorialPanel.tsx # 奏折阁(迁移自 v1.0) + ├── SettingsPanel.tsx # 系统设置(迁移自 v1.0) + └── GlobalSearch.tsx # 全局搜索 +``` + +--- + +## 七、评审修订记录 + +### 司马懿 Mail #278 评审 → 修复 + +| 项目 | 级别 | 修复内容 | +|------|------|---------| +| P0: priority 类型 | 必修 | `string` → `number`(1-10),前端 PRIORITY_META 按整数映射 | +| P1: 状态管线 | 必修 | 4 步 → 5 步线性 + blocked 旁路,8 状态全覆盖 | +| P1: VALID_TRANSITIONS | 必修 | 完全对齐后端 `db.py` 的 8×N 映射 | +| P2: 遗漏字段 | 建议 | V2Task 补 `depends_on`, `parent_task` | + +### 司马懿 Mail #279 确认 + +> ✅ P0 priority: number +> ✅ P1 STATUS_ORDER 含 review/blocked +> ✅ P1 TaskModal 状态映射含 reviewing/blocked/cancelled(17 处引用) +> 构建产物 12:37 部署在 8083,前端可访问。 diff --git a/docs/design/v2.7-subtask-model.md b/docs/design/v2.7-subtask-model.md new file mode 100644 index 0000000..0056444 --- /dev/null +++ b/docs/design/v2.7-subtask-model.md @@ -0,0 +1,348 @@ +# v2.7 数据模型设计:Project → Task → SubTask + +> 日期:2026-05-18 +> 版本:v2.1(经 08:02 ~ 10:56 讨论 + 评审修正定稿) +> 作者:庞统 +> 状态:评审通过 +> 司马懿评审:Mail #298(2 必修 + 4 OBS,全部采纳) + +--- + +## 一、设计推导过程 + +### 1.1 起因:Card 层为什么被回滚 + +v2.7 原设计是 `Project → Card → Task` 三级层次结构。经过讨论发现 Card 是一个位置尴尬的中间层: + +| 概念 | 用户视角 | Card 层的位置 | +|------|---------|-------------| +| Project | 仓库/组织(sanguo_quant_live) | ✅ 一致 | +| Task | 用户的一条需求/目标("动量策略v1") | Card 吃掉了 Task 的语义 | +| 原子步骤 | Agent 执行的具体动作(张飞编码) | Card 下的 Task 才是真正的原子步骤 | + +**Card 既不是 Task(用户不会说"帮我建一个 Card"),也不是 SubTask(太粗了),更不是 Campaign(被锁在 Project 内)。** 它是一个多余抽象。 + +正确模型应该是用户自然使用的三层: + +``` +Project(项目/仓库) + └── Task(用户需求/意图/目标) + └── SubTask(Agent 执行的原子任务/Stage) +``` + +### 1.2 SubTask 表的讨论:为什么最终没建 + +**提出**:最初设计新建 `subtasks` 表,包含独立的状态、指派、依赖。 + +**质疑**: +1. **调度意义**:没有 SubTask 表也能调度——当前 Ticker 扫描 Task 就调度了。SubTask 作为调度单位的好处理论上是并行,但 Task 自引用(parent_task)已经能做到。 +2. **和已有设计的重叠**:v2.6 的 Task 表已有 `parent_task` 字段,拆解结果就是子 Task(Task 自引用)。课题4 的 planner.md 已定义了拆解流程(四步+组件库+PlanChecker)。新建 SubTask 表和这些重叠。 +3. **plan_json 也重叠**:已有 planner.md 模板指导庞统拆解,产出是黑板上的子 Task,不需要额外的 plan_json 字段。 + +**结论**:**不建 SubTask 表,复用 Task 自引用(parent_task)。** 子 Task 和父 Task 用同一张表,通过 `parent_task` 字段形成父子关系。 + +### 1.3 跨项目协作的讨论 + +**最初设计**:SubTask 级跨项目(姜维在 vnpy 为 quant_live 的某个 SubTask 工作)。 + +**修正**: +- 跨项目应该在 Task 级,不是 SubTask 级 +- 把 Task 整体信息(outputs、comments、黑板讨论)共享出去,具体用哪部分由 Agent 自己判断 +- 通过 bootstrap 注入实现(build_bootstrap 在 Agent 启动前把依赖 Task 的产出摘要拼装进 prompt) + +**结论**:Task 级跨项目,不改 API,通过 bootstrap 注入。 + +### 1.4 关系模型的讨论 + +任务之间存在三种可能的关系: + +| 关系 | 字段 | 语义 | 例子 | +|------|------|------|------| +| **父子** | `parent_task` | 组成归属——"我是谁的一部分" | 因子研究 **属于** 动量策略v1 | +| **依赖** | `depends_on` | 执行顺序——"我必须等谁完成" | 策略编码 **必须等** 因子研究完成 | +| **引用** | 新字段? | 成果物关联——"我用了谁的产出" | 动量策略v1 **用了** 回测引擎搭建的产出 | + +**背靠背讨论**(Mail #302 → #297): + +司马懿的独立判断: +- **父子**:有必要,提供聚合进度 + 前端分组。Card 回滚后 parent_task 是唯一的分组聚合手段 +- **依赖**:已确定必须保留,调度引擎消费 +- **引用**:当前没必要,没有消费者。和依赖有本质区别(依赖是调度的前置条件,引用是审计的溯源链),但当前审计场景不存在 +- **补充**:第三种关系可能是"模板/实例"(多个策略用相同 stages 流程),但 stages_json 已能表达 + +**最终结论**: + +| 字段 | 保留 | 用途 | +|------|------|------| +| `parent_task` | ✅ | 聚合进度 + 前端分组 | +| `depends_on` | ✅ | 调度顺序 | +| 引用 | ❌ | 未来按需加 `references TEXT` | +| `stages_json` | ✅ 新增 | 动态 Stage 定义 | + +### 1.5 状态的讨论 + +**最初提出** `planning` / `challenging` 两个新状态。 + +**修正**:课题3 已定义了完整的审查流水线(plan_review / output_review / guardrail / final_review),课题4 已定义了拆解流程。不需要额外加这两个状态。 + +**结论**:不加新状态,复用已有机制。 + +### 1.6 前端展示的讨论 + +**Stage 进度条**:和当前的固化 5 阶段(pending→claimed→working→review→done)一个意思,只是变成动态的 stages_json 定义的阶段。 + +**百分比问题**:Stage 是动态的,后续节点还没执行到,百分比不可靠。改用 `■■□□□ 2/5` + 当前 Stage 名。 + +--- + +## 二、最终设计 + +### 2.1 数据模型 + +**不建新表,只改现有 Task 表:** + +```sql +-- 新增字段 +ALTER TABLE tasks ADD COLUMN stages_json TEXT DEFAULT '[]'; +``` + +**stages_json 格式**: + +```json +[ + {"id": "research", "label": "因子研究", "order": 1}, + {"id": "data_prep", "label": "数据准备", "order": 2}, + {"id": "coding", "label": "策略编码", "order": 3}, + {"id": "backtest", "label": "回测验证", "order": 4}, + {"id": "optimize", "label": "参数优化", "order": 5} +] +``` + +**父子关系**(已有字段,启用使用): + +```sql +-- 已存在,不需要 ALTER +parent_task TEXT -- 子 Task 填父 Task 的 id,顶层 Task 为 NULL +``` + +**关系总结**: + +| 字段 | 语义 | 消费者 | +|------|------|--------| +| `parent_task` | 组成归属 | 前端分组、进度聚合 | +| `depends_on` | 执行顺序 | Ticker 调度引擎 | +| `stages_json` | 动态阶段定义(仅顶层 Task) | 前端 Stage 进度展示 | + +### 2.2 父子关系行为规则 + +**子 Task 创建**: +- 庞统拆解时,为每个原子步骤创建子 Task(`parent_task=父Task.id`) +- 子 Task 有自己独立的 `status`、`assignee`、`depends_on`、`stage` +- 子 Task 的 `stage` 字段绑定到父 Task 的 `stages_json` 中的某个 stage id + +**父 Task 状态 = 子 Task 聚合**(v2.8 更新): + +| 父 Task 状态 | 推导规则 | +|-------------|---------| +| `pending` | 所有子 Task 都是 pending,或无子 Task | +| `escalated` | 有子 Task escalated(v2.8 新增,视同 working) | +| `waiting_human` | 有子 Task waiting_human(v2.8/M3 新增,视同 working) | +| `working` | 有子 Task 在 claimed / working | +| `review` | 有子 Task 在 review | +| `done` | 所有子 Task 都是 done | +| `failed` | 有子 Task failed 且无 active 子 Task | +| `blocked` | 有子 Task blocked 且无 active 子 Task | +| `cancelled` | 用户/AI 取消(手动设置,**不参与聚合**) | +| `paused` | 用户暂停(手动设置,**不参与聚合**) | + +**聚合优先级**(v2.8 更新): +escalated > waiting_human > review > working > pending > failed > blocked + +**不参与聚合**:paused, cancelled(手动状态) + +**手动状态保护**:父 Task 被手动设为 cancelled 或 paused 后,聚合不覆盖。 + +**聚合刷新时机**:每次 Ticker tick 时,全量扫描有子 Task 的父 Task,刷新聚合状态。 + +**边界条件**: +1. 全量扫描(当前任务量 <100,性能无忧) +2. 手动状态不覆盖(cancelled 的父 Task 不参与聚合) + +### 2.3 示例数据 + +``` +tasks 表: + +| id | title | parent_task | depends_on | stage | status | assignee | +|-----------|-------------|-------------|---------------|-----------|---------|-----------| +| task-001 | 动量策略v1 | NULL | NULL | NULL | working | NULL | ← 父 Task +| task-002 | 因子研究 | task-001 | [] | research | done | zhangfei | ← 子 Task +| task-003 | 分钟线下载 | task-001 | [] | data_prep | done | zhaoyun | ← 子 Task +| task-004 | 策略编码 | task-001 | [task-002,task-003] | coding | working | zhangfei | ← 子 Task +| task-005 | 回测验证 | task-001 | [task-004] | backtest | pending | NULL | ← 子 Task + +task-001 的 stages_json: +[{"id":"research","label":"因子研究","order":1}, + {"id":"data_prep","label":"数据准备","order":2}, + {"id":"coding","label":"策略编码","order":3}, + {"id":"backtest","label":"回测验证","order":4}, + {"id":"optimize","label":"参数优化","order":5}] +``` + +### 2.4 跨项目协作 + +**方式**:通过 bootstrap 注入(不改 API)。 + +`build_bootstrap()` 在 spawn Agent 前: +1. 查黑板找到依赖 Task 的 outputs 和 comments +2. 拼装到 Agent 的 prompt 里 +3. Agent 开箱就能看到依赖信息,不需要自己跨项目查询 + +### 2.5 Ticker 变更 + +**和 v2.6 基本一致,只加两步**: + +```python +async def _tick_project(self, project_id, project_info): + # 1. 扫描当前状态(不变) + # 2. 依赖推进(不变) + # 3. 僵尸/超时处理(不变) + # 4. 调度 pending 子 Task(不变——和调度普通 Task 一样) + # 5. 调度审查(不变) + # 6. 【新增】聚合父 Task 状态 + # - 全量扫描所有有子 Task 的父 Task + # - 跳过手动状态(cancelled)的父 Task + refresh_parent_task_statuses(db_path) + # 7. 写 daemon_tick 事件(不变) +``` + +**调度逻辑完全不变**——子 Task 和普通 Task 一样被扫描、调度。唯一的变更是 tick 末尾加一步聚合刷新。 + +### 2.6 前端改动 + +#### 2.6.1 Task 看板(EdictBoard.tsx) + +- 任务列表只显示**顶层 Task**(`parent_task IS NULL`) +- Task 卡片新增: + - `■■□□□ 2/5`(已完成子 Task 数 / 总子 Task 数) + - `当前:策略编码`(当前活跃子 Task 的 stage label) + +#### 2.6.2 Task 详情弹窗(TaskModal.tsx) + +- **Stage 进度条**:从 `stages_json` 读取,按子 Task 状态标记每个 Stage(✅ done / 🔄 working / ⬜ pending) +- **子 Task 列表**:按 Stage 分组展示,每个子 Task 显示标题、Agent、状态、产出 +- 和现有的进度条 + 实时动态是一个意思,只是从固化的 5 阶段变成动态 Stage + +#### 2.6.3 Mail Tab + +- 新增 Tab,列表展示 Sanguo Mail +- 后续独立实现 + +#### 2.6.4 不做的 + +- 不做独立的 SubTask 管理页 +- 不做跨 Project 的 Product 视图 +- 不做 Card 看板 + +--- + +## 三、回滚范围 + +### 3.1 代码回滚(开发目录 `~/.openclaw/sanguo_projects/sanguo_moziplus_v2/`) + +| 文件 | 操作 | +|------|------| +| `src/blackboard/models.py` | 删除 Card dataclass + CARD_VALID_STATUSES + CARD_TYPE_SET + CARD_MANUAL_STATUSES | +| `src/blackboard/db.py` | 删除 `_CARDS_SCHEMA`、`_migrate_v27()`;tasks DDL 移除 `card_id`(**保留 `stage`**);保留其他所有内容 | +| `src/blackboard/operations.py` | 删除 CardOps 类全部方法 | +| `src/blackboard/queries.py` | 移除 card_id 过滤参数 | +| `src/api/card_routes.py` | **删除整个文件** | +| `src/api/mail_routes.py` | **删除整个文件**(Mail Tab 延后,后续重新设计后端 API) | +| `src/daemon/ticker.py` | 移除 per-card 扫描逻辑(`_tick_card`、`CardOps` 引用、`from ... import CardOps`),恢复 per-project 直扫 | +| `src/main.py` | 移除 card_routes、mail_routes 注册;移除 v2.7 迁移调用;版本号保持 2.7.0 | +| `tests/test_v27_cards.py` | **删除整个文件** | + +### 3.2 设计文档 + +| 文件 | 操作 | +|------|------| +| `docs/design/v2.7-three-tier-hierarchy.md` | 移至 `docs/design/archive/` | + +### 3.3 保留 + +| 文件/功能 | 原因 | +|-----------|------| +| `registry.db` + ProjectRegistry + 自动发现 | Project 管理需要 | +| per-project `blackboard.db` | 多项目隔离需要 | +| Project API 路由 | 前端项目选择器需要 | +| `docs/design/product-direction-notes.md` | 新增,Product 方向备忘 | + +--- + +## 四、新增内容 + +### 4.1 后端 + +| 序号 | 任务 | 文件 | 说明 | +|------|------|------|------| +| 1 | tasks DDL 加 `stages_json` | `db.py` | `ALTER TABLE tasks ADD COLUMN stages_json TEXT DEFAULT '[]'` | +| 2 | 子 Task 聚合函数 | `operations.py` | `refresh_parent_status(db_path, parent_task_id)` | +| 3 | 按 parent_task 查询 | `queries.py` | `list_subtasks(db_path, parent_task_id)` | +| 4 | Ticker 加聚合刷新 | `ticker.py` | tick 末尾调用聚合函数 | +| 5 | API 路由微调 | `blackboard_routes.py` | Task 详情接口返回子 Task 列表 + Stage 进度 | + +### 4.2 前端 + +| 序号 | 任务 | 文件 | 说明 | +|------|------|------|------| +| 6 | Task 卡片加进度指示 | `EdictBoard.tsx` | `■■□□□ 2/5` + 当前 Stage 名 | +| 7 | Task 详情弹窗改造 | `TaskModal.tsx` | 动态 Stage 进度条 + 子 Task 列表 | +| 8 | store/api 适配 | `store.ts` + `api.ts` | 加载子 Task 数据、Stage 进度 | + +### 4.3 测试 + +| 序号 | 任务 | 文件 | +|------|------|------| +| 9 | 子 Task CRUD + 聚合 | `test_v27_subtasks.py` | +| 10 | 现有测试不回归 | 全量 pytest | + +--- + +## 五、实施顺序 + +| 阶段 | 任务 | 预计 | +|------|------|------| +| **Phase 1:回滚** | 删除 Card 层代码 + 归档设计文档 | 2h | +| **Phase 2:后端** | stages_json + 聚合 + 查询 + Ticker + API | 4h | +| **Phase 3:前端** | 卡片指示器 + 详情弹窗改造 | 4h | +| **Phase 4:测试** | 新测试 + 全量回归 | 2h | +| **Phase 5:评审** | 发司马懿评审 + 修复 | 2h | +| **合计** | | **~14h** | + +--- + +## 六、Product 方向备忘(不实施,记录未来) + +详见 `docs/design/product-direction-notes.md`。 + +核心思路:Product = 跨 Project 的 Task 聚合,多个 Task 的产出物组合成完整产品。和 Project 是正交维度。等有实际场景再建模。 + +--- + +## 七、讨论参与 + +| 时间 | 参与者 | 内容 | +|------|--------|------| +| 08:02 | 用户 | 质疑 Card 层存在必要性,提出 Project → Task → SubTask 三层 | +| 08:07 | 用户 | 提出 Campaign/Product 概念(跨项目 Task 聚合) | +| 08:13 | 用户 | 确认四层:Project → Task → SubTask → Product(未来),决定回滚 Card | +| 08:21 | 用户 | 确认三条:回滚 Card、完善三层、记录 Product 方向 | +| 08:47 | 用户 | 追问 SubTask 表用途、parent_project 归属、plan_json 重叠、状态必要性 | +| 09:55 | 用户 | 确认 Task 级跨项目、不需要 planning/challenging 状态、追问 bootstrap 注入含义 | +| 09:59 | 用户 | 确认进度展示方式,追问 parent_task 含义 | +| 10:00 | 用户 | 追问父子 vs 引用 vs 依赖的业务选择 | +| 10:03 | 用户 | 要求背靠背发给司马懿讨论 | +| 10:10 | 司马懿 | 回复:留父子+依赖,砍引用,补充模板/实例关系 | +| 10:50 | 用户 | 确认最终方案,要求更新设计文档并统一发评审 | +| 10:56 | 司马懿 | 评审:2 必修 + 4 OBS,方向正确 | +| 10:56+ | 庞统 | 全部采纳:claimed 归入 working、聚合优先级、手动状态保护、stage 保留、Mail 延后 | diff --git a/docs/design/v2.7.1-mail-envelope-separation.md b/docs/design/v2.7.1-mail-envelope-separation.md new file mode 100644 index 0000000..c26dca0 --- /dev/null +++ b/docs/design/v2.7.1-mail-envelope-separation.md @@ -0,0 +1,172 @@ +# v2.7.1 Mail 信封/载荷分离优化 + +**版本**: v1.0 +**日期**: 2026-05-24 +**作者**: 庞统 +**状态**: 实施中(评审通过,用户确认) + +--- + +## 1. 背景 + +v2.7.0 Mail 飞鸽传书功能已稳定上线。实测"庞统发 request 给赵云"一封来回邮件: + +- 庞统侧 6 次 LLM 调用(input 1,702 + output 430) +- 赵云侧 4 次 LLM 调用(input 12,528 + output 277) +- 合计 10 次 LLM 调用,~14,937 tokens(不含 cache) + +**核心问题**:Agent 同时管信封(状态流转)和载荷(业务内容)。状态转换命令(claimed→working→done)占用 LLM tokens,且不可靠。 + +## 2. 业界调研 + +| 系统 | 信封(系统管) | 载荷(Agent/LLM 管) | +|------|--------------|---------------------| +| **Hermes Kanban** | Dispatcher 负责 claim → spawn → reclaim → 状态流转;Agent 通过 `kanban_complete()` 声明完成,状态写入由系统工具执行 | Agent 调 `kanban_show()` 读任务、调 `kanban_heartbeat()` 保活、调 `kanban_complete(summary)` 声明完成 | +| **MCP Agent Mail** | 系统管投递、线程聚合、消息路由 | Agent 调 `fetch_inbox()` + `acknowledge_message()` 读内容和 ack | +| **ClawTeam Inbox** | JSON 文件即消息,系统管 claim → ack 原子操作 | Agent 读文件内容、执行业务、写产出文件 | +| **EIP 信封模式** | Header(from/to/type/routing)由消息基础设施处理 | Body(业务内容)由接收方处理 | + +**共识**:状态流转是信封,属于系统。业务内容是载荷,属于 Agent。 + +## 3. 设计原则 + +1. **系统管信封**:claimed → working → done/failed 全部由系统完成 +2. **Agent 管载荷**:只看业务内容,只做业务决策(回复/不回复/触发行动) +3. **统一超时机制**:Mail 任务走标准 task 超时路径(`default_task_timeout_minutes`),不开特殊路径 +4. **产出验证**:request 类型邮件验证 Agent 是否真的回复了(幻觉门控) + +## 4. 新生命周期 + +### 4.1 正常流 + +``` +系统(Dispatcher) Agent(Main Session) +───────────── ───────────── +1. 邮件创建(Mail API) +2. Dispatcher 路由到目标 Agent +3. [新增] 系统标 working → 5. 收到精简 prompt +4. spawn Agent → 6. LLM 理解内容 + → 7. [inform] 只读,无需操作 + → [request] 回复发件者(curl) + → 8. Agent turn 结束 +9. on_complete 触发(进程退出) +10. [新增] 系统标 done/failed: + - inform → 直接标 done + - request → 幻觉门控验证后标 done/failed +``` + +### 4.2 异常流 + +| 异常 | 处理方 | 方式 | 现有机制? | +|------|--------|------|-----------| +| spawn 失败 | 系统 | 标 failed(reason: spawn_failed),不标 working | ❌ 需新增 | +| Agent 执行超时/崩溃 | 系统 | Ticker `_check_timeouts`:working 超 `default_task_timeout_minutes` 标 failed | ✅ 已有 | +| Agent 忘记回复(request) | 系统 | 幻觉门控:on_complete 时查 DB 有无 in_reply_to 的回复邮件 | ❌ 需新增 | +| Agent 回复 type 写错 | prompt | 模板提醒 | ✅ 已有 | +| on_complete 标 done API 失败 | 系统 | 重试 3 次 + 写日志 | ❌ 需新增 | + +### 4.3 on_complete 的触发时机 + +on_complete 在 openclaw 子进程退出时触发(spawner `_handle_exit`)。子进程的生命周期是: + +``` +moziplus spawn openclaw 子进程 + → 子进程调 Gateway API 投递消息到 Agent session + → Agent main session 收到 prompt + → LLM 生成回复 + → turn 结束 + → openclaw 子进程退出 + → spawner 检测退出 → on_complete 触发 +``` + +**on_complete 触发 = Agent 已处理完这封邮件(turn 结束)**。 + +如果 Agent spawn 了子任务继续干,on_complete 仍会触发(主 turn 结束 ≠ 子任务完成)。这是合理的——邮件投递 = 信息传递到位,不等后续衍生工作。 + +### 4.4 超时机制 + +Mail 任务走标准 task 超时路径,不做特殊处理: + +``` +Ticker 每 30s 扫一次 working 状态任务 + → 有 task.deadline?→ timeout = deadline - started_at + → 无 deadline?→ timeout = default_task_timeout_minutes(30 分钟) + → elapsed > timeout → 标 failed +``` + +## 5. Prompt 模板 + +### 5.1 inform 模板(~80 tokens,当前 ~200 tokens) + +``` +你收到一封飞鸽传书(纯通知)。 + +发件者: {from_agent} +主题: {title} +内容: {text} + +已阅即可,无需操作。 +``` + +### 5.2 request 模板(~150 tokens,当前 ~400 tokens) + +``` +你收到一封飞鸽传书,需要处理并回复。 + +发件者: {from_agent} +主题: {title} +内容: {text} + +请处理后回复发件者: +curl -s -X POST http://localhost:8083/api/mail \ + -H 'Content-Type: application/json' \ + -d '{"from": "{agent_id}", "to": "{from_agent}", "title": "回复: {title}", "text": "你的回复内容", "type": "inform", "in_reply_to": "{task_id}"}' + +⚠️ 将"你的回复内容"替换为实际回复。type 必须用 inform 防止循环。 +``` + +**变化**:去掉所有状态转换命令(working/done 的 curl),Agent 只看业务内容。 + +## 6. 幻觉门控 + +**来源**:Hermes v0.13 实践——Agent 声称完成时验证产出是否真实存在。 + +**Mail 场景**:request 类型邮件 on_complete 时,系统查 `_mail` DB 有无 `in_reply_to = task_id` 的回复邮件记录: + +``` +on_complete 触发 + → _classify_outcome 判断 outcome + → outcome 正常(release_counter) + → inform → 直接标 done + → request → 查 DB 有无回复邮件 + → 有 → 标 done + → 无 → 标 failed(reason: no_reply_found) +``` + +inform 类型不需要门控——不需要回复,on_complete 触发即完成。 + +## 7. 改动范围 + +| 文件 | 改动 | 行数 | 说明 | +|------|------|------|------| +| `src/daemon/spawner.py` | 精简两个模板常量 | 改 ~40 行 | 去掉 working/done curl 命令 | +| `src/daemon/dispatcher.py` | spawn 前标 working + on_complete 增强 + 幻觉门控 | 新增 ~50 行 | `_mail` 专用的 on_complete 回调 | +| `src/daemon/ticker.py` | 不改 | 0 | 走标准超时机制 | + +总计 ~90 行改动,涉及 2 个文件。 + +## 8. 预期效果 + +| 场景 | 当前 v2.7.0 | 优化后 v2.7.1 | 说明 | +|------|------------|-------------|------| +| inform 邮件(Agent 侧) | 4 次 LLM, ~13k tokens | **1 次 LLM** | Agent 读内容后无操作,系统标 done | +| request 邮件(Agent 侧) | 4 次 LLM, ~13k tokens | **2 次 LLM** | Agent 读+回复,系统标 done | +| inform prompt | ~200 tokens | **~80 tokens** | 省掉状态转换命令 | +| request prompt | ~400 tokens | **~150 tokens** | 省掉状态转换命令 | +| 状态可靠性 | 依赖 LLM 遵守 | **系统保证** | 最大收益 | + +## 9. 不做的事 + +- ❌ isolated session 投递(必须是主 Agent 来决定是否 spawn) +- ❌ Mail 专用超时配置(走标准 task 超时路径) +- ❌ inform 类型跳过 LLM(Agent 需要读内容,信息不能丢) diff --git a/docs/design/v2.7.2-counter-lifecycle-fix.md b/docs/design/v2.7.2-counter-lifecycle-fix.md new file mode 100644 index 0000000..3c7bc37 --- /dev/null +++ b/docs/design/v2.7.2-counter-lifecycle-fix.md @@ -0,0 +1,173 @@ +# v2.7.2 防重复调用 & 防无限续杯 — 完整方案 + +**版本**: v2.0 +**日期**: 2026-05-26 +**作者**: 庞统 +**状态**: 已实现 + 待最终评审 + +--- + +## 1. 核心原则 + +> **每次 agent 调用都是独占的。** openclaw 无论成功失败都会返回,最差情况 timeout 返回。谁占用谁持有,进程退出就 release。 + +--- + +## 2. 根因分析 + +### 2.1 2026-05-25/26 事件复盘 + +司马懿 + 庞统被 moziplus-v2 连续 spawn,叠加 API 调用触发 zhipu 429,双模型不可用导致 Gateway 假死。 + +### 2.2 发现的三个根因 + +| 根因 | 影响 | 严重度 | +|------|------|--------| +| **P0:`_parse_stdout_json` 解析路径错误** | `data.get("meta")` 应为 `data["response"]["meta"]`。68% 的 spawn 结果 transport=null(62/91 次)。A 场景分类全部失效 | **致命** | +| counter 生命周期是任务级 | retry 绕过 dispatcher 直接 spawn,不检查 counter | 高 | +| spawn 前缺 session state 检查 | webchat 占用 main session 时仍然 spawn,注定失败 | 高 | + +### 2.3 P0 详细说明 + +openclaw agent `--json` 输出格式: +```json +{ "kind": "agent-response", "response": { "meta": { "transport": "gateway", ... } } } +``` + +代码取的是 `data.get("meta")` 而不是 `data["response"]["meta"]`,导致: +- 所有 transport=null → A1/A5/A6 分类失效 +- session lock 阻塞退出被误判为 gateway_timeout(A2/A3)→ 续杯循环 +- 续杯循环叠加 API 调用 → 429 → Gateway 假死 + +--- + +## 3. 完整场景清单 + +### 3.1 Spawn 前检查(拦截无效 spawn) + +| # | 场景 | 检测方法 | 检测到后方案 | +|---|------|---------|-------------| +| L1 | moziplus 内部并发 | counter.can_acquire() | AgentBusyError → 等 ticker | +| L2 | API 429 冷却期 | counter.is_cooling_down() | AgentBusyError → 等 ticker | +| L3a | main session 被外部占用 | _check_session_state → lock_pid_alive | AgentBusyError → 等 ticker | +| L3b | main session 正在执行 | _check_session_state → status=processing | AgentBusyError → 等 ticker | +| L3c | main session 正在 compact | _check_session_state → recent_compact | AgentBusyError → 等 ticker | + +L1-L3 统一结果:不 spawn,任务保持 working,ticker 30 秒后重新调度。 + +L1+L3 互补:counter 防 moziplus 内部并发,session state 防外部占用(webchat/Control UI/cron)。 + +### 3.2 Spawn 后 — 进程退出(情况 A,0-630 秒内) + +| # | 场景 | 检测方法 | 条件 | 检测到后方案 | +|---|------|---------|------|-------------| +| A1 | 正常完成 | stdout transport + 任务状态 | exit=0 + transport≠embedded + 终态 | release counter → 结束 | +| A2/A3 | Gateway timeout | stdout transport + 任务状态 | exit=0 + 非终态 + transport 正常 | release counter → 续杯 | +| A4 | Agent 自标 failed | 任务状态 | 任务=failed | release counter → 结束 | +| A5/A6 | Gateway fallback | stdout transport=embedded + fallbackReason | exit=0 + transport=embedded | release counter → 标 failed + escalate | +| A7 | 认证失败 | exit≠0 + stderr | exit≠0 + stderr 含 401/403 | release counter → 标 failed + escalate | +| A8 | Gateway 不可达 | exit≠0 + stderr | exit≠0 + stderr 含 ECONNREFUSED/ETIMEDOUT | release counter → 等 ticker | +| A9 | API 429 | exit≠0 + stderr | exit≠0 + stderr 含 rate_limit/500/503 | release counter → 推回 pending + 冷却 120s | +| A10 | Compact 失败 | exit≠0 + stderr | exit≠0 + stderr 含 compaction-diag | release counter → 等 ticker | +| A11 | Session lock 冲突 | exit≠0 + stderr | exit≠0 + stderr 含 lock/busy/concurrent | release counter → 等 ticker | +| A12 | 兜底未知错误 | 兜底 | exit≠0 不匹配以上 | release counter → 等 ticker | +| A2+P2 | transport=null 兜底 | stderr 辅助判断 | exit=0 + transport=null + 非终态 | 检查 stderr → lock/compact/api_error 或走 gateway_timeout | + +**续杯:只有 A2/A3 才触发续杯。其他都 release counter,等 ticker 或推回 pending。** + +### 3.3 Spawn 后 — 进程不退出(情况 B,630 秒后) + +| # | 场景 | 检测方法 | 条件 | 检测到后方案 | +|---|------|---------|------|-------------| +| B1 | 假死(lock PID 死了) | _check_session_state | lock_pid 存在但 PID 不存活 | 标 failed + escalate + release counter | +| B2 | Compact 进行中 | _check_session_state + stderr | lock_pid 存活 + stderr 有 compact | 继续等(不递增计数,独立上限) | +| B3 | 进程不退出 | _check_session_state | lock_pid 存活 + 无 compact | 继续等(递增计数,≥3 次 → failed) | +| B4 | Session 状态不匹配 | _check_session_state | sessions.json status≠running | 等 60s → 按 B3 处理 | + +### 3.4 续杯限制 + +| # | 计数器 | 上限 | 超限后方案 | +|---|--------|------|-----------| +| R1 | retry_count | 3 次(每次 ~10 分钟) | 标 failed + escalate | +| R2 | connect_retry_count | 3 次 | 标 failed | +| R3 | api_retry_count | 3 次 | 标 failed | +| R4 | lock_retry_count | 3 次 | 标 failed | +| R5 | monitor_timeout_count | 3 次 | 标 failed | + +### 3.5 Ticker 兜底(每 30 秒检查) + +| # | 检查 | 方案 | +|---|------|------| +| T1 | 进程存活性:counter 占用但 PID 不存活 | release counter + 推回 pending | +| T2 | 任务超时:working 超时(默认 30 分钟) | 标 failed | + +### 3.6 假死复活术(2026-05-04 经验) + +**现象**:sessions.json 状态为 running 但 agent 长时间无响应。 + +**根因**:Gateway 认为 session 还活着(running)但实际连接已断开,无超时清理。 + +**复活步骤**: +1. 修改 sessions.json,把对应 session 的 status 从 running 改为 idle +2. 发心跳激活(把当前任务再发一遍给 agent) + +**待设计**:将此复活术集成到 ticker `_check_timeouts` 中,检测到 sessions.json status=running 但 lock PID 不存活时自动执行。 + +--- + +## 4. 改动文件清单 + +| 文件 | 改动 | 版本 | +|------|------|------| +| `counter.py` | 新增 cooldown 机制(is_cooling_down / set_cooldown) | v1 | +| `spawner.py` | 新增 AgentBusyError | v1 | +| `spawner.py` | spawn_full_agent 内部 counter acquire/release + wrapped_on_complete(try/finally) | v1 | +| `spawner.py` | spawn_full_agent 加 L3 session state 检查 | v2 | +| `spawner.py` | _classify_outcome 去掉 release_counter,只有 A2/A3 触发 retry | v1 | +| `spawner.py` | _classify_outcome 加 P2 transport=null 兜底 | v2 | +| `spawner.py` | _handle_exit:A9 推回 pending + 冷却,A5/A6 标 failed + context 日志 | v1 | +| `spawner.py` | _do_retry 手动 release counter + 通过 spawn_full_agent 重试 | v1 | +| `spawner.py` | P0:_parse_stdout_json 改为 data["response"]["meta"] | v2 | +| `spawner.py` | 新增 get_session_by_agent(进程存活性检查用) | v1 | +| `dispatcher.py` | 去掉 counter acquire/release,on_complete 只含业务逻辑 | v1 | +| `ticker.py` | _spawn_available_agents 不再管 counter | v1 | +| `ticker.py` | _check_timeouts 加进程存活性检查 + 推回 pending | v1 | +| `ticker.py` | 新增 _is_pid_alive | v1 | +| `main.py` | counter 创建提前,传给 spawner | v1 | + +--- + +## 5. 司马懿评审记录 + +### 第一次评审(mail-1779726169654) + +| 评审意见 | 结论 | 理由 | +|---------|------|------| +| wrapped_on_complete 加 try/finally | ✅ 采纳 | 防御性编程 | +| A5/A6 加 context 日志 | ✅ 采纳 | 排查方便 | +| per-provider 冷却 | ⏭ 延后 | 低优先级 | +| crash_count per-agent 累计,禁用 agent | ❌ 不采纳 | 崩溃可能是任务问题不是 agent 问题 | +| can_acquire 失败推回 claimed | ❌ 不采纳 | asyncio 单线程无竞态 | +| release 和 acquire 之间有竞态窗口 | ❌ 不存在 | 内存同步操作 | + +### 第二次评审(_do_retry counter 时序) + +| 评审意见 | 结论 | +|---------|------| +| _do_retry 续杯退化为 AgentBusyError | ✅ 采纳:_do_retry 入口手动 release counter | + +### 第三次评审(P0/P1/P2 补丁) + +| 评审意见 | 结论 | +|---------|------| +| P0 stdout 解析路径修复 | 待评审 | +| P1 spawn 前检查 | 待评审 | +| P2 transport=null 兜底 | 待评审 | + +--- + +## 6. 待办 + +- [ ] 假死复活术集成到 ticker +- [ ] 历史数据清理(P0 修复前 62 条 transport=null 的 task_attempts) +- [ ] per-provider 冷却(低优先级) diff --git a/docs/design/v2.8-changelog.md b/docs/design/v2.8-changelog.md new file mode 100644 index 0000000..016715e --- /dev/null +++ b/docs/design/v2.8-changelog.md @@ -0,0 +1,59 @@ +# v2.8+ 变更记录 + +> 日期:2026-05-20 +> 作者:庞统 + +## 项目下拉改造 + +### 项目发现 +- 自动扫描 `~/.openclaw/sanguo_projects/sanguo_*` 目录(`discover_sanguo_projects()`) +- 启动时扫描 + 注册到 registry(`source: sanguo_projects_scan`) +- 下拉菜单:📋全部任务 / 📝一般任务(`_general`)/ 正式项目 / ➕新建 + +### 项目 CRUD +| 操作 | API | 说明 | +|------|-----|------| +| 自动发现 | 启动时自动 | 扫描 sanguo_projects 目录 | +| 手动创建 | `POST /api/projects` | 填写 id + name | +| 归档 | `POST /api/projects/{id}/archive` | status→archived,不显示 | +| 删除 | `DELETE /api/projects/{id}` | status→deleted(逻辑删除,不物理删) | +| 修改 | `PATCH /api/projects/{id}` | 更新 name/description | +| 移动任务 | `POST /api/projects/{id}/tasks/{tid}/move` | 跨项目移动任务 | + +### 项目状态 +- `active` → 正常显示 +- `archived` → 不显示(归档) +- `deleted` → 不显示(逻辑删除) +- 两个状态独立,不重叠 + +### 特殊项目 +| ID | 用途 | 显示 | +|----|------|------| +| `_general` | 一般任务(无归属) | 下拉菜单 + 看板 | +| `_mail` | 邮件系统 | 独立 Tab | + +## 轮询 → SSE 改造 + +### 改造前(轮询) +- 每 5-30 秒 `loadAll()` → `loadLive()` + `loadProjects()` + `loadV2Tasks()` +- 全量替换 `v2tasks` 数组 → 卡片全部重渲染 → 闪屏 + 输入失焦 + +### 改造后(SSE + 轻量轮询) +- **轮询**:只刷 `loadLive()`(ticker 数据),10-30 秒间隔 +- **SSE**:监听 `/api/events`,收到 `task_updated`/`task_completed`/`task_failed` 时只更新单个任务 +- **任务列表**:首次加载 / 切项目 / 手动刷新时才全量拉 +- **输入保护**:有输入焦点时跳过轮询,blur 2s 后补刷 + +## 铃铛通知中心 + +### 对接设计文档(topic7-9) +- 按推送级别分组(🔴🟡🟢🔵) +- 每条通知:时间 + 级别图标 + 内容摘要 + 关联任务链接 +- 展开操作按钮(查看任务 / 标记已读) +- 全部已读 +- 数据来源:未读邮件 + SSE 实时事件 + +## TaskModal 项目归属选择器 + +- 任务详情页显示当前所属项目 +- 下拉选择可移动任务到其他项目(调用 move API) diff --git a/docs/design/v2.8-state-enhancement.md b/docs/design/v2.8-state-enhancement.md new file mode 100644 index 0000000..1131643 --- /dev/null +++ b/docs/design/v2.8-state-enhancement.md @@ -0,0 +1,432 @@ +# v2.8 + M3 完整设计:状态增强 + 前端易用性 + Checkpoint + Artifact + +> 状态: ✅ 评审通过(Mail #303/#304/#305),待用户确认 +> 日期: 2026-05-18 +> 作者: 庞统 +> 评审: 司马懿 + +--- + +## 📋 概述 + +### 背景 +v2.7 已完成 Card 回滚、SubTask 聚合、Mail Tab。本设计覆盖 v2.8 状态增强 + 前端易用性 + M3 Checkpoint/Artifact。 + +### 问题 +1. v2.7 状态机缺少 `paused`、`escalated`、`waiting_human`(v1.0 已有) +2. 前端缺少已取消/已归档筛选,无法管理已完成任务 +3. 卡片没有快捷操作按钮,每次操作需点进 Modal +4. 项目切换在 header,不在筛选栏 +5. Checkpoint 人工干预和成果物展示是 M3 核心功能,依赖 `waiting_human` 状态 + +### 目标 +1. 补齐 3 个缺失状态 +2. 归档机制 + 前端两行筛选栏 + 卡片快捷按钮 +3. 项目下拉移入筛选栏 + 跨项目聚合 +4. Checkpoint 三种类型 + Artifact 成果物面板 +5. 新建军令 title 可选 + +--- + +## 一、状态机增强 + +### 1.1 新增状态定义 + +| 状态 | 中文 | 颜色 | 类型 | 说明 | +|------|------|------|------|------| +| `paused` | 已暂停 | `#818cf8` 紫 | 手动 | 用户主动暂停,不参与聚合、不超时 | +| `escalated` | 已升级 | `#ff5270` 红 | 手动 | Agent 升级求助,需人工介入 | +| `waiting_human` | 等待人工 | `#f59e0b` 橙 | 中间态 | Checkpoint 等人工确认后推进 | + +### 1.2 完整状态转换表(共 11 个状态) + +``` +pending: [claimed, cancelled] +claimed: [working, paused, pending, cancelled] +working: [review, blocked, failed, paused, escalated, waiting_human, cancelled] +paused: [working, cancelled] +review: [done, pending, failed, escalated, waiting_human, cancelled] +blocked: [pending, escalated, cancelled] +failed: [pending, escalated, cancelled] +escalated: [working, pending, cancelled] +waiting_human: [working, done, cancelled] +done: [] +cancelled: [] +``` + +### 1.3 各状态设计细节 + +#### paused(已暂停) +- **触发**:用户点暂停按钮(卡片右上角 ⏸) +- **恢复**:用户点 ▶ 继续 → 回到 working +- **调度**:Ticker 跳过(不 claim、不超时回收) +- **聚合**:不参与父 Task 聚合(同 cancelled) +- **超时**:无,无限等待 + +#### escalated(已升级) +- **触发**:Agent 遇到无法处理的问题,通过 status API 设置 +- **恢复**:用户介入后手动恢复到 working 或 pending +- **调度**:Ticker 跳过 +- **聚合**:视同 working 参与(表示活跃但需关注) +- **字段迁移**:废弃 `task.escalated` 布尔字段判断,改用 `task.status === 'escalated'`。保留 DB 字段做向后兼容 +- **旧数据处理**:旧 DB 中 `escalated=1` 但 `status=working` 的任务,按 working 继续调度,可接受 +- **UI**:卡片红色高亮 + ⚠️ 图标 + +#### waiting_human(等待人工) +- **触发**:Agent 执行到 Checkpoint,写 checkpoint 数据 + 设置此状态 +- **推进**:人工 approve → working/done;reject → working(带回退信息) +- **调度**:Ticker 跳过(等人工不等超时) +- **聚合**:视同 working 参与 +- **UI**:卡片橙色高亮 + 🔔 图标 + Checkpoint 面板入口 + +### 1.4 聚合规则 + +``` +父 Task 聚合优先级(高→低): + escalated > waiting_human > review > working/claimed > pending > failed > blocked + +不参与聚合: + paused, cancelled(手动/被动状态) + +全部 done → done +``` + +### 1.5 Ticker 调度规则 + +| 状态 | claim | 超时回收 | review 调度 | +|------|-------|---------|------------| +| pending | ✅ | - | - | +| claimed | - | ✅(超时→pending) | - | +| working | - | ✅(超时→failed) | - | +| review | - | - | ✅ | +| paused | ❌ | ❌ | ❌ | +| escalated | ❌ | ❌ | ❌ | +| waiting_human | ❌ | ❌ | ❌ | +| done | 终态 | | | +| failed | ❌ | | | +| blocked | ❌ | | | +| cancelled | 终态 | | | + +--- + +## 二、归档机制 + +### 2.1 后端 + +- `tasks` 表新增:`archived INTEGER DEFAULT 0`、`archived_at TEXT` +- `Task` dataclass 新增:`archived: int = 0`、`archived_at: Optional[str] = None` +- `Blackboard` 新增方法: + - `archive_task(task_id)` → archived=1 + - `unarchive_task(task_id)` → archived=0 + - `archive_all_done()` → 批量归档当前项目 done + cancelled 的任务(单项目操作) +- API: + - `POST /api/projects/{pid}/tasks/{tid}/archive` — 归档/取消归档 + - `POST /api/projects/{pid}/tasks/archive-done` — 一键归档 +- 列表查询 API 默认 `?archived=0` + +### 2.2 前端 + +- 第 1 行:活跃/归档/全部 切换 +- 一键归档按钮(有可归档任务时显示) +- 卡片上 done/cancelled 显示归档按钮 + +### 2.3 Schema 迁移(db.py) + +```sql +-- 新增字段 +ALTER TABLE tasks ADD COLUMN archived INTEGER DEFAULT 0; +ALTER TABLE tasks ADD COLUMN archived_at TEXT; + +-- 更新 CHECK 约束(重建 tasks 表或用新连接 pragma) +-- 完整 CHECK: status IN ('pending','claimed','working','review','done','failed','blocked','cancelled','paused','escalated','waiting_human') +``` + +--- + +## 三、前端易用性改造 + +### 3.1 筛选栏两行布局(仿 v1.0) + +**第 1 行(项目 + 归档控制)**: +``` +[📦 项目▾] [活跃] [归档] [全部] 活跃12 · 归档5 · 共17 [📦 一键归档] +``` +- 项目下拉:`📋 全部任务`("")→ `📁 一般任务`("projects")→ 动态项目列表 +- 全部任务:聚合所有项目的 tasks + +**第 2 行(状态筛选 + 搜索)**: +``` +[📋全部] [📋待认领] [👤已认领] [⚔️执行中] [🔍审查中] [⏸已暂停] [⚠️已升级] [🔔等人工] [✅已完成] [❌失败] [🚧阻塞] [🚫已取消] 🔍[搜索框] +``` + +### 3.2 卡片改造 + +**右上角状态图标**: +- working/claimed → `⏸` 暂停按钮 +- paused → `▶` 恢复按钮 +- escalated → `⚠️` 图标 +- waiting_human → `🔔` 图标 + +**卡片按钮(最多 3 个)**: + +| 状态 | 卡片按钮 | +|------|---------| +| pending | `👤 认领` `🚫` | +| claimed | `⚔️ 开始` `🚫` | +| working | `🔍 提审` `⏸` `🚫` | +| paused | `▶ 继续` `🚫` | +| review | `✅ 通过` `🚫` | +| escalated | `▶ 继续` `🚫` | +| waiting_human | `✅ 确认` `🚫` | +| done | `📦 归档` | +| failed | `🔄 重试` | +| blocked | `🔄 解除` `🚫` | +| cancelled | `📦 归档` | + +**Modal 完整按钮(最多 4 个)**: + +| 状态 | Modal 按钮 | +|------|-----------| +| pending | `👤 认领` `🚫 取消` | +| claimed | `⚔️ 开始` `⏸ 暂停` `🚫 取消` | +| working | `🔍 提审` `⏸ 暂停` `⚠️ 升级` `🚫 取消` | +| paused | `▶ 继续` `🚫 取消` | +| review | `✅ 通过` `🔄 打回` `❌ 驳回` `⚠️ 升级` | +| escalated | `▶ 继续` `🔄 重置` `🚫 取消` | +| waiting_human | `✅ 确认` `❌ 驳回` | +| done | `📦 归档` | +| failed | `🔄 重试` `⚠️ 升级` `🚫 取消` | +| blocked | `🔄 解除` `⚠️ 升级` `🚫 取消` | +| cancelled | `📦 归档` | + +- 点击标题区域打开 Modal +- 动作按钮直接调 API + toast 反馈 + +### 3.3 新建军令 title 自动生成 + +- 前端 title 可选,placeholder "不填将自动生成" +- 后端 title 为空时:description 前 30 字 + "…" + +--- + +## 四、Checkpoint 机制(M3) + +### 4.1 概念 + +Agent 执行到需要人工确认的节点时,创建 Checkpoint 并设置 `waiting_human` 状态。用户在前端看到 Checkpoint 面板,审核后任务继续。 + +### 4.2 三种 Checkpoint 类型 + +| 类型 | 图标 | 主色 | 场景 | 核心交互 | +|------|------|------|------|----------| +| **验证** | 🔍 | `#6a9eff` 蓝 | 产出物确认、效果验收 | 自动核验 + 人工核验 + 双按钮审批 | +| **决策** | 🎯 | `#818cf8` 紫 | 多方案定夺、技术选型 | 三方案并排 + 利弊 + 推荐 + 确认 | +| **执行** | 🔧 | `#f59e0b` 橙 | 人工操作、环境变更 | 分步打勾 + 命令行 + 进度条 | + +### 4.3 approve/reject 状态推进规则 + +| Checkpoint 类型 | approve → | reject → | +|----------------|-----------|----------| +| verify | **done** | working(带回退信息) | +| decision | working(Agent 按选定方案继续) | working(带驳回信息) | +| action | working(Agent 继续后续步骤) | working(带失败信息) | + +**统一规则**:verify approve → done(验证通过即完成),其余 approve → working。reject 一律 → working。 + +### 4.4 后端数据模型 + +#### checkpoints 表 + +```sql +CREATE TABLE checkpoints ( + id TEXT PRIMARY KEY, + task_id TEXT NOT NULL, + type TEXT NOT NULL CHECK (type IN ('verify', 'decision', 'action')), + title TEXT NOT NULL, + description TEXT, + status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')), + payload TEXT NOT NULL, -- JSON,含 version 字段 + created_at TEXT NOT NULL DEFAULT (datetime('now')), + resolved_at TEXT, + resolved_by TEXT, + resolve_note TEXT, + FOREIGN KEY (task_id) REFERENCES tasks(id) +); +``` + +#### payload 结构(version: 1) + +**验证(verify)**: +```json +{ + "version": 1, + "auto_checks": [{"label": "测试通过", "passed": true}], + "human_steps": ["确认代码风格", "确认功能正确"], + "preview_url": "http://..." +} +``` + +**决策(decision)**: +```json +{ + "version": 1, + "options": [ + {"id": "opt1", "label": "方案A", "desc": "...", "pros": ["..."], "cons": ["..."], "recommended": true}, + {"id": "opt2", "label": "方案B", "desc": "...", "pros": ["..."], "cons": ["..."]}, + {"id": "opt3", "label": "方案C", "desc": "...", "pros": ["..."], "cons": ["..."]} + ] +} +``` + +**执行(action)**: +```json +{ + "version": 1, + "steps": [ + {"id": "s1", "desc": "备份数据库", "command": "mysqldump ..."}, + {"id": "s2", "desc": "执行迁移", "command": "python migrate.py"} + ], + "verify_command": "python check.py" +} +``` + +### 4.5 API + +``` +GET /api/projects/{pid}/tasks/{tid}/checkpoints — 列出 task 的所有 checkpoint +POST /api/projects/{pid}/tasks/{tid}/checkpoints — Agent 创建 checkpoint +POST /api/projects/{pid}/tasks/{tid}/checkpoints/{cid}/approve — 用户通过 +POST /api/projects/{pid}/tasks/{tid}/checkpoints/{cid}/reject — 用户驳回 +``` + +approve/reject 时后端自动将 task 从 `waiting_human` 推进: +- approve: verify → done, decision/action → working +- reject: 一律 → working + +### 4.6 Agent 触发流程 + +``` +1. Agent 执行到需要人工确认的节点 +2. Agent 调 POST /checkpoints 创建 checkpoint(type + payload) +3. Agent 调 POST /tasks/{id}/status 设置 waiting_human +4. Ticker 检测到 waiting_human → 跳过调度 +5. 前端轮询检测到 waiting_human + checkpoint → 渲染 CheckpointPanel +6. 用户 approve/reject +7. 后端自动推进状态 → working/done +8. Ticker 下次 tick 继续调度 +``` + +### 4.7 前端 CheckpointPanel 组件 + +文件已存在:`CheckpointPanel.tsx`(350 行),未被引用。**扩展不重写**,对接 v2.8 新 API。 + +在 TaskModal 中当 `status=waiting_human` 时自动显示 CheckpointPanel: +- **验证面板**:自动核验结果 ✅/❌ + 人工核验步骤 + 预览链接 + 双按钮 +- **决策面板**:三列方案卡片 + 利弊 + 推荐 + 御批备注 + 确认 +- **执行面板**:步骤列表(可打勾)+ 命令行高亮 + 进度条 + 验证确认 + +--- + +## 五、Artifact 成果物面板(M3) + +### 5.1 后端 + +扩展现有 `outputs` 表: + +```sql +ALTER TABLE outputs ADD COLUMN file_name TEXT; +ALTER TABLE outputs ADD COLUMN file_size INTEGER; +ALTER TABLE outputs ADD COLUMN file_path TEXT; -- NAS/本地路径 +ALTER TABLE outputs ADD COLUMN mime_type TEXT; +``` + +API: +``` +GET /api/projects/{pid}/tasks/{tid}/outputs — 列出成果物 +GET /api/projects/{pid}/tasks/{tid}/outputs/{oid}/download — 下载文件 +``` + +### 5.2 前端 ArtifactPanel 组件 + +文件已存在:`ArtifactPanel.tsx`(185 行),未被引用。**扩展不重写**。 + +- **文件类型图标**:📄文档 / 💻代码 / 📊数据 / ⚙️配置 / 📁其他 +- **卡片布局**:文件名 + 类型标签 + 大小 + 时间 + 下载按钮 +- **下载**:直接下载 + +--- + +## 六、影响范围 + +### 后端(8 文件) + +| 文件 | 改动 | 阶段 | +|------|------|------| +| `db.py` | CHECK 约束加 3 状态 + archived 字段 + checkpoints 表 | v2.8 | +| `models.py` | Task 加 archived/archived_at | v2.8 | +| `operations.py` | 归档方法 + 状态转换 + Checkpoint CRUD | v2.8+M3 | +| `queries.py` | 聚合规则更新 | v2.8 | +| `blackboard_routes.py` | 归档 API + title 自动生成 + 新状态 | v2.8 | +| `ticker.py` | 调度规则 + 聚合更新 | v2.8 | +| **新增** `checkpoint_routes.py` | Checkpoint CRUD API | M3 | +| `main.py` | 注册 checkpoint router | M3 | + +### 前端(7 文件) + +| 文件 | 改动 | 阶段 | +|------|------|------| +| `EdictBoard.tsx` | 两行布局 + 项目下拉 + 卡片按钮 + 新状态 | v2.8 | +| `TaskModal.tsx` | 新状态转换按钮 | v2.8 | +| `App.tsx` | 移除 header 项目 select | v2.8 | +| `store.ts` | 全项目聚合 + 归档 + 新状态 | v2.8 | +| `api.ts` | 新 API | v2.8+M3 | +| `CheckpointPanel.tsx` | 扩展对接新 API | M3 | +| `ArtifactPanel.tsx` | 扩展对接新 API | M3 | + +--- + +## 七、实施计划 + +### Phase 1:v2.8 后端状态增强 +1. `db.py` — schema migration + CHECK + MANUAL_STATUSES +2. `models.py` — archived/archived_at +3. `operations.py` — 归档方法 + 新状态转换 +4. `queries.py` — 聚合规则 +5. `blackboard_routes.py` — 归档 API + title 自动生成 +6. `ticker.py` — 调度规则 +7. 测试验证 + +### Phase 2:v2.8 前端改造(可与 Phase 3 并行) +1. `store.ts` — 全项目聚合 + 归档 +2. `EdictBoard.tsx` — 两行布局 + 项目下拉 + 卡片按钮 +3. `App.tsx` — 移除 header select +4. `TaskModal.tsx` — 新状态按钮 +5. 前端构建验证 + +### Phase 3:M3 Checkpoint 后端(可与 Phase 2 并行) +1. checkpoints 表 + CRUD +2. checkpoint_routes.py(approve/reject 推进表写入注释) +3. Agent 触发流程 +4. outputs 表扩展 + +### Phase 4:M3 前端(依赖 Phase 2 + 3) +1. CheckpointPanel.tsx 对接 API +2. ArtifactPanel.tsx 对接 API +3. TaskModal 集成 +4. E2E 测试 + +### Phase 5:集成 + 部署 +1. 全量测试 +2. 前端构建 +3. 部署上线 + +--- + +## 八、评审记录 + +| # | 时间 | 内容 | +|---|------|------| +| Mail #303 | 2026-05-18 22:45 | 庞统发设计文档给司马懿 | +| Mail #304 | 2026-05-18 22:48 | 司马懿评审:BUG-29(escalated 字段冲突)+ BUG-30(approve 推进)+ 5 OBS + 2 Q | +| Mail #304 | 2026-05-18 22:50 | 庞统回复:BUG-29 废弃布尔改 status、BUG-30 明确推进规则、采纳 OBS-30/32/33 | +| Mail #305 | 2026-05-18 22:51 | 司马懿最终结论:✅ 评审通过 | diff --git a/docs/design/v3.0-router-refactor.md b/docs/design/v3.0-router-refactor.md new file mode 100644 index 0000000..d08d6dc --- /dev/null +++ b/docs/design/v3.0-router-refactor.md @@ -0,0 +1,475 @@ +# v3.0 调度重构方案:去掉独立 LLM,改为广播认领 + 确定性交接 + +**日期**: 2026-05-21 +**状态**: 待司马懿评审 +**影响文件**: `router.py`, `dispatcher.py`, `ticker.py`, `main.py`, `config/default.yaml` + +--- + +## 1. 问题 + +### 1.1 LLMDriver 是设计之外的野路子 + +当前 Router 的 `LLMDriver` 用独立 `OpenAI()` 客户端直连 zhipu API 做路由决策: +- 不属于 L1(调了 LLM)、不属于 L2(不是 openclaw agent)、不属于 L3(不是完整 Agent) +- 不走 Gateway(无模型路由、无 fallback、无计费) +- 需要单独维护 api_base/api_key(和 Gateway 配置重复) + +**已导致实际故障**:`general-20260521-0004` 因凭据为空反复调度失败。 + +### 1.2 _dispatch_pending 绕过了认领机制 + +设计文档核心原则(§1.2): +> "编排是 AI Agent 在黑板上**自主领活**(动态协作)" +> "Daemon 是**投递员,不是决策者**" + +但 `_dispatch_pending` 的实际行为是: +``` +pending → Router 决定 agent → 强制 claimed + spawn → Agent 被动执行 +``` + +代码里认领的全部基础设施(API + CAS + inbox 事件)都已经实现,但被跳过了。 + +### 1.3 设计违背 + +architecture-v2.6.md §1.1: +> "Agent 决策,Daemon 执行" — 庞统做 plan、张飞领任务、关羽发现风险,都写在黑板上。 + +Daemon 替 Agent 做决策(Router 决定分给谁),违反了"Agent 决策"原则。 + +--- + +## 2. 设计文档中已有的完整方案 + +### 2.1 认领基础设施(已实现) + +| 组件 | 代码位置 | 说明 | +|------|----------|------| +| **claim API** | `POST /tasks/{id}/claim` | 原子 CAS 认领 | +| **claim_task()** | `operations.py:155` | `WHERE status='pending' AND (assignee IS NULL OR assignee=?)` | +| **inbox agent_claim** | `inbox.py:264` | Agent 通过 JSONL 通知 Daemon 认领 | +| **claim_timeout** | `ticker.py:530` | claimed 超时 5 分钟 → 重置为 pending(续杯) | + +### 2.2 竞态解决(已设计,§3.6) + +1. **默认:先到先得** — SQLite CAS,谁先 claim 谁做 +2. **升级:庞统仲裁** — 争议时 @庞统 请求仲裁 +3. **最终:用户拍板** — @user 请求用户决定 + +### 2.3 续杯兜底(已实现) + +``` +pending → 广播 → 无人认领 → claim_timeout(5min) → pending → 再广播 → ... → 庞统兜底 +``` + +`claim_timeout_minutes = 5.0`,`_check_timeouts` 自动把超时的 claimed 重置为 pending。 + +### 2.4 三层执行模型(§4.2) + +| 层级 | 方式 | 命令 | 适用场景 | +|------|------|------|---------| +| **L1 Daemon 直接操作** | SQLite/文件 | — | 状态更新、机械验证 | +| **L2 spawn sub** | 隔离 session | `openclaw agent --agent --session-id ` | scope guard、格式校验 | +| **L3 run agent** | 完整黑板参与者 | `openclaw agent --agent ` | 编码、审查、策略 | + +> **核心原则:系统只有两种 LLM 调用方式,都通过 Gateway,没有第三种。** + +--- + +## 3. 方案:广播认领 + 确定性交接 + +### 3.1 核心思路 + +任务调度分两条路径: + +**确定性路径**:已明确知道下一步该谁做 → 直接 spawn,不需要广播 +- retry → 原执行者 +- Agent handoff(next_capability)→ 能力匹配 +- 有 assignee → 直接分 +- review 生命周期 → 能力匹配 + +**广播认领路径**:首次分配 / 不确定场景 → spawn 所有空闲 Agent,自主 claim +- Agent 读黑板 → 自己判断是否适合 → claim +- 无人认领 → 续杯 → 庞统兜底 + +### 3.2 完整调度流程 + +``` +Ticker._tick_project() + │ + ├─ 1. 扫描状态 + ├─ 2. 依赖推进 + ├─ 3. 超时检测(claimed 5min → pending,working 30min → failed) + │ + ├─ 4. 调度 pending 任务:_dispatch_pending() + │ for each pending task: + │ ├─ 有 next_capability?→ 能力匹配 → 直接 spawn 对应 Agent + │ ├─ 有 assignee?→ 直接 spawn 该 Agent + │ ├─ retry?→ spawn 原执行者 + │ └─ 都没有?→ _broadcast_claim() + │ → spawn 每个空闲 Agent,传入"黑板上有新任务,请认领"的 prompt + │ → Agent 读黑板(GET /tasks?status=pending) + │ → Agent 判断是否适合自己 + │ → Agent claim(POST /tasks/{id}/claim)或退出 + │ + ├─ 5. 调度 review 任务:_dispatch_reviews()(不变) + └─ 6-10. 其他处理(不变) +``` + +### 3.3 广播认领的 Spawn Prompt + +广播 spawn 的 Agent 收到的 prompt: + +``` +你是 {agent_id}。黑板上有新的待认领任务。 + +## 操作 +1. 读黑板查看待认领任务: + curl http://{api_host}:{api_port}/api/projects/{project_id}/tasks?status=pending + +2. 分析每个 pending 任务,判断是否适合你: + - 你的能力:{capabilities} + - 任务类型、描述、优先级是否匹配你的专长 + +3. 如果有适合你的任务,立即认领: + curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/claim \ + -H 'Content-Type: application/json' \ + -d '{"agent": "{agent_id}"}' + +4. 认领成功后,开始执行(状态改为 working) + +5. 如果没有适合你的任务,直接退出 +``` + +### 3.4 Claim 竞争处理 + +多个 Agent 同时 claim 同一个任务: +- **SQLite CAS**:`WHERE status='pending'` — 只有第一个成功,其余 rowcount=0 +- **Agent 行为**:claim 失败 → 检查其他 pending 任务 → 没有适合的 → 退出 +- **自然负载均衡**:不同 Agent 倾向认领不同类型的任务(张飞→coding,司马懿→review) + +### 3.5 无人认领兜底 + +``` +广播 → 5 分钟内无人 claim → claim_timeout → pending → 下轮 ticker 再广播 +→ 连续 3 轮无人认领 → spawn 庞统 → 庞统决定分配或自己执行 +``` + +连续无人认领的检测:在 `events` 表中记录 `broadcast_sent` 事件,`_check_timeouts` 中统计广播轮次,超过阈值 escalate to 庞统。 + +--- + +## 4. 改动清单 + +### 4.1 删除 `LLMDriver` 类(router.py) + +删除整个 `LLMDriver` 类(~120 行)。`AgentRouter.route()` 末尾改为返回 delegate。 + +### 4.2 `AgentRouter.__init__` 去掉 `llm_driver` 参数 + +### 4.3 新增 `_broadcast_claim`(ticker.py) + +司马懿建议:攒一批任务,每轮 ticker 最多广播一次(而非每个 pending 任务触发一次广播)。 +5 个任务只需 spawn 5 个 Agent,而不是 25 个。 + +广播前检查全局并发(司马懿建议 1):接近上限时跳过本轮广播。 + +```python +async def _broadcast_claim(self, tasks, db_path, project_id): + """广播一批待认领任务给所有空闲 Agent,每轮最多广播一次""" + # 全局并发检查 + if self.counter and self.counter.global_active >= self.counter._max_global - 1: + logger.info("Skipping broadcast: global concurrent near limit") + return [] + + idle_agents = self._get_idle_agents() + if not idle_agents: + return [] + + spawned = [] + for agent_id in idle_agents: + if not await self.counter.can_acquire(agent_id): + continue + prompt = self._build_claim_prompt(agent_id, tasks, project_id) + await self.counter.acquire(agent_id) + session_id = await self.spawner.spawn_full_agent( + agent_id=agent_id, + message=prompt, + on_complete=lambda aid, _: self.counter.release(aid), + ) + spawned.append(agent_id) + return spawned +``` + +广播 prompt 包含所有 pending 任务列表: +```python +def _build_claim_prompt(self, agent_id, tasks, project_id): + task_list = "\n".join([ + f"- ID: {t.id}, 标题: {t.title}, 类型: {t.task_type}, 优先级: {t.priority}" + for t in tasks + ]) + return f"""你是 {agent_id}。黑板上有 {len(tasks)} 个待认领任务。 + +## 待认领任务 +{task_list} + +## 操作 +1. 读黑板查看详情: + curl http://{api}/api/projects/{project_id}/tasks?status=pending + +2. 选择适合你的任务并认领: + curl -X POST http://{api}/api/projects/{project_id}/tasks/{task_id}/claim \ + -H 'Content-Type: application/json' -d '{{"agent": "{agent_id}"}}' + +3. 认领成功后开始执行(状态改为 working) +4. 没有适合你的任务则退出 +""" +``` + +### 4.4 Dispatcher 增加 delegate 模式(dispatcher.py) + +`_build_spawn_message` 增加 `delegate` 分支(庞统兜底 prompt),和广播认领共存: +- 广播认领失败 → 任务回到 pending → 多轮后 → spawn 庞统 delegate + +### 4.5 main.py 去掉 LLMDriver 初始化 + +### 4.6 config/default.yaml 去掉 routing 节 + +### 4.7 无人认领检测(复用 retry_count) + +司马懿建议:不需要新增 broadcast_sent 事件。无人认领重置 pending 时 retry_count 已在递增,直接用它判断阈值。当 `retry_count >= 3` 时 escalate to 庞统。 + +--- + +## 5. 广播认领完整生命周期 + +> 本节记录从任务创建到最终闭环的完整链路,包括所有已实现但此前未在设计文档中记录的机制。 + +### 5.1 完整流程图 + +``` +用户创建任务 → pending + ↓ +Ticker 检测到 pending 任务 + ↓ +有确定性路径?(retry/handoff/assignee/生命周期) + ├─ 是 → 直接 spawn 指定 Agent + └─ 否 → 进入广播认领 + ↓ + 广播前检查全局并发(counter.is_near_limit()) + → 接近上限?跳过本轮 + ↓ + 获取空闲 Agent 列表(从 config agents 列表取) + → 无空闲?log warning,不递增 retry_count,等下轮 + ↓ + spawn 所有空闲 Agent,传入 claim prompt + retry_count++(本轮广播计数) + ↓ + ┌─ 有人 claim → 正常执行流程 + └─ 无人 claim + ↓ + claimed 超时(5min)→ _check_timeouts 重置为 pending + retry_count 已递增 + ↓ + retry_count < 3?→ 下轮 Ticker 继续广播 + retry_count >= 3?→ escalate to 庞统 +``` + +### 5.2 retry_count 的三种递增场景 + +| 场景 | 触发位置 | retry_count++? | 说明 | +|------|----------|-----------------|------| +| 广播后无人认领 | `_broadcast_claim` L536 | ✅ 是 | 每轮广播递增,记录广播轮次 | +| claimed 超时回收 | `_check_timeouts` L724 | ✅ 是 | Agent 认领后未执行,视为失败 | +| 无空闲 Agent | `_broadcast_claim` L527 | ❌ 否 | 系统容量问题,不是任务问题 | + +### 5.3 超时阈值(与 working 状态一致) + +| 状态 | 超时 | 处理 | +|------|------|------| +| claimed | `claim_timeout_minutes = 5.0` | → pending(retry_count < 3)或 escalated(retry_count >= 3) | +| working | `default_task_timeout_minutes = 30.0` | → failed | + +超时后 retry_count >= 3 时的处理与 working 超时一致:标记 escalated,触发用户介入流程。 + +### 5.4 庞统兜底(delegate 模式) + +当任务进入以下状态时,Router 路由到 `FALLBACK_AGENT = "pangtong-fujunshi"`,mode="delegate": +- 广播 3 轮无人认领(retry_count >= 3) +- 确定性路由无法匹配(模糊场景) + +庞统收到的 delegate prompt 包含:任务信息 + 团队能力列表 + API 端点。庞统可以: +1. 直接分配给指定 Agent(通过 claim API) +2. 自己执行 +3. 判断任务不合理 → cancel + +### 5.5 用户介入链路(已完整实现) + +当任务 escalated 后,用户介入的完整链路: + +``` +Agent 超时/广播无人认领 + → Ticker 标记 status="escalated" + → SSE broker.publish_sync("task_updated", {...}) + → 前端 EventSource 实时收到事件 + → 通知中心推送 "{old_status} → escalated" + → 卡片红色高亮 + ⚠️ 图标 + → 用户点开 TaskModal + → 看到 escalated 按钮: + · ▶ 继续执行 (→ working) + · 🔄 重新分配 (→ pending, retry_count 重置) + · 🚫 取消 (→ cancelled) + ▸ 高级操作: + · (可扩展更多手动干预) + → 用户点击 → POST /tasks/{id}/status → SSE 推送 → 状态更新 +``` + +**已实现代码对应表:** + +| 环节 | 代码文件 | 具体实现 | +|------|----------|----------| +| 标记 escalated | `ticker.py` _check_timeouts / _broadcast_claim | _transition_status(conn, task_id, "escalated", ...) | +| SSE 推送 | `blackboard_routes.py` update_status | broker.publish_sync("task_updated", ...) | +| SSE 基础设施 | `sse.py` / `sse_routes.py` | SSEBroker + EventSource endpoint | +| 前端 SSE 监听 | `store.ts` startSSE() | addEventListener('task_updated', ...) | +| 前端通知中心 | `store.ts` _pushSseEvent() | 通知列表 + 未读计数 | +| escalated 按钮 | `TaskModal.tsx` PRIMARY_ACTIONS[escalated] | ▶ 继续执行 + 🔄 重新分配 | +| escalated 高级操作 | `TaskModal.tsx` ADVANCED_ACTIONS | 🚫 取消 | +| 状态转换 API | `blackboard_routes.py` POST /tasks/{id}/status | VALID_TRANSITIONS 校验 | +| 转换矩阵 | `db.py` VALID_TRANSITIONS | escalated → {working, pending, cancelled} | + +### 5.6 前端按钮完整矩阵(v3.1 用户确认版) + +**设计原则:** +- 暂停和取消是用户的通用权利,所有非终态都有 +- AI 自动流转的操作(认领、开始、提审、通过、打回)不需要按钮 +- escalated / waiting_human 是 AI 发起后等待用户决策,不是通用按钮 +- blocked 任务本身没在动,暂停无意义 +- failed 的升级是 AI 发起不是按钮 + +#### 状态定义 + +| 状态 | 含义 | 谁触发 | 颜色 | +|------|------|--------|------| +| pending | 等待 AI 认领 | 用户创建/AI流转 | 蓝 | +| claimed | AI 已认领 | AI 认领 | 紫 | +| working | AI 执行中 | AI 开始 | 蓝 | +| paused | 用户暂停 | 用户 | 紫 | +| review | AI 审查中 | AI 提交审查 | 紫 | +| failed | 失败 | AI 或 daemon | 红 | +| blocked | 缺条件卡住 | Agent 遇到外部阻塞 | 橙 | +| escalated | AI 解决不了,升级求助 | Agent | 红 | +| waiting_human | 流程验收点等用户确认 | Agent Checkpoint | 橙 | +| done | 完成 | 审查通过/用户确认 | 绿 | +| cancelled | 取消 | 用户 | 灰 | + +**escalated vs waiting_human 区别:** +- escalated:Agent 遇到**意外问题**解决不了(如技术选型不确定)→ 红色 → 严重 +- waiting_human:执行到**计划中的验收点**(如 Checkpoint)→ 橙色 → 正常进度 + +#### 按钮矩阵 + +| 状态 | 用户按钮 | 说明 | +|------|---------|------| +| **pending** | ⏸ 暂停、🚫 取消 | 等 AI 认领时用户可暂停或取消 | +| **claimed** | ⏸ 暂停、🚫 取消 | AI 已认领未开始 | +| **working** | ⏸ 暂停、🚫 取消 | AI 执行中 | +| **paused** | ▶ 恢复、🚫 取消 | 恢复回暂停前状态 | +| **review** | ⏸ 暂停、🚫 取消 | AI 审查中 | +| **failed** | 🔄 重试、🚫 取消 | 重试回 pending | +| **blocked** | 🔓 解除(→pending)、🚫 取消 | 任务本身没在动,暂停无意义 | +| **escalated** | ▶ 继续(→working)、🔄 重分配(→pending)、🚫 取消 | AI 发起升级后用户决策 | +| **waiting_human** | ✅ 确认、🔄 拒绝(→working)、🚫 取消 | 流程验收点 | +| **done** | 📦 归档 | 终态 | +| **cancelled** | 🔄 重新启动(→pending)、📦 归档 | 可重启保留历史 | + +#### 状态机变更 + +暂停是通用权利,需要在更多状态允许 → paused 转换: + +```python +# 当前 +VALID_TRANSITIONS = { + "pending": {"claimed", "cancelled"}, + "claimed": {"working", "paused", "pending", "cancelled"}, + "working": {"review", "blocked", "failed", "paused", "escalated", "waiting_human", "cancelled"}, + "paused": {"working", "cancelled"}, + "review": {"done", "pending", "failed", "escalated", "waiting_human", "cancelled"}, + "blocked": {"pending", "escalated", "cancelled"}, + "failed": {"pending", "escalated", "cancelled"}, + "escalated": {"working", "pending", "cancelled"}, + "waiting_human": {"working", "done", "cancelled"}, + "done": set(), + "cancelled": set(), +} + +# 变更后 +VALID_TRANSITIONS = { + "pending": {"claimed", "paused", "cancelled"}, # +paused + "claimed": {"working", "paused", "pending", "cancelled"}, # 不变 + "working": {"review", "blocked", "failed", "paused", "escalated", "waiting_human", "cancelled"}, # 不变 + "paused": {"pending", "cancelled"}, # 恢复统一回 pending + "review": {"done", "pending", "failed", "paused", "escalated", "waiting_human", "cancelled"}, # +paused + "blocked": {"pending", "escalated", "cancelled"}, # 不变(blocked暂停无意义) + "failed": {"pending", "escalated", "cancelled"}, # 不变(暂停语义不清晰) + "escalated": {"working", "pending", "paused", "cancelled"}, # +paused + "waiting_human": {"working", "done", "paused", "cancelled"}, # +paused + "done": {"cancelled"}, # +cancelled(取消已完成任务) + "cancelled": {"pending"}, # +pending(重新启动) +} +``` + +**paused 恢复机制:** paused 记录 `resumed_from` 字段,恢复时回到暂停前状态,从暂停的节点继续执行(允许一个 stage 的冗余重跑)。 +- working → paused → ▶恢复 → working +- review → paused → ▶恢复 → review +- claimed → paused → ▶恢复 → claimed + +#### 取消 ADVANCED_ACTIONS 折叠区 + +v3.1 不再使用 ADVANCED_ACTIONS 折叠区。所有用户操作直接展示在主操作区。 +原因:暂停和取消是通用权利,不应隐藏在折叠区里。 + +--- + +## 6. 场景对比 + +| 场景 | 改前(独立 LLM 分配) | 改后(广播认领 + 确定性交接) | +|------|---------------------|---------------------------| +| retry | 原执行者 | 不变(确定性) | +| Agent handoff | 能力匹配 | 不变(确定性) | +| 有 assignee | 直接分 | 不变(确定性) | +| review 生命周期 | 能力匹配 | 不变(确定性) | +| **首次分配** | **独立 LLM 决定分给谁** | **广播所有空闲 Agent,自主认领** | +| **无人认领** | **无此场景(强制分配)** | **续杯 → 庞统兜底** | + +--- + +## 7. 代码量 + +- **删**:~130 行(`LLMDriver` + routing config 初始化 + config.yaml routing 节) +- **改**:~30 行(`Router.route()` 末尾 + `Dispatcher._build_spawn_message()` delegate 分支) +- **新增**:~60 行(`_broadcast_claim` + `_build_claim_prompt`) +- **净减**:~40 行 + +--- + +## 8. 风险与缓解 + +| # | 风险 | 评估 | 缓解 | +|---|------|------|------| +| 1 | 广播 spawn 消耗资源(每个 pending 任务都 spawn 所有空闲 Agent) | 中 | 只有"无确定性路径"的任务才广播;且 Agent 读黑板后无适合任务会快速退出 | +| 2 | 多 Agent 竞争 claim | 低 | SQLite CAS 先到先得,已实现 | +| 3 | 无人认领 | 低 | 续杯机制兜底,多轮后庞统接管 | +| 4 | Agent 认领了不适合的任务 | 低 | Agent 有完整上下文(SOUL+AGENTS+能力),比 LLM 判断更准确 | +| 5 | 广播速度比直接分配慢 | 低 | 首次分配不需要快,准确比快重要 | + +--- + +## 9. 实施步骤 + +1. `router.py`:删除 `LLMDriver` 类 + `AgentRouter` 去掉 `llm_driver` + `route()` 末尾改 delegate +2. `ticker.py`:新增 `_broadcast_claim` + `_build_claim_prompt`,修改 `_dispatch_pending` 增加 广播路径 +3. `dispatcher.py`:`_build_spawn_message()` 增加 delegate 分支(庞统兜底) +4. `main.py`:删除 `llm_driver` 初始化块 +5. `config/default.yaml`:删除 `routing` 节 +6. 测试:创建 pending 任务 → 观察广播 spawn → Agent claim → 执行 diff --git a/docs/design/v3.0-ux-simplification.md b/docs/design/v3.0-ux-simplification.md new file mode 100644 index 0000000..668fd67 --- /dev/null +++ b/docs/design/v3.0-ux-simplification.md @@ -0,0 +1,119 @@ +# v3.0 UX 精简 — AI Native 交互原则 + +> 日期:2026-05-21 +> 作者:庞统 +> 状态:待评审 + +## 1. 核心原则 + +**AI Native 的交互:Agent 能解决的 Agent 解决,需要人类干预才人类干预。** + +| 原则 | 说明 | +|------|------| +| 看板只看不做 | 卡片 = 状态标签 + 信息,不是操作面板 | +| 操作台按需呈现 | TaskModal 只显示**当前状态人需要做的那个动作** | +| 意图必须明确 | 按钮文字必须是"你要我做什么",不是"系统状态转换" | +| 非必要不出现 | Agent 自动流转的状态不显示操作按钮 | + +## 2. 状态与人机交互矩阵 + +| 状态 | 含义 | Agent 自行解决? | 需要人干预? | 给人的按钮 | 卡片显示 | +|------|------|:---:|:---:|------|------| +| pending | 等待调度 | ✅ 自动认领 | — | 无 | 🟡 待调度 | +| claimed | 已分配 | ✅ 自动开始 | — | 无 | 🟡 已分配 | +| working | 执行中 | ✅ Agent 执行 | — | 无 | 🟢 执行中 | +| review | 审查中 | ✅ Agent 审查 | — | 无 | 🔵 审查中 | +| **waiting_human** | 等人工确认 | — | ✅ Agent 明确请求 | **确认完成 / 拒绝** | 🟠 待确认 | +| **escalated** | 升级求助 | — | ✅ Agent 处理不了 | **继续 / 重新分配** | 🔴 需介入 | +| **failed** | 失败 | ✅ 自动重试 | 超重试上限 | **重试 / 取消** | 🔴 失败 | +| **blocked** | 阻塞 | — | ✅ 需外部帮助 | **解除阻塞** | 🔴 阻塞 | +| paused | 用户暂停 | — | 用户主动 | **继续 / 取消** | ⚪ 已暂停 | +| done | 完成 | ✅ 终态 | 可选归档 | **归档**(可选) | ⚪ 已完成 | +| cancelled | 取消 | — | 终态 | **归档**(可选) | ⚪ 已取消 | + +## 3. 卡片设计变更 + +### 3.1 卡片 = 状态标签 + 信息 + +**移除**:卡片上所有操作按钮(working 的"人工审核"、pending 的"认领"等) + +**保留**: +- 标题、描述摘要 +- 状态指示圆点(替代原来的 risk_level 标签) +- 指派人、时间 +- 点击打开 TaskModal + +### 3.2 状态指示圆点 + +替代卡片左下角的 `risk_level: standard` 标签: + +| 圆点 | 颜色 | 对应状态 | +|------|------|---------| +| 🟢 | `#2ecc8a` 绿色 | working, claimed | +| 🟡 | `#f5c842` 黄色 | pending, review | +| 🟠 | `#f59e0b` 橙色 | waiting_human | +| 🔴 | `#ff5270` 红色 | failed, blocked, escalated | +| ⚪ | `#6b7280` 灰色 | done, cancelled, paused | + +**非标准风险**(high/critical)时,圆点旁额外显示风险标签(如"⚠️ 高风险")。 + +## 4. TaskModal 状态操作变更 + +### 4.1 移除冗余的"状态操作"区域 + +当前 TaskModal 总览里有一个"🔄 状态操作"区域,列出所有合法状态转换按钮(working 有 7 个)。 + +**改为**:只显示**当前状态需要人做的那个动作**,用意图明确的按钮。 + +### 4.2 各状态的按钮方案 + +| 状态 | 显示的按钮 | 按钮含义 | +|------|----------|---------| +| pending | 无 | 等待调度引擎自动分配 | +| claimed | 无 | 等待 Agent 自动开始 | +| working | **暂停** | 主动暂停任务 | +| review | 无 | 等待审查 Agent 完成 | +| waiting_human | **✅ 确认完成** / **🔄 拒绝,继续做** | 对 Agent 的 Checkpoint 做出决策 | +| escalated | **▶ 继续执行** / **🔄 重新分配** | 对升级任务给出指导 | +| failed | **🔄 重试** | 手动重试 | +| blocked | **🔓 解除阻塞** | 帮助 Agent 继续前进 | +| paused | **▶ 继续** / **🚫 取消** | 用户主动恢复或取消 | +| done | **📦 归档** | 可选归档 | +| cancelled | **📦 归档** | 可选归档 | + +### 4.3 高级操作(收起) + +对于需要手动干预的高级场景(如手动提交审查、强制标记失败),提供"高级操作"收起区域,不默认展示。 + +## 5. Ticker 扫描 _general 修复 + +### 问题 +`_general` 不在 `registry.db` → ticker 不扫描 → 任务创建后不被调度。 + +### 方案 +在 `Ticker._tick_all()` 中,遍历 registry 项目后,额外检查 `_general/blackboard.db` 是否存在。存在则作为虚拟项目扫描一次。 + +```python +# ticker.py _tick_all 方法末尾 +general_db = Path(self.registry.root) / "_general" / "blackboard.db" +if general_db.exists(): + pr = await self._tick_project("_general", {"status": "active", "source": "virtual"}) + results["projects"]["_general"] = pr +``` + +## 6. 变更范围 + +### 前端 +- `EdictBoard.tsx`:移除 CARD_ACTIONS,卡片不再显示操作按钮;risk_level 改为状态圆点 +- `TaskModal.tsx`:精简 StatusButtons,每个状态只显示必要的按钮 + +### 后端 +- `ticker.py`:_tick_all 额外扫描 _general + +### 设计文档 +- 本文档为 v3.0 UX 精简设计,评审通过后归档 + +## 7. 不改的 +- 状态机本身(11 个状态、转换矩阵不变) +- 后端 API(PATCH/POST 端点保留,前端不再调用不代表后端删掉) +- TaskModal 其他选项卡(产出/审查/经验/子任务不变)