AI代理循环架构:耐久性与编排

djfarrelly 发布于 2026-06-20 阅读 137

本文探讨了AI代理系统中的循环(loop)架构,强调耐久性和编排的重要性。作者提出三层架构:循环(带调度和决策的cron)、技能(可重试、可组合的耐久工作流)和编排器(管理调度、重试、并发和历史记录)。核心论点是,仅靠基础while循环无法应对生产环境中的崩溃、重启和并发问题,需要耐久执行层确保每个步骤被检查点保存,失败后可恢复。文章还介绍了代理如何编写自身技能并自我迭代,通过编排器实现热部署、观察和故障处理。最后强调,模型并非壁垒,循环和技能构成的系统才是企业的长期资产。

图像

每个人都在问“循环到底是什么?”但没人问的是:谁在运行这个循环?

AI 领域的讨论已经把循环归结为 Agent 系统的核心原语。Matt Van Horn(@mvanhorn)从 ReAct 追溯到工具使用,再到编排循环,最后是循环监督循环,梳理了 Agent 循环的演变脉络。Addy Osmani(@addyosmani)则拆解了 循环内部的构建模块:自动化、工作树、技能、连接器、子 Agent。Van Horn 重点强调了持久性,认为无法在重启后存活的循环就不是真正的循环。Osmani 的核心线索是编排:设计一个系统来替你去提示 Agent。

我想把他们的观点再往前推一步。持久性不仅是循环本身的属性,更是其底层整个执行层的属性。重要的是,持久的编排是构建 Agent 循环架构的基础。让我们来拆解这个架构。

循环在何处失效

/loop/goal 模式能很好地处理单 Agent、单会话的任务。Agent 循环执行,直到任务完成。这覆盖了很多场景。但下一阶段(Van Horn 框架中的 Stage 5)就会出问题:

  • 循环监督其他循环
  • 循环按计划运行,而不仅仅由人类触发
  • 能承受进程重启、部署和崩溃的循环
  • 能生成子 Agent 并等待结果(有时需要数小时)
  • 需要事后可观测的循环

这不再是提示工程的问题,而是基础设施的问题。

Van Horn 引用了 @runes_leo 的话:“AI 编码中最昂贵的不再是写代码,而是管理 Agent 循环。” 终端里的 while True 给不了你任何这些东西。VM 或沙箱中的长进程同样做不到。

想象一下在服务器上运行 Agent 循环时会发生什么。进程会死掉或重启——部署、内存溢出、竞价实例回收都会导致循环重启。但循环之前在做哪一步?它已经发送了那条 Slack 消息吗?它已经调用了子 Agent 吗?

你不知道。它会重新开始,重新获取已经拥有的数据,重新调用 LLM 来做已经做过的决策,发送重复的通知,生成重复的子 Agent。你醒来时发现三条相同的 Slack 消息和一个困惑的团队。

解决方案不是“更好的错误处理”——而是一个执行模型:每一步都有检查点,每个决策都持久化,恢复意味着从最后成功的一步继续。

三层 Agent 循环架构

三个层,每一层都对应一个具体的原语。

第一层:循环

循环是 cron 加上一个决策者。它按计划(或触发条件)运行,评估状态,并决定下一步做什么。

这就是 Van Horn 定义的具象化:cron 从未拥有的正是中间的决策。是 Agent 在做决定,而不是你。cron 是心跳,LLM 是决策者,步骤是检查进度的持久执行。

export const infraHealthCheck = inngest.createFunction(
  { id: "infra-health-check" },
  { cron: "*/30 * * * *" }, // 每30分钟
  async ({ step }) => {
    const metrics = await step.run("fetch-service-metrics", async () => {
      return await fetchServiceMetrics(); // 错误率、延迟、内存、CPU
    });

    const assessment = await step.run("assess-health", async () => {
      return await callLLM({
        prompt: `根据这些服务指标,将系统整体健康状况分类为
                 "正常"、"降级"或"严重"。解释你的推理。
                 指标: ${JSON.stringify(metrics)}`,
      });
    });

    if (assessment.status === "degraded" || assessment.status === "critical") {
      await step.invoke("triage-incident", {
        function: incidentTriage,
        data: { metrics, assessment, services: assessment.affectedServices },
      });
    }
  }
);

每周一上午9点,循环触发。它获取数据,询问 LLM 是否需要生成报告,如果需要则调用一个技能。如果进程在步骤之间重启,已经完成的步骤不会重新执行。这就是循环——不是 LLM,而是围绕 LLM 的循环。

第二层:技能

在这个上下文中,技能不是提示词,而是一个持久的工作流。多步骤、可重试、可组合、独立部署。

Van Horn 说:“循环是管道,技能才是资产。” 这部分会不断积累。系统每学会一项新技能,所有循环的能力都会增强。

export const incidentTriage = inngest.createFunction(
  { id: "incident-triage", retries: 3 },
  { event: "infra.incident.triage" },
  async ({ event, step }) => {
    const details = await step.run("fetch-detailed-metrics", async () => {
      return await fetchDetailedMetrics({ services: event.data.services });
    });

    const deploys = await step.run("fetch-deploy-history", async () => {
      return await fetchRecentDeploys({ since: hoursAgo(2) });
    });

    const analysis = await step.run("correlate-incident", async () => {
      return await callLLM({
        prompt: `将这些服务指标与最近的部署关联起来。
                 找出可能的根本原因和严重程度。
                 指标: ${JSON.stringify(details)}
                 最近部署: ${JSON.stringify(deploys)}`,
      });
    });

    await step.run("post-triage-summary", async () => {
      await slack.postMessage({
        channel: "#incidents",
        text: formatTriageSummary({
          analysis,
          affectedServices: event.data.services,
          recommendedActions: analysis.recommendations,
        }),
      });
    });

    return analysis;
  }
);

这个技能负责获取、分类和路由。它是一个带有内置容错能力的工作单元。技能可以是一个中间包含 LLM 的 AI 工作流,也可以是确定性的代码。

第三层:编排器

编排器是运行一切的引擎:调度 cron、执行步骤、管理重试、强制并发限制、存储运行历史、热部署新功能/工作流而不中断正在运行的实例。

这一层没人谈论,因为它本该是不可见的。但它是一切的基础。

大多数人把 Agent 视为“LLM + 工具”。Agent 循环架构则重新定义了 Agent:Agent 是“循环 + 技能 + 编排”。LLM 和工具在循环内部。LLM 和工具可以更换或调整,而架构保持不变。编排使架构成为可能。

当事情出错时会发生什么

理想路径很容易。但这是运行在生产环境中的软件,事情真的总会按计划进行吗?

你的事故分类技能触发了,但指标 API 超时了。读取需要落到磁盘,而内存缓存中没有数据。调用这个 API 的步骤现在重试,再次命中 API。数据被部分缓存了,API 完成。技能像什么都没发生一样继续下一步。

有时事情没这么简单。如果 API 密钥过期了,或者你的托管服务商宕机了30分钟,所有重试都用完了,那会怎样?你还得处理失败情况。

export const incidentTriage = inngest.createFunction(
  {
    id: "incident-triage",
    retries: 3,
    onFailure: async ({ error, event, step }) => {
      // 函数在耗尽重试次数后失败。
      // 我们仍然拥有原始事件数据。没有丢失任何东西。
      await step.run("notify-failure", async () => {
        await slack.postMessage({
          channel: "#agent-ops",
          text: `⚠️ 事故分类失败: ${error.message}. ` +
                `将在下一次健康检查周期重试。` +
                `受影响服务: ${event.data.services.join(", ")}`,
        });
      });
    },
  },
  { event: "infra.incident.triage" },
  async ({ event, step }) => {
    /* 与上面技能相同的逻辑 */
  }
);

onFailure 处理器在所有重试都耗尽后触发。它会向运维频道发送消息,让相关人员知晓。事件被保留,没有丢失任何信息。下一个预定运行会从失败的地方继续。

持久的编排必须提供针对瞬时错误的步骤级重试,以及针对不可恢复错误的失败处理Hook。没有这些,事情就会出错(它们总会出错),而你几小时或几天后才会发现。

瞬时错误也很昂贵。如果你的技能或 Agent 从头开始重试,你会多次调用 LLM,白白消耗 Token。LLM 调用可以被检查点记录。再乘以系统中 10 个或 30 个 Agent,那就很贵了。

步骤级检查点不仅是正确性功能,更是省钱功能。

自己构建技能的 Agent

这里变得更有趣了。系统不是静态的,它被设计成能够自我演进和扩展。

Agent 不仅是在循环内运行——它还会编写新的循环并将其注册到编排引擎中。每个部署的函数都是一个独立的持久技能,可以从循环、Agent 触发,也可以按计划运行,并带有自己的重试逻辑。技能不断积累。

这是一个具有编排认知的 Agent。

工作原理如下:AI Agent 可以访问编排 SDK 作为工具。它可以编写新函数,将其注册到引擎中,然后它们立即开始运行。Agent 进程会热加载新函数,而无需重启或中断正在进行的运行。

通过一个具体例子来说明:

  1. 人类表达了一个需求。工程师说:“我们的服务经常在夜间出现延迟飙升,但直到早上才有人发现。” 这就是触发条件。Agent 不需要从环境数据中推断模糊的模式,它收到了清晰的指令。

  2. Agent 编写了一个技能。两个多步骤函数:一个每30分钟运行的健康检查循环,拉取错误率、延迟和资源使用情况,并由 LLM 将系统健康状态分类为正常、降级或严重。另一个是事故分类技能,负责获取详细的指标和最近的部署历史,用 LLM 关联根因,并向 Slack 发布带有建议操作的分类摘要。错误处理:如果指标 API 宕机,退避并重试。如果 LLM 失败,则回退到基于规则的严重性分类。

  3. Agent 部署了技能。Agent 编写函数代码,由副进程接收。新函数自动注册。它们立即生效,无需部署流水线,无需 PR。

  4. 技能自主运行。每30分钟,引擎触发健康检查。如果发现问题,它会调用分类技能。没有人类参与,完全持久。

  5. Agent 根据信号进行迭代。这是人们容易忽略的部分,所以让我具体说明“迭代”是什么意思。Agent 不会神奇地发现模式。它有一个独立的审查循环:一个由 cron 触发的函数,每周运行,读取编排器的运行历史,并评估性能:

export const reviewSkillPerformance = inngest.createFunction(
  { id: "review-skill-performance" },
  { cron: "0 10 * * 5" }, // 每周五上午10点
  async ({ step }) => {
    const runs = await step.run("fetch-run-history", async () => {
      return await getInngestRuns({
        functionId: "incident-triage",
        since: daysAgo(7),
      });
    });

    const analysis = await step.run("analyze-performance", async () => {
      const successRate = runs.filter(r => r.status === "completed").length / runs.length;
      const avgDuration = average(runs.map(r => r.duration));
      const incidents = await fetchIncidentOutcomes(); // 事故是否与实际宕机相关?

      return await callLLM({
        prompt: `回顾过去一周该技能的表现。
                 成功率: ${successRate}
                 平均持续时间: ${avgDuration}ms
                 与实际宕机相关的事故: ${incidents.confirmed}/${incidents.total}
                 误报: ${incidents.falsePositives}
                 团队根据告警采取了行动: ${incidents.actedOn}/${incidents.total}

                 我们是否应该调整阈值或分类?具体如何修改?`,
      });
    });

    if (analysis.shouldModify) {
      await step.invoke("update-skill", {
        function: coreAgent,
        data: { prompt: `根据以下建议更改更新 incident-triage 技能: ${analysis.proposedChanges}` },
      });
    }
  }
);

“审查”是一个函数。它读取运行历史,检查事故是否与实际宕机相关,并将该信号输入给 LLM。如果健康检查一直将某个服务标记为降级,但团队因为阈值过于敏感而忽略它,审查循环就会发现这个问题,技能会被更新以调整分类。不是魔法,只是一个带有 LLM 决策的 cron 作业。

那验证呢?Agent 写代码的好坏取决于围绕它的护栏。代码可以被类型检查。Agent 可以自己调用函数进行测试,因为它能够与编排引擎本身交互。虽然这并非万无一失,但你可以让核心 Agent 在其操作的系统中原生地调试它编写的技能。审查循环会捕获初始调试未能发现的问题。

再进一步,Agent 可以使用 onFailure Hook来触发自身评估给定的失败。这是一个不断改进的反馈循环。

那冲突呢?具体来说,流控制——并发控制或单例模式可以处理简单情况(concurrency: [{ limit: 1, key: "[event.data](https://event.data/).service" }]),这意味着每个服务每次只能运行一个事故分类。但更深层的问题是:如果两个健康检查同时检测到同一个服务的问题怎么办?编排器会将它们排队。第二个分类等待第一个完成。没有重复告警,没有竞争条件。这不是理论上的,这与你会在任何任务队列中使用的并发原语是一样的。

Agent 不仅在执行任务,它还在为自己构建基础设施。每个技能都在创建它的对话之外持久存在。杀死 Agent 进程再重启,技能继续运行。更换底层模型,技能继续运行。Agent 是短暂的,但它的输出是持久的。

图像

Agent 循环架构系统概览

开发者的视角

这一点很重要,因为如果开发者无法看到 Agent 部署了什么、无法调试出了什么问题、无法审计凌晨3点运行了什么,那么整个架构就是一个巨大的隐患。

编排引擎存储每次运行、每一步、每个输入、每个输出、每次重试。Agent 上周二部署的一个技能在凌晨4点失败了?你可以精确看到哪一步失败了,输入是什么,抛出了什么错误,以及它重试了多少次才放弃。完整的步骤级追踪是编排引擎本身的输出。

这不是事后才加上的仪表板,而是持久执行的固有特性。每一个 step.run() 都是一个检查点,每一个检查点都是可观测的。当编写代码的不是人类时,可观测性就不再是锦上添花——而是信任层。

日常工作流的开发者会这样:早上检查运行仪表板,查看哪些技能在夜间运行,哪些成功,哪些失败。如果 Agent 编写的技能行为异常,你可以直接阅读代码、编辑它、删除它,或者告诉 Agent 修复它。代码是 Agent 写的,但你拥有它。Agent 及其技能仍是一个你需要照管的花园。

为什么持久性是基础

Van Horn 说:“这些东西必须能承受重启。”

以下是持久性在实践中的含义:

需求 含义 为什么基本的 while 循环会失败
独立步骤重试 如果5步中的第3步失败,只重试第3步,而不是第1步和第2步 循环重启会从头重新运行所有步骤
子 Agent 生命周期 生成一个子任务,等待它(可能数小时),如果父任务被取消则取消子任务 没有内置的父子生命周期管理
保证事件投递 如果 Agent 宕机时事件被触发,它仍应被处理 如果进程没有运行,事件就丢失了
事后可观测性 事后能看到发生了什么:每一步、每个决策、每次重试 日志是唯一选择,而且它们是临时的
不停机热部署 部署新函数版本而不杀死正在进行的运行 进程重启会杀死一切
并发控制 一次只运行 N 个技能实例 没有内置的并发原语

“把它放进容器里运行”能给你运行时间,但不能给你正确性。崩溃后重启的容器能恢复进程,但每个进行中的循环都会重新开始。每一步都重新执行,每次 LLM 调用都重新进行。循环看起来在运行,但它是盲目地在运行。

与现有工具的比较

某些工具可能为你提供这类系统的“漂亮”一站式解决方案,或者你可能会选择拼凑一些底层工具自己搭建系统。两种选择都没有错,但正确的架构层应该允许你和你的 Agent 随时间演进——灵活、动态、持久。

持久执行原语要能很好地适应 Agent,能让 Agent 轻松编写,并且提供可观测性和 API,使 Agent 本身具备编排认知。

一个工作示例

我们正在 Inngest 内部测试这些模式,你可以在“utah”项目仓库中看到这个概念:https://github.com/inngest/utah —— 这是一个构建在 Inngest 持久编排之上的 Agent 框架,它本身也具有编排认知。

该系统有一个副进程,允许主 Agent 在自己的工作区中编写和编辑 Inngest 函数,通过“技能”(本文语境下)扩展自身。很快,我们计划提供一个包含起始循环示例的完整系统,但那里的想法可以更清晰地展示本文中的概念。

复合循环

Satya Nadella 最近的帖子点出了整个行业已经感受到的一点:护城河不是模型,而是循环。

他的框架:有两种资本。人力资本,即你的团队多年积累的知识和判断力。以及他所说的 Token 资本,即公司在基础模型之上构建的 AI 工作流、决策模式和学到的技能。

论点是:这两者会复合在一起。每一个改进的工作流都会产生更好的信号。更好的信号带来更精准的 AI 行为。更精准的行为将人类注意力解放出来,用于更高判断力的工作。一台爬山机器。

这正是 Agent 循环架构所具体实现的能力:

Agent 部署的每一个持久技能都是编码为可执行基础设施的制度化知识。它持久存在,无论是否有人类在看,它都会运行。

一个由 cron 触发的审查循环,评估技能性能并迭代。这就是实实在在的爬山机器——不是演示文稿中的飞轮图,而是一个带有 cron 触发器的函数。

如果你的技能在进程重启时死亡,那么复合效果就会重置为零。持久性使投资得以延续。

Nadella 的关键点:“一家公司应该能够更换一个‘通才’模型,而不会丢失其学习系统中内置的‘公司老将’专业知识。”这就是技能库模式。持久函数不在乎是哪个 LLM 调用它们。

据此构建

之前的讨论集中在 Agent 做什么:循环、工具、推理、上下文工程。下一个讨论是关于什么来运行 Agent。

三个层:循环、技能、编排器。循环是工作单元,技能是资产,编排引擎则是使两者持久的基础。副进程模式是模型:Agent 编写自己的持久技能,部署它们,审查它们的表现,并迭代。这不是思想实验,而是一个工作模型。

我们构建 Inngest 的目的就是成为这类系统的编排引擎:step.run()、step.invoke()、cron 触发器、事件驱动控制流、并发控制以及完整的步骤级可观测性。但这个架构模式超越了任何单一工具。如果你在生产环境中构建 Agent 循环,请定义好这三个层。

这些原语今天已经存在。据此构建。

  • 原文链接: x.com/djfarrelly/status/...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~

相关文章

0 条评论