# 主动优化顺序约束拆分执行计划 ## 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 相关职责文件,避免继续堆史山。