Files
sanguo_moziplus_v2/docs/design/v2.7-subtask-model.md
T
cfdaily 0d7425b88c
Deploy / ci (push) Waiting to run
Deploy / deploy (push) Blocked by required conditions
Deploy / notify-deploy-failure (push) Blocked by required conditions
auto-sync: 2026-06-07 01:35:53
2026-06-07 01:35:53 +08:00

14 KiB
Raw Blame History

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(用户需求/意图/目标)
        └── SubTaskAgent 执行的原子任务/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 表:

-- 新增字段
ALTER TABLE tasks ADD COLUMN stages_json TEXT DEFAULT '[]';

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}
]

父子关系(已有字段,启用使用):

-- 已存在,不需要 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 有自己独立的 statusassigneedepends_onstage
  • 子 Task 的 stage 字段绑定到父 Task 的 stages_json 中的某个 stage id

父 Task 状态 = 子 Task 聚合v2.8 更新):

父 Task 状态 推导规则
pending 所有子 Task 都是 pending,或无子 Task
escalated 有子 Task escalatedv2.8 新增,视同 working
waiting_human 有子 Task waiting_humanv2.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 基本一致,只加两步

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

  • 任务列表只显示顶层 Taskparent_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_cardCardOps 引用、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 延后