# 主动优化顺序约束拆分执行计划

## 1. 本轮目标

本轮要解决的不是单点 bug，而是一个架构错位：

1. 主动优化希望 LLM 在窗口内自主微调，围绕负载、节奏、容错做多轮观察与挪动。
2. 现有顺序保护却是“全局 suggested 基线 + 收口时自动复原”，本质是事后抢救。
3. 两者叠加后，LLM 前面刚优化完，后面又可能被 `order_guard` 否掉，甚至否不回去，只能带着异常结果交付。

因此，本轮的核心目标是：

1. 把“顺序约束”从 graph 收口节点，下沉为写工具层的前置约束。
2. 把“全局顺序冻结”改成“允许跨科目交错，但锁住同任务类内部顺序”。
3. 顺手修掉当前主动优化链路里由旧守卫带来的提示污染、卡片误导、兼容性 bug。
4. 借这次改造，把 `node/execute.go` 继续拆职责，避免后续主动优化逻辑继续堆在单文件里。

---

## 2. 当前问题诊断

### 2.1 产品语义错位

当前系统默认语义仍是：

1. `AllowReorder=false` 时，尽量保持所有 suggested 的全局相对顺序。
2. 若被打乱，则在 `order_guard` 节点尝试按 baseline 复原。

这和我们已经对齐的新产品语义冲突：

1. 用户默认不是“完全不许动顺序”。
2. 用户要的是“每门课内部别乱序，但不同课之间可以交错来换负载”。
3. 主动优化阶段的目标是优化坑位分布，不是死守粗排全局序列。

### 2.2 约束位置放错了

当前顺序保护发生在：

1. `execute` 完成后。
2. `graph/order_guard` 收口前。

这会导致三个问题：

1. 非法移动已经发生，后面只能补救。
2. 补救失败也不会阻断交付，只会吐一句“顺序异常但未复原”。
3. LLM 在执行时完全不知道哪些移动其实不该做，容易白跑。

### 2.3 约束粒度过粗

当前基线是“所有 suggested 任务的时间顺序快照”，这会把下面两类本来合理的操作也一起误伤：

1. 不同任务类之间为了均衡负载而做的交错。
2. 在不破坏科目内部先后关系的前提下做的跨天平衡。

### 2.4 当前 bug 已经暴露

从日志看，至少已有这些具体问题：

1. `order_guard` 尝试复原时出现 `slot_incompatible`。
   - 本质说明旧复原逻辑对“任务时长单位”和“坑位跨度单位”的理解并不稳。
   - 这条链本来就不该继续扩展，而该整体退场。
2. 前端会收到“已记录本轮建议任务顺序基线”“顺序异常但未执行自动复原”这类对用户价值很低的系统话术。
3. `execute` prompt 仍在强调“默认保持 suggested 相对顺序”，这会继续把模型往旧目标上拽。
4. `spread_even` / `move` / `swap` / `batch_move` 当前都不知道“同任务类兄弟节点边界”，所以无法在写入前拦住越界调整。

### 2.5 代码结构已经不适合继续堆功能

当前 `node/execute.go` 已经承载了：

1. execute 主循环。
2. 工具执行。
3. 工具结果摘要。
4. feasibility 守门。
5. task class 写入状态回盘。
6. preview 实时写。
7. 顺序相关拦截。
8. scope 解析。

这类文件继续加主动优化逻辑，后续回归会越来越难定位。

---

## 3. 目标行为

改造后的目标行为如下：

1. LLM 仍然可以主动观察、主动微调、再观察，不退化成一次性确定性求解。
2. 默认允许跨任务类交错调整。
3. 默认不允许打乱同一任务类内部的学习顺序。
4. 每次写工具调用前，后端都能判断这次移动是否越过“同任务类上一个/下一个任务”的合法边界。
5. 如果越界，工具直接返回失败原因，让 LLM 换别的任务或别的坑位，而不是先写进去、最后再抢救。
6. 交付阶段不再出现旧 `order_guard` 的提示文案，也不再依赖它去修复顺序。

一句话概括：

> 允许跨科目穿插优化，但每门课内部始终保持原有学习推进顺序。

---

## 4. 必须补齐的数据

这是本轮最关键的数据面。没有这些字段，后端没法在写工具层判断“这个任务能挪到哪”。

### 4.1 任务类内部顺序 rank

当前 `ScheduleTask` 里有：

1. `TaskClassID`
2. `SourceID`（`task_item.id`）

但没有：

1. 该 `task_item` 在所属任务类里的 `order`

这意味着后端知道“它属于哪门课”，但不知道“它是这门课里的第几个任务”。

本轮需要补：

1. 在 `schedule.ScheduleTask` 增加类似 `TaskOrder` 的运行态字段。
2. 在 `conv/schedule_state.go` 从 `model.TaskClassItem.Order` 映射进来。

### 4.2 顺序边界计算所需的同类兄弟信息

有了 `TaskClassID + TaskOrder` 后，不一定非要把前后兄弟 ID 也落进 state；两种方案都可行：

1. 轻量方案：运行时动态扫描同任务类任务，按 `TaskOrder` 算前驱/后继。
2. 预计算方案：在 state 初始化时直接建立 sibling index。

本轮建议先走轻量方案，原因：

1. 改动面更小。
2. 不引入新的状态同步负担。
3. 足够支撑写工具前置校验。

### 4.3 合法时间边界的统一定义

需要明确一个统一规则：

1. 一个任务的目标位置，必须晚于同任务类前驱任务的结束时间。
2. 必须早于同任务类后继任务的开始时间。
3. 若前驱/后继不存在，则该侧边界开放。
4. 若前驱/后继当前是 pending、未落位，则该侧边界暂不收紧。

这样 LLM 仍有自由度，但自由度被严格限制在“本任务合法活动区间”里。

---

## 5. 方案总览

### 5.1 总体策略

本轮不再沿用“先放任移动，最后 graph 收口时修”的模式，而改成：

1. 写工具调用前先验边界。
2. 合法才允许写。
3. 非法直接返回失败。
4. 收口阶段只做轻量断言，不再自动复原。

### 5.2 顺序保护新哲学

旧哲学：

1. 保护粗排全局时间序列。

新哲学：

1. 保护每个任务类内部的推进顺序。
2. 不保护不同任务类之间的相对先后。

### 5.3 对主动优化的意义

这套改法的直接意义是：

1. LLM 终于可以真的做“负载优化”而不是被全局顺序锁死。
2. LLM 即使选错目标，也会在写工具层收到具体失败原因。
3. 失败原因足够明确时，模型下一步就知道该换任务、换天、还是换工具。

---

## 6. 具体拆分与改动计划

## 6.1 第一步：给 ScheduleState 补顺序语义

涉及文件：

1. `backend/newAgent/tools/schedule/state.go`
2. `backend/newAgent/conv/schedule_state.go`

计划动作：

1. 在 `ScheduleTask` 增加任务类内部顺序字段。
   - 建议名：`TaskOrder int`
2. 仅 `source=task_item` 时填充该字段。
3. 从 `model.TaskClassItem.Order` 注入运行态。
4. 对缺失 order 的历史数据做兜底。
   - 优先使用数据库 order。
   - 若为空，则按 `TaskClass.Items` 当前顺序补稳定序号。

验收结果：

1. 每个 `task_item` 在工具层都能知道自己是所属任务类里的第几项。
2. 查询工具输出里不一定要暴露这个字段给 LLM，但后端必须可用。

## 6.2 第二步：新增“局部顺序约束”公共层

涉及文件：

1. 新增 `backend/newAgent/tools/schedule/order_constraints.go`
2. 复用 `backend/newAgent/tools/schedule/write_helpers.go`

计划动作：

1. 抽一个独立公共层，不把顺序判断散落在每个写工具里重复写。
2. 公共层职责只做一件事：判断某个任务能否落到某个目标时段。
3. 需要提供的核心能力：
   - 找到同任务类前驱任务
   - 找到同任务类后继任务
   - 计算合法最早起点 / 最晚终点
   - 判断目标位置是否越界
   - 输出中文失败原因

建议返回信息：

1. `ok=true/false`
2. 失败原因中文摘要
3. 命中的前驱/后继任务是谁
4. 合法范围描述

这样后面各写工具都能直接复用，不再复制逻辑。

## 6.3 第三步：把约束前置到基础写工具

涉及文件：

1. `backend/newAgent/tools/schedule/write_tools.go`

计划动作：

1. `move` 接入局部顺序约束。
2. `swap` 在交换前对双方交换后的目标位置分别校验。
3. `batch_move` 在克隆态上统一校验整批目标是否都满足局部顺序约束。
4. `place` 也要接入。
   - 因为被 `unplace` 后再次放回，仍然可能破坏同类顺序。
5. `unplace` 暂时不做顺序阻断。
   - 它只是把任务拿出来，不直接打乱同类内部先后。
   - 真正的顺序问题应在后续 `place/move` 时拦截。

验收目标：

1. 任一基础写工具都不能把任务挪出自己的合法兄弟区间。
2. 非法时工具直接失败，且提示能被 LLM 看懂。

## 6.4 第四步：让复合写工具也遵守边界

涉及文件：

1. `backend/newAgent/tools/schedule/compound_tools.go`

计划动作：

1. `spread_even` 生成候选位置后，回填前逐任务校验局部顺序边界。
2. 若规划器给出的结果越界，整次复合写失败并给出明确原因。
3. `min_context_switch` 继续维持 P1 不暴露。
4. 即使未来重开，也必须走同一套局部顺序约束，不允许绕过。

原因：

1. 复合工具最容易“整体看起来更均匀，但把单科内部顺序打乱”。
2. 如果只拦基础写工具，不拦复合工具，系统规则会不一致。

## 6.5 第五步：退役旧 order_guard

涉及文件：

1. `backend/newAgent/node/order_guard.go`
2. `backend/newAgent/graph/common_graph.go`
3. `backend/newAgent/model/common_state.go`
4. `backend/newAgent/node/execute.go`

计划动作：

1. 移除“全局 baseline + 收口复原”的主逻辑。
2. 删除或停用 `SuggestedOrderBaseline` 运行态。
3. 删除 `order_guard` 节点在主动优化链路中的强依赖。
4. 交付前若仍需要安全兜底，只保留一个轻量 final assert：
   - 仅检查每个任务类内部顺序是否仍合法
   - 不自动复原
   - 若非法，视为执行层 bug，直接中止交付并打日志

推荐做法：

1. P1 先彻底切掉 graph 层 `order_guard` 分支。
2. 若担心过渡期风险，再补一个极轻的校验函数在 deliver 前调用。

## 6.6 第六步：同步调整 prompt 与模型目标

涉及文件：

1. `backend/newAgent/prompt/execute_context.go`
2. `backend/newAgent/prompt/execute.go`
3. 视需要补充 `prompt/execute_rule_packs.go`

计划动作：

1. 删除“默认保持 suggested 相对顺序”的旧表述。
2. 改成新的明确描述：
   - 默认保持同任务类内部顺序
   - 允许跨任务类交错调整
   - 不得擅自突破同任务类内部先后
3. 把“非法时工具会直接失败”作为模型可感知规则写进 prompt。

这样 LLM 会更接近真实规则，不会一直沿着旧目标空转。

## 6.7 第七步：拆 execute.go 职责

涉及文件：

1. `backend/newAgent/node/execute.go`
2. 新增若干并行文件

建议拆分方向：

1. `node/execute.go`
   - 只保留主循环、决策分发、节点入口
2. `node/execute_scope_guard.go`
   - 当前步骤作用域解析与日期范围守门
3. `node/execute_tool_runtime.go`
   - `executeToolCall` / `executePendingTool` / preview 写入
4. `node/execute_tool_summary.go`
   - 工具摘要、参数摘要、结果摘要
5. `node/execute_taskclass_runtime.go`
   - task class upsert 状态回盘相关
6. `node/execute_health_runtime.go`
   - feasibility / health 快照更新

这一步的目的不是“为了好看”，而是避免后面继续把主动优化规则、task class 流程规则、工具结果摘要全塞回一个文件。

---

## 7. 本轮顺手修复的 bug 清单

## 7.1 bug A：顺序异常提示污染用户体验

现象：

1. 前端会看到“已记录本轮建议任务顺序基线”
2. 以及“检测到顺序异常，但本次未执行自动复原”

修法：

1. 随 `order_guard` 退役一起移除这两类状态文案。
2. 这类内部守卫信息不再面向用户显式展示。

## 7.2 bug B：`slot_incompatible` 兼容性问题

现象：

1. 日志里出现 `expected_duration=1 slot_duration=2`

判断：

1. 这是旧 `order_guard` 复原链上的单位不一致问题。
2. 该问题不值得单独继续修补。

修法：

1. 旧复原链退役后，这条 bug 自然消失。
2. 本轮只保留一个动作：确认写工具本身的时长计算口径仍正确。

## 7.3 bug C：prompt 仍把模型往“全局不乱序”上引

现象：

1. `execute_context` 里仍写着默认保持 suggested 相对顺序。

修法：

1. 改成“默认保持同任务类内部顺序”。

## 7.4 bug D：复合工具可能绕过新规则

现象：

1. `spread_even` 当前只校验冲突，不校验同类前后边界。

修法：

1. 接入统一局部顺序约束层。

## 7.5 bug E：active optimize 链路和 execute 文件职责缠得太紧

现象：

1. 任何主动优化 bug 都容易改进 `execute.go`，继续涨文件体积。

修法：

1. 本轮同步拆文件，至少把工具执行与摘要逻辑拆出去。

---

## 8. 实施顺序

建议按下面顺序推进，避免中途状态既不兼容旧逻辑，也没完全切到新逻辑。

### 阶段 1：补数据

1. 给 `ScheduleTask` 增加 `TaskOrder`
2. 在 state loader 中完成映射
3. 保证查询 / 粗排 / 预览链路不受影响

### 阶段 2：落局部顺序约束公共层

1. 实现前驱/后继查找
2. 实现目标落位合法性判断
3. 输出中文失败原因

### 阶段 3：接入基础写工具

1. `move`
2. `swap`
3. `batch_move`
4. `place`

### 阶段 4：接入复合写工具

1. `spread_even`
2. 保持 `min_context_switch` 继续禁用

### 阶段 5：切掉旧 order_guard

1. 删除 graph 分支
2. 删除 baseline 运行态
3. 去掉用户可见状态文案

### 阶段 6：更新 prompt

1. 改目标描述
2. 改顺序策略说明
3. 明确非法写工具会被后端拒绝

### 阶段 7：拆 execute.go

1. 先无行为变化拆文件
2. 再补必要注释与最小验证

---

## 9. 验证口径

## 9.1 正向场景

要验证这些场景能通过：

1. 同任务类内部顺序不变，但不同任务类交错后负载更均衡。
2. LLM 将某任务从第 3 天挪到第 20 天，只要仍在其前后兄弟之间，就允许。
3. `spread_even` 可以把多门课拉开，但不会把某一门课内部顺序反过来。

## 9.2 反向场景

要验证这些场景被拦住：

1. 把某门课的第 4 个任务挪到第 1 个任务前面。
2. 把某门课的中间任务挪到其后继任务之后。
3. `swap` 后导致同任务类内部出现逆序。
4. `batch_move` 中有一条越界时整批失败。

## 9.3 交付场景

要确认这些旧副作用消失：

1. 不再出现 `order_guard_initialized`
2. 不再出现 `order_guard_restore_skipped`
3. 不再依赖 `SuggestedOrderBaseline`

## 9.4 代码结构场景

要确认：

1. `execute.go` 文件职责明显变轻
2. 局部顺序约束逻辑只存在一份公共实现

---

## 10. 本轮建议的最小落地范围

如果要控制风险，本轮建议先做到这里：

1. `TaskOrder` 注入
2. 局部顺序约束公共层
3. `move/swap/batch_move/place` 接入
4. `spread_even` 接入
5. prompt 改口径
6. 切掉旧 `order_guard`
7. 拆出 `execute_tool_runtime.go` 与 `execute_tool_summary.go`

这个范围已经足够让主动优化链路从“旧哲学打架”切到“新哲学能跑”。

---

## 11. 预期收益

做完之后，预期表现会变成：

1. LLM 会更敢做真实优化，因为它不再被全局顺序锁死。
2. 后端会在写工具层直接给出“能不能这么挪”的明确反馈。
3. 同一门课的学习推进顺序能被稳定锁住。
4. 不同门课之间仍有足够空间做均衡、分散、减压。
5. 前端不会再收到旧 `order_guard` 带来的迷惑状态。
6. 后续如果继续加主动优化策略，也有更干净的承载位置，不必继续往 `execute.go` 里堆。

---

## 12. 本文档对应的实施结论

本轮建议按以下原则执行：

1. 删除“全局 suggested 顺序守卫”思路。
2. 改为“同任务类内部顺序约束前置到写工具层”。
3. 允许跨任务类交错优化。
4. 顺手清理旧 guard 带来的用户可见噪音与兼容性问题。
5. 同步拆分 execute 相关职责文件，避免继续堆史山。

