Essay
我为什么决定把 Aido Planner 从“重排器”改成“今日候选任务选择器”
这次 Aido Planner 优化,我真正想解决的不是再把 today 任务重排得更花,而是让智能体开始从未完成任务、偏好、自然语言、长期计划和模板里判断“今天最该做什么”。
这次我在优化 Aido 的 Planner 智能体时,最先推翻的不是某个 prompt 细节,而是它原本的产品定位。
因为我越来越明确地感觉到,当前这版 Planner 虽然“能跑”,但它做的事情其实不太对。
它更像一个:
把今天已有任务重新排一遍的排期器
而不是用户真正想要的那个东西:
帮我判断今天最该做什么的计划助手
这两者看起来只差一点,但体验上差得很大。
现在这版 Planner,问题不在“排得不够聪明”
这次我重新看了一遍 Aido 当前的 Planner 链路,越来越确定一个判断:
当前核心问题不是模型还不够聪明,而是任务本身就定义错了。
现在的 Planner 主要还是在做这样一件事:
已有上下文输入
-> 生成排期建议
-> 转成结构化 JSON
也就是说,它默认前提其实是:
今天要做什么,大体已经有了
我只是帮你重新排一下
但真实用户需求并不是这样。
用户点“生成计划”时,真正希望 AI 帮他判断的是:
- 今天最值得推进什么
- 哪些任务虽然存在,但今天不该继续塞进来
- 哪些旧任务应该继续追
- 哪些长期计划和模板里的内容今天才值得出现
- 用户刚刚补充的一句自然语言,今天要不要直接变成任务
这说明 Planner 真正该先解决的问题不是“怎么排”,而是“今天该做什么”。
所以这次优化,本质上是一次角色重定义
我给这次 Planner 优化下的核心定义是:
从“已有任务排期器”
升级为
“多来源候选任务选择器”
也就是说,第一版优化的重点不再是把 today 任务重新编排得更丝滑,而是让 Planner 能从多个来源里先挑出“今天值得做的候选项”。
这个方向一旦确定,很多设计判断就自然清楚了。
我想接入的,不再只是 today 任务
这次我明确希望 Planner 不再只围着 today 任务池打转,而是接住这些信息源:
- 用户最近没有完成的任务
- 用户的偏好记忆
- 用户这次输入的自然语言补充
- 用户的长期计划
- 用户自己创建的模板
这里最重要的变化不是“多加几个字段”,而是:
Planner 的输入模型,终于要从单一任务列表升级成多源上下文了。
只有这样,它才有可能真正理解“今天该做什么”,而不是永远在“今天已经有什么”里打转。
为什么我没有直接冲完整时间表,而是先走 B+
如果只看产品想象,最理想的形态当然是 AI 一上来就直接给你完整日程表。
但我这次没有直接走这条路,而是先选择了一个更稳的版本:
B+:先生成高质量候选任务卡片,再由用户手动接受加入 today
这里我刻意说的是 B+,而不是旧版那种普通建议卡片。
因为旧版 B 的问题很明显:AI 虽然生成了内容,但前端最后只剩下一张很扁的建议卡片,很多决策信息都被丢掉了。
我更希望首版做到的是:
- 还是候选卡片
- 但每张卡片保留来源
- 保留推荐原因
- 保留预计时长
- 保留优先级
- 保留建议时段
这样用户虽然还是自己点选加入 today,但已经能明显感受到:
AI 不只是随便给建议
而是在认真做“今天该做什么”的判断
这也是我现在不急着上完整时间表的原因。
不是完整时间表没有价值,而是我觉得:
在“先选什么”还不稳的时候,直接做“怎么排时间”只会把问题包装得更漂亮,但不会真正变好用。
这次最关键的设计变化,是把 Planner 拆成“先选,再排”
我现在更认可的 Planner v2 思路是:
先判断今天应该做什么
再考虑怎么排
但当前前端版本只展示“选”的结果
也就是:
多源上下文收集
-> 候选池构建
-> 候选筛选与排序
-> 输出 B+ 候选卡片
-> 用户手动接受加入 today
这个变化看起来只是流程顺序调整,但其实决定了整个产品气质。
原来是:
先默认你今天已经知道要做什么
我来帮你排
现在变成:
今天到底该做什么
先由我来帮你判断
今天已有任务不是不要了,而是要分层
很多人一听“多源选择”,会以为我是不是要把 today 任务完全降权甚至拿掉。
其实不是。
我现在更认同的做法是把 today 任务拆成两层:
1. 锁定任务
比如:
- 已经有明确
startTime/endTime的任务 - 当前
in_progress的任务
这类任务不适合再拿去和别的候选一起竞争,它们更像当天的约束条件。
2. 弹性任务
比如:
- 今天已有
- 还没完成
- 没有固定时间
- 不是
in_progress
这类任务可以继续参与候选池竞争。
这样做之后,Planner 的视角就会更干净:
- 锁定任务告诉它“今天哪些东西不能乱动”
- 弹性任务告诉它“今天已有任务里哪些还值得继续争取名额”
这比把 today 任务一股脑全塞进去要合理得多。
历史未完成任务也能接,但不能无脑回捞
这轮我比较明确的一个设计决策是:
默认只回看 7 天内未完成任务
这个窗口不长不短,主要是为了避免两种极端:
- 只看昨天,信息太少
- 把很久之前用户自己都忘了的任务都捞回来,严重污染上下文
同时,这部分任务还要再过滤:
- 排除
completed - 排除
archived - 排除明显已经被用户放弃的任务
而且就算在 7 天窗口内,也不应该做成“全部硬塞”,而是:
较旧任务降权
与今天相关的优先
这里我还顺手想清楚了一件事:
“回看 7 天”这种东西,不属于
private_manual。
它更像产品显式设置,而不是长期稳定偏好。
所以更合适的归属应该是:
planner_setting / user_setting
而不是写进偏好说明书。
自然语言输入,第一版不强拆,但必须升级成独立信号源
前端这次已经有了对话框,用户可以补充一句自然语言。
但如果这句输入只是普通 user_message 一路往下透传,它的价值其实很有限。
所以我这次比较明确的判断是:
- 第一版不强制做结构化拆分
- 还是让大模型去理解用户自然语言
- 但系统建模上,必须把它升级成独立字段
也就是类似这样的东西:
{
"planning_brief": {
"raw_text": "用户自然语言输入"
}
}
这样做的意义不只是名字变了,而是它终于成为 Planner 上下文里一个正式信息源,而不再只是“顺手带的一句话”。
另外一个我觉得很关键的点是:
如果用户自然语言里直接说了一个新事项,Planner 应该允许把它直接变成候选任务。
比如用户说:
下午去银行办卡
这时候它不应该只影响排序语气,而应该允许直接产出一张候选卡片。
这一点我觉得会非常影响“好用感”。
模板和长期计划都接,但它们不是首版主轴
这轮我也没有把长期计划和模板无限拔高。
我现在的态度更偏向:
模板
- 接入用户自己的模板
- 接入 auto-apply 相关模板
- 不接官方模板
- 去重后进入候选池
原因很简单:官方模板语义太宽,首版噪音会偏大。
长期计划
- 只取
active + 未完成 + 与今天相关的长期计划子任务 - 弱接入
- 不做强约束
原因也很现实:长期计划模块本身还不够稳定,如果首版就把它强行压进 Planner 主逻辑,很容易直接拖累核心体验。
所以这两个来源在当前版本里更适合扮演:
补充候选来源
而不是绝对主轴
我这次特别在意的一点:偏好记忆接进来了,就要真的吃满
现在 backend 其实已经把偏好记忆接进 Planner context 了。
包括:
user_preferencesprivate_manual
但如果 prompt 里只是轻描淡写地提一下 user_preferences,没有真正去利用 private_manual.model_text,那这条记忆链路的价值其实没有被吃透。
所以这次优化我很在意的一件事就是:
不只是把记忆塞进上下文,而是让 Planner 真正开始使用这份稳定偏好视图。
这样用户才可能慢慢感觉到:
它越来越懂我
而不是每次都像一个失忆排期器。
第二个真正让我兴奋的点,是“草案会话”
如果说多源候选是在解决“第一次生成质量不够”的问题,那么我这次新增的另一个重要方向,是在解决:
生成一次之后
用户怎么继续改
这也是我觉得 Planner 从“一次性建议器”往“可迭代助手”迈进的关键一步。
我现在想做的,不是普通聊天式改计划,而是一个更明确的东西:
Planner 草案会话
它的理想体验应该是:
- 第一次生成一批候选卡片
- 用户确认其中一部分
- 用户继续输入一句修改意见
- AI 只修改剩余未确认卡片
- 如果用户补充了新事项,允许新增候选卡片
这个交互会比“每次重新来一轮全新生成”自然很多。
为什么我不想只靠复用 thread_id
表面看,这个需求好像只要把对话接起来就行。
但我这次比较明确的一点是:
真正需要复用的不是完整聊天历史,而是当前草案状态。
如果只是简单复用同一个 thread_id,但每轮都把完整上下文和完整消息历史继续堆上去,会有两个明显问题:
- token 不会自然降,反而可能越改越贵
- 历史冗余越来越多,模型后续修改质量会下降
所以这块更合理的做法不是“永远续聊”,而是:
同一个草案会话
+ 结构化状态压缩
我更认可的状态模型:accepted、editable、dismissed
如果做草案会话,我觉得前端和 backend 至少都需要开始围绕卡片状态来思考。
最核心的三类状态是:
editable:还允许 AI 继续修改accepted:用户已经确认,不再默认参与修改dismissed:用户明确不要,避免下一轮又被推荐回来
其中我最坚持的一条规则是:
已确认卡片默认不再参与后续 AI 修改。
如果以后真要支持“再改这张卡片”,也应该由用户显式点击,把它重新拉回可编辑区,而不是 AI 自己擅自回头改。
这样用户掌控感会稳很多。
requestId 和 planner_session_id,我觉得必须拆开
这一点虽然很工程,但非常关键。
现在很多系统一开始会偷懒,把“单次请求追踪 ID”和“多轮会话 ID”混着用。
但这次如果真要做草案会话,我觉得这两个角色必须拆开:
requestId
它负责:
- 单次请求追踪
- 日志排查
- SSE 事件关联
planner_session_id
它负责:
- 草案会话连续修改
- 多轮状态复用
- 已确认 / 已忽略 / 可编辑卡片的状态持久化
这样我们才能真正避免:
每次请求看似在续聊
实际上都被系统当成了一个新会话
如果后续继续修改,我希望模型只看“当前草案”,而不是全量历史
这也是我现在比较认同的输入收敛方式。
后续某一轮“继续修改”时,我希望模型拿到的核心信息不是整坨历史,而是:
planner_session_idlatest_user_instructioneditable_cardsaccepted_cards_summarydismissed_cards_summaryrevision_summaryuser_preferencesprivate_manualplanning_brief
这样模型面对的是:
当前这份还没定稿的草案
而不是一大堆历史消息和原始候选池。
这对 token 成本、对话稳定性和后续质量,都会更友好。
这次设计下来,我对首版边界反而更清楚了
我现在觉得首版必须做的,不是“什么都想接”,而是几件很关键的事情:
- 把自然语言输入升级成独立的
planning_brief - 增加
carry_over_tasks,默认 7 天窗口 - 排除
completed / archived / 明显放弃的任务 - 把 today 任务拆成
locked / flexible - 接入用户模板和 auto-apply 模板候选
- 长期计划做弱接入
- prompt 真正使用
private_manual - 前端保留更多 AI 卡片字段
- 支持草案会话内多轮修改
- 引入独立的
planner_session_id
而首版先不做的东西也应该明确:
- 官方模板候选池
- 完整自动时间表 UI
- 自然语言强结构化解析
- 长期计划强约束调度
- 已确认卡片默认允许回流修改
边界清楚之后,整个方案就没那么飘了。
我今天最核心的结论
如果只让我用一句话概括这次 Planner 优化,我会这么说:
真正让 Planner 变好用的,不是让它更会“排”,而是先让它更会“选”。
它要先能理解:
- 今天哪些任务值得做
- 哪些旧任务该回来了
- 哪些旧任务该放过
- 哪些偏好真的应该影响今天
- 用户刚刚说的一句话,到底只是情绪补充,还是一个应该立刻进入候选池的新事项
而在这之后,再去谈完整时间表、自动时间块、负载控制,才是更顺的下一步。
所以这次对我来说,Aido Planner 的升级重点不是“加一个更复杂的 prompt”,而是:
把它从一个 today 重排器
真正推进成一个面向今天决策的候选任务选择器
这是我觉得它开始变得“好用”的第一步。