Agent Runtime 如何管理任务的执行

目录

Agent Runtime 如何管理任务的执行

在第一篇里,我们用“谁决定下一步”区分了 AI 应用的自主性;第二篇则通过一个最小 Agent,说明了模型判断、工具执行和结果回填如何组成 Agent Loop。这篇继续看第二篇示意图中负责“执行和控制”的 Agent Runtime,看看它怎样管理这个循环,以及任务需要等待审批、收到用户的新要求或意外中断时,该如何处理。

一个简单的 Mini AI Agent,几十行代码就能跑起来:用 while 循环把用户问题交给大模型,执行模型返回的工具调用,再把结果送回模型,直到任务结束。

任务运行得久一些,就会遇到这个循环没有回答的问题:

  • 智能体在任务执行到一半时重启,任务该从哪里继续? 如果上一步已经调用了发送邮件或扣款接口,再执行一次会有什么后果?
  • 一个任务已经跑了 20 分钟,用户此时更换了全局默认模型或权限配置,这些变更要不要影响正在执行的任务?
  • 用户发现 Agent 走错了方向,在运行中补充了一句话。这条输入什么时候生效?会不会与当前动作冲突?
  • 历史对话越来越长,超出了 Token 预算,哪些内容可以压缩?原始记录还要不要保留?

这些问题涉及执行状态、配置、输入和存储,需要由模型之外的程序处理。在 Haifa Agent 项目中,承担这些职责的是 Agent Runtime。

本文讨论 Haifa Agent 中的 Runtime 设计。编码智能体 和个人助手(PA)承载不同的业务,SDK 提供开发接口,Runtime 则管理它们共同需要的执行机制:任务如何启动,模型和工具如何调用,何时等待用户,以及执行中断后如何处理已有状态。可以把它类比为应用之下的操作系统内核,不过这里讨论的范围是 Agent 任务的运行。

智能体应用、SDK 组装层与 Agent Runtime 的分层关系

一、任务启动时,先确定配置和身份

假设一个任务启动时使用 Qwen模型,执行期间管理员把全局默认模型改成了 DeepSeek。如果每一轮都重新读取全局配置,同一个任务就可能在中途换用另一套配置。权限范围和技能清单也有同样的问题。

Haifa Runtime 在 Run 启动时冻结已经解析出的配置,包括模型绑定、权限范围和技能清单。后续执行使用这份快照,全局配置的变更不会悄悄替换当前 Run 的配置。这样,即使任务持续运行半个小时,也能知道它依据哪套配置执行。

任务的身份也需要独立于执行线程。线程可能退出,任务也可能因等待人工审批而挂起;仅靠线程状态,无法完整记录这件事的执行过程。

Runtime 为此区分了两个概念:

  • Run:逻辑上的一次任务,记录这件事的身份和状态。
  • Attempt:一次具体的执行尝试。同一个 Run 在暂停、审批和恢复过程中,可以先后对应多个 Attempt,以web search工具调用为例子,可能第一次web search发生超时,第二次重试调用成功,Run一次,Attempt两次。

这层区分让任务状态可以跨越某一次执行尝试持续追踪。底层使用 Java 21 虚拟线程进行异步调度时,Run 的生命周期也不必与某个线程绑定。异常中断后是否能够继续执行,还需要结合工具结果和外部副作用判断,后文会展开说明。

二、一轮 Agent Loop 要处理什么

观察、决策、行动,可以概括 Agent 循环的基本过程。落实到一个生产级的Agent实现,还需要在模型调用前后处理恢复、预算和状态保存。

在 Haifa Runtime 的 DefaultAgentLoop 中,一轮循环涉及以下十个步骤:

  1. 恢复检查点:检查是否有刚通过的人工审批结果。
  2. 检查控制信号和预算:通过 Guard 机制检查暂停、取消信号,以及 Token、调用次数和运行时间是否超限。
  3. 接收运行中追加的输入:在约定的处理位置合入用户的新消息。
  4. 核对待确认的工具执行:检查此前是否有已经发起、尚未得到结果的外部工具调用。
  5. 评估上下文压缩:判断上下文是否需要先做摘要压缩。
  6. 构建 Context IR:组装指令、历史消息、记忆和可用工具,形成结构化上下文。
  7. 调用模型:通过模型适配器请求 LLM,处理流式输出与重试。
  8. 分发决策:根据模型输出,调用工具、派发子 Agent、请求人工交互,或结束任务。
  9. 保存执行结果:持久化本轮结果,并向外广播持久化事件。
  10. 推进或退出:确定是否进入下一轮循环。

模型调用位于这条执行流程中。它产生的决策还要经过 Runtime 的分发和检查,执行结果也需要保存下来,供后续循环或恢复过程使用。

三、上下文怎样构建,历史怎样压缩

模型每次收到的上下文可能包含系统指令、历史会话、长期记忆、已激活的 Skill 说明和工具声明。这些内容的来源和用途不同,还需要共同满足 Token 预算。

Haifa Runtime 使用 Context IR,也就是上下文的中间表示。这个概念借用了编译原理中 IR 的思路:上下文构建器先将各部分内容组装成结构化表示,再投影为 OpenAI、Anthropic 或 Gemini 等模型所需的报文格式。上下文的组织与具体模型的请求格式由此分开处理。

当任务持续运行、历史内容超出预算时,语义压缩协调器(Compaction)会划定需要压缩的历史范围,调用模型生成摘要。摘要通过校验后,替换上下文中的对应历史内容。

这里需要区分模型当前看到的内容和存储中的原始记录。摘要是供模型使用的压缩视图;原始历史事实仍保存在底层存储中,不会因为这次压缩被物理删除。

四、工具调用如何校验,结果未知时怎么办

工具调用会让模型的决策影响外部系统。修改文件、执行命令、发送邮件或扣款,都可能产生副作用;外部接口所需的凭据也需要与模型上下文隔离。

Haifa Runtime 通过 Tool Pipeline 管理工具执行。其中,执行日志、调用校验和凭据处理各自承担不同的职责。

工具调用经过权限与策略校验、执行日志和凭据隔离后访问外部 API

先记录执行意图

真正调用外部 API 前,Runtime 会在 Tool Execution Journal 中记录准备执行的工具操作。

如果请求发出后进程崩溃,恢复时可能只知道调用已经派发,却不知道外部系统是否完成了操作。例如,邮件可能已经发出,只是返回结果没来得及保存。此时重新发送请求,就可能发出第二封邮件。

对于这种“已派发但结果未知”的状态,Runtime 不会盲目自动重放。执行日志保留了这项尚未确认的事实,恢复过程需要据此处理任务。

校验调用并执行策略裁决

工具调用需要依次经过工具绑定检查、权限校验和输入 Schema 校验,并由策略中心(Policy)裁决:

  • ALLOW:允许执行。
  • DENY:拒绝执行。
  • ASK:挂起,等待人工审批。

这些检查决定模型提出的工具调用能否执行,以及是否需要用户介入。

在调用边界处理凭据

外部 API 所需的 API Key 不应写入 Prompt。Runtime 在实际发起调用的可信边界内,通过 Credential Broker 解析密钥并注入请求,同时对返回结果中的敏感信息做脱敏处理,避免凭据随结果进入模型上下文。

五、用户介入、异常中断和子 Agent 如何处理

长任务可能需要等待审批,也可能在执行中收到用户的新要求。这两种交互对任务状态的影响不同。任务还可能意外中断,或者派发新的子任务,Runtime 都需要记录对应的生命周期变化。

等待审批与接收追加输入

Haifa Runtime 为人机交互区分了两条通道。

Interaction 用于人工审批和提问澄清。任务执行到需要人类回应的位置时,保存检查点,进入等待挂起状态,并释放计算资源。收到回应后,再从保存的检查点恢复执行。

Interaction 保存检查点并挂起,收到人工审批或澄清后启动新的 Attempt 继续执行

Run Input(Steer) 用于接收运行中追加的输入。例如 Agent 正在执行时,用户补充“顺便把单元测试也写了”。这条消息会先被暂存,在下一轮循环开头的安全点(Safe Point)合入模型上下文,当前正在执行的动作不会因此被打断。

追加输入还涉及一个并发问题:用户刚发出消息,任务恰好结束。Runtime 对这类竞争进行处理,避免消息已经提交,却在任务结束时被静默丢弃。

Run Input 暂存用户追加的输入,在下一轮循环的安全点合入上下文

异常中断后核对已有状态

等待审批时,Runtime 有明确的挂起位置和检查点。机器断电或进程被杀时,情况就不同了:程序可能停在任意位置,某个外部操作也可能已经完成,但结果尚未记录。

当任务失去执行所有权、成为孤儿任务时,InterruptedRunSettler 会核对已知的工具结果,取消停留在中途的步骤,收敛任务状态,并给出终止报告。它不会在尚有未知外部副作用时直接重启循环。

因此,“重启后恢复”需要说明恢复到什么程度。对于有检查点的主动挂起,可以恢复执行;对于异常中断,需要先处理已有事实,结果也可能是终止任务并报告中断情况。

进程异常中断后,InterruptedRunSettler 核对工具日志、终止失效 Attempt 并输出终止报告

子 Agent 的身份、权限与派发防重

主 Agent 派发子任务时,Runtime 会为子 Agent 创建具有独立身份和完整生命周期的标准 AgentRun。

子 Agent 的可用工具列表通过取交集收窄,限制其权限范围。派发还需要处理重试:子任务 ID 由父任务 ID 和工具调用 ID 派生,同一个派发请求再次出现时,系统会关联已有子任务,避免重复创建。

这样,子任务的执行状态、权限范围和重复派发,都有对应的记录与处理机制。

六、回到一次任务的执行

用户交给 Agent 一个任务后,看到的是它查资料、读文件、调用工具,最后返回结果。这个过程可能持续很久,期间用户会补充要求,工具可能报错,程序也可能意外退出。Runtime 需要记录任务执行到了哪里,在合适的时机接收新输入,并根据已有结果决定如何继续处理。

第一篇讨论了 AI 可以获得多少决定权,第二篇解释了模型如何通过 Agent Loop 持续判断和行动。这篇补充的是程序如何组织和控制这个过程:模型提出工具调用请求后,Runtime 检查权限、执行操作、保存结果,再把结果交给下一轮模型调用。模型参与的决策越多,这些执行规则就越需要明确。

本文介绍的 Haifa Agent Runtime 目前运行在单节点上,对中断的处理也有边界。等待审批时,任务可以保存检查点,收到回应后继续执行;进程意外退出时,则需要先核对已经发生的操作。如果一封邮件可能已经发出,只是结果没来得及保存,程序就不能直接再发一次。此时,核对记录、停止任务并说明哪些结果尚未确认,也是 Runtime 需要完成的工作。