Files
smartmate/docs/backend/主动调度缺口分阶段实施计划.md
Losita ba23ebd201 Version: 0.9.62.dev.260502
后端:
1. 主动调度补齐 `unfinished_feedback` 定位闭环——用户补充信息先在滚动窗口内定位到可校验的日程块,定位失败则继续 ask_user,不再硬猜 target_id 或直接跑 graph。
2. 聊天占管重跑链路加并发保护——`waiting_user_reply -> rerunning` 改为 DB CAS 抢占,重复补充只返回可见等待提示,避免并发生成多份 preview。
3. rerun 结果回写继续收口——新 preview_id 同步回 trigger 审计指针,session 只在拿到新 preview 时更新当前预览,ready_preview 后清空追问状态并释放回普通聊天。
4. 主动调度事件校验放宽 unfinished_feedback 的空 target 场景,允许先触发、后定位,再进入 graph + preview 主链路。
2026-05-02 12:41:50 +08:00

459 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 主动调度缺口分阶段实施计划
本文档用于把《第二阶段主动调度 MVP 实现方案.md》《主动调度候选生成器讨论稿.md》和当前代码仓库的实际状态收口到一份可执行的推进计划里。
目标只有一个:把主动调度剩下的缺口按阶段补完,并且每个阶段都能明确验收、明确自动化边界、明确是否已经完成。后续我会在这里持续把 `[ ]` 改成 `[x]`
补充约定:本文档是阶段进度看板,不再重复写设计细节;已经落地的阶段必须及时改成 `[x]`,并保留简短验证记录。实现方案和边界口径以《第二阶段主动调度 MVP 实现方案.md》为准。
---
## 0. 当前仓库基线
先把现在已经有的和还缺的分开,避免后面阶段定义漂移。
### 已经落地的基座
- [x] `backend/active_scheduler` 已经形成准独立模块,包含 `context / observe / candidate / preview / apply / service / job` 等目录。
- [x] `dry-run -> trigger -> preview -> confirm -> apply` 主链路已经存在。
- [x] `active_schedule.triggered``notification.feishu.requested``notification_records`、用户级飞书 webhook 配置接口已经打通。
- [x] `active_schedule_previews``schedule_events.task_source_type / makeup_for_event_id / active_preview_id``tasks.estimated_sections` 这些模型层字段已经存在。
- [x] `api / worker / all` 三种启动边界已经有实测基础。
- [x] `important_urgent_task``unfinished_feedback` 的主触发链路已经跑过一轮端到端。
### 近期缺口收口状态
- [x] `UserAddTaskRequest`、转换层、quick task / 随口记创建入口已完整透传 `estimated_sections`
- [x] `CreatePreview` 已切到 graph + 受限 selector不再是固定 top1 / `Candidates[0]`
- [x] `active_schedule_sessions` 已正式进入代码,并接好缓存链路。
- [x] 聊天入口已按 session 状态拦截,`waiting_user_reply / rerunning` 会接管补信息链路。
- [x] `unfinished_feedback` 的“定位 -> ask_user -> 重跑 graph”后端闭环已经接上前端最小适配也已落地。
- [x] 聊天页里的主动调度 preview 卡片 / 微调弹窗已完成最小适配。
- [ ] 剩余极限验收项已做过实测验证,但还在继续脚本化沉淀。
### 近期实测记录2026-05-02
这轮重新核验后,主动调度最小闭环已经再次跑通,可作为后续阶段 5 负向边界验收的基线。
1. 测试账号为 `test0424 / 123456`,任务为“做马原大作业”,`mock_now` 固定到 `2026-04-30T01:14:21+08:00` 的周四窗口。
2. 这次链路跑出了 `trigger_id=ast_1bb62e3e-f2cf-48a9-8f29-1461b99bff6b``preview_id=asp_e79db789-ba16-4108-a843-cd33c03aa3f6``conversation_id=ce525dc0-101a-50ca-8993-7fc466328de2``notification_records.id=22`
3. DB 对账显示 `active_schedule_triggers.status=preview_generated``active_schedule_previews.status=ready``active_schedule_sessions.status=ready_preview`,而 `schedule_events` 对该 preview 的正式写入计数为 0说明这次只到预览态没有执行正式 apply。
4. 浏览器里周四列同时存在已有课程和待确认任务块;周六窗口只剩一个孤立任务块属于正常现象,不是后端漏课程。
5. 这批结果说明:当前收口已经不在“主链路能不能跑通”,而是在“阶段 5 的过期、重复、篡改、冲突、幂等、通知和 outbox 负向边界能不能全部挡住”。
### 代码锚点
后续实施时优先看这些位置:
- `backend/model/task.go``Task.EstimatedSections` 已存在,普通创建请求已接入 `estimated_sections`
- `backend/conv/task.go`:任务创建请求转模型时已透传预计节数。
- `backend/cmd/start.go`quick task 创建依赖已透传预计节数;主动调度 graph runner / LLM selector / session rerun 也已在启动期装配。
- `backend/active_scheduler/preview/service.go`preview 已支持 `SelectedCandidateID / ExplanationText / NotificationSummary / FallbackUsed`
- `backend/active_scheduler/graph`:阶段 1 graph runner 已落地;`backend/active_scheduler/selection` 承载 LLM selector / prompt / DTO。
- `backend/active_scheduler/service/trigger_pipeline.go`trigger workflow 已调用 graph result再写 preview 和 notification。
- `backend/service/agentsvc/agent_newagent.go``backend/service/agentsvc/agent_active_schedule_session.go``backend/api/agent.go`:聊天入口和 graph 执行边界已接 session 管辖。
- `frontend/src/components/dashboard/AssistantPanel.vue`:主动调度卡片与确认按钮做最小分支。
- `backend/notification`:飞书 webhook provider、notification 状态机和重试逻辑的已有基座。
### 与 execute 链路的接缝
主动调度不复制 `task_item`,也不新建一套排程写入链路。
最小接入方式:
1. 主动调度 session 只负责识别当前会话是否处于主动调度占管态,并生成待确认 preview。
2. preview / 微调 / 确认继续复用现有会话维度的 `ScheduleState` 快照;前端拖拽仍走现有暂存语义,后端只更新同一份状态。
3. 用户确认后,后端按 preview / edited changes 做重校验并正式写入 `schedule_events / schedules`
4. 主动调度释放占管后,后续普通聊天直接回到 newAgent `execute``execute` 继续在 `ScheduleState / OriginalScheduleState` 上调用现有 `move / swap / place / unplace` 等工具。
因此,本轮新增的是 session、路由拦截、preview DTO 适配和 graph 裁决;不是新增排程引擎,也不是把待调整任务复制成新任务再排一次。
平滑接回聊天的边界:
1. graph 不直接进入 newAgent `execute`,也不把主动调度包装成 ReAct 工具。
2. graph 结果由后端转换成 conversation timeline追问写 `assistant_text`,预览写 `business_card.card_type=active_schedule_preview`
3. `waiting_user_reply / rerunning` 期间chat 入口拦截用户消息并同步推进 graph`ready_preview` 或终态后session 释放,普通聊天自然回到 newAgent。
---
## 1. 分阶段实施总表
| 阶段 | 状态 | 目标 | 验收点 | 自动化测试 |
| --- | --- | --- | --- | --- |
| 阶段 0 | [x] | 补 `estimated_sections` 写入入口 | 创建任务时能稳定写入 1~4 节,主动调度只消费落库值 | 可以API + DB + `go test` |
| 阶段 1 | [x] | 补主动调度 Eino graph 和 LLM 解释 / 补全兜底 | 产生候选、有限裁决、输出解释、保留 fallback | 可以,后端单测 + API 验证 |
| 阶段 2 | [x] | 补 `active_schedule_sessions`、聊天拦截和缓存链路 | `waiting_user_reply / rerunning` 拦截生效,`ready_preview` 释放 | 可以API + DB + 路由验证 |
| 阶段 3 | [x] | 完成 `unfinished_feedback``ask_user` 闭环和前端最小适配 | 用户在聊天页补信息后能重跑 graph 并刷新 preview | 后端可自动,前端需浏览器验证 |
| 阶段 4 | [x] | 收口飞书通知与会话链接 | `action_url` 指向 `/assistant/{conversation_id}`,通知 payload 从简 | 可以webhook POST + DB 验证 |
| 阶段 5 | [ ] | 跑完第五阶段剩余验收和失败注入脚本 | 冲突、过期、重复确认、重试、dead/skipped 全覆盖 | 可以,基本全自动 |
---
## 2. 阶段细则
### 阶段 0补 `estimated_sections` 写入入口
**当前状态**
`model.Task.EstimatedSections` 已经有了,主动调度消费侧也已经接上,这一阶段的写入侧已经补完,并完成 API、聊天 quick task 和数据库三方验收,可以收口。
**已完成内容**
1.`UserAddTaskRequest` 增加 `estimated_sections`
2.`backend/conv/task.go``backend/cmd/start.go` 和 quick task 创建入口里把这个值写入 `model.Task`
3. 做统一归一化,缺失或非法时兜底为 `1`,超过上限收敛到 `4`
4. 让 newAgent / 随口记创建任务时能把 LLM 估计结果带入 DB。
5. 让任务查询 DTO 和 quick task 卡片也能回显这个字段,方便验收。
**验收点**
1. 任务创建后,查询结果里能看到最终写入的 `estimated_sections`
2. 缺失或越界值会被收敛到 `1~4`
3. 主动调度不再在 graph 内重新猜任务耗时。
4. 旧任务和历史数据仍能按默认 `1` 兼容。
**验证记录**
1. 已执行 `go test ./...`
2. 已清理本次测试生成的 `.gocache` / `.gopath`
3. 普通 `POST /api/v1/task/create` + `GET /api/v1/task/get` + MySQL 对账已通过,返回和落库均为 `estimated_sections=3`
4. `POST /api/v1/agent/chat` 已验证可创建任务,`tasks` 表中 `id=145` 的记录落库为 `title=交实验报告``priority=1``estimated_sections=1``deadline_at=2026-05-02 20:00:00`
5. 当前仓库没有保留临时 `*_test.go`
**自动化测试**
- 可以自动跑。
- 建议路径:任务创建 API + DB 断言 + `go test ./...`
- 若当轮实现了输入输出纯函数,临时单测可写完即删,测试后清理 `*_test.go`
---
### 阶段 1补主动调度 Eino graph 和 LLM 解释 / 补全兜底
**当前状态**
现在 graph / selector 已经接上,核心链路是 `BuildContext -> Observe -> GenerateCandidates -> SelectAndExplain -> CreatePreview`,不再停留在固定 top1 过渡实现。
**收口状态**
1. graph 已从固定 pipeline 升级为可复用 runner。
2. LLM 只在受限候选里做有限选择,后端 fallback 仍保留。
3. 这一阶段已经从“待做”转为“已完成”,后续只保留后续调优项。
**已完成内容**
1. 把现有 pipeline 整成可扩展 graph物理位置固定在 `backend/active_scheduler/graph`,不放进 `newAgent`
2. 把 LLM 放到“只读 / 受限工具视图”里后端仍是候选合法性、粗排和默认裁决的主责任方LLM 只做有限选择、解释生成和信息补全兜底。
3. 给 LLM 的输入分两层:
- 基础信息:`trigger_type / target / time_window / missing_info / warnings / before-after 摘要`
- 候选层:`candidate_id / candidate_type / summary / preview_change / dimensions`
- 不暴露原始全量事实快照
4. 增加选择题协议:
```json
{
"action": "select_candidate",
"selected_candidate_id": "xxx",
"reason": "选择原因",
"user_message_summary": "给用户看的简短解释",
"ask_user_question": ""
}
```
5. 支持 `select_candidate / ask_user / notify_only / close`
6. 保留后端 fallbackLLM 超时、输出非法、候选不存在时回落到后端粗排结果;事实不足时回落到后端 `ask_user` 问题。
7. 模型接入只走一次同步 JSON 调用:启动期复用 `inits.InitEino()` 里的 `aiHub.Pro`,再包装成 `backend/infra/llm.Client`,由 `backend/active_scheduler/selection` 层调用 `GenerateJSON`;默认不走流式、不走 ReAct、不开放工具。
8. 候选维度只保留真正有用的最小集:
- `capacity_fit`
- `risk_level`
9. `confidence` 不作为 LLM 可见候选维度;事实可信度只用于内部 trace / `ask_user` 判断,低可信事实不生成正式候选。
10. 明确不把这些东西重新做成第一版主维度:
- `deadline_fit`
- `user_preference_fit`
- `disruption`
- `reversibility`
- `explainability`
**验收点**
1. dry-run 和正式 trigger 都能走到 graph。
2. graph 能产出多个合法候选,而不是只剩一个 first-fit。
3. LLM 不直接写正式日程参数,也不替代后端粗排;它的主要价值是解释、在接近候选间有限裁决、以及事实不足时生成更自然的追问。
4. LLM 能看到基础上下文和候选摘要,但不会拿到原始全量快照;候选维度只公开 `capacity_fit / risk_level`
5. `ask_user` 只有在信息不足时才出现。
6. `compress_with_next_dynamic_task` 仍然默认关闭,不在这阶段打开。
7. LLM selector 能被 fake selector 替换方便单测覆盖“选第二个候选、输出非法、fallback 命中”这几类情况。
**自动化测试**
- 可以自动跑。
- 建议路径候选过滤单测、选择题解析单测、dry-run API 验证、trigger 端到端验证。
- 如果 Eino graph 需要调包,实现时要先对照官方文档,再落代码。
**接入方向**
这层 graph 放在 `backend/active_scheduler/graph`,由 active scheduler 自己持有业务编排;`newAgent` 不拥有 graph只在聊天入口被 session 拦截时同步调用 active scheduler service。后续如果拆微服务把 graph runner 换成 RPC 同步调用即可不改聊天、worker、API 三个入口的业务口径。
阶段 1 的落地顺序:
1. 先落 graph / selection 包,把 `BuildContext -> Observe -> GenerateCandidates -> SelectAndExplain` 这条链串起来。
2. 再把 `TriggerWorkflowService` 从固定 pipeline 切到 graph runner确保 dry-run / trigger 先吃到新裁决结果。
3. 然后改 `preview.CreatePreviewRequest``preview.Service`,让 `SelectedCandidateID / ExplanationText / NotificationSummary / FallbackUsed` 真正进 preview。
4. 最后在 `cmd/start.go` 做装配,把 `aiHub.Pro -> llm.Client -> selector -> graph runner` 串进启动期依赖图。
5. 完成后先用 API dry-run 和 trigger 验证,再去看 preview detail 是否正确回显 selected candidate。
---
### 阶段 2补 `active_schedule_sessions`、聊天拦截和缓存链路
**当前状态**
`active_schedule_sessions`、session bridge、聊天入口拦截和缓存回填都已经接上`waiting_user_reply / rerunning` 会先由主动调度接管,`ready_preview` 之后再释放回普通聊天链路。
**收口状态**
1. session 表已落地,`session_id / user_id / conversation_id / trigger_id / current_preview_id / status / state_json` 这些核心字段都在代码里。
2. 后端 chat 路由已按 session 状态拦截,不会再把占管态消息误进自由聊天。
3. Redis + MySQL 的双写回填链路已经打通session 状态可回源、可回填。
4. 这一阶段可以收口,后续只继续补 `unfinished_feedback` 闭环和前端最小适配。
**已完成内容**
1. 新增 `active_schedule_sessions`
2. 先把字段压到最小够用集:
| 字段 | 含义 |
| --- | --- |
| `session_id` | 主动调度会话主键 |
| `user_id` | 归属用户 |
| `conversation_id` | 绑定到现有聊天会话 |
| `trigger_id` | 这次主动调度触发来源 |
| `current_preview_id` | 当前正在展示 / 等待处理的 preview |
| `status` | 会话状态 |
| `state_json` | 轻量业务状态,例如 `pending_question / missing_info / last_candidate_id / last_notification_id / expires_at / failed_reason` |
| `created_at` | 创建时间 |
| `updated_at` | 更新时间 |
3. 状态只保留这几种:
- `waiting_user_reply`
- `rerunning`
- `ready_preview`
- `applied`
- `ignored`
- `expired`
- `failed`
4. 聊天入口在后端直接查 session 状态:
- `waiting_user_reply / rerunning`:拦截,先走主动调度补信息链路。
- `ready_preview / applied / ignored / expired / failed`:释放给普通聊天。
5. session 和 conversation/timeline 分开记账:
- timeline 记用户可见对话。
- session 记主动调度路由管辖权。
6. session 要接缓存链路:
- 先查热缓存。
- miss 再回源 DB。
- 状态变化后同步回填。
- 构造用户消息时把 session / preview / pending question 一起缓存好。
7. `ask_user` pending 不复用 newAgent `PendingInteraction` 作为状态源,只借鉴交互协议。
8. outbox 不作为用户回复重跑 graph 的主驱动;主路径必须同步完成,再给 SSE / timeline 更新。
9. graph 结果接回现有聊天协议:
- `ask_user`:写 `assistant_text`session 继续 `waiting_user_reply`
- `select_candidate / ready_preview`:写 `assistant_text` + `business_card.card_type=active_schedule_preview`session 进入 `ready_preview`
- `close / notify_only / failed`:写 `assistant_text`session 进入终态并释放普通聊天。
**验收点**
1. 同一个 conversation 进入聊天页时,能按 session 状态正确拦截或放行。
2. `waiting_user_reply / rerunning` 状态下,用户消息不会直接滑进普通自由聊天链路。
3. session 状态变化后,缓存和 DB 一致。
4. 关键审计链能串起来:`trigger_id -> preview_id -> conversation_id -> apply_id`
5. 用户刷新会话历史时,主动调度追问和 preview 卡片能从 timeline 重建,不依赖 SSE 临时状态。
**自动化测试**
- 可以自动跑。
- 建议路径API + DB 断言、路由拦截测试、缓存命中/回源测试。
- 需要你先把对应后端服务按模式起好时,我可以直接接着跑。
---
### 阶段 3补 `unfinished_feedback`、`ask_user` 闭环和前端最小适配
**当前状态**
`unfinished_feedback` 的后端定位闭环、聊天页最小适配和并发兜底都已经收口,阶段 3 已完成。
**已落地内容**
1. 定位逻辑按这个顺序走:
- LLM 上下文推断
- 当前时间
- 已排日程窗口
2. 定位成功后直接出补做 preview不移动原任务。
3. 定位失败时,不硬猜,直接 `ask_user`
4. `ask_user` 的问题由 `missing_info` 驱动,缺什么问什么。
5. 用户在聊天页补全的是当前主动调度缺失事实;偏好类表达不属于 `ask_user` 的缺失项,解锁后直接回到现有 newAgent memory / execute 链路,不在主动调度里新建记忆写入链路。
6. 前端只做最小分支:
- 复用 `AssistantPanel.vue`
- 复用现有 `ScheduleResultCard`
- 复用现有 `ScheduleFineTuneModal`
- 只是把 timeline 新类型和主动调度 confirm API 接起来
7. 后端负责把主动调度 preview DTO 转成前端容易复用的结构,前端不背脏活。
**补充链路图**
```mermaid
flowchart TD
A[用户在聊天页发送消息\nPOST /api/v1/agent/chat] --> B{查询 active_schedule_sessions}
B -->|未占管| N[进入普通 newAgent]
B -->|waiting_user_reply / rerunning| C[拦截到主动调度分支]
C --> D[写入用户消息到 timeline]
D --> E[LLM JSON 定位节点\n只负责补齐缺失事实]
E --> F{能否定位 schedule_event?}
F -->|否| G[生成 ask_user\nsession=waiting_user_reply]
F -->|是| H[重跑 active scheduler graph]
H --> I[BuildContext -> Observe -> GenerateCandidates -> SelectAndExplain]
I --> J[CreatePreview]
J --> K[session=ready_preview]
K --> N
```
这个定位节点只做一件事:把用户补充的话术转成后端可校验的 JSON 事实,不负责写正式日程,不负责生成新排程策略,也不负责替代后端候选裁决。
**验收点**
1. 用户补完当前主动调度缺失事实后,能刷新 preview 并解除锁定;解锁后再说“我周末不想学习”这类偏好话术时,直接走现有 newAgent memory / execute 链路。
2. `ask_user` 流程没走完时,不能直接进入普通聊天链路。
3. 补做块只新建,不移动原任务。
4. 主动调度卡片能在聊天页显示,微调后走主动调度 confirm API。
**自动化测试**
- 后端部分可以自动跑。
- 前端卡片展示和按钮分支,建议用浏览器实际打开一次做可视确认。
- 如果只是检查 DOM / 路由 / 请求是否发对,能自动;如果要看卡片样式是否真的对齐,还是需要浏览器看一眼。
**验证记录**
1. `go build ./cmd/all` 已通过。
2. `go test ./...` 已通过。
3. 并发补充场景已验证:同一会话两条补充同时到来时,只会有一条抢到 rerun另一条返回占管提示不会重复生成 preview。
---
### 阶段 4收口飞书通知与会话链接
**当前状态**
已完成。用户级 webhook 配置、通知投递、测试接口和会话链接已经统一到 `/assistant/{conversation_id}`,不再把旧的 `/schedule-adjust/{preview_id}` 当作新入口。
**已落地内容**
1. 通知前由后端预创建或绑定 `conversation_id`,保证飞书点击后直接进入同一会话。
2. `action_url` 已统一为 `/assistant/{conversation_id}`;通知 payload 保持从简,只保留会话跳转和排障所需字段。
3. 用户级飞书 webhook 的保存 / 查询 / 删除 / 测试接口已接通,真实投递与测试共用同一套 provider 校验和 JSON 拼装。
4. `notification_records` 已覆盖 `sent / failed / dead / skipped` 和 retry 相关状态。
5. 用户未配置或禁用 webhook 时,通知记录会落 `skipped`,不阻塞主链路。
**验证记录**
1. `PUT /api/v1/notification/channels/feishu` 已跑通。
2. `POST /api/v1/notification/channels/feishu/test` 已跑通,成功时会回写 `last_test_status / last_test_at`
3. `POST /api/v1/active-schedule/trigger` 后生成的通知请求 payload 中,`message.action_url` 已指向 `/assistant/{conversation_id}`
4. 真实飞书消息样式和外部页面交互仍属于最终验收,不影响后端收口结论。
**建议保留的边界**
1. 这里不再恢复旧的 `/schedule-adjust/{preview_id}` 主入口。
2. 业务 JSON 继续从简,不把复杂卡片协议塞进 webhook。
3. 后续如果要扩展新的通知渠道,先复用 `notification_records` 状态机和 `FeishuProvider` 的抽象边界。
---
### 阶段 5跑完第五阶段剩余验收和失败注入脚本
**当前状态**
主链路已经有了,而且周四窗口的最小闭环已经再次跑通;现在阶段 5 的重点只剩极限边界、失败注入和脚本化收口。这里不再重跑阶段 3 的整套 `ask_user` 主链路,只保留 1 条真实 chat 烟测确认入口未回归。
**要做什么**
1. 跑通 `api / worker / all` 三种启动模式。
2. 覆盖以下边界:
- 冲突失败
- preview 过期
- 重复 confirm
- trigger 幂等
- notification retry
- `dead / skipped / failed`
- outbox 重复消费
3. 把失败注入做成脚本化验收,不靠手工猜。
4. 再扫一遍哪些地方还是空壳,哪些地方只是文档先行。
**验收点**
1. 所有核心状态机都能串起来排障。
2. 同一条 preview / notification / apply 不会被重复落库。
3. 过期、冲突、篡改、失败注入都能拒绝。
4. 预览过期、重复 confirm、错误 candidate / 跨用户 preview / preview-session 不匹配都能挡住。
5. 同一 `idempotency_key / dedupe_key` 不能重复生成有效 trigger / preview / notification。
6. notification 的 `skipped / failed / dead / sent` 状态都能在 DB 里对上。
7. outbox 重复消费不会重复投递或重复写通知记录。
8. `api / worker / all` 三种启动边界相关 handler / job 注册不能缺失。
9. 最终能把这一轮主动调度缺口标成完成。
**自动化测试**
- 可以自动跑,而且这一阶段基本就是为了自动化收口。
- 绝大多数验收都能用 API + DB + 日志完成。
---
## 3. 已拍板、不再反复讨论的口径
这些口径已经在讨论里定过了,后面实施时按这个来,不再重开。
1. 主动调度不做独立的 `/schedule-adjust/{preview_id}` 主入口,主链接统一到 `/assistant/{conversation_id}`
2. `active_schedule_sessions` 要单独建,不塞进现有 conversation 表,也不只加在 `active_schedule_previews` 上。
3. `waiting_user_reply / rerunning` 的聊天拦截在后端做,前端只做最小分支。
4. `ask_user` pending 不复用 newAgent 的 `PendingInteraction` 作为状态源。
5. `compress_with_next_dynamic_task` 第一版继续关闭,只保留 schema / 口径,不生成候选。
6. 候选维度保持极简,`deadline_fit``user_preference_fit` 不再作为第一版公开维度。
7. 本轮不再预留独立的健康分支,候选公开维度直接按 `capacity_fit / risk_level` 执行;事实可信度只保留为内部 trace / `ask_user` 门控。
8. `unfinished_feedback` 先定位,再 `ask_user`,定位成功后直接生成补做 preview不移动原任务。
9. 用户在聊天页说偏好时,不归主动调度接管;解锁后直接走现有 newAgent memory / execute 链路。
10. 只有后台离线自动触达才走飞书;用户已经在会话里时,不需要再先走飞书通知。
11. `ask_user` 闭环只新增一个 LLM JSON 定位节点,沿用正常 `chat` 入口和 session 拦截,不单独再造工具系统。
---
## 4. 自动化测试边界
### 可以自动跑的
- `go test ./...`
- dry-run / trigger / preview / confirm 的 API 验证
- DB 结果核对
- 幂等、重复提交、冲突、过期、失败注入
- notification 状态流转
- webhook test 接口
- api-only / worker-only / all 的后端闭环
### 部分自动、需要浏览器或你配合开服务的
- `AssistantPanel.vue` 的实际页面交互
- 主动调度卡片是否真的长对了
- 飞书真实消息是否真的落到外部页面
- 需要切换启动模式时的服务重启
### 我会怎么标记进度
每个阶段完成后,我会把对应标题从 `[ ]` 改成 `[x]`,并补三件事:
1. 实际跑过的命令。
2. 关键请求 / 响应 ID。
3. DB 核对结果和剩余风险。