diff --git a/.scratch/v0.6/issues/01-campaign-persistence-and-api.md b/.scratch/v0.6/issues/01-campaign-persistence-and-api.md new file mode 100644 index 0000000..f60d475 --- /dev/null +++ b/.scratch/v0.6/issues/01-campaign-persistence-and-api.md @@ -0,0 +1,16 @@ +# 01 — 活动持久化 + 创建/查询 API + +**What to build:** 用户能通过 API 创建一个「评估活动」:提交一个绑定单个评测对象的静态活动计划(窗口长度、time_scale、若干"在窗口某偏移时刻对某场景跑几次"的计划条目),活动被持久化、状态为"计划中",并能按 id 查询取回完整活动。为后续调度与聚合打好数据地基。 + +**Blocked by:** 无 — 可立即开始。 + +**Status:** ready-for-agent + +- [ ] 新增评估活动持久化:窗口长度、状态(计划中/进行中/已完成/已取消/失败)、time_scale(默认 1.0)、静态计划(JSON 存取,仿现有 cases)、目标对象、时间戳 +- [ ] 评测运行增加可空的活动归属字段(campaign_id);非活动派生的手动 Run 该字段为空;不破坏现有 Run 的创建/查询/更新 +- [ ] 新增活动仓储,沿用现有仓储的 to_db/from_db 映射与 Session 管理约定 +- [ ] Alembic batch mode 迁移(SQLite),沿用现有迁移链与 render_as_batch 约定;全新库经 init_db 的 create_all 亦能建出新表与新列 +- [ ] `POST /api/campaigns` 校验并创建活动(计划非空、对象存在、窗口/倍速合法),返回活动 id 与初始状态 +- [ ] `GET /api/campaigns` 列表、`GET /api/campaigns/{id}` 详情(含计划与状态) +- [ ] 活动通过率等汇总口径为空占位,待后续票填充;本票不含调度与派生 +- [ ] API 集成测试(httpx AsyncClient 同现有 runs 测法):创建→取回一致、非法计划/不存在对象报错、campaign_id 迁移后既有 Run 不受影响 diff --git a/.scratch/v0.6/issues/02-scheduler-decision-and-run-spawn.md b/.scratch/v0.6/issues/02-scheduler-decision-and-run-spawn.md new file mode 100644 index 0000000..d979c40 --- /dev/null +++ b/.scratch/v0.6/issues/02-scheduler-decision-and-run-spawn.md @@ -0,0 +1,14 @@ +# 02 — 调度决策纯函数 + 派生子 Run + +**What to build:** 给定一个活动的静态计划和"当前活动时钟位置",一个纯函数能算出此刻应派生哪些子 Run(以及活动是否已到窗口终点)。据此实际派生普通 Run——每个子 Run 复用现有单次评测的执行路径、带上活动归属,跑完后其结果照常入库。这一票让"活动能真的按计划生出评测"成立,但不含自动循环(由测试或手动推进时钟驱动)。 + +**Blocked by:** 01(需要活动持久化与 campaign_id 归属字段)。 + +**Status:** ready-for-agent + +- [ ] 纯函数:输入活动计划 + 活动时钟位置(由真实经过时间 × time_scale 映射到窗口偏移),输出"到点应派生的计划条目"与"是否已达窗口终点";不触碰任何判定/报告口径 +- [ ] time_scale 只影响"何时到点",正式线 1.0=真实墙钟、开发线可压缩;判定与结果口径与倍速无关 +- [ ] 据纯函数结果派生普通 Run:复用现有评测执行入口(existing_run 路径),子 Run 携带 campaign_id,triggered_by 体现活动来源 +- [ ] 幂等:同一计划条目在同一到点不重复派生(推进进度可追踪) +- [ ] 纯函数单元测试(仿 test_orphan_runs 的纯 db_session 风格):注入不同时钟/倍速覆盖"未到点/到点/多条同时到点/已达终点"矩阵 +- [ ] 集成测试:手动推进时钟驱动一个压缩倍速活动,断言派生出的子 Run 数量、归属、场景与计划一致;子 Run 跑完后结果与 summary 正常入库 diff --git a/.scratch/v0.6/issues/03-durable-scheduler-loop-and-recovery.md b/.scratch/v0.6/issues/03-durable-scheduler-loop-and-recovery.md new file mode 100644 index 0000000..accc441 --- /dev/null +++ b/.scratch/v0.6/issues/03-durable-scheduler-loop-and-recovery.md @@ -0,0 +1,15 @@ +# 03 — 耐久调度回路 + 重启恢复 + 取消 + +**What to build:** 平台自带一个耐久调度循环,无需外部驱动即可让一个"进行中"的活动按其(可压缩的)窗口自动推进到终点:到点派生子 Run、窗口走完把活动置为"已完成"。服务重启后,调度从数据库恢复所有未完成活动、继续推进且不重复派生。用户可中途取消进行中的活动,已完成的子 Run 结果保留。 + +**Blocked by:** 02(需要调度决策纯函数与派生能力)。 + +**Status:** ready-for-agent + +- [ ] 调度循环挂在 Web 应用 lifespan:启动时加载所有"进行中"活动并推进,关闭时优雅停止;循环是薄壳,决策仍走 02 的纯函数 +- [ ] 活动进度(窗口起止、已派生/已完成到哪)为库中权威状态,进程内不持有权威态 +- [ ] 重启恢复:进程重启后据库续跑未完成活动,不重复派生已到点条目;沿用 mark_orphans_failed 思路处理重启时正在跑的子 Run +- [ ] 窗口走完活动状态转"已完成";派生/执行异常不拖垮整个活动(单条失败可跳过并记录) +- [ ] 取消:`POST /api/campaigns/{id}/cancel` 将进行中活动置"已取消"、停止后续派生,已完成子 Run 保留 +- [ ] `GET /api/campaigns/{id}` 详情反映实时进度(当前窗口位置、已派生/已完成子 Run 数、状态) +- [ ] 集成测试:压缩倍速活动端到端自动跑完并置"已完成";模拟重启(进行中活动 + 部分子 Run)后恢复续跑,断言不重复派生、不丢进度;取消后不再派生 diff --git a/.scratch/v0.6/issues/04-campaign-report-dual-axis.md b/.scratch/v0.6/issues/04-campaign-report-dual-axis.md new file mode 100644 index 0000000..88d5dd4 --- /dev/null +++ b/.scratch/v0.6/issues/04-campaign-report-dual-axis.md @@ -0,0 +1,15 @@ +# 04 — 活动周期报告(双主轴) + +**What to build:** 一个活动能产出周期评估报告,主轴一是**时间趋势**(通过率/可用性/时延随时段桶的曲线),主轴二是**能力汇总**(按场景/能力维度聚合的整窗表现)。报告消费该活动全部子 Run 的 summary,通过率口径与单 Run 一致(用例级、含执行失败),并可复用现有导出能力。 + +**Blocked by:** 02(需要活动已能派生出子 Run 以供聚合;可对进行中的活动做部分聚合)。 + +**Status:** ready-for-agent + +- [ ] 活动报告聚合纯函数:输入活动 + 其全部子 Run,输出双主轴结构 + - [ ] 时间趋势轴:按时段桶聚合通过率/可用性/时延(仿 stats.py trend 的分桶取均值) + - [ ] 能力汇总轴:按场景/能力维度聚合整窗表现(仿 stats.py dashboard 的分组取均值) +- [ ] 通过率沿用单 Run 的用例级、含执行失败口径(ADR-0002),保证跨层可比;故障子 Run 照常计入 +- [ ] `GET /api/campaigns/{id}/report` 返回结构化报告;复用现有报告导出路径 +- [ ] 单元测试(仿 test_report 的 _seed_run×N):构造跨多时段、多场景、含失败的子 Run,断言两轴数值正确;空/仅部分完成活动的边界(无除零、部分聚合合理) +- [ ] API 测试:对已有子 Run 的活动取报告,结构与数值符合预期 diff --git a/.scratch/v0.6/issues/05-frontend-campaigns-page.md b/.scratch/v0.6/issues/05-frontend-campaigns-page.md new file mode 100644 index 0000000..eed2059 --- /dev/null +++ b/.scratch/v0.6/issues/05-frontend-campaigns-page.md @@ -0,0 +1,16 @@ +# 05 — 前端「评估活动」页 + +**What to build:** 前端新增一个「评估活动」页(keep-alive tab),让用户能:创建活动(选对象、设窗口长度与 time_scale、编排静态计划条目)、在列表里一览所有活动(对象/窗口/状态/整窗通过率)、查看进行中活动的实时进度、以及查看结束活动的双主轴报告(时间趋势曲线 + 能力汇总)。 + +**Blocked by:** 01(创建/列表/详情 API)、03(实时进度与取消)、04(周期报告)。 + +**Status:** ready-for-agent + +- [ ] `api.ts` 新增 campaignsApi(创建/列表/详情/取消/报告)与 Campaign、CampaignReport 类型 +- [ ] 新增「评估活动」页并注册为 keep-alive tab(仿现有页面注册与标签方式) +- [ ] 创建活动交互:选评测对象、窗口长度、time_scale、编排计划条目(时段偏移 + 场景 + 次数) +- [ ] 活动列表:对象/窗口/状态/整窗通过率一览;进行中活动展示实时进度;可取消 +- [ ] 双主轴报告视图:时间趋势曲线 + 能力汇总(复用现有图表/报告组件与导出入口) +- [ ] 子 Run 可下钻:从活动报告能打开某个子 Run 的标准单 Run 报告(复用现有报告页) +- [ ] 版本标签、触发来源、通过率色阶等展示沿用现有约定;时间戳走 utils/date 的 UTC 处理 +- [ ] tsc 通过;浏览器验证创建→进度→报告全流程(用压缩倍速活动快速验证) diff --git a/.scratch/v0.6/spec.md b/.scratch/v0.6/spec.md new file mode 100644 index 0000000..708807f --- /dev/null +++ b/.scratch/v0.6/spec.md @@ -0,0 +1,92 @@ +# v0.6 Spec — 评估活动(Campaign)v1 静态地基 + +**状态**: ready-for-agent +**领域词汇**: 见 CONTEXT.md「周期评估」章节(评估活动 / 服务周期窗口 / 活动计划) +**关键决策**: ADR-0003(分期:v1 静态地基、v2 自适应回路;OpenClaw 进调度热回路的取舍) + +## Problem Statement + +被评智能体(AI 数字员工 / AI 助手)提供的是 7×24 的持续服务,但现有平台只能做「一个对象 × 一个场景」的单次评测(Run)。用户拿不到「这个智能体在一个完整服务周期内,服务质量如何随时间波动」的画像——夜间是否降级、高峰是否变慢、凌晨无人值守时表现如何,这些只有跨时段持续采样才能暴露。用户需要一种能在一个可配置长度的服务周期窗口内、按计划持续对同一对象施加多场景评测、并汇总成周期报告的能力。 + +## Solution + +引入**评估活动(Campaign)**:针对单个评测对象、跨一个服务周期窗口(可配 6/12/24/48/72 小时)的评估聚合。用户定义一份**静态活动计划**(在窗口的哪些时间点、对哪个场景、跑几次),平台的**耐久调度器**在窗口内按计划持续派生普通 Run,最后聚合成**双主轴周期报告**(时间趋势 + 能力汇总)。活动状态入库、抗重启:服务重启后调度器从数据库恢复,继续未完成的窗口。多对象通过并行多个活动 + 跨活动对比实现(跨活动对比可后续迭代)。 + +v1 计划是静态的(手工定义,或后续由 OpenClaw 在启动时一次性生成);OpenClaw 在窗口内依结果自适应重规划的能力是 v2(ADR-0003)。 + +活动支持**可配时间倍速**:正式线用真实墙钟(24h 活动真的持续 24h,采到真实的凌晨/高峰表现);开发线可设压缩倍速(如把 24h 压成几分钟)快速验证调度与聚合,不改变任何判定语义。 + +## User Stories + +1. 作为质量负责人,我想对一个数字员工发起一个 24 小时评估活动,以便看到它一整天的服务质量画像。 +2. 作为质量负责人,我想自定义活动窗口长度(6/12/24/48/72h),以便匹配不同的服务周期考察需求。 +3. 作为质量负责人,我想在活动计划里指定"哪些时段跑哪个场景、各跑几次",以便模拟真实用户在一天里不同时间的不同诉求。 +4. 作为质量负责人,我想让活动只针对单个评测对象,以便活动身份清晰、报告维度单纯。 +5. 作为质量负责人,我想在活动进行中查看它的实时进度(已派生多少 Run、当前窗口位置、已完成时段的通过率),以便掌握活动健康度。 +6. 作为质量负责人,我想在活动结束后得到一份周期报告,主轴一是**时间趋势**(通过率/可用性/时延随时段的曲线),以便发现质量随时间的波动。 +7. 作为质量负责人,我想周期报告的主轴二是**能力汇总**(按场景/能力维度聚合的整窗表现),以便看到这一周期里哪些能力强、哪些弱。 +8. 作为质量负责人,我想活动派生的每个 Run 仍是标准 Run(可单独打开看逐轮对话与判定),以便下钻排查具体失败。 +9. 作为运维,我想活动在服务重启后能自动恢复继续跑,以便长窗口活动不因一次部署而中断作废。 +10. 作为运维,我想中途取消一个进行中的活动,以便及时止损;已完成的子 Run 结果保留。 +11. 作为开发者,我想在开发线用时间压缩倍速把 24h 活动压成几分钟跑完,以便快速验证调度与报告聚合。 +12. 作为质量负责人,我想活动列表能看到每个活动的对象、窗口、状态(计划中/进行中/已完成/已取消/失败)、整窗通过率,以便一览管理。 +13. 作为质量负责人,我想活动的通过率口径与单 Run 一致(用例级、含执行失败,ADR-0002),以便跨层数字可比。 +14. 作为质量负责人,当某个时段的 Run 因通道故障失败时,我想它照常计入周期报告(失败也是质量信号),以便报告反映真实可用性。 +15. 作为质量负责人,我想活动报告能导出(复用现有导出能力),以便存档与分享。 + +## Implementation Decisions + +### 领域与聚合 +- **评估活动是 Run 之上的新聚合**,不是巨型长命 Run。活动按计划派生多个普通 Run(每个仍是"一个对象 × 一个场景 × 一个时段"),完整复用现有引擎、规则、判定(judgement)、单 Run 报告。 +- **单对象**:一个活动绑定一个 `target_id` + 一份计划(计划条目引用若干 `scenario_id`)。多对象=并行多个活动。 +- 子 Run 通过一个新的可空外键 `campaign_id` 归属到活动;非活动派生的手动 Run 该字段为空。子 Run 复用现有 `EvalEngine.run(existing_run=...)` 派生路径。 + +### 活动计划(静态) +- 计划是一组条目,每条描述:目标场景、在窗口内的触发时间点(相对窗口起点的偏移)、重复次数/强度。存为活动记录上的 JSON(仿现有 cases 的 JSON 列存取),不新建计划子表。 +- 计划由用户创建活动时提供;v1 不含 OpenClaw 生成/自适应(ADR-0003)。 + +### 耐久调度器 +- 平台新建一个耐久调度子系统,生命周期挂在 Web 应用 lifespan:启动时从数据库加载所有"进行中"的活动并恢复推进,关闭时优雅停止。 +- **调度决策是纯函数**:给定活动计划 + 当前活动时钟位置,算出"此刻应派生哪些 Run / 活动是否已达窗口终点"。外层是薄的异步循环 + 派生 Run 的副作用壳。这是主要的新可测接缝,仿 v0.5 的 `combine_case_outcome`。 +- **抗重启**:活动状态(窗口起止、已完成/已派生进度)入库;进程内不持有权威状态。重启后调度器据库恢复。沿用 `mark_orphans_failed` 的思路处理重启时正在跑的子 Run。 +- **时间倍速**:活动记录带 `time_scale`(默认 1.0=真实墙钟)。活动时钟 = (真实经过时间 × time_scale) 映射到窗口位置。倍速只影响"何时派生 Run",不影响任何判定/报告口径。开发线可设大倍速加速;正式线用 1.0。 + +### 报告聚合(双主轴) +- 新增活动报告聚合纯函数:消费该活动全部子 Run 的 summary,产出两轴: + - **时间趋势轴**:按时段桶聚合通过率/可用性/时延(仿 stats.py 的 trend 按时间分桶取均值)。 + - **能力汇总轴**:按场景/能力维度聚合整窗表现(仿 stats.py 的 dashboard 分组取均值)。 +- 通过率口径沿用单 Run 的用例级、含执行失败(ADR-0002),保证跨层可比。 +- 报告导出复用现有 report 渲染/导出能力。 + +### API 与前端 +- 新增活动 API:创建 / 列表 / 详情(含进度)/ 报告 / 取消,形状与命名沿用现有 `runsApi` 风格。 +- 前端新增「评估活动」页(keep-alive tab,仿现有页面注册方式):活动列表 + 创建 + 进度 + 双主轴报告视图。`api.ts` 新增 `campaignsApi` 与 `Campaign`/`CampaignReport` 类型。 + +### 持久化与迁移 +- 新增活动表(含窗口、状态、计划 JSON、time_scale、时间戳);`EvalRunDB` 加可空 `campaign_id`。 +- Alembic batch mode 迁移(SQLite),沿用现有迁移链与 `render_as_batch=True` 约定;`init_db` 的 create_all 覆盖全新库。 + +## Testing Decisions + +- **好测试只验外部行为**:断言 API 契约、调度决策的输出、报告聚合的数值,不耦合内部实现细节。 +- **三接缝**(已与用户确认): + 1. **API 集成接缝**:`POST/GET /api/campaigns`、`GET /api/campaigns/{id}/report`、取消——用 `test_runs_api.py` 同款 httpx AsyncClient + MockChannel + monkeypatch get_session 端到端测。 + 2. **调度决策纯函数接缝**:`due_entries(plan, clock) -> 待派生条目`,注入任意时钟/倍速,无需真实定时器,仿 `test_orphan_runs.py` 的纯 `db_session` 风格。 + 3. **活动聚合纯函数接缝**:`generate_campaign_report`,用 `_seed_run × N` 构造多子 Run 后断言两轴数值,仿 `test_report.py`。 +- **时间压缩用于测试**:调度决策纯函数吃注入时钟,测试可瞬间推进整个窗口,无需等待。 +- **抗重启测试**:构造"进行中"活动 + 部分子 Run,模拟重启后调度恢复,断言不重复派生、不丢进度。 +- 现有 `conftest.py` 的 `db_session`/TestClient 夹具与显式模型 import 列表需加入新活动模型。 + +## Out of Scope + +- **OpenClaw 生成/自适应计划**(v2,ADR-0003):v1 计划静态、执行期不依赖 OpenClaw。 +- **Layer 2 逐轮自由漫游**:时段内对话仍由现有动态用例生成器产出,不做 OpenClaw 逐轮即兴。 +- **跨活动对比视图**:多对象对比留待后续迭代;v1 先把单活动闭环。 +- **人设(persona)深度建模**:v1 计划条目可带轻量人设标签,但不强制影响生成;深度人设留待 v2。 +- **告警/通知**:活动异常主动通知不在 v1。 + +## Further Notes + +- 与既有决策的一致性:单对象聚合 + 通过率用例级含故障,均与 ADR-0001/0002 对齐。 +- 风险点:耐久调度器是全新子系统,长窗口 + 重启恢复是主要复杂度来源;建议 to-tickets 时把「调度决策纯函数 + 持久化 + 恢复」作为最早的 tracer-bullet,先立稳地基再叠 API/前端/报告。 +- 时间倍速是把双刃剑:务必保证它只影响派生时机,绝不渗入判定/报告口径,否则开发线与正式线数字不可比。