文章

鸣金收兵:给Agent一个控制执行时间的秒表

鸣金收兵:给Agent一个控制执行时间的秒表

1. 背景

在排障和答疑场景中,源码分析通常不是主 Agent 顺手完成的一次工具调用,而是一项可以独立承接的能力。承担这项能力的源码分析智能体,可以看成一个领域特定的微服务。

上游传入问题背景和分析目标,智能体在指定代码范围内查找源码、追踪调用链并整理证据,最后返回分析结果。

源码分析智能体通常遵循 ReAct(Reasoning and Acting)模式。它先根据已有证据做出判断,再调用工具获取信息。观察结果后,它决定下一步。

这个过程不是一条固定流水线。源码分析智能体可能先定位入口,再追踪调用链。发现新线索后,它还要核对配置和异常分支。因此,即使面对相似的问题,执行轮数和耗时也可能相差很大。

当上游同步阻塞调用这套系统时,必须限制任务的执行时间。问题也随之从“能不能完成分析”,变成了“如何在有限时间内交付有用结果”。

2. 硬超时为什么不够

上游通过一次 RPC 调用源码分析智能体,就像调用普通微服务一样。这次调用必须设置时间上限。调用方不能无限等待,任务也不能无限占用资源。

本文把一次任务允许使用的总时长称为【任务时间预算】。服务端根据这份预算建立【任务级 deadline】。预算表达“允许投入多少时间”。deadline 负责在到点后取消整次任务。

问题在于,任务级 deadline 的职责是到点取消。它不会主动整理已经得到的结果。

假设一个任务有 60 秒预算。源码分析智能体用前 50 秒找到了关键调用链,正准备整理报告,deadline 却先到了。调用方最终只得到一个超时错误。前面的推理、工具调用和已确认证据,都没有变成可交付结果。

这就是硬超时最大的痛点:任务并非没有结果,而是结果停留在“差最后一步”的状态。硬超时一到,接近完成的工作也会失去交付价值。

但硬超时只回答一个问题:什么时候必须停止。它没有回答另外三个问题:

  • 什么时候不该再扩大分析范围?
  • 什么时候应该停止验证新的方向?
  • 什么时候必须留出时间整理结论?

如果 Agent 直到最后一秒才知道任务即将结束,它很可能还在启动新的工具调用。等工具返回后,剩余时间已经不足以组织答案。

所以,时间预算至少有两层职责:

  1. 到点强制停止,限制资源消耗。
  2. 提前反馈水位,让 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%停止扩展,开始整理已有结论
交付剩余时间不超过交付阈值停止调用工具,立即输出当前结论

这套水位设计有六个要点:

  1. 阈值只是工程配置。 工具耗时、模型响应速度和报告长度不同,合适的水位也会不同。
  2. 水位不一定逐级经过。 以 60 秒预算为例,10% 是 6 秒,交付阈值取 15 秒。25% 也是 15 秒,所以收敛区间为空,任务会从收紧直接进入交付。
  3. 水位必须对应明确动作。 重点不是 50% 或 25% 是否“标准”,而是 Agent 到达水位后应该做什么。
  4. 反馈和准入必须使用同一套判断。 否则,后置提醒可能还在说“可以继续”,前置检查却已经拒绝工具。
  5. 任务时间预算覆盖整次任务。 准备代码、确认版本、调用工具和生成响应,都会消耗同一份预算。
  6. 提醒和 deadline 各负其责。 水位提醒只能影响下一轮决策,不能中断正在运行的工具或模型生成。真正到点时,仍由任务级 deadline 取消任务。

5. 并行 SubAgent 的预算注意事项

并行 SubAgent 共享同一个任务预算时,需要注意三点:

  1. 共享同一个任务级 deadline。 不论由哪个 SubAgent 执行,工具调用和最终输出都消耗同一份任务时间预算。
  2. 不要使用任务级的单一“已提醒”标记。 每次工具调用产生结果后,都应重新计算水位,再将提醒注入对应 SubAgent 的下一轮决策。少量重复提醒可以接受。预算信息漏传,却可能让某个分支一直探索到硬超时。
  3. 工具失败也要反馈剩余预算。 失败同样消耗时间,下一轮决策仍然需要知道最新水位。只有任务已经取消、中断或结束时,才不必继续注入提醒。

6. Claude Code 如何承载这条控制链

在 Claude Code 方案中,可以使用 Claude Agent SDK 提供的工具 Hook。它类似 RPC 责任链中的拦截器,在工具执行前后介入流程。

一次完整的工具调用会经过以下步骤:

  1. Agent 发起工具调用。 例如,Agent 准备读取一个文件。
  2. PreToolUse 在执行前拦截。 Hook 可以看到工具名称和参数。回调读取共享的任务时间预算,再计算当前水位。预算充足时放行。进入交付水位后,拒绝调用并把原因返回给 Agent。
  3. 工具真正执行。 只有通过前置检查,读取文件等操作才会发生。
  4. PostToolUse 在执行后追加提醒。 它读取工具结果,重新计算剩余时间,再通过 additionalContext 返回时间提醒。
  5. 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. 代价与边界

这套方案的代价和边界可以归纳为五点:

  1. 提醒会增加上下文开销。 additionalContext 会进入下一轮推理并消耗 token。是否值得,要结合额外开销和超时任务的交付改善来判断。
  2. 提醒不能替代合理的预算。 如果一次跨模块分析需要几分钟,却只分配几十秒,Agent 最多交付一份局部结论。
  3. 这套机制有适用范围。 它适合执行时间不确定、但阶段性结果仍有价值的开放任务。对于耗时稳定,或者结果必须完整才有意义的任务,普通超时可能已经足够。
  4. 三类时间限制不能混用。 上游 RPC timeout 限制调用方等待时间;任务级 deadline 取消整次 Agent 任务;工具级 timeout 限制一次具体调用。它们各自解决不同问题。上游 timeout 还要覆盖任务预算和响应传输余量。否则下游还没到 deadline,调用方就已经超时。
  5. Hook 行为依赖框架契约。 升级 Claude Code 或 Claude Agent SDK 后,应重新核验 Hook 的触发事件、决策字段和上下文注入行为。

秒表不会让 Agent 跑得更快。它只是让 Agent 更早知道什么时候该收兵,尽量把已经确认的证据变成一份来得及交付的结论。

9. 参考

本文由作者按照 CC BY 4.0 进行授权