后端: 1. execute 主链路重构为“上下文工具域 + 主动优化候选闭环”——移除 order_guard,粗排后默认进入主动微调,先诊断再从后端候选中选择 move/swap,避免 LLM 自由全局乱搜 2. 工具体系升级为动态注入协议——新增 context_tools_add / remove、工具域与二级包映射、主动优化白名单;schedule / taskclass / web 工具按域按包暴露,msg0 规则包与 execute 上下文同步重写 3. analyze_health 升级为主动优化唯一裁判入口——补齐 rhythm / tightness / profile / feasibility 指标、候选扫描与复诊打分、停滞信号、forced imperfection 判定,并把连续优化状态写回运行态 4. 任务类能力并入新 Agent 执行链——新增 upsert_task_class 写工具与启动注入事务写入;任务类模型补充学科画像与整天屏蔽配置,粗排支持 excluded_days_of_week,steady 策略改为基于目标位置/单日负载/分散度/缓冲的候选打分 5. 运行态与路由补齐优化模式语义——新增 active tool domain/packs、pending context hook、active optimize only、taskclass 写入回盘快照;区分 first_full / global_reopt / local_adjust,并完善首次粗排后默认 refine 的判定 前端: 6. 助手时间线渲染细化——推理内容改为独立 reasoning block,支持与工具/状态/正文按时序交错展示,自动收口折叠,修正 confirm reject 恢复动作 仓库: 7. newAgent 文档整体迁入 docs/backend,补充主动优化执行规划与顺序约束拆解文档,删除旧调试日志文件 PS:这次科研了2天,总算是有些进展了——LLM永远只适合做选择题、判断题,不适合做开放创新题。
16 KiB
主动优化顺序约束拆分执行计划
1. 本轮目标
本轮要解决的不是单点 bug,而是一个架构错位:
- 主动优化希望 LLM 在窗口内自主微调,围绕负载、节奏、容错做多轮观察与挪动。
- 现有顺序保护却是“全局 suggested 基线 + 收口时自动复原”,本质是事后抢救。
- 两者叠加后,LLM 前面刚优化完,后面又可能被
order_guard否掉,甚至否不回去,只能带着异常结果交付。
因此,本轮的核心目标是:
- 把“顺序约束”从 graph 收口节点,下沉为写工具层的前置约束。
- 把“全局顺序冻结”改成“允许跨科目交错,但锁住同任务类内部顺序”。
- 顺手修掉当前主动优化链路里由旧守卫带来的提示污染、卡片误导、兼容性 bug。
- 借这次改造,把
node/execute.go继续拆职责,避免后续主动优化逻辑继续堆在单文件里。
2. 当前问题诊断
2.1 产品语义错位
当前系统默认语义仍是:
AllowReorder=false时,尽量保持所有 suggested 的全局相对顺序。- 若被打乱,则在
order_guard节点尝试按 baseline 复原。
这和我们已经对齐的新产品语义冲突:
- 用户默认不是“完全不许动顺序”。
- 用户要的是“每门课内部别乱序,但不同课之间可以交错来换负载”。
- 主动优化阶段的目标是优化坑位分布,不是死守粗排全局序列。
2.2 约束位置放错了
当前顺序保护发生在:
execute完成后。graph/order_guard收口前。
这会导致三个问题:
- 非法移动已经发生,后面只能补救。
- 补救失败也不会阻断交付,只会吐一句“顺序异常但未复原”。
- LLM 在执行时完全不知道哪些移动其实不该做,容易白跑。
2.3 约束粒度过粗
当前基线是“所有 suggested 任务的时间顺序快照”,这会把下面两类本来合理的操作也一起误伤:
- 不同任务类之间为了均衡负载而做的交错。
- 在不破坏科目内部先后关系的前提下做的跨天平衡。
2.4 当前 bug 已经暴露
从日志看,至少已有这些具体问题:
order_guard尝试复原时出现slot_incompatible。- 本质说明旧复原逻辑对“任务时长单位”和“坑位跨度单位”的理解并不稳。
- 这条链本来就不该继续扩展,而该整体退场。
- 前端会收到“已记录本轮建议任务顺序基线”“顺序异常但未执行自动复原”这类对用户价值很低的系统话术。
executeprompt 仍在强调“默认保持 suggested 相对顺序”,这会继续把模型往旧目标上拽。spread_even/move/swap/batch_move当前都不知道“同任务类兄弟节点边界”,所以无法在写入前拦住越界调整。
2.5 代码结构已经不适合继续堆功能
当前 node/execute.go 已经承载了:
- execute 主循环。
- 工具执行。
- 工具结果摘要。
- feasibility 守门。
- task class 写入状态回盘。
- preview 实时写。
- 顺序相关拦截。
- scope 解析。
这类文件继续加主动优化逻辑,后续回归会越来越难定位。
3. 目标行为
改造后的目标行为如下:
- LLM 仍然可以主动观察、主动微调、再观察,不退化成一次性确定性求解。
- 默认允许跨任务类交错调整。
- 默认不允许打乱同一任务类内部的学习顺序。
- 每次写工具调用前,后端都能判断这次移动是否越过“同任务类上一个/下一个任务”的合法边界。
- 如果越界,工具直接返回失败原因,让 LLM 换别的任务或别的坑位,而不是先写进去、最后再抢救。
- 交付阶段不再出现旧
order_guard的提示文案,也不再依赖它去修复顺序。
一句话概括:
允许跨科目穿插优化,但每门课内部始终保持原有学习推进顺序。
4. 必须补齐的数据
这是本轮最关键的数据面。没有这些字段,后端没法在写工具层判断“这个任务能挪到哪”。
4.1 任务类内部顺序 rank
当前 ScheduleTask 里有:
TaskClassIDSourceID(task_item.id)
但没有:
- 该
task_item在所属任务类里的order
这意味着后端知道“它属于哪门课”,但不知道“它是这门课里的第几个任务”。
本轮需要补:
- 在
schedule.ScheduleTask增加类似TaskOrder的运行态字段。 - 在
conv/schedule_state.go从model.TaskClassItem.Order映射进来。
4.2 顺序边界计算所需的同类兄弟信息
有了 TaskClassID + TaskOrder 后,不一定非要把前后兄弟 ID 也落进 state;两种方案都可行:
- 轻量方案:运行时动态扫描同任务类任务,按
TaskOrder算前驱/后继。 - 预计算方案:在 state 初始化时直接建立 sibling index。
本轮建议先走轻量方案,原因:
- 改动面更小。
- 不引入新的状态同步负担。
- 足够支撑写工具前置校验。
4.3 合法时间边界的统一定义
需要明确一个统一规则:
- 一个任务的目标位置,必须晚于同任务类前驱任务的结束时间。
- 必须早于同任务类后继任务的开始时间。
- 若前驱/后继不存在,则该侧边界开放。
- 若前驱/后继当前是 pending、未落位,则该侧边界暂不收紧。
这样 LLM 仍有自由度,但自由度被严格限制在“本任务合法活动区间”里。
5. 方案总览
5.1 总体策略
本轮不再沿用“先放任移动,最后 graph 收口时修”的模式,而改成:
- 写工具调用前先验边界。
- 合法才允许写。
- 非法直接返回失败。
- 收口阶段只做轻量断言,不再自动复原。
5.2 顺序保护新哲学
旧哲学:
- 保护粗排全局时间序列。
新哲学:
- 保护每个任务类内部的推进顺序。
- 不保护不同任务类之间的相对先后。
5.3 对主动优化的意义
这套改法的直接意义是:
- LLM 终于可以真的做“负载优化”而不是被全局顺序锁死。
- LLM 即使选错目标,也会在写工具层收到具体失败原因。
- 失败原因足够明确时,模型下一步就知道该换任务、换天、还是换工具。
6. 具体拆分与改动计划
6.1 第一步:给 ScheduleState 补顺序语义
涉及文件:
backend/newAgent/tools/schedule/state.gobackend/newAgent/conv/schedule_state.go
计划动作:
- 在
ScheduleTask增加任务类内部顺序字段。- 建议名:
TaskOrder int
- 建议名:
- 仅
source=task_item时填充该字段。 - 从
model.TaskClassItem.Order注入运行态。 - 对缺失 order 的历史数据做兜底。
- 优先使用数据库 order。
- 若为空,则按
TaskClass.Items当前顺序补稳定序号。
验收结果:
- 每个
task_item在工具层都能知道自己是所属任务类里的第几项。 - 查询工具输出里不一定要暴露这个字段给 LLM,但后端必须可用。
6.2 第二步:新增“局部顺序约束”公共层
涉及文件:
- 新增
backend/newAgent/tools/schedule/order_constraints.go - 复用
backend/newAgent/tools/schedule/write_helpers.go
计划动作:
- 抽一个独立公共层,不把顺序判断散落在每个写工具里重复写。
- 公共层职责只做一件事:判断某个任务能否落到某个目标时段。
- 需要提供的核心能力:
- 找到同任务类前驱任务
- 找到同任务类后继任务
- 计算合法最早起点 / 最晚终点
- 判断目标位置是否越界
- 输出中文失败原因
建议返回信息:
ok=true/false- 失败原因中文摘要
- 命中的前驱/后继任务是谁
- 合法范围描述
这样后面各写工具都能直接复用,不再复制逻辑。
6.3 第三步:把约束前置到基础写工具
涉及文件:
backend/newAgent/tools/schedule/write_tools.go
计划动作:
move接入局部顺序约束。swap在交换前对双方交换后的目标位置分别校验。batch_move在克隆态上统一校验整批目标是否都满足局部顺序约束。place也要接入。- 因为被
unplace后再次放回,仍然可能破坏同类顺序。
- 因为被
unplace暂时不做顺序阻断。- 它只是把任务拿出来,不直接打乱同类内部先后。
- 真正的顺序问题应在后续
place/move时拦截。
验收目标:
- 任一基础写工具都不能把任务挪出自己的合法兄弟区间。
- 非法时工具直接失败,且提示能被 LLM 看懂。
6.4 第四步:让复合写工具也遵守边界
涉及文件:
backend/newAgent/tools/schedule/compound_tools.go
计划动作:
spread_even生成候选位置后,回填前逐任务校验局部顺序边界。- 若规划器给出的结果越界,整次复合写失败并给出明确原因。
min_context_switch继续维持 P1 不暴露。- 即使未来重开,也必须走同一套局部顺序约束,不允许绕过。
原因:
- 复合工具最容易“整体看起来更均匀,但把单科内部顺序打乱”。
- 如果只拦基础写工具,不拦复合工具,系统规则会不一致。
6.5 第五步:退役旧 order_guard
涉及文件:
backend/newAgent/node/order_guard.gobackend/newAgent/graph/common_graph.gobackend/newAgent/model/common_state.gobackend/newAgent/node/execute.go
计划动作:
- 移除“全局 baseline + 收口复原”的主逻辑。
- 删除或停用
SuggestedOrderBaseline运行态。 - 删除
order_guard节点在主动优化链路中的强依赖。 - 交付前若仍需要安全兜底,只保留一个轻量 final assert:
- 仅检查每个任务类内部顺序是否仍合法
- 不自动复原
- 若非法,视为执行层 bug,直接中止交付并打日志
推荐做法:
- P1 先彻底切掉 graph 层
order_guard分支。 - 若担心过渡期风险,再补一个极轻的校验函数在 deliver 前调用。
6.6 第六步:同步调整 prompt 与模型目标
涉及文件:
backend/newAgent/prompt/execute_context.gobackend/newAgent/prompt/execute.go- 视需要补充
prompt/execute_rule_packs.go
计划动作:
- 删除“默认保持 suggested 相对顺序”的旧表述。
- 改成新的明确描述:
- 默认保持同任务类内部顺序
- 允许跨任务类交错调整
- 不得擅自突破同任务类内部先后
- 把“非法时工具会直接失败”作为模型可感知规则写进 prompt。
这样 LLM 会更接近真实规则,不会一直沿着旧目标空转。
6.7 第七步:拆 execute.go 职责
涉及文件:
backend/newAgent/node/execute.go- 新增若干并行文件
建议拆分方向:
node/execute.go- 只保留主循环、决策分发、节点入口
node/execute_scope_guard.go- 当前步骤作用域解析与日期范围守门
node/execute_tool_runtime.goexecuteToolCall/executePendingTool/ preview 写入
node/execute_tool_summary.go- 工具摘要、参数摘要、结果摘要
node/execute_taskclass_runtime.go- task class upsert 状态回盘相关
node/execute_health_runtime.go- feasibility / health 快照更新
这一步的目的不是“为了好看”,而是避免后面继续把主动优化规则、task class 流程规则、工具结果摘要全塞回一个文件。
7. 本轮顺手修复的 bug 清单
7.1 bug A:顺序异常提示污染用户体验
现象:
- 前端会看到“已记录本轮建议任务顺序基线”
- 以及“检测到顺序异常,但本次未执行自动复原”
修法:
- 随
order_guard退役一起移除这两类状态文案。 - 这类内部守卫信息不再面向用户显式展示。
7.2 bug B:slot_incompatible 兼容性问题
现象:
- 日志里出现
expected_duration=1 slot_duration=2
判断:
- 这是旧
order_guard复原链上的单位不一致问题。 - 该问题不值得单独继续修补。
修法:
- 旧复原链退役后,这条 bug 自然消失。
- 本轮只保留一个动作:确认写工具本身的时长计算口径仍正确。
7.3 bug C:prompt 仍把模型往“全局不乱序”上引
现象:
execute_context里仍写着默认保持 suggested 相对顺序。
修法:
- 改成“默认保持同任务类内部顺序”。
7.4 bug D:复合工具可能绕过新规则
现象:
spread_even当前只校验冲突,不校验同类前后边界。
修法:
- 接入统一局部顺序约束层。
7.5 bug E:active optimize 链路和 execute 文件职责缠得太紧
现象:
- 任何主动优化 bug 都容易改进
execute.go,继续涨文件体积。
修法:
- 本轮同步拆文件,至少把工具执行与摘要逻辑拆出去。
8. 实施顺序
建议按下面顺序推进,避免中途状态既不兼容旧逻辑,也没完全切到新逻辑。
阶段 1:补数据
- 给
ScheduleTask增加TaskOrder - 在 state loader 中完成映射
- 保证查询 / 粗排 / 预览链路不受影响
阶段 2:落局部顺序约束公共层
- 实现前驱/后继查找
- 实现目标落位合法性判断
- 输出中文失败原因
阶段 3:接入基础写工具
moveswapbatch_moveplace
阶段 4:接入复合写工具
spread_even- 保持
min_context_switch继续禁用
阶段 5:切掉旧 order_guard
- 删除 graph 分支
- 删除 baseline 运行态
- 去掉用户可见状态文案
阶段 6:更新 prompt
- 改目标描述
- 改顺序策略说明
- 明确非法写工具会被后端拒绝
阶段 7:拆 execute.go
- 先无行为变化拆文件
- 再补必要注释与最小验证
9. 验证口径
9.1 正向场景
要验证这些场景能通过:
- 同任务类内部顺序不变,但不同任务类交错后负载更均衡。
- LLM 将某任务从第 3 天挪到第 20 天,只要仍在其前后兄弟之间,就允许。
spread_even可以把多门课拉开,但不会把某一门课内部顺序反过来。
9.2 反向场景
要验证这些场景被拦住:
- 把某门课的第 4 个任务挪到第 1 个任务前面。
- 把某门课的中间任务挪到其后继任务之后。
swap后导致同任务类内部出现逆序。batch_move中有一条越界时整批失败。
9.3 交付场景
要确认这些旧副作用消失:
- 不再出现
order_guard_initialized - 不再出现
order_guard_restore_skipped - 不再依赖
SuggestedOrderBaseline
9.4 代码结构场景
要确认:
execute.go文件职责明显变轻- 局部顺序约束逻辑只存在一份公共实现
10. 本轮建议的最小落地范围
如果要控制风险,本轮建议先做到这里:
TaskOrder注入- 局部顺序约束公共层
move/swap/batch_move/place接入spread_even接入- prompt 改口径
- 切掉旧
order_guard - 拆出
execute_tool_runtime.go与execute_tool_summary.go
这个范围已经足够让主动优化链路从“旧哲学打架”切到“新哲学能跑”。
11. 预期收益
做完之后,预期表现会变成:
- LLM 会更敢做真实优化,因为它不再被全局顺序锁死。
- 后端会在写工具层直接给出“能不能这么挪”的明确反馈。
- 同一门课的学习推进顺序能被稳定锁住。
- 不同门课之间仍有足够空间做均衡、分散、减压。
- 前端不会再收到旧
order_guard带来的迷惑状态。 - 后续如果继续加主动优化策略,也有更干净的承载位置,不必继续往
execute.go里堆。
12. 本文档对应的实施结论
本轮建议按以下原则执行:
- 删除“全局 suggested 顺序守卫”思路。
- 改为“同任务类内部顺序约束前置到写工具层”。
- 允许跨任务类交错优化。
- 顺手清理旧 guard 带来的用户可见噪音与兼容性问题。
- 同步拆分 execute 相关职责文件,避免继续堆史山。