diff --git a/docs/design/v2.7-three-tier-hierarchy.md b/docs/design/v2.7-three-tier-hierarchy.md new file mode 100644 index 0000000..e2be0f5 --- /dev/null +++ b/docs/design/v2.7-three-tier-hierarchy.md @@ -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=冷,按需加载 |