长任务支持

长任务不是把上下文压小,而是让状态可恢复。从 Maka 的 append-only log 到 Bubu 的中断恢复。

bubu agent 的设计理念就决定了它必定要有执行 24h+ 任务的能力,但碍于模型限制,注定了这是一个非常复杂的工程。

业内早期的压缩方案是保留首次 System Prompt + user 消息,对之后的 System Prompt 进行删减,保留之后每次的 user input 和 Model Response。之后的方案是使用 AI 对上下文生成摘要。但这些方案要不然就是压缩性能太差,要不然就是丢失细节太多,这些缺点在长工作中是致命的,生产环境中不允许出现这种风险。

bubu 一直在寻找能支持实际生产的长任务执行解决方案,最终在 maka 里找到了最符合需求的。

Maka 的这篇 Log Is the Runtime: Managing Agent State and Context with Append-Only Logs 讲得很清楚,地址是 https://github.com/apache/maka/blob/main/docs/blogs/log-is-the-runtime.md。

简单说就是 maka 用了多种优秀的系统思维来设计 Agent,在 maka 中,一个会话实际上是多个分页。

在一个窗口中产生的所有记录格式化后以 log 形式存入 SQLite,在 maka 中被称为日志。每条日志的正文是若干种事件内容之一:Text、Thinking、FunctionCall、FunctionResponse、Error。除了内容,事件还挂着一组动作字段,比如 stateDelta、permissionRequest、permissionDecision、tokenUsage、toolDispatch、toolRecovery 和 endInvocation。下次模型调用会重新组装,日志存档完毕后会做校验。为此设计了一整套系统,详细在 maka 的 blog 里,这里就不展开说了。

这种优秀的工程思维给 bubu 带来了启发,bubu 设计了一套轻量的任务中断恢复系统。一次用户请求进入执行后,Bubu 会在会话上打上 open_turn 标记,表示这条任务的执行边界尚未闭合。Turn 正常结束、用户主动停止、或应用有序退出并等完落盘,标记都会被清掉。

需要区分的是用户主动点停止,它不属于这种线索。停止会闭合 turn,不是崩溃,因此不会在重启后自动续跑。

SQLite 里的消息链是用户请求、模型给出的工具调用(T1)、工具结果(T2)。工具执行被拆成两个可观察的状态:T1 Dispatch 表示模型已决定调用、意图已落盘,T2 Outcome 表示结果已落盘、历史配对完成。

崩溃若发生在两者之间,系统面对的是「副作用可能已经发生,也可能没有」。Bubu 在这里采用与 Maka 相同的保守原则:无法证明幂等与安全时,禁止静默重做;能证明的,才允许静默重试。

于是恢复策略有两个清晰的规则。只读类工具,比如读文件、搜索、回忆记忆,恢复时直接静默重跑,把真实结果补回历史。会改环境的工具,比如写文件、改文件、跑命令,则不自动重做,系统写入一条明确的中断结果:不要假设成败,先核对工作区再决定是否重试,模型带着这份事实继续推理。

在配对完未完成的工具调用之后,Bubu 会在没有新的用户输入的情况下再次打开 agent loop,让模型从已恢复的前缀上继续任务。恢复开始时,界面会给出一条明确提示:检测到未完成任务、正在自动恢复;哪些步骤已静默重试;哪些写操作或命令已挂起待核对。用户可以关掉提示,续跑本身不受影响。