Files
sanguo_moziplus_v2/docs/design/04-blackboard-collaboration-model.md
cfdaily eccb4d2723
CI / lint (pull_request) Successful in 7s
CI / test (pull_request) Successful in 9s
CI / notify-on-failure (pull_request) Successful in 0s
docs: 设计文档编号重排(20→14, 24→15) + 已完成文档状态标注更新
2026-06-13 10:12:39 +08:00

347 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# #04 黑板协作模型设计
> 版本: v1.1
> 日期: 2026-05-30
> 作者: 庞统(副军师)
> 状态: ✅ 已完成(@mention + mention_queue 已实现)
> 前置: #02 Main Session + Delegation, #03 Prompt 进化
---
## 一、问题陈述
当前黑板有 7 种信息类型(Task/Comment/Output/Event/Review/Decision/Observation),全部通过 `task_id` 平铺关联。E2E 测试暴露了三个设计缺陷:
### 问题 1assignee 和 @mention 功能重叠
| 机制 | 触发方式 | 路由行为 |
|------|---------|---------|
| assignee 字段 | 创建任务时传 `assignee: "zhangfei-dev"` | 确定性路由 |
| @mention | Comment 中 `mentions: ["zhaoyun-data"]` | mention_queue → spawn |
**问题**:两者都能触发"找人做事",语义不同但边界模糊。
- Agent 不知道什么时候用 assignee,什么时候用 @
- assignee 是单人的,但任务可能需要多人协作
- @mention 需要额外传 `mentions` 数组,容易遗漏
### 问题 2:一个任务只能有一个 assignee
当前 `assignee` 是单个字符串。但现实中:
- 一个数据准备任务可能需要赵云(获取)+ 关羽(风控审核)
- 一个部署任务可能需要姜维(部署)+ 关羽(安全检查)
- 庞统创建子任务时需要同时指定多人
### 问题 3:信息组织是平铺的,缺乏结构化视图
当前 `expand=all` 只展开当前 task 的子表,不递归子任务。用户想看"数据准备子任务的所有 comments 和成果物",需要:
1. 先查父任务,找到子任务 ID
2. 再逐个查每个子任务的 `expand=all`
信息之间的关联(比如 Output #3 是 Comment #2 的结果)无法表达。
---
## 二、优秀实践调研
### 2.1 项目管理工具的共识
**Jira / Linear / Asana 共同模式**
| 洞察 | 来源 | 启示 |
|------|------|------|
| 单表存所有任务 + 类型字段区分 | Jira `issuetype` | 不需要为不同任务类型建不同表 |
| `parent_id` + `topmost_id` 实现层级 | Jira | 父子关系用指针,不是嵌套 |
| Project 是逻辑隔离边界 | Jira/Linear | 一个 Project 一个黑板 DB |
| 一个 Task 可属于多个 Project | Asana | 多对多关系(cross-project |
### 2.2 AI Agent 编排系统的共识
**LangGraph / CrewAI / AutoGen 共同模式**
| 洞察 | 来源 | 启示 |
|------|------|------|
| 子任务隔离 + 结果摘要回传 | LangGraph Subgraph | 子任务有独立状态,完成后折叠 |
| 共享 Memory + Crew 边界 | CrewAI | 同一 Crew 共享记忆 |
| 对话历史即上下文 | AutoGen | 不需要额外机制,消息本身就是上下文 |
| 扁平 Handoff + Context Variables | OpenAI Swarm | 最简模型,适合简单场景 |
### 2.3 学术和实践前沿
| 洞察 | 来源 | 启示 |
|------|------|------|
| **共享产出物 > 共享消息** | O'Reilly 2026 | 围绕 artifact 协作,不是围绕消息 |
| **Phase gates + shared artifacts + final supervisor** | O'Reilly 2026 | 2026 存活系统的共同特征 |
| **共享工作区 + 结构化产出 > 消息传递** | Claude Code 实践 | 文件系统 + JSON 接口 |
| **对话历史传递 surprisingly effective** | OpenAI Handoff | 转交时携带完整上下文 |
| **bMAS 迭代收敛到共识** | arXiv:2507.01701 | Agent 轮流行动直到黑板达成共识 |
| **propose→validate→commit 原子写入** | Network-AI | 消除 split-brain |
| **Auftragstaktik 任务式指挥** | ClawTeam #10 | Intent→End State→Constraints,不指定步骤 |
### 2.4 核心设计原则(从调研提炼)
1. **黑板是唯一真相源** — 所有 Agent 通过黑板共享信息
2. **产出物 > 消息** — 围绕共享产出物协作,不是围绕消息传递
3. **结构对称** — 不把任何 Agent 当"主"Canonical IR 原则
4. **单一职责** — 每种信息类型只表达一件事
5. **可追溯** — 不可变事件日志,写入即审计
6. **任务式指挥** — Intent→End State→ConstraintsAgent 自主决定怎么做
---
## 三、设计方案
### 3.1 统一路由:assignee 降级为显示字段,路由统一走 @mention
**核心决策**:保留 `assignee` 字段作为「负责人」显示字段,但路由触发统一走 @mention
| 之前 | 之后 |
|------|------|
| 创建任务传 `assignee` → 确定性路由 | 创建任务时在 description 中 `@zhangfei-dev` → daemon 自动推断 assignee + 路由 |
| Agent 不知道用 assignee 还是 @mention | 只有一种路由入口:@ |
| assignee 是单人的 | @ 可以 @ 多人 |
**为什么保留 assignee 字段(不取消)**
1. **前端展示**:「这个任务谁负责」是刚需
2. **Mail 特殊处理**Mail 的 assignee 是收件人,不能取消
3. **向后兼容**:存量任务已有 assignee 值
4. **查询效率**`WHERE assignee=?` 比扫描 description 中的 @ 快
**assignee 从「路由入口」变为「路由结果」**
- API 调用方不再直接设置 assignee(废弃)
- daemon 从 description/comment 中的 @mention 自动推断 assignee
- router 快速路径保持不变(仍然读 assignee 字段,只是赋值来源变了)
#### 迁移方案
**阶段 1#04 Phase 1**:双轨并行
- 创建任务时仍接受 `assignee` 参数(向后兼容,标记为 deprecated
- 新增:daemon 从 description 中提取 @mention → 如果有匹配 → 自动覆盖 assignee
- router 快速路径保持不变(读 assignee 字段路由)
- 无 assignee 且无 @mention → 广播认领
**阶段 2#04 Phase 2**:全面切换
- API 不再接受 `assignee` 参数(返回 deprecation warning
- assignee 完全由 daemon 从 @mention 推断
- router 快速路径不变(仍然读 assignee 字段,只是赋值来源变了)
**阶段 3(未来)**:清理
- 移除 API 的 assignee 参数
- 存量任务:assignee 值保留不动(已经是正确的负责人信息)
**代码影响评估**
| 文件 | 影响 | 改动 |
|------|------|------|
| router.py L155-160 | assignee 快速路径 | ✅ 保持不变(仍然读 assignee 字段) |
| dispatcher.py L427-446 | assignee 作为 dispatch 依据 | ✅ 保持不变 |
| ticker.py _transition_status | pending 时清空 assignee | ✅ 保持不变 |
| API POST /tasks | 接受 assignee 参数 | Phase 1 保留,Phase 2 废弃 |
| ticker.py _tick_project | 新增:创建后扫描 description 提取 @mention | 新代码 |
**API 变化**
```python
# Phase 1:两种方式都支持,@mention 优先
POST /tasks {
"title": "写 hello.py",
"description": "@zhangfei-dev 写一个 hello.py"
}
# daemon 自动提取 assignee=zhangfei-dev
# 也可以旧方式:assignee="zhangfei-dev"deprecated
# Phase 2:只用 @mention
POST /tasks {
"title": "写 hello.py",
"description": "@zhangfei-dev 写一个 hello.py"
}
```
### 3.2 @mention 语义增强
**当前问题**Comment 需要**同时**写 `body` 中的 `@zhaoyun-data` **和** `mentions` 数组。Agent 容易遗漏。
**改进方案**daemon 自动从 `body` 中提取 `@Agent` 模式,自动填充 `mentions`
```python
# Agent 只需要写 body
POST /comments {
"author": "pangtong-fujunshi",
"body": "@zhaoyun-data 你来获取沪深300行情数据,@guanyu-dev 数据到了你做风控检查"
}
# daemon 自动提取 → mentions: ["zhaoyun-data", "guanyu-dev"]
# 写入 mention_queue → 两人都收到 mention spawn
```
**提取规则**D4 决策:只支持 @agent-id 格式):
- 正则 `@([a-z]+-[a-z]+-[a-z]+)` 匹配已知 Agent ID 列表
- 匹配到的 Agent 写入 `mentions` 字段(如果前端也传了 `mentions`,取并集)
- 不认识的 @ 目标忽略(可能是 @文档 @链接
- **不支持中文昵称**@张飞)—— agent-id 是系统唯一标识,无歧义。未来如需支持,加 alias 映射即可
### 3.3 多人协作模式
@ 多人 ≠ 多人同时执行同一任务。而是**每人收到 mention spawn,自主协调**
| @ 模式 | 含义 | Daemon 行为 |
|--------|------|------------|
| `@zhangfei-dev` | 指定张飞做这个任务 | 确定性路由给张飞 |
| `@zhaoyun-data @guanyu-dev` | 需要两人协作 | 创建两个子任务,分别路由 |
| `@所有人` | 不知道谁合适 | 广播认领 |
| 无 @ | 不需要特定人 | 广播认领 |
**@多人 → 自动拆子任务**(Phase 2,暂不实现):
```
庞统创建任务:@zhaoyun-data 获取数据,@zhangfei-dev 写策略代码
Daemon 自动拆解:
├── 子任务 1: 获取数据 → assignee: zhaoyun-data
└── 子任务 2: 写策略代码 → assignee: zhangfei-dev, depends_on: [子任务 1]
```
Phase 1 只做:@多人 → 每个人收到 mention spawn,各自决定如何协作。
### 3.4 信息关联模型重构
**当前**7 种信息平铺挂在 `task_id` 上,互相之间无关联。
**改进**:引入 `parent_id` 串联信息流。
#### Output 关联 Comment
```python
# 当前
POST /outputs {agent, content_type, summary}
# 产出物和讨论没有关联
# 改进:output 关联到触发它的 comment
POST /outputs {
agent: "zhaoyun-data",
content_type: "data",
summary: "沪深300日频行情",
triggered_by_comment: 42 # ← 新字段,关联到 @赵云 的那条 comment
}
```
#### 反向索引
```sql
CREATE INDEX IF NOT EXISTS idx_outputs_triggered_by ON outputs(triggered_by_comment);
```
支持查询「这条 comment 触发了哪些 outputs」。
#### Review 关联 Output
当前 `Review.output_id` 已有关联,保持不变。
#### 信息流视图
```
Comment #1: "@zhaoyun-data 获取沪深300行情"
└── mention_queue → spawn 赵云
└── Comment #2: "数据已获取,见附件"
└── Output #1: content_path=/data/market.csv, triggered_by_comment=#1
└── Review #1: verdict=approved, output_id=#1
```
### 3.5 层级查询 API
**当前痛点**:想看"数据准备子任务的所有信息"需要多次查询。
**改进**:新增聚合查询 API(D3 决策:Phase 2 实施,含子任务的完整版 Phase 3)。
#### Phase 2:单任务 timeline
```
GET /api/projects/{pid}/tasks/{task_id}/timeline?limit=50&offset=0
返回:
{
"task": {...},
"timeline": [
{
"task": {...},
"comments": [...],
"outputs": [...],
"reviews": [...]
}
],
"timeline": [
{"ts": "...", "type": "comment", "author": "pangtong", "body": "@zhaoyun-data ..."},
{"ts": "...", "type": "mention", "agent": "zhaoyun-data", "status": "notified"},
{"ts": "...", "type": "status", "from": "pending", "to": "claimed"},
{"ts": "...", "type": "comment", "author": "zhaoyun-data", "body": "数据已获取"},
{"ts": "...", "type": "output", "agent": "zhaoyun-data", "summary": "沪深300行情"},
{"ts": "...", "type": "review", "verdict": "approved"}
],
"total": 42,
"has_more": true
}
```
- 只聚合当前任务(不含子任务)的 comments + outputs + reviews + events + mentions
- 支持 `limit` + `offset` 分页
-`created_at` DESC 排序
#### Phase 3:含子任务的完整 timeline
聚合子任务信息,考虑 materialized view 或 cache 优化性能(WARN-3)。
---
## 四、实施计划
### Phase 1(与 #02/#03 一起验证)
| 改动 | 影响 |
|------|------|
| daemon 自动从 body 提取 @mention | Agent 不再需要手动传 `mentions` 数组 |
| claim prompt 补充完整 APIRC-2 | Agent 知道怎么写产出物 |
| assignee 保留但降级为显示字段 | 向后兼容 |
### Phase 2#04 独立实施)
| 改动 | 影响 |
|------|------|
| 创建任务时从 description 提取 @ → 自动 assignee | 统一路由入口 |
| API assignee 参数废弃(返回 deprecation warning | 向后兼容过渡 |
| @mention 自动提取(blackboard_routes.py | Agent 不再需要手动传 mentions |
| Output 新增 triggered_by_comment 字段 + 索引 | 信息关联 |
| /timeline 聚合查询 API(单任务版) | 结构化视图 |
### Phase 3(未来)
| 改动 | 影响 |
|------|------|
| @多人自动拆子任务LLM 辅助) | 复杂协作 |
| @所有人 广播语法 | 统一广播入口 |
| /timeline 含子任务完整版 + materialized view | 全景视图 |
| Context Folding(子任务完成后折叠) | 上下文管理 |
| Fidelity 三档信息路由 | 按 Agent 角色分档展示 |
---
## 五、设计决策(已确认)
| # | 决策 | 结论 | 理由 |
|---|------|------|------|
| D1 | assignee 是否完全取消? | **保留为显示字段,路由统一走 @mention** | 前端展示/Mail/向后兼容/查询效率 |
| D2 | @多人 Phase 1 是否自动拆子任务? | **不自动拆,Phase 3 再做** | daemon 无法可靠推断依赖关系 |
| D3 | timeline API 是否 Phase 1 就做? | **Phase 2 做(单任务版)** | 先让 E2E 跑通 |
| D4 | @ 模式支持中文昵称? | **只支持 @agent-id** | 无歧义,低维护成本 |
---
## 附录:调研来源
- Jira 数据库设计(单表 + issuetype + parent_id
- Linear/Asana 层级模型(Workspace 隔离 + 逻辑分组)
- LangGraph Subgraph 模式(子任务隔离 + State 映射回传)
- CrewAI 共享 MemoryCrew 边界)
- bMAS 黑板架构(Control Unit 动态路由 + 迭代收敛)
- O'Reilly 2026(共享产出物 > 共享消息 + Phase gates
- OpenAI Handoff(对话历史传递 surprisingly effective
- Network-AIpropose→validate→commit 原子写入)
- ClawTeam #10Auftragstaktik 任务式指挥)
- Opal-BridgeCanonical IR + Fidelity 三档)