# Batch J Worker C:ACP 稳定性与 benchmark checklist 草案 > 创建时间:2026-05-21 > > 只读审查,不修改代码。 ## 1. 读取过的关键文件 | 文件 | 行数 | 内容 | |------|------|------| | `design/07-ai/process/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md` | 1029 | ACP 统一层设计:架构、协议、session lifecycle | | `design/07-ai/process/7-34-acp-runtime-cleanup-availability-stability-tail-v1.md`| 300+ | 本任务上游:P0/P1/P2 checklist | | `design/07-ai/done/7-25-acp-session-runtime-enhancement-plan-v1.md` | 371 | Session 持久化与管理增强规划 | | `design/07-ai/done/7-30-acp-session-load-resume-checklist-v1.md` | ~80 | `session/load` 闭环已归档 | | `design/07-ai/done/7-31-acp-permission-decision-loop-checklist-v1.md` | ~80 | Permission 决策闭环已归档 | | `design/07-ai/done/7-32-acp-tool-location-and-open-action-checklist-v1.md` | ~80 | Tool locations + open action 已归档 | | `design/07-ai/done/7-33-acp-session-info-plan-ui-checklist-v1.md` | ~80 | Session info + plan UI 已归档 | | `rust/crates/mnote-web/src/acp_runtime.rs` | 500+ | Runtime lifecycle manager | | `rust/crates/mnote-web/src/acp_session_manager.rs` | 700+ | Session lifecycle + event dispatch | | `rust/crates/mnote-web/src/acp_bridge.rs` | 400+ | ACP ↔ SSE bridge | | `rust/crates/mnote-web/src/acp_client.rs` | 746 | JSON-RPC 2.0 client | | `rust/crates/mnote-web/src/acp_types.rs` | 800 | ACP 协议类型定义 | ## 2. ACP 多会话稳定性 smoke 最小脚本方案 ### 2.1 测试目标 验证 ACP runtime 在处理 3+ 并发 session 时的稳定性:session 创建、prompt 流式、cancel、子进程异常恢复、事件去重。 ### 2.2 测试设计 **方案类型**:Playwright 独立脚本 + Rust 端到端测试 **输入**: 1. 3 个参数化的 ACP run profile(reasonix + hermes × 2 不同 profile) 2. 每个 session 的 prompt 文本(简单固定 prompt:"请用中文说'你好',不要做其他操作") 3. 超时配置(每个 run 最长 15s) **操作步骤**: ``` Step 1: 创建 3 个 ACP session(POST /api/hermes/client/sessions) ├─ session_1: profile=reasonix ├─ session_2: profile=hermes (profile=mnoteai) └─ session_3: profile=hermes (profile=default) Step 2: 在 3 个 session 上启动 run(POST /api/hermes/client/sessions/{id}/runs) 并发发起,每个 session 间隔 ≤500ms Step 3: 通过 SSE 流式读取事件(GET /api/hermes/client/events/{run_id}) 对每个 run 读取到 run.completed 或超时 15s 记录:message.delta 事件数量、tool.started、tool.completed Step 4: cancel 验证 Step 2 后立即 cancel session_2(POST /api/hermes/client/runs/{run_id}/abort) 验证收到 run.failed / abort.completed Step 5: 验证事件去重 对 session_1 的 SSE 流,检查 textDelta 的 deduplication 逻辑 (accumulated text 不重复增长) Step 6: 清理 删除 3 个 session(DELETE /api/hermes/client/sessions/{session_id}) ``` **断言**: | # | 断言 | 优先级 | |---|------|--------| | 1 | 3 个 session 均创建成功(返回 200 + sessionId) | P0 | | 2 | 3 个 run 均收到 `run.completed`(无超时) | P0 | | 3 | session_2 的 cancel 在 3s 内生效(收到 `run.failed` 或 `abort.completed`) | P0 | | 4 | 所有 SSE 流在 `run.completed` 后 500ms 内自动关闭(无泄漏) | P1 | | 5 | session_1 的 message.delta 累计 length 不小于 4(中文"你好") | P1 | | 6 | 事件去重正常:textDelta 无重复或突然截断 | P1 | | 7 | 3 个 session 互相不干扰(事件不交叉) | P1 | | 8 | ACP 子进程数 = session 数(无 zombie) | P2(度量) | **证据文件**: - `artifacts/acp-stability/events-session-1.jsonl` - `artifacts/acp-stability/events-session-2.jsonl` - `artifacts/acp-stability/events-session-3.jsonl` - `artifacts/acp-stability/runtime-metrics.json` - `artifacts/acp-stability/process-list.txt`(`ps aux | grep -E 'hermes|acp'`) ### 2.3 浏览器可见验证点 1. **session 列表** — 打开页面 AI drawer → session list 应显示 3 个活跃 session 2. **run 状态** — 每个 session 的 run 状态从 `sending` → `running` → `completed` 3. **cancel 反馈** — 被 cancel 的 session 显示 "已请求停止" + 后续不再有 tool call 4. **无 UI 异常** — 无 502/500 错误,无工具 manifest 加载失败 建议截图: - 3 session 同时运行的页面 AI drawer 快照 - cancel 后的 UI 状态 - 清理后 session 列表为空 ## 3. Reasonix cache benchmark 最小脚本方案 ### 3.1 测试目标 对比 Hermes / Reasonix 在相同 prompt 下的首次运行、二次运行(cache 命中)的耗时差异。 ### 3.2 测试设计 **输入**: | 维度 | 值 | |------|-----| | Prompt | "读取当前页面,列出前三段的主题"(约 20 token) | | 样本数 | 每 runtime × 2(cold + cached)= 4 次 | | Runtime | Hermes (mnoteai profile)、Reasonix | | 页面 | 已知内容的本地 Markdown 文档(>50 行) | **操作步骤**: ``` Step 1: 预热 创建一个 ACP session(profile=reasonix),发送 prompt,等待完成,删除 session Step 2: 冷启动测试(Reasonix) 新 session(profile=reasonix)→ prompt → 计时 → 记录 total_ms Step 3: Cache 命中测试(Reasonix) 同一 session re-prompt → 计时 → 记录 total_ms 注意:确保 prompt 内容一致,观察 response 中是否有 cache hit 提示 Step 4: 冷启动测试(Hermes) 新 session(profile=hermes/mnoteai)→ prompt → 计时 → 记录 total_ms Step 5: Cache 命中测试(Hermes) 同一 session re-prompt → 计时 → 记录 total_ms Step 6: 重复 Step 2-5 共 1 轮(共 8 次测量) ``` **输出格式**(JSON): ```json { "schema": "mnote.acp_cache_benchmark.v1", "timestamp": "2026-05-21T12:00:00Z", "runs": [ { "runtime": "reasonix", "phase": "cold", "trial": 1, "totalMs": 4230, "firstTokenMs": 1200, "completionMs": 3030, "toolCalls": 1, "messageCharCount": 85, "cacheHit": false }, { "runtime": "reasonix", "phase": "cached", "trial": 1, "totalMs": 890, "firstTokenMs": 210, "completionMs": 680, "toolCalls": 1, "messageCharCount": 85, "cacheHit": true } ], "summary": { "reasonixColdAvgMs": 4440, "reasonixCachedAvgMs": 920, "hermesColdAvgMs": 5100, "hermesCachedAvgMs": 3100, "speedupRatio": 4.8 } } ``` ### 3.3 浏览器验证点 1. **页面 AI drawer 运行时选择器** — 切换 hermes/reasonix 时的 UI 响应 2. **首次对话速度** — 冷启动时 UI 从 "thinking" 到首次 token 的延迟 3. **cache 提示** — Reasonix cached run 的 tool.call/complete 速度应更快 4. **内存观察** — 浏览器 devtools → Performance → JS heap 无异常增长 ### 3.4 建议测试脚本存放位置 ``` scripts/task-acp-stability-smoke.js ← 多会话稳定性 scripts/task-acp-cache-benchmark.js ← cache benchmark ``` 参考已有:`scripts/task-page-block-ai-tools-smoke.js`、`scripts/task-hermes-page-ai-baseline-smoke.js`。 ## 4. P0 vs P2 归类 | 测试项 | 级别 | 理由 | |--------|------|------| | 3 个 session 创建 + 正常完成 | **P0** | 并发是基本稳定性要求 | | Cancel 在合理时间内生效 | **P0** | UX 基础:用户停止必须工作 | | SSE 流在 run 完成后关闭 | **P1** | 防止资源泄漏 | | 事件去重正确 | **P1** | 文本重复或截断影响 UX | | Session 间事件不交叉 | **P1** | 数据隔离是基本正确性 | | Cold vs cached 耗时对比 | **P2** | 度量性质,不影响正确性 | | 子进程数量 | **P2** | 度量性质 | | 具体第一 token 时间 | **P2** | 度量性质 | ## 5. 建议的现有 targeted test ```bash # 现有 ACP 单元测试(可复用) cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web acp_session_manager -- --test-threads=1 cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web acp_bridge -- --test-threads=1 cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web acp -- --test-threads=1 # 现有 smoke 测试(参考模式) node scripts/task-page-block-ai-tools-smoke.js node scripts/task-hermes-page-ai-baseline-smoke.js ```