auto-sync: 2026-05-17 22:11:22
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
# v2.7 三级层次结构设计:Project / Card / Task
|
||||
|
||||
> 日期:2026-05-17
|
||||
> 作者:庞统 + 司马懿(共识方案)
|
||||
> 状态:待用户确认
|
||||
|
||||
---
|
||||
|
||||
## 一、核心设计决策
|
||||
|
||||
### 1.1 三层定义
|
||||
|
||||
```
|
||||
Project(项目)
|
||||
└── Card(卡片/策略)
|
||||
├── Task(任务)
|
||||
├── Output(产出)
|
||||
├── Review(审查)
|
||||
├── Comment(评论)
|
||||
├── Event(事件)
|
||||
└── routing_decisions(路由审计)
|
||||
```
|
||||
|
||||
| 层级 | 语义 | 例子 | 生命周期 |
|
||||
|------|------|------|---------|
|
||||
| **Project** | 业务领域/产品线 | sanguo_quant_live、sanguo_vnpy | 长期(月/年) |
|
||||
| **Card** | 独立的工作流/策略 | 动量策略v1、数据平台P3 | 中期(天/周) |
|
||||
| **Task** | 具体执行单元 | 编写回测脚本、清洗分钟线 | 短期(小时/天) |
|
||||
|
||||
### 1.2 Card = DB 隔离边界
|
||||
|
||||
**双方共识,司马懿论证:**
|
||||
|
||||
- ❌ Project = DB:sanguo_quant_live 会有几十个策略,几百个 task + outputs + events 混一起,Ticker 扫描爆炸,Agent prompt 噪音大
|
||||
- ❌ Task = DB:太碎,同策略的任务有 outputs 引用和 depends_on 依赖,拆 DB 失去事务一致性
|
||||
- ✅ **Card = DB**:一个策略的所有 task 在同一个 SQLite,事务一致、JOIN 快、黑板上下文干净
|
||||
|
||||
**调研支撑(Context Folding / ByteDance 2025):**
|
||||
> Card 完成后,整个黑板的详细数据"折叠"为摘要,只保留对上层有用的结论。这天然对应 Card 级别的归档。
|
||||
|
||||
### 1.3 归档/恢复 — 只改 metadata,不移动文件
|
||||
|
||||
**双方共识:**
|
||||
|
||||
- cards 的 status 字段:`active` / `closed` / `archived` / `deleted`
|
||||
- 归档 = 改 status → Ticker 跳过 → DB 不再被打开 → 内存释放
|
||||
- 恢复 = 改回 active
|
||||
- 磁盘不够时手动脚本批量移 archived cards 到冷存储
|
||||
- 删除 = status=deleted + 7 天后异步清理文件
|
||||
|
||||
### 1.4 跨 Card 依赖 — 不支持
|
||||
|
||||
**双方共识:**
|
||||
|
||||
- 打破 DB 边界 = 跨库事务 = SQLite 做不了
|
||||
- 引入分布式依赖 = 一堆 edge case
|
||||
- 业务上策略之间应该独立;如果策略B 用策略A 的数据,那是数据层面的事,不是编排层面的事
|
||||
|
||||
---
|
||||
|
||||
## 二、数据模型
|
||||
|
||||
### 2.1 Registry DB(全局唯一,管理 Project + Card 元数据)
|
||||
|
||||
路径:`data/registry.db`
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id TEXT PRIMARY KEY, -- 项目标识,如 'sanguo_quant_live'
|
||||
name TEXT NOT NULL, -- 显示名称
|
||||
description TEXT DEFAULT '',
|
||||
status TEXT DEFAULT 'active', -- active/archived/deleted
|
||||
config_json TEXT DEFAULT '{}', -- 项目级配置(agents、routing 等)
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cards (
|
||||
id TEXT PRIMARY KEY, -- 卡片标识,如 'strategy-momentum-v1'
|
||||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||||
name TEXT NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
status TEXT DEFAULT 'active', -- active/closed/archived/deleted
|
||||
db_path TEXT NOT NULL, -- 相对路径,如 'sanguo_quant_live/cards/strategy-momentum-v1/blackboard.db'
|
||||
card_type TEXT DEFAULT 'default', -- default/strategy/data_pipeline/research
|
||||
labels_json TEXT DEFAULT '[]', -- 标签
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT,
|
||||
archived_at TEXT,
|
||||
FOREIGN KEY (project_id) REFERENCES projects(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cards_project ON cards(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_cards_status ON cards(status);
|
||||
```
|
||||
|
||||
### 2.2 Card DB(per-card,当前 blackboard.db 不变)
|
||||
|
||||
每个 Card 独立一个 SQLite,内含当前所有表(tasks、outputs、reviews、comments、events、task_attempts、routing_decisions)。
|
||||
|
||||
**无任何表结构变更**——Task/Output/Review 等模型完全不变。
|
||||
|
||||
### 2.3 目录结构
|
||||
|
||||
```
|
||||
data/
|
||||
├── registry.db ← 全局注册表
|
||||
├── sanguo_quant_live/ ← Project 目录
|
||||
│ ├── cards/
|
||||
│ │ ├── strategy-momentum-v1/
|
||||
│ │ │ ├── blackboard.db ← Card 级黑板
|
||||
│ │ │ └── artifacts/ ← 产出物文件
|
||||
│ │ ├── strategy-mean-reversion/
|
||||
│ │ │ ├── blackboard.db
|
||||
│ │ │ └── artifacts/
|
||||
│ │ └── ...
|
||||
│ └── project.json ← 项目级配置(可选)
|
||||
├── sanguo_vnpy/
|
||||
│ └── cards/
|
||||
│ └── ...
|
||||
└── inbox/ ← 快捷创建(单 Card 项目)
|
||||
└── fetch-data-001/
|
||||
├── blackboard.db
|
||||
└── artifacts/
|
||||
```
|
||||
|
||||
### 2.4 迁移映射
|
||||
|
||||
| 当前 | 迁移后 |
|
||||
|------|--------|
|
||||
| `data/demo/blackboard.db` | `data/_migration/demo/default/blackboard.db`(默认 Card) |
|
||||
| `data/projects/blackboard.db` | `data/_migration/projects/default/blackboard.db` |
|
||||
| `data/e2e-routing-test/blackboard.db` | `data/_migration/e2e-routing-test/default/blackboard.db` |
|
||||
| `data/_registry.yaml` | `data/registry.db`(projects + cards 表) |
|
||||
|
||||
---
|
||||
|
||||
## 三、API 路由变更
|
||||
|
||||
### 3.1 新路由
|
||||
|
||||
```
|
||||
# Project 级
|
||||
GET /api/projects → 列出所有项目
|
||||
POST /api/projects → 创建项目
|
||||
GET /api/projects/{pid} → 项目详情
|
||||
PATCH /api/projects/{pid} → 更新项目配置
|
||||
DELETE /api/projects/{pid} → 删除项目
|
||||
|
||||
# Card 级
|
||||
GET /api/projects/{pid}/cards → 列出项目下所有 Card
|
||||
POST /api/projects/{pid}/cards → 创建 Card
|
||||
GET /api/projects/{pid}/cards/{cid} → Card 详情
|
||||
PATCH /api/projects/{pid}/cards/{cid} → 更新 Card(改名/归档/恢复)
|
||||
DELETE /api/projects/{pid}/cards/{cid} → 删除 Card
|
||||
|
||||
# Task 级(嵌套在 Card 下)
|
||||
GET /api/projects/{pid}/cards/{cid}/tasks → 列出 Card 下所有 Task
|
||||
POST /api/projects/{pid}/cards/{cid}/tasks → 创建 Task
|
||||
GET /api/projects/{pid}/cards/{cid}/tasks/{tid} → Task 详情
|
||||
POST /api/projects/{pid}/cards/{tid}/status → 更新状态
|
||||
POST /api/projects/{pid}/cards/{tid}/outputs → 添加产出
|
||||
POST /api/projects/{pid}/cards/{tid}/reviews → 添加审查
|
||||
POST /api/projects/{pid}/cards/{tid}/comments → 添加评论
|
||||
|
||||
# 便利路由(前端兼容)
|
||||
GET /api/projects/{pid}/tasks → 遍历该 project 所有 active cards 的 tasks
|
||||
```
|
||||
|
||||
### 3.2 向后兼容
|
||||
|
||||
- `GET /api/projects/{pid}/tasks` 保留,内部遍历该 project 的所有 active cards 合并返回
|
||||
- 前端迁移期间,旧 API 继续工作
|
||||
|
||||
---
|
||||
|
||||
## 四、Ticker 变更
|
||||
|
||||
### 4.1 当前逻辑
|
||||
|
||||
```python
|
||||
# 遍历 registry 中的所有 project,每个 project 打开一个 DB
|
||||
for project_id, project_info in registry.projects.items():
|
||||
db_path = data_root / project_id / "blackboard.db"
|
||||
# _tick_project(db_path, ...)
|
||||
```
|
||||
|
||||
### 4.2 变更后
|
||||
|
||||
```python
|
||||
# 从 registry.db 查所有 status=active 的 cards
|
||||
for card in registry.get_active_cards(project_id):
|
||||
db_path = data_root / card.db_path
|
||||
# _tick_card(card, db_path, ...)
|
||||
```
|
||||
|
||||
### 4.3 性能
|
||||
|
||||
- SQLite 打开/关闭很快(ms 级)
|
||||
- 如果 active cards > 50,分批扫描(per-tick 限制 max_cards_per_tick=20)
|
||||
- Ticker 只扫 active cards,closed/archived 完全不打开 DB
|
||||
|
||||
---
|
||||
|
||||
## 五、前端变更
|
||||
|
||||
### 5.1 左侧导航
|
||||
|
||||
```
|
||||
当前: Project List → Task List
|
||||
变更: Project List → Card List → Task List
|
||||
```
|
||||
|
||||
### 5.2 新增 CardList 组件
|
||||
|
||||
- 卡片式展示每个 Card(名称、状态、任务数、进度)
|
||||
- 新建 Card 按钮
|
||||
- 归档/恢复操作
|
||||
|
||||
### 5.3 TaskList 适配
|
||||
|
||||
- 切换 Card 时重新加载 Task 列表
|
||||
- 便利模式:显示当前 Project 所有 active cards 的 tasks(按 card 分组)
|
||||
|
||||
---
|
||||
|
||||
## 六、Registry API
|
||||
|
||||
```python
|
||||
class ProjectRegistry:
|
||||
"""管理 registry.db 中的 projects + cards"""
|
||||
|
||||
def create_project(self, project_id: str, name: str, ...) -> dict
|
||||
def get_project(self, project_id: str) -> dict | None
|
||||
def list_projects(self, status: str = None) -> list[dict]
|
||||
def update_project(self, project_id: str, **kwargs) -> bool
|
||||
|
||||
def create_card(self, card_id: str, project_id: str, name: str, ...) -> dict
|
||||
def get_card(self, card_id: str) -> dict | None
|
||||
def list_cards(self, project_id: str, status: str = None) -> list[dict]
|
||||
def get_active_cards(self, project_id: str) -> list[dict]
|
||||
def update_card(self, card_id: str, **kwargs) -> bool
|
||||
def archive_card(self, card_id: str) -> bool
|
||||
def restore_card(self, card_id: str) -> bool
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、路由方案兼容性
|
||||
|
||||
**完全兼容**(司马懿 Mail #289 确认):
|
||||
|
||||
- routing_decisions 表在 Card 级 DB 内(每个 Card 独立审计)
|
||||
- current_agent / previous_agent 在 task 级别,自然在 Card DB 内
|
||||
- agent_profiles 和 routing 配置在 Project 级(config_json 或 project.yaml)
|
||||
- 唯一变化:Ticker 扫描从"per-project"变为"per-card"
|
||||
|
||||
---
|
||||
|
||||
## 八、实施计划
|
||||
|
||||
### v2.7.0 范围
|
||||
|
||||
| 序号 | 任务 | 预计工作量 |
|
||||
|------|------|-----------|
|
||||
| 1 | registry.db + ProjectRegistry 类 | 0.5 天 |
|
||||
| 2 | 数据迁移脚本(旧 project → Card) | 0.5 天 |
|
||||
| 3 | API 路由变更(新增 Card 层) | 1 天 |
|
||||
| 4 | Ticker 适配(per-card 扫描) | 0.5 天 |
|
||||
| 5 | 前端 CardList + 导航适配 | 1 天 |
|
||||
| 6 | 端到端测试 | 0.5 天 |
|
||||
| **合计** | | **4 天** |
|
||||
|
||||
### 前置条件
|
||||
|
||||
- ✅ v2.6.1 路由方案已落地并验证
|
||||
- ✅ 司马懿代码评审通过
|
||||
- ✅ E2E 验证通过(review 正确路由到司马懿)
|
||||
|
||||
### 不在 v2.7.0 范围内
|
||||
|
||||
- 跨 Card 依赖(不支持)
|
||||
- Card 级向量检索/摘要(Phase 2)
|
||||
- Card 模板(Phase 3)
|
||||
- 冷存储迁移脚本(按需)
|
||||
|
||||
---
|
||||
|
||||
## 九、风险评估
|
||||
|
||||
| 风险 | 影响 | 缓解 |
|
||||
|------|------|------|
|
||||
| 数据迁移丢失 | 高 | 迁移脚本 + 旧数据备份 + 回滚方案 |
|
||||
| 前端三层导航体验复杂 | 中 | 保留便利 API,前端渐进迁移 |
|
||||
| active cards 过多导致 Ticker 慢 | 低 | max_cards_per_tick 限制 + 分批 |
|
||||
| registry.db 并发写冲突 | 低 | WAL + busy_timeout + 单 Ticker 写入 |
|
||||
|
||||
---
|
||||
|
||||
## 十、与调研报告的对齐
|
||||
|
||||
| 调研发现 | 方案对齐 |
|
||||
|---------|---------|
|
||||
| Jira:单表 + 类型字段 + project_id 隔离 | Task 保持单表,Card 做 DB 隔离(比 Jira 粒度更细) |
|
||||
| LangGraph:Subgraph 独立 State + 映射回传 | Card 独立黑板,未来 Card 完成后摘要写入 Project 级 |
|
||||
| Context Folding:子任务折叠为摘要 | Card 归档时折叠,保留元数据索引 |
|
||||
| Hot/Cold Memory 分离 | active=热、closed=温、archived=冷,按需加载 |
|
||||
Reference in New Issue
Block a user