鸣金收兵:给Agent一个控制执行时间的秒表
1. 背景
在排障和答疑场景中,源码分析通常不是主 Agent 顺手完成的一次工具调用,而是一项可以独立承接的能力。承担这项能力的源码分析智能体,可以看成一个领域特定的微服务。
上游传入问题背景和分析目标,智能体在指定代码范围内查找源码、追踪调用链并整理证据,最后返回分析结果。
源码分析智能体通常遵循 ReAct(Reasoning and Acting)模式。它先根据已有证据做出判断,再调用工具获取信息。观察结果后,它决定下一步。
这个过程不是一条固定流水线。源码分析智能体可能先定位入口,再追踪调用链。发现新线索后,它还要核对配置和异常分支。因此,即使面对相似的问题,执行轮数和耗时也可能相差很大。
当上游同步阻塞调用这套系统时,必须限制任务的执行时间。问题也随之从“能不能完成分析”,变成了“如何在有限时间内交付有用结果”。
2. 硬超时为什么不够
上游通过一次 RPC 调用源码分析智能体,就像调用普通微服务一样。这次调用必须设置时间上限。调用方不能无限等待,任务也不能无限占用资源。
本文把一次任务允许使用的总时长称为【任务时间预算】。服务端根据这份预算建立【任务级 deadline】。预算表达“允许投入多少时间”。deadline 负责在到点后取消整次任务。
问题在于,任务级 deadline 的职责是到点取消。它不会主动整理已经得到的结果。
假设一个任务有 60 秒预算。源码分析智能体用前 50 秒找到了关键调用链,正准备整理报告,deadline 却先到了。调用方最终只得到一个超时错误。前面的推理、工具调用和已确认证据,都没有变成可交付结果。
这就是硬超时最大的痛点:任务并非没有结果,而是结果停留在“差最后一步”的状态。硬超时一到,接近完成的工作也会失去交付价值。
但硬超时只回答一个问题:什么时候必须停止。它没有回答另外三个问题:
- 什么时候不该再扩大分析范围?
- 什么时候应该停止验证新的方向?
- 什么时候必须留出时间整理结论?
如果 Agent 直到最后一秒才知道任务即将结束,它很可能还在启动新的工具调用。等工具返回后,剩余时间已经不足以组织答案。
所以,时间预算至少有两层职责:
- 到点强制停止,限制资源消耗。
- 提前反馈水位,让 Agent 逐步从探索切换到收敛。
第一层解决“不能无限运行”,第二层解决“不要把已经得到的结果浪费掉”。
3. 把时间预算放进决策循环
ReAct 循环为时间控制提供了天然的切入点。每次工具调用都是一个明确的控制节点。系统可以在执行前检查是否允许继续,也可以在执行后反馈剩余时间。这种做法不依赖某个特定框架。
flowchart LR
A[Agent 决定下一步] --> B{执行前拦截}
B -->|允许| C[调用工具]
C --> D[执行后拦截]
D --> E[反馈剩余时间和行为建议]
E --> A
B -->|进入交付阶段| F[拒绝新工具调用]
F --> G[基于已有证据输出]
任务开始时记录开始时间和任务级 deadline。之后每次工具调用结束,都用当前时间重新计算剩余预算,再把结果带入下一轮决策。
执行后的拦截器负责反馈。它告诉 Agent 当前还剩多少时间,以及下一步应该继续推进、缩小范围,还是直接整理结论。
执行前的拦截器负责准入。如果预算已经进入交付阶段,它会拒绝新的工具调用,并要求 Agent 基于已有证据输出当前结论。
这里的“拦截器”可以用 Hook 实现。Hook 是一种生命周期回调,类似责任链上的拦截器。它在工具执行前后观察或改变流程,但不负责完成分析。
4. 用水位引导 Agent 收敛
只告诉 Agent“还剩 30 秒”还不够。30 秒是继续探索,还是立即收尾,取决于任务总预算和当前所处阶段。
因此,可以把剩余时间映射为几个行为水位。水位不是精确的性能模型,而是给决策循环使用的控制信号。
先计算【交付阈值】。它取总预算的 10% 和 15 秒中的较大值。本文示例的最低预算为 60 秒,所以交付阈值不会超过总预算的 25%。如果允许更短的预算,就需要重新校准水位。接着,再用这个阈值划分四个区间。
| 水位 | 触发条件 | 行为指令 |
|---|---|---|
| 正常 | 剩余时间超过 50% | 按当前方向推进,优先获取关键证据 |
| 收紧 | 剩余时间超过 25%,且不超过 50% | 不再扩大范围,完成当前方向的核验 |
| 收敛 | 剩余时间超过交付阈值,且不超过 25% | 停止扩展,开始整理已有结论 |
| 交付 | 剩余时间不超过交付阈值 | 停止调用工具,立即输出当前结论 |
这套水位设计有六个要点:
- 阈值只是工程配置。 工具耗时、模型响应速度和报告长度不同,合适的水位也会不同。
- 水位不一定逐级经过。 以 60 秒预算为例,10% 是 6 秒,交付阈值取 15 秒。25% 也是 15 秒,所以收敛区间为空,任务会从收紧直接进入交付。
- 水位必须对应明确动作。 重点不是 50% 或 25% 是否“标准”,而是 Agent 到达水位后应该做什么。
- 反馈和准入必须使用同一套判断。 否则,后置提醒可能还在说“可以继续”,前置检查却已经拒绝工具。
- 任务时间预算覆盖整次任务。 准备代码、确认版本、调用工具和生成响应,都会消耗同一份预算。
- 提醒和 deadline 各负其责。 水位提醒只能影响下一轮决策,不能中断正在运行的工具或模型生成。真正到点时,仍由任务级 deadline 取消任务。
5. 并行 SubAgent 的预算注意事项
并行 SubAgent 共享同一个任务预算时,需要注意三点:
- 共享同一个任务级 deadline。 不论由哪个
SubAgent执行,工具调用和最终输出都消耗同一份任务时间预算。 - 不要使用任务级的单一“已提醒”标记。 每次工具调用产生结果后,都应重新计算水位,再将提醒注入对应
SubAgent的下一轮决策。少量重复提醒可以接受。预算信息漏传,却可能让某个分支一直探索到硬超时。 - 工具失败也要反馈剩余预算。 失败同样消耗时间,下一轮决策仍然需要知道最新水位。只有任务已经取消、中断或结束时,才不必继续注入提醒。
6. Claude Code 如何承载这条控制链
在 Claude Code 方案中,可以使用 Claude Agent SDK 提供的工具 Hook。它类似 RPC 责任链中的拦截器,在工具执行前后介入流程。
一次完整的工具调用会经过以下步骤:
- Agent 发起工具调用。 例如,Agent 准备读取一个文件。
PreToolUse在执行前拦截。 Hook 可以看到工具名称和参数。回调读取共享的任务时间预算,再计算当前水位。预算充足时放行。进入交付水位后,拒绝调用并把原因返回给 Agent。- 工具真正执行。 只有通过前置检查,读取文件等操作才会发生。
PostToolUse在执行后追加提醒。 它读取工具结果,重新计算剩余时间,再通过additionalContext返回时间提醒。- Agent 开始下一轮决策。 此时模型会同时看到工具结果和时间提醒,再决定继续探索还是开始收敛。
追加到上下文中的提醒可以是这样:
1
[时间预算] 当前剩余 28 秒,已进入收敛水位。停止扩大分析范围,优先整理已有证据。
如果工具执行失败,则由 PostToolUseFailure 完成第 4 步。失败也会消耗任务预算,所以失败结果后面同样要追加时间提醒。
Hook 只能在工具调用前后介入,不能打断正在运行的工具或模型生成。因此,这条控制链仍然需要任务级 deadline 兜底。后置 Hook 负责提醒收敛,前置 Hook 负责阻止新调用。deadline 负责到点终止任务。
7. 用 effort 表达语义化资源预算
时间只是 Agent 任务的一种资源。工具调用次数、模型 token、并发额度和外部服务配额,也可能需要治理。
如果让上游 Agent 直接填写秒数,它就必须判断 90 秒和 120 秒的差异。这个判断通常没有稳定依据。结果要么过于保守,要么为了保险总是选择最大值。
更好的方式,是让调用方表达语义化的资源预算。调用方只需要选择低、中或高等投入档位。执行层再把档位映射为具体时间和其他限制。
这里可以借鉴 Claude Code /effort 的交互思路:调用者表达“这轮任务需要投入多少”,而不是手动编排每一个底层参数。
这个类比只用于建立心智模型。Claude Code 的 /effort 控制模型的推理投入,并不管理任务时间、工具次数或并发额度。源码分析工具只借用【语义档位】这种表达方式。具体资源预算,仍由自己的执行层解释。
例如,源码分析工具可以把投入档位映射成如下预算:
| 投入档位 | 示例时间预算 | 适用场景 |
|---|---|---|
| 低 | 60 秒 | 首次探索、定位入口、验证单个简单事实 |
| 中 | 120 秒 | 沿明确方向还原主要调用链、验证候选原因 |
| 高 | 180 秒 | 跨模块分析、解决多条证据之间的冲突 |
表中的秒数只是配置示例。具体映射应由能力提供方统一维护,不让上游 Agent 在每次调用时临时猜一个数字。
这样分层后,上游负责表达投入程度,下游负责解释资源含义。调用方不再猜测具体秒数,而是通过语义档位申请资源。
8. 代价与边界
这套方案的代价和边界可以归纳为五点:
- 提醒会增加上下文开销。
additionalContext会进入下一轮推理并消耗 token。是否值得,要结合额外开销和超时任务的交付改善来判断。 - 提醒不能替代合理的预算。 如果一次跨模块分析需要几分钟,却只分配几十秒,Agent 最多交付一份局部结论。
- 这套机制有适用范围。 它适合执行时间不确定、但阶段性结果仍有价值的开放任务。对于耗时稳定,或者结果必须完整才有意义的任务,普通超时可能已经足够。
- 三类时间限制不能混用。 上游 RPC timeout 限制调用方等待时间;任务级 deadline 取消整次 Agent 任务;工具级 timeout 限制一次具体调用。它们各自解决不同问题。上游 timeout 还要覆盖任务预算和响应传输余量。否则下游还没到 deadline,调用方就已经超时。
- Hook 行为依赖框架契约。 升级 Claude Code 或 Claude Agent SDK 后,应重新核验 Hook 的触发事件、决策字段和上下文注入行为。
秒表不会让 Agent 跑得更快。它只是让 Agent 更早知道什么时候该收兵,尽量把已经确认的证据变成一份来得及交付的结论。