很多 AI Agent Demo 都建立在一个隐含前提上:
从用户提交任务,到 Agent 完成所有步骤,整个系统不会重启、网络不会中断、第三方接口不会超时,模型也不会调用失败。
做 Demo 时,这个假设通常没什么问题。
但当 Agent 真正进入企业业务,它可能需要查询数据库、调用多个 Tool、访问 MCP Server、等待人工审批,甚至持续运行几个小时、几天。
这时候,一个传统 Java 后端非常熟悉的问题重新出现了:
Agent 执行到一半,服务挂了怎么办?
假设一个 Agent 正在执行这样的任务:
flowchart LR
A["读取项目资料"] --> B["分析项目风险"]
B --> C["查询合同"]
C --> D["读取施工日志"]
D --> E["调用业务系统"]
E --> F["等待人工确认"]
F --> G["生成整改方案"]
G --> H["创建整改任务"]
H --> I["通知负责人"]
如果已经执行到“等待人工确认”,Java 服务突然发布重启,那么新的问题就来了:
已经完成的步骤需要重新执行吗?
之前调用成功的接口会不会再次调用?
已经发送过的通知会不会重复发送?
人工已经确认过一次,系统恢复之后还需要重新确认吗?
模型前面已经完成的推理,要不要重新跑?
这些问题已经不是 Prompt Engineering 能解决的。
它们本质上属于一个经典的分布式系统问题:
如何让一个跨越多个步骤、多个服务甚至数天时间的任务,在进程崩溃、机器重启或者网络异常之后仍然能够继续执行?
这就是 Durable Execution,持久化执行。
一、Agent 正在突破传统 HTTP 请求的生命周期
传统 Spring Boot 应用最常见的执行模型是 Request-Response。
flowchart LR
A["HTTP Request"] --> B["Controller"]
B --> C["Service"]
C --> D["Database / Redis"]
D --> C
C --> B
B --> E["HTTP Response"]
一次请求通常只持续几十毫秒到几秒。
如果执行失败,可以直接返回:
throw new BusinessException("处理失败,请稍后重试");
用户重新请求一次即可。
但 Agent 的执行模式完全不同。
一个稍微复杂一点的 Agent,内部可能包含:
LLM 推理 → Tool Calling → 数据库查询 → 再次推理 → MCP 调用 → 外部 API → 人工审批 → 再次执行 Tool。
flowchart LR
A["用户目标"] --> B["LLM 推理"]
B --> C["Tool Calling"]
C --> D["外部系统"]
D --> E["LLM 再次推理"]
E --> F["人工审批"]
F --> G["执行业务操作"]
G --> H["任务完成"]
这个流程已经不像一次普通 HTTP 请求。
它更像一个:
长时间运行、能够暂停、恢复和不断推进状态的 Workflow。
如果整个状态只存在 JVM 内存里,那么服务一旦重启,执行上下文也就随之消失。
例如:
AgentContext context = new AgentContext();
context.setCurrentStep("WAITING_APPROVAL");
context.setTaskId("agent-10001");
只要 JVM 进程结束,这些状态全部丢失。
因此,生产级 Agent 首先要解决的问题不是:
怎么让模型再聪明一点?
而是:
怎么保证任务不会因为一次服务重启就消失。
二、把 Agent 状态存进 MySQL 就够了吗?
最容易想到的方案,是给 Agent 建一张任务表。
例如:
CREATE TABLE agent_task (
id BIGINT PRIMARY KEY,
task_id VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
current_step VARCHAR(64),
result TEXT,
created_at DATETIME,
updated_at DATETIME
);
每执行完一步就更新一次数据库。
flowchart LR
A["执行 Step 1"] --> B["保存状态"]
B --> C["执行 Step 2"]
C --> D["保存状态"]
D --> E["执行 Step 3"]
E --> F["保存状态"]
这样确实比把状态全部放在 JVM 内存可靠得多。
但它仍然没有解决最关键的问题。
假设 Agent 需要发送一封通知邮件:
mailService.send(message);
task.setStatus("MAIL_SENT");
taskRepository.save(task);
第一行代码执行成功。
邮件已经发出去了。
但是数据库更新之前,服务器突然宕机。
此时数据库里可能依然是:
status = 'WAITING_SEND'
服务恢复以后,定时任务重新扫描到这条数据,于是再次执行:
mailService.send(message);
最终用户收到两封邮件。
这就是典型的分布式一致性问题:
业务操作已经成功,但任务状态没有成功记录。
同样的问题还可能出现在支付、短信、审批、工单创建、消息推送、数据修改以及第三方 API 调用中。
所以真正的持久化执行,并不是简单增加一个 current_step 字段。
它需要同时考虑 状态持久化、事件记录、失败重试、超时控制、幂等执行、任务恢复和人工等待。
三、Durable Execution 到底是什么?
Durable Execution 可以简单理解为:
让程序的执行过程本身也具备持久化能力。
普通 Java 程序执行过程可能是:
flowchart LR
A["Step 1"] --> B["Step 2"]
B --> C["Step 3"]
C --> D["Step 4"]
如果程序运行到 Step 3 时 JVM 崩溃,调用栈、局部变量和当前执行位置都会消失。
而 Durable Execution 的核心思想,是把执行过程中发生的重要状态和事件保存下来。
flowchart LR
A["执行 Step 1"] --> B["记录 Event"]
B --> C["执行 Step 2"]
C --> D["记录 Event"]
D --> E["执行 Step 3"]
E --> F{"Worker 崩溃"}
F --> G["Worker 重启"]
G --> H["读取 Event History"]
H --> I["恢复 Workflow"]
I --> J["继续执行"]
以 Temporal 为例,它将 Workflow Execution 定义为一种 durable、reliable、scalable 的函数执行模型。Workflow 在执行过程中会形成 Event History,当 Worker 发生故障后,可以通过 Replay 恢复状态,并从已有历史继续推进。(Temporal 文档)
这里最关键的区别是:
传统方案主要记录:
当前执行到第几步。
而 Durable Execution 更关注:
到目前为止,整个 Workflow 究竟发生过什么。
例如模型是否调用成功、哪个 Activity 已经执行、Timer 是否创建、人工是否已经确认、某一步是否发生过 Retry,这些都属于 Workflow 的执行历史。
四、为什么 Agent 特别需要 Durable Execution?
传统业务系统当然也存在长事务和工作流。
但 Agent 出现以后,这类问题会更加突出。
1. Agent 的执行路径不是固定的
传统程序往往提前定义好:
A → B → C → D
而 Agent 的下一步通常取决于模型当前的判断。
flowchart LR
A["用户目标"] --> B["LLM 判断"]
B --> C["Tool A"]
B --> D["Tool B"]
B --> E["Tool C"]
C --> F["返回结果"]
D --> F
E --> F
F --> G["LLM 再次判断"]
G --> H{"任务完成?"}
H -- "否" --> B
H -- "是" --> I["结束"]
也就是说,Agent 实际上是一个动态 Workflow。
模型每次推理都有可能改变后面的执行路径。
2. 模型调用不适合无脑重新执行
普通 Java 方法重新执行一次,成本可能几乎可以忽略。
但 Agent 的一次推理可能需要携带大量上下文,还可能已经进行了多轮 Tool Calling。
如果一个任务已经执行十几轮模型调用,仅仅因为服务重启就全部从第一步重新执行,不仅增加延迟和 Token 消耗,还可能产生不同的推理结果。
因此,已经成功完成的步骤应该尽可能被保存和复用。
3. Agent 会大量调用外部系统
真正有价值的企业 Agent,最终一定会从“回答问题”走向“执行操作”。
flowchart LR
A["Agent"] --> B["Tool"]
B --> C["CRM"]
B --> D["ERP"]
B --> E["审批系统"]
B --> F["邮件服务"]
B --> G["MCP Server"]
B --> H["第三方 API"]
而这些外部系统随时可能出现超时、限流、服务不可用或者网络异常。
于是原本 Java 分布式系统里非常熟悉的 Retry、Timeout、Backoff、Idempotency,又全部回来了。
AI 并没有消灭这些问题。
相反,Agent 把它们重新放大了一遍。
五、Workflow 和 Activity 应该分开
理解 Durable Execution,一个很重要的概念就是:
控制流程和真正产生副作用的业务操作应该分离。
以 Temporal 的设计思路来看,可以大致分成 Workflow 和 Activity 两层。
| 类型 | 主要职责 | 典型内容 |
|---|---|---|
| Workflow | 控制执行流程和状态 | 判断、等待、分支、编排、任务推进 |
| Activity | 执行真实业务操作 | HTTP、数据库、邮件、支付、模型调用 |
| Tool | Agent 对业务能力的抽象 | 查询订单、创建任务、发送通知 |
| Service | 企业已有业务逻辑 | OrderService、ProjectService、MailService |
整体关系可以设计成:
flowchart LR
A["Workflow"] --> B["Agent / LLM"]
A --> C["Activity"]
B --> D["Tool"]
C --> D
D --> E["Business Service"]
E --> F["Database / API / MQ"]
Workflow 管理的是:
什么时候执行、执行哪个步骤、失败后怎么办、需要等待多久以及接下来走哪个分支。
Activity 管理的是:
真正去调用模型、数据库、第三方接口或者企业业务系统。
这种边界对于 Agent 非常重要。
因为 Agent 的“思考过程”和真实世界中的“执行动作”,风险完全不是一个等级。
六、Retry 不能只是 catch Exception
很多业务系统最初的重试逻辑都类似:
for (int i = 0; i < 3; i++) {
try {
externalService.call();
break;
} catch (Exception e) {
Thread.sleep(1000);
}
}
短请求里这么做可能问题不大。
但对于长时间运行的 Agent,它存在明显缺陷。
例如第一次失败后等待 10 秒,第二次失败等待 30 秒,在第三次等待期间服务器重启。
这时候应用需要知道:
之前已经失败几次?
下一次什么时候重试?
Backoff 已经等待多久?
这次失败是不是应该继续重试?
如果这些信息全部存在 JVM 内存里,服务重启以后就没有了。
真正的 Durable Retry 会把重试状态纳入 Workflow 生命周期管理。
Temporal 的 Activity Retry 可以在 Worker 崩溃后继续维护指数退避等重试状态,而不是要求业务代码自己通过 Thread.sleep() 维持整个过程。(Temporal 文档)
因此复杂 Agent 更合理的设计应该是:
flowchart LR
A["调用 Tool"] --> B{"成功?"}
B -- "是" --> C["进入下一步"]
B -- "否" --> D["记录失败"]
D --> E["Retry Policy"]
E --> F["Backoff"]
F --> A
Retry 不应该只是某段代码里的循环。
它本身就是任务状态的一部分。
七、Agent 真正危险的不是失败,而是重复执行
分布式系统里一个非常重要的问题是:
系统有时候无法确定某次操作到底成功了没有。
假设 Agent 调用了支付系统。
sequenceDiagram
participant A as Agent
participant P as Payment Service
participant W as Worker
A->>P: createPayment()
P-->>A: 创建成功
W--xA: Worker 崩溃
支付已经成功。
但 Worker 还没有来得及记录 Activity 已完成,就崩溃了。
服务恢复以后,为了保证任务最终完成,系统可能再次调用:
paymentService.createPayment(orderId);
如果支付接口不是幂等的,就可能产生两笔支付。
Temporal 官方文档也明确指出,Activity 可能因为故障和重试而实际执行多次,因此执行写操作的 Activity 应该设计为幂等;典型做法就是通过唯一的 idempotency key 识别重复请求。(Temporal 文档)
所以未来设计 Agent Tool 时,不能只考虑:
模型能不能调用这个 Tool?
还必须考虑:
模型把这个 Tool 调两次会发生什么?
例如:
public PaymentResult createPayment(
String orderId,
String idempotencyKey) {
Payment payment =
paymentRepository.findByIdempotencyKey(idempotencyKey);
if (payment != null) {
return PaymentResult.from(payment);
}
return doCreatePayment(orderId, idempotencyKey);
}
同样的原则适用于创建工单、发送业务指令、提交审批、写入 CRM 以及修改项目状态。
对于只读 Tool,重复执行通常问题不大。
对于具有副作用的 Tool,幂等性应该成为默认设计。
八、Human in the Loop 本质上也是一种持久化等待
企业 Agent 真正投入生产以后,很难允许 AI 自动执行所有操作。
尤其是删除数据、发送正式通知、修改合同、创建付款、审批流程等高风险操作,通常都需要人工确认。
例如:
flowchart LR
A["Agent 分析风险"] --> B["生成处理方案"]
B --> C["等待负责人审批"]
C --> D{"审批结果"}
D -- "通过" --> E["执行操作"]
D -- "拒绝" --> F["终止任务"]
问题是,用户什么时候审批并不确定。
可能 5 分钟以后。
也可能第二天。
甚至三天以后。
传统代码显然不能这样:
while (!approved) {
Thread.sleep(5000);
}
因为线程不应该为了一个三天后的审批一直挂在那里。
更合理的方式是:
让 Workflow 进入等待状态,在外部审批事件到来以后重新继续执行。
Temporal 官方提供的 Human-in-the-loop Agent 示例就是这种思路:当操作存在风险时,Workflow 暂停并等待人工审批,通过 Signal 接收用户决定,同时使用 durable timer 处理超时;等待期间不需要持续占用计算资源。(Temporal 文档)
对于企业 Agent,这一点非常重要。
Human in the Loop 并不是一个额外的小功能。
它实际上会成为:
高风险 AI 操作进入生产环境的重要安全边界。
九、Spring AI 已经开始进入 Durable Agent 这个方向
对于 Java 开发者来说,这个方向现在值得关注,还有一个很直接的原因:
Temporal 已经提供了官方 Spring AI Integration。
截至 2026 年 8 月,该集成仍处于 Public Preview。官方文档给出的最低要求包括 Java 17、Spring Boot 3.x、Spring AI 1.1.0 和 Temporal Java SDK 1.35.0。(Temporal 文档)
其核心能力不是再封装一个 ChatClient,而是:
让 Spring AI 的模型调用和 Agent Tool 进入 Temporal 的 Durable Workflow。
模型调用可以通过 Temporal Activity 执行,并记录进 Workflow Event History;官方集成还可以根据依赖自动提供 Vector Store、Embedding 和 MCP 等相关 Activity。(Temporal 文档)
整个架构可以变成:
flowchart LR
A["用户"] --> B["Spring Boot API"]
B --> C["Temporal Workflow"]
C --> D["Spring AI"]
C --> E["Tool Activity"]
C --> F["MCP Activity"]
D --> G["LLM"]
E --> H["Business Service"]
F --> I["MCP Server"]
C --> J["Event History"]
例如 Maven 中可以加入:
<dependency>
<groupId>io.temporal</groupId>
<artifactId>temporal-spring-ai</artifactId>
<version>${temporal-sdk.version}</version>
</dependency>
同时配合 Temporal Spring Boot Starter 和对应的 Spring AI Model Starter 使用。(Temporal 文档)
这意味着未来类似这样的 Agent 流程:
flowchart LR
A["LLM"] --> B["选择 Tool"]
B --> C["执行 Tool"]
C --> D["LLM 再次判断"]
D --> E["等待人工确认"]
E --> F["继续执行"]
F --> G["完成任务"]
不必再完全依赖开发者自己设计任务表、扫描器、重试线程和状态恢复逻辑。
Agent 可以真正运行在一个 Durable Workflow 之上。
十、Java Agent 应该怎么分层?
如果把 Durable Execution 引入一个企业级 Java AI 系统,我更倾向于把架构拆成四层。
flowchart LR
A["API Layer"] --> B["Workflow Layer"]
B --> C["Agent Layer"]
C --> D["Tool Layer"]
D --> E["Business Service"]
B --> F["Temporal"]
C --> G["Spring AI"]
E --> H["MySQL / Redis / MQ / API"]
API Layer:负责接收任务
API 层只负责用户交互、权限校验、启动任务和查询状态。
例如:
@PostMapping("/agent/tasks")
public TaskResponse createTask(
@RequestBody TaskRequest request) {
return agentTaskService.start(request);
}
对于长时间 Agent,不应该让 HTTP 请求一直等待任务全部执行完成。
更合理的返回方式是:
{
"taskId": "agent-20260812-001",
"status": "RUNNING"
}
前端后续可以通过轮询、SSE 或 WebSocket 获取任务进度。
Workflow Layer:负责“怎么跑”
Workflow 主要负责执行顺序、状态流转、Retry、Timeout、等待、恢复和 Human in the Loop。
它可以理解成整个 Agent 的:
执行操作系统。
Agent Layer:负责“怎么想”
Agent Layer 才是真正使用 Spring AI、LLM 和上下文的地方。
它主要负责目标理解、推理、Tool Selection、任务规划和结果生成。
这部分关注的是:
下一步应该做什么?
Tool Layer:负责“真正做”
Tool 不应该重新实现企业原来的业务逻辑。
更合理的结构是:
flowchart LR
A["Agent"] --> B["Tool"]
B --> C["OrderService"]
B --> D["ProjectService"]
B --> E["ApprovalService"]
C --> F["Repository"]
D --> F
E --> G["External API"]
Tool 应该成为 AI 和传统业务系统之间的一层适配器。
例如:
@Tool(description = "根据订单编号查询订单信息")
public OrderInfo queryOrder(String orderNo) {
return orderService.getOrder(orderNo);
}
这样做最大的好处是:
AI 可以变化,但原来的业务边界不用推翻。
十一、不要把 Durable Execution 理解成“所有 Agent 都必须上 Temporal”
Durable Execution 是一种架构思想。
Temporal 是其中一种成熟实现方式。
并不是所有 AI 应用都需要引入 Workflow Engine。
例如一个简单知识库问答:
flowchart LR
A["用户提问"] --> B["RAG 检索"]
B --> C["LLM"]
C --> D["返回答案"]
整个过程可能 2 秒钟完成。
失败以后重新请求一次完全可以接受。
这种系统如果强行加入复杂的 Workflow Engine,反而会增加部署和维护成本。
真正应该考虑 Durable Execution 的,是下面这些场景:
| 场景 | 是否值得考虑 |
|---|---|
| 普通知识库问答 | 通常没必要 |
| 一次模型调用 | 通常没必要 |
| 简单 RAG | 通常没必要 |
| 多步骤 Tool Calling | 开始值得考虑 |
| 跨多个业务系统 | 建议考虑 |
| 存在写操作 | 强烈建议考虑 |
| 需要人工审批 | 非常适合 |
| 任务持续几小时或几天 | 非常适合 |
| 服务重启后任务不能丢 | 基本属于刚需 |
| 涉及支付、审批、工单等副作用 | 必须认真设计 |
判断标准其实很简单:
如果“失败以后从头再跑一次”已经不能接受,就应该开始考虑 Durable Execution。
十二、真正的生产级 Agent Runtime 会越来越像基础设施
过去 Java 后端常见的基础设施是:
MySQL、Redis、MQ、Gateway、Registry、Nginx、Tracing。
Agent 出现以后,系统正在增加新的基础设施层。
flowchart LR
A["Application"] --> B["Agent Runtime"]
B --> C["LLM"]
B --> D["Workflow"]
B --> E["Tools"]
B --> F["Memory"]
B --> G["Tracing"]
B --> H["Human Approval"]
D --> I["Durable State"]
一个真正成熟的 Agent Runtime,需要解决的已经远远不只是:
怎么调用大模型。
它还需要处理模型调用状态、任务生命周期、工具调度、失败重试、执行超时、幂等、恢复、审批、权限、审计和可观测性。
这也是为什么 AI Agent 真正进入生产阶段以后,技术重点会逐渐发生变化。
早期大家比的是:
谁能快速做出一个 Agent Demo?
生产环境真正要解决的是:
谁能保证这个 Agent 在各种异常情况下仍然正确完成任务?
十三、Java 开发者其实非常适合做这件事
第一次看到 Durable Agent,很多 Java 开发者可能会感觉这是一个新的 AI 概念。
但如果把这些问题拆开,会发现绝大部分都非常熟悉。
| Agent 世界 | Java / 分布式系统世界 |
|---|---|
| Agent Retry | 分布式重试 |
| Tool 重复调用 | 接口幂等 |
| Agent State | 状态机 |
| Tool Timeout | RPC 超时 |
| Event History | 事件日志 |
| Agent Workflow | 工作流编排 |
| Human in the Loop | 审批流 |
| Tool 副作用 | 事务边界 |
| Agent Recovery | 故障恢复 |
| Agent Tracing | 分布式链路追踪 |
所以 AI 并没有让过去十几年积累的后端工程能力失效。
恰恰相反。
当 AI 开始从“回答问题”走向“执行任务”以后,这些能力的重要性会进一步提高。
模型更适合负责:
理解目标、推理、规划、选择工具和生成内容。
Java 系统更应该负责:
可靠性、安全性、权限、持久化、幂等、恢复和审计。
两者并不是谁替代谁。
而是开始形成新的职责边界。
十四、一个生产级 Agent,至少应该考虑这些问题
如果准备把一个 Agent 真正接入业务,可以在上线之前检查下面这些问题。
| 检查项 | 需要回答的问题 |
|---|---|
| 状态持久化 | 服务重启以后任务还在吗? |
| Workflow | 当前任务执行到了哪里? |
| Retry | 第三方接口失败以后怎么重试? |
| Timeout | 一个 Tool 最长允许执行多久? |
| Idempotency | 同一个 Tool 调用两次会怎么样? |
| Recovery | Worker 崩溃后能否继续执行? |
| Human in the Loop | 高风险操作是否需要人工确认? |
| Audit | 能否知道 Agent 做过哪些操作? |
| Observability | 能否定位一次 Agent 失败在哪一步? |
| Permission | Agent 是否只能执行当前用户有权限的操作? |
一个 Agent 能调用多少 Tool,并不能说明它已经具备生产能力。
真正困难的是:
在异常、重启、超时、重试和人工等待全部存在的情况下,仍然保证最终结果正确。
十五、结语
很多 Agent Demo 都假设:
整个任务可以一次顺利运行到底。
但真实生产环境从来不会这么理想。
网络会超时,模型调用会失败,第三方接口会返回 500,Worker 会重启,服务器需要发布,用户甚至可能三天以后才完成审批。
所以当 Agent 从聊天机器人走向真正的企业执行系统之后,一个越来越重要的问题就是:
如果任务不能一次执行成功,我们还能不能让它继续?
Durable Execution 解决的正是这个问题。
一个生产级 Agent,除了 LLM、RAG、MCP 和 Tool Calling,还需要补齐 Workflow、Persistence、Retry、Idempotency 和 Recovery 这一整套可靠性基础设施。
Spring AI 解决的是 Java 应用如何连接模型、工具、向量数据库和 MCP;Temporal 这类 Durable Execution 平台解决的,则是这些能力如何在一个可能持续数小时甚至数天的任务中可靠运行。当前 Temporal 官方的 Spring AI Integration 已经开始直接连接这两个世界。(Temporal 文档)
因此,一个 Agent 能不能调用十几个 Tool,只能证明它:
具备执行能力。
而当服务重启、接口超时、任务重试、人工等待甚至部分操作已经成功的情况下,它仍然能够正确完成任务,才真正证明它:
具备生产能力。

评论区 0