让 Agent 持续研究半小时:从执行预算到完整报告的工程实践
一个问题只需核对费率时,Agent 读到公告就能回答。换成“比较几只基金,核对跨期披露,算清组合暴露,再解释主要分歧”,执行过程就长了:模型提出下一步,工具拿回材料,计算改变判断,另一个研究分支补上反证,主模型最后组织报告。
如果希望这项工作持续推进二三十分钟,需要让每一次交接都可靠。新的材料要进入下一轮输入,旧历史不能拖慢服务,研究中途的补充要求要留得住,最后还得有资源把发现整理成文。
我们在 ETF Agent 中围绕这些问题做了多轮调整。超长研究全景解释整体关系;本文展开实际工程中的几处转折:什么现象改变了判断,修复落在哪里,为什么没有采用看起来更直接的办法。
文中的“半小时”是任务尺度。这里依据截至 2026 年 11 月 12 日核实的私有工程实现与历史记录写作,几个案例发生在不同研究和实验中;它们不能拼成一次完整的成功运行,也不代表公开教学仓库已经包含全部能力。
一、先给研究空间,也给它留下结束的机会
把总超时调大,是合理的第一步。原有上限确实过小时,复杂任务连必要的材料都来不及取得。不过,一个研究循环通常受多种约束共同限制。
单次模型请求有自己的等待边界;一项工具操作有执行期限;整次研究还受实际执行时间、墙钟时间、请求数量与用户期限约束。即使还剩二十分钟,一次请求也可能已经达到它自己的限制。反过来,连接仍在发送心跳,也不足以证明模型还在取得有效进展。
我们逐步把这些含义拆开,让错误说明对应真实阶段。模型和工具正在执行的时间,与研究从开始至今经过的墙钟时间分别观察;并发活动需要按实际区间合并计时。等待、停放与恢复还各有条件,不能用“当前进程启动了多久”代替整项研究已用的时间。
目前新 Run 的默认实际执行时间上限是 60 分钟,当前 Deep 档位目标为 90 分钟,墙钟安全上限为 120 分钟。旧 Run 保留原先准入的策略,其他期限和额度仍可能更早限制执行。这组数值说明系统提供的空间;研究是否需要这么久、能否充分使用这份空间,要看实际任务。
批准升档之后,正在运行的循环也得用上它
Deep 的实现给过我们一个很具体的提醒:持久记录里的额度增加,与当前进程真正采用新额度,是两件事。
如果数据库已经批准更高时间上限,内存中的计时器仍按旧期限触发,模型照样会提前停止。如果模型看到更多剩余轮次,实际请求入口却还按旧值拒绝,规划也会落空。为此,原预算模块保存一次升级事实,同一个有效版本交给后续请求准入、主循环、时间限制和提醒使用;已用量、预留和未知结算全部保留。确认丢失后重读同一记录,不能再乘一次额度。
对于仅有固定短任务的系统,统一配置加上明确上限通常已经够用。我们需要运行中升档,才需要处理“已批准、已应用、实际使用”的区别。Deep 专题另有完整档位与配对实验,这里只保留与持续执行直接相关的交接。
探索额度不能吃掉成稿机会
另一个真实续答样本曾出现:总请求还有余额,收尾阶段的请求却已用尽。若界面只报一个“剩余请求数”,模型可能继续计划下一轮,而当前发布响应已经占用了最后一次收尾机会。
现在,研究机会、物理请求余额与收尾预留分别表达。默认主模型有独立的收尾轮次,时间预算也留有输出空间。接近边界时,模型可以停止低价值探索,用已有材料形成有边界的结果。系统限制后续探索,已有合法正文仍沿原路径保留。
这会占用一部分本可用于搜索的容量,换来的是复杂任务能够结束并交付的机会。若只把所有额度一起调大,同样的问题会在更晚的时候重现。
二、研究越长,服务自身的计算也会累积
长任务失败时,很容易先怀疑模型慢或数据库卡住。我们遇到的一次租约故障,原因落在同步 CPU 计算上。
当时事件循环阻塞约 30.4 秒;真正的租约查询只用了约 1ms,执行到检查时,租约已经过期。CPU 分析进一步定位到一段历史投影:每处理一条动作,都为同一个候选重新构造前缀、授权证明与封存校验。历史越长,同一份内容被重复处理的代价越明显。
租约心跳依赖事件循环得到调度。代码即使没有等待网络,也可能因为长时间占用执行线程,让心跳迟到。Node.js 官方关于避免阻塞事件循环的说明解释了这一通用机制;本次具体原因来自我们自己的 CPU 分析。
直接延长租约,可以容忍更长暂停,却也会让真正死亡的执行者更晚被识别。把所有校验结果永久缓存,又容易在新回执或用户调整后使用过期事实。我们最后把复用范围限定在同一次同步推导、同一个候选对象:这一小段执行中不变的证明只算一次;下一份快照、新回执或更正到来后仍重新核对。
在原有 46 轮、111 条回执快照的纯函数对照中,这段处理从 27,887ms 降到 358ms,111 条关系的哈希一致。这个结果描述的是一段固定历史计算,不是整项研究的提速比例;它足以说明,原先接近半分钟的同步重复工作可以移除。
这一经历改变了我们的排查顺序。长任务出现心跳、租约或取消延迟时,要把时间拆成等待与计算,并观察历史规模。若瓶颈随动作和回执增长,先找重复扫描、序列化、证明重建和复制。只有确认剩余计算确实必要且仍很重,才讨论迁往工作线程或其他执行域。
三、把结果搬出上下文以后,仍要留够工作的桌面
模型连续读网页、表格和 PDF 时,即使每次结果都不大,累积正文仍会膨胀。重复发送已经处理过的全文会增加负担;只留下一个短摘要,又可能让下一轮无从核对日期、数字与原句。
我们把两种减负手段放在不同位置:结果外置处理材料体积,过程压缩处理研究历史。
一个工具结果先按原身份保存,再给模型足够的当前内容与可回读定位。随着研究推进,已有可靠定位的旧正文可以退出当前请求;需要核对时,通过原读取入口取回。同一个来源读了多少、当前拿到哪个范围、是否还有后续部分,都随实际结果保留。找得到文件,并不等于模型已经读过全部内容。
这件事有一个容易漏掉的前提:小结果也要有定位符。只给大文件生成引用,会让许多小结果累积后无处退役;按相同引用去重,也处理不了大量不同的小结果。因此生产者、保存和请求投影必须配合,不能只在最后发送模型时粗暴裁掉旧消息。
一次压得太小的尝试
我们曾把旧正文驻留目标设为 96KiB,想给后续请求减负。实验中却出现反复读回,最后耗尽原有轮次。实际回读已经带回正文,模型仍可能表示“没有数据”;诊断必须查看它收到的内容,不能把这句话直接当作工具失败事实。
后来把目标调整到 192KiB。在同阶段的一项合成教学任务中,真实模型用六次请求完成了八份文档的要点和指定数值整理,并实际触发了一次旧正文转为引用。这个样本支持了该配置下的可用性,没有给出适用于所有任务的最佳容量。
它揭示的取舍很实用:工作集太大,历史不断占据输入;工作集太小,模型需要频繁重新取得刚用过的材料。全文短、关系紧密的资料适合暂时保留;篇幅大、只在少数争议点需要精读的资料,更适合定位回读。
Anthropic 的上下文工程文章也将压缩、外部笔记与子代理作为不同手段讨论。它们各自适合承载不同内容。对我们而言,真正要观察的是下一次判断需要哪些材料,以及回读增加了多少操作,而非追求一个尽可能小的请求。
四、摘要要真正接到后续工作中
工具正文减负之后,模型自己的过程记录仍会增长:哪些方法失败了,哪两个来源冲突,哪个结论附带什么条件,下一步还缺哪一项依据。这些内容适合形成工作摘要。
我们选择对已经结算的完整历史区间生成摘要。一次调用与对应结果保持完整;不同段落的源区间不重叠;后一次摘要不再拿前一次摘要重新概括。原始用户要求、适用调整和执行事实保留在各自记录中,摘要承担辅助理解的职责。
这样减少了反复改写同一份摘要的机会,代价是各段摘要本身也会积累。原文、摘要、工具定义和输出预留都占容量,整个请求仍需按实际提供方边界拟合。窗口很大、任务很短时,直接保留近期完整上下文通常更简单,无须为了展示“长期能力”多发一次摘要请求。
给摘要机会,再检查真实收益
摘要机制也有过多余限制。原先的预检查按“可能生成的最大摘要加上最坏转义”估算,可能在调用前就拒绝一段实际上有机会变短的历史;生成后,如果没有一次达到软目标,有用的局部缩减也可能被放弃。
后续调整保留真实输入硬上限、已配置的摘要大小和最小收益要求。预检查判断最小合法摘要是否仍可能有用;结果返回后,再用真实摘要组成完整请求,测量是否确实变小并能装入硬容量。软目标表达希望收缩到哪里,有实际价值的部分改善也可以采用。
另一次真实研究中,辅助摘要撞到了内部写死的 900-token 输出上限,未能形成可用摘要。后来这条辅助请求改为使用已经配置的主请求上限与已知提供方上限,取其中较小者;没有可靠上限时不另猜一个数字。失败的摘要不替换原上下文,也不触发一条专门追求摘要成功的无限修复链。额外请求仍计入原研究消耗。
怎样确认它真的起作用?
10 月 25 日的一次真实研究自然产生了两段摘要。我们核对实际发送给主模型的输入:第一段出现在后续 19 次请求中,第二段出现在 11 次请求中;后面这 11 次同时保留两段。两段来自不同的原始区间,第二次摘要请求没有拿第一段再总结。
随后从新进程读取持久检查点,核对摘要与实际输入一致,并检查原始用户文本、引用和动作身份仍然对应。这里最有价值的证据,是摘要从生成端走到了后续消费端,原始任务也没有在途中被改写。
摘要内容仍需要审阅。这个样本中的一处期间表述有缺陷,而原始参数保留不变。这正是原文与摘要分开保存的意义:模型可以写错工作笔记,系统仍应让后续核查回到最初依据。
五、让长任务继续围绕同一个问题工作
时间和上下文解决以后,还要给用户留下调整研究的入口。后台执行一项长研究时,用户可能离开页面、回来追问,或补充“请把跟踪误差也纳入比较”。这些动作需要不同处理。
已获准后台执行的研究,页面断线只解除订阅;用户明确停止,才沿原取消路径执行。重新打开页面应取得原任务的进度和成果。若用户显式发起后继研究,新执行需要带上原问题、适用限制和获准背景;普通新提问则独立处理。
我们有过真实 Continue:父子 Run 关联正确,正文能显示,历史也能读回,研究方向却偏离原来的多指数问题。核查实际后继输入后,发现原用户上下文为空。这说明,关联身份和保存成功都不足以证明任务连续。后续准备流程保留原始用户文本,将助手摘要、来源记录和问题清单分开交接,再到实际主模型输入里核对。
对子代理也采用同样的观察方式。独立调查可以扩展取证广度,但主模型仍需留有综合材料的机会。子代理完成、结果保存、进入主请求、被最终论证正确采用,是连续的不同环节;前几项可通过执行记录核对,最后一项仍要审阅报告。主任务和子任务的执行额度因此分别表达,真实请求与成本照常记录,不能因分账就消失。
同一 Run 的进程恢复还涉及已用时间与执行所有权。截至本文取材时,崩溃留下未闭合活动区间后的某些 Child/Peer 唤醒路径,仍在补齐安全结算与恢复验证。系统不能把已知下界当成完整消耗,给后继执行悄悄退回时间。设计长任务时,应明确支持哪些恢复路径,同时先保证已保存的独立成果可取得。
六、研究能持续了,报告还需要收敛
我们起初的渐进交付强调:有依据、能够独立阅读的部分,可以先发给用户,再继续研究。这对普通问题很合适。一项费用事实已经核清,不必等待其他不相关分支。
复杂研究带来另一种摩擦。几份真实报告在过程中追加了多轮更正,一些事实后来撤回,引用与论证也出现不一致。用户最终需要把前文、补充和勘误自己合起来,才能知道当前结论是什么。持续输出给了人进展感,却没有自然形成完整报告。
后来我们调整 Deep 的指导:先反馈正在核什么、遇到什么冲突;经过必要证据比较,再组织依赖这些证据的综合结论。 独立成熟的小节仍允许提前交付,系统不增加一个判定“证据已经充分”的布尔门,也不强制等到某个时间比例才准许写作。
更正要回到原章节
已有发布路径支持版本替换,因此后续修正应更新对应章节,让用户直接读到当前报告;旧版本保留自己的历史。只在末尾加一句“以上第二点作废”,会把整理责任继续留给读者。
Deep 默认更正空间也随之扩大:每块默认从 2 次到 6 次,整次研究从 8 次到 24 次。显式配置的上限仍优先,零也继续是零;已消费与预留不重置。容量让合法改判有地方发生,是否改得正确,仍取决于来源和论证。
这一步会增加模型组织与修订内容的机会,也可能增加消耗。它适合来源有修订、结论依赖多路材料的任务。对短小事实问答,直接给出核实答案更简单。已有配对实验的结果也有好有坏:更多额度和更审慎的指导,没有保证每次都答得更全、更快。
临近边界时,结束也属于研究工作
局部工具失败、可选提醒失败、某次引用附件失效,应先限制在各自影响范围。统一的运行终结职责再根据真实状态,决定是否能沿原机制继续,或交给主模型用保留空间完成有限收尾。不能让每个抛出异常的位置都顺手取得整项任务的停止权。
收尾需要围绕原问题整合仍成立的结论、主要依据、更正和关键缺口。若材料只支持部分判断,就交付相应范围;若后续一步失败,先前合法生成与保存的正文继续保留。新阶段不能凭空产生新预算,已经发生的未知效果也不能通过换身份重做而消失。
七、给自己的 Agent 增加长研究能力,先观察什么?
实践中,我更愿意先选一项确实需要多轮材料处理的任务,沿实际输入、执行和报告定位瓶颈。不同现象对应的下一步差别很大:
| 观察到的现象 | 值得先核对的内容 |
|---|---|
| 总时间还有余量,却无法继续 | 当前阶段、下一请求实际额度、单次操作期限和收尾预留 |
| 历史越长,心跳或取消越迟 | 事件循环阻塞、重复投影、序列化和证明重建的增长 |
| 反复读取刚看过的材料 | 实际输入是否已有正文、定位是否完整、驻留空间是否过小 |
| 摘要保存成功,研究却忘了任务 | 原始要求和摘要是否真正进入下一次主模型请求 |
| 专家都有结果,报告仍缺一块 | 主模型是否收到结果,是否保留综合机会,以及论证是否采用它 |
| 最终正文充满补充和勘误 | 进度与结论的发布节奏、原章节修订和完整报告版本 |
这些观察不要求一开始就搭齐所有模块。窗口足够时可以先保留原文,任务较短时可以先用固定预算;只有出现具体瓶颈,再增加回读、压缩、独立研究分支或恢复机制。
“持续研究半小时”最终牵动的是整条工作链:执行有空间,历史成本可控,模型拿得到下一步所需材料,用户要求仍在,报告能够随证据更新。时长只是容纳这些工作的窗口。值得保留的,是每段时间之后,原问题有了什么更清楚、更可靠、也更容易取得的答案。
相关延伸:超长研究全景 · Deep 的资源与配对实验 · 同一 Run 上下文压缩 · 后台研究与前台交互。