生产环境中的 Agent 经常需要连续完成模型推理、知识检索、外部 API 调用、人工审批和结果生成。一旦任务持续数分钟甚至数小时,Java 服务重启、Pod 被 Kubernetes 重新调度、Worker 异常退出或网络暂时中断都会成为正常运行条件。此时最核心的问题是:已经完成的步骤怎样保留,服务恢复以后怎样继续执行,同时避免前面的模型调用和业务操作被无意义地重新执行。

本期只讨论这个问题,并将核心方案收敛到 Durable Execution。实现侧以 Temporal Java SDK 为例,同时说明它与 Spring AI 的衔接方式。截至 2026 年 8 月 11 日,Temporal Java SDK 最新正式版本为 1.37.0;Temporal 已提供 temporal-spring-ai 集成,使 Spring AI 的模型调用和部分 Tool 调用能够进入 Temporal Activity。该集成当前仍处于 Public Preview,生产项目需要评估 API 稳定性。(GitHub)

目录

一、Agent 长任务为什么会在服务重启后“失忆”

1. 一个典型的生产场景

二、核心方案:把任务执行状态交给 Durable Execution

1. Temporal 保存的是执行历史

2. Workflow 和 Activity 必须分开

三、Java 中怎样实现一个可恢复的 Agent

1. 先把模型调用定义为 Activity

2. Workflow 只保留 Agent 的任务编排

四、为什么“可以恢复”仍然不能忽略幂等性

五、如何验证 Worker 崩溃以后真的能够恢复

六、Temporal Spring AI 集成进一步减少了什么代码

七、这套方案能够覆盖哪些相近问题

八、方案的边界

九、上线以后应该观察什么

十、工程结论

十一、官方来源


一、Agent 长任务为什么会在服务重启后“失忆”

1. 一个典型的生产场景

假设一个企业研究 Agent 接收到“读取内部资料并生成分析报告”的任务。它首先让模型制定研究计划,然后调用知识库检索,再访问几个外部业务接口,随后把检索结果交给模型生成初稿,最后执行格式检查和结果保存。整个过程可能持续数分钟。

最简单的 Java 实现通常是在一个 Service 方法里顺序调用这些步骤。计划、检索结果和模型输出暂存在 Java 对象中。当 JVM 正常运行时,这种方式没有明显问题。一旦进程在第三步结束后退出,内存中的执行位置会立即消失。服务重新启动以后,系统只能重新创建任务,或者自己维护一张状态表判断任务此前执行到了哪里。

问题随后会迅速复杂化。开发者需要保存当前步骤、输入参数、输出结果、错误信息和重试次数,还需要判断某一步究竟已经执行成功,还是执行成功后尚未来得及更新数据库。代码中会逐渐出现大量 RUNNINGSUCCESSFAILEDRETRYING 状态和恢复分支。长任务越复杂,这套状态机越难维护。

LangGraph 的生产文档也把这类问题直接归入 Durable Execution:持久化层在执行步骤之间保存 Checkpoint,Worker 因失败或超时中断后,可以从最近保存的状态继续执行。(Docs by LangChain)

二、核心方案:把任务执行状态交给 Durable Execution

1. Temporal 保存的是执行历史

Temporal 的核心机制是 Event History。Workflow 执行过程中产生的 Activity 调度、Activity 完成、Timer、Signal 等事件会被持久化。当 Worker 退出后,Temporal Service 中的 Workflow Execution 仍然存在;新的 Worker 上线以后可以根据 Event History 重放 Workflow 代码并恢复到此前的逻辑位置。(Temporal Docs)

这里需要理解一个关键细节。Temporal 并没有把 JVM 堆和线程栈直接保存成快照。恢复时,Workflow 代码会重新执行,Runtime 根据已经记录的 Event History 提供之前的结果。例如第一次模型调用已经以 Activity 的形式完成,其返回值已经记录到历史中,那么 Replay 到这一行时会取得已有结果,随后继续执行后面的逻辑。(Temporal Docs)

因此,Agent 中的代码可以继续保持接近同步程序的写法:

制定计划
→ 查询资料
→ 调用模型
→ 执行业务 Tool
→ 生成结果

持久化、重放和 Worker 故障恢复由 Runtime 处理。

2. Workflow 和 Activity 必须分开

Temporal 要求 Workflow 代码具有确定性。数据库请求、HTTP 调用、LLM 请求、随机数和当前系统时间都属于可能产生不同结果的外部操作,应通过 Activity 或 Temporal 提供的其他安全机制执行。官方文档明确建议把 API 请求和 LLM 调用等易失败、非确定性操作放入 Activity,并通过 Retry Policy 管理失败。(Temporal Docs)

对于 Agent,可以采用一个简单原则:Workflow 管“下一步做什么”,Activity 管“真正访问外部世界”。 模型调用、数据库写入、MCP 请求和业务 API 都属于后者。

三、Java 中怎样实现一个可恢复的 Agent

1. 先把模型调用定义为 Activity

下面使用 Spring AI 的 ChatClient 完成模型请求,再由 Temporal Activity 包裹这次调用。示例只保留理解 Durable Execution 所需的核心代码。

// @ActivityInterface 表示这个接口定义的是 Temporal Activity。
// Activity 专门负责调用外部系统,例如 LLM、数据库或 HTTP API。
@ActivityInterface
public interface AgentActivities {

    // @ActivityMethod 表示该方法会作为一个 Activity 被 Temporal 调度。
    // 调用结果完成后会进入 Workflow 的 Event History。
    @ActivityMethod
    String callModel(String prompt);
}

Activity 的真正实现继续使用熟悉的 Spring AI:

// @Component 把这个实现类交给 Spring 容器管理。
// 因此构造器中的 Spring AI 依赖可以继续通过依赖注入获得。
@Component
public class AgentActivitiesImpl implements AgentActivities {

    // final 表示对象构造完成后不再更换 ChatClient。
    // ChatClient 是 Spring AI 面向模型调用的高层客户端。
    private final ChatClient chatClient;

    // Spring 默认支持构造器注入。
    // ChatClient.Builder 通常由 Spring AI 自动配置提供。
    public AgentActivitiesImpl(ChatClient.Builder builder) {

        // build() 创建真正用于模型请求的 ChatClient。
        this.chatClient = builder.build();
    }

    // @Override 表示当前方法实现了 AgentActivities 接口中的 callModel。
    @Override
    public String callModel(String prompt) {

        // prompt() 开始创建一次新的模型请求。
        return chatClient.prompt()

                // user(prompt) 把参数作为用户消息传递给模型。
                .user(prompt)

                // call() 发起同步模型调用。
                // 网络访问发生在 Activity 中,因此允许失败和重试。
                .call()

                // content() 只取模型最终返回的文本内容。
                .content();
    }
}

Temporal 官方 Java 文档将 Activity 定义为适合网络请求、数据库访问和其他外部操作的执行单元。Activity 发生暂时性失败时可以按照配置进行重试。(Temporal Docs)

2. Workflow 只保留 Agent 的任务编排

然后定义一个最小的 Agent Workflow。这里假设 Agent 先生成执行计划,再根据计划生成最终分析结果。

// @WorkflowInterface 声明这是一个 Temporal Workflow 接口。
// 一个 Workflow 可以理解为一个具有持久执行状态的业务任务。
@WorkflowInterface
public interface ResearchAgentWorkflow {

    // @WorkflowMethod 标识 Workflow 的主入口。
    // 调用这个方法会启动一次新的 Workflow Execution。
    @WorkflowMethod
    String run(String userRequest);
}

真正的 Workflow 实现如下:

public class ResearchAgentWorkflowImpl implements ResearchAgentWorkflow {

    // Workflow.newActivityStub(...) 创建 Activity 的代理对象。
    // 这里并没有直接 new AgentActivitiesImpl()。
    // 调用这个代理的方法时,Temporal 会负责调度真正的 Activity。
    private final AgentActivities activities =
            Workflow.newActivityStub(
                    AgentActivities.class,

                    // ActivityOptions 用于设置 Activity 的超时与重试规则。
                    ActivityOptions.newBuilder()

                            // 单次 Activity 最长允许执行 2 分钟。
                            // LLM 请求超过该时间后,本次尝试会被视为超时。
                            .setStartToCloseTimeout(Duration.ofMinutes(2))

                            // 配置模型调用发生暂时性故障时的重试策略。
                            .setRetryOptions(
                                    RetryOptions.newBuilder()

                                            // 第一次失败后等待 2 秒再进行下一次尝试。
                                            .setInitialInterval(Duration.ofSeconds(2))

                                            // 每次等待时间按 2 倍增长,即指数退避。
                                            .setBackoffCoefficient(2.0)

                                            // 最多执行 3 次,避免异常模型服务造成无限成本。
                                            .setMaximumAttempts(3)

                                            // build() 创建最终的 RetryOptions 对象。
                                            .build())

                            // build() 创建最终 ActivityOptions。
                            .build());

    @Override
    public String run(String userRequest) {

        // 第一次模型调用负责生成计划。
        // 如果 Activity 已经成功完成,它的结果会进入 Event History。
        String plan = activities.callModel(
                "请为下面的研究任务生成简洁执行计划:\n" + userRequest);

        // 第二次模型调用根据第一步结果继续工作。
        // Worker 如果在两步之间退出,恢复后可以取得前一步已有结果。
        String report = activities.callModel(
                "任务:" + userRequest
                        + "\n执行计划:" + plan
                        + "\n请根据计划生成最终分析结果。");

        // Workflow 返回最终结果以后,本次 Workflow Execution 完成。
        return report;
    }
}

这段代码真正解决的问题出现在 Worker 异常退出以后。假设第一次 callModel() 已经成功,并且 Temporal 已经收到 Activity Completion。此时 Worker 崩溃,新的 Worker 会根据 Event History 重放 Workflow。第一次 Activity 的结果可以直接由历史恢复,执行逻辑随后继续进入第二次模型调用。Temporal 官方架构文档明确描述了这一 Replay 机制。(Temporal Docs)

四、为什么“可以恢复”仍然不能忽略幂等性

Durable Execution 解决了任务状态丢失问题,但外部副作用仍然存在一个经典的分布式系统边界:Activity 已经成功调用外部服务,Worker 随后在向 Temporal Service 报告成功之前崩溃。Event History 此时没有记录成功结果,Temporal 会再次调度这个 Activity。(Temporal Docs)

如果 Activity 只是读取文档,重复调用通常影响有限。如果 Activity 执行付款、创建订单、发送通知或修改资源,重复执行可能产生真实业务错误。因此具有副作用的 Tool 必须设计幂等键。Temporal 官方建议可以使用 Workflow Run ID 与 Activity ID 等稳定标识构造幂等键,让下游系统识别重复请求。(Temporal Docs)

例如一个创建工单的 Tool 可以把 workflowId + activityId 发送给工单服务。工单服务首先查询该幂等键是否已经处理。相同 Activity 因故障再次执行时,服务器返回第一次创建的结果,从而避免重复创建。

因此,生产级 Agent 的恢复能力可以概括为两个互补机制:Event History 保证任务能够继续,业务幂等保证外部操作能够安全重试。

五、如何验证 Worker 崩溃以后真的能够恢复

这个问题不应该只通过正常单元测试验证。Temporal 官方的生产前测试建议明确提出主动破坏 Workflow 依赖,包括让数据库不可用、增加外部 API 延迟、暂停消息队列,并观察 Activity 重试、Heartbeat、超时和幂等性行为。(Temporal Docs)

一个最小的故障注入实验可以这样进行。首先启动本地 Temporal Server,然后运行 Agent Worker。在第一步 Activity 已完成、第二步仍在执行时直接终止 Worker 进程,再重新启动 Worker。Temporal Service 中的 Workflow Execution 应继续保持 Open,新 Worker 获取任务后通过 Event History 重建状态,并继续剩余执行。官方文档说明,当 Worker 不可用时,Task 可以等待新的 Worker;Workflow Event History 会持续保存在 Temporal Service 中。(Temporal Docs)

本地 Temporal Server 可以通过官方 CLI 启动:

# 启动 Temporal 本地开发服务器。
# 该命令同时提供 Temporal Service 和本地 Web UI。
temporal server start-dev

如果希望直接运行官方 Spring AI 示例,可以使用 Temporal 官方 samples-java 仓库,其中已经提供 Basic、MCP、Multi-Model 和 RAG 示例。(GitHub)

# 克隆 Temporal 官方 Java 示例仓库。
git clone https://github.com/temporalio/samples-java

# 进入示例项目目录。
cd samples-java

# 设置模型 API Key。
# 实际 Key 应通过环境变量或 Secret 管理,不应写入源码。
export OPENAI_API_KEY="your-api-key"

# 启动 Spring AI Basic 示例。
# 该示例演示模型调用、Tool 和 Chat Memory 与 Temporal 的结合。
./gradlew :springai:basic:bootRun

这里不提供虚构的“恢复耗时”或“成功率”数据。不同部署方式、Task Queue 负载、Worker 启动速度和下游服务状态都会影响恢复时间。测试时应记录真实环境结果。

六、Temporal Spring AI 集成进一步减少了什么代码

Temporal 已经提供专门的 temporal-spring-ai 模块。它会把 Spring AI 的模型调用转成 Temporal Activity,并根据 Tool 类型选择合适的执行方式。官方文档目前要求最低 Java 17、Spring Boot 3.x、Spring AI 1.1.0 和 Temporal Java SDK 1.35.0。该模块仍处于 Public Preview。(Temporal Docs)

其中 ActivityChatModel 可以直接作为 Spring AI ChatModel 使用。官方默认配置为模型 Activity 提供 2 分钟 Start-to-Close Timeout 和最多 3 次尝试,同时将部分明显无法通过重试解决的 AI 异常标记为 non-retryable。MCP、Vector Store 和 Embedding 等 Spring AI 组件也已经存在对应 Activity 集成。(Temporal Docs)

对于新项目,可以关注这一集成。对于要求接口长期稳定的生产系统,当前阶段更适合充分验证 Public Preview 的升级风险,或者直接使用稳定的 Temporal Workflow/Activity 原语包裹现有 Spring AI Service。

七、这套方案能够覆盖哪些相近问题

Durable Execution 同样适用于人工审批、长时间 RAG、批量文档分析、多 Agent 协作和异步业务 Tool。LangGraph 的 Interrupt 机制同样要求持久化 Checkpointer,并通过稳定的 thread_id 恢复任务;OpenAI Agents SDK 当前也通过可序列化的 RunState 保存模型响应、生成项和审批状态,用于中断后的继续执行。(Docs by LangChain)

这些框架采用的具体数据结构不同,工程问题高度一致:长任务需要一个独立于进程内存的持久执行状态。随着 Agent 开始运行几十分钟、等待人工输入或跨多个外部系统工作,这一能力会逐渐从增强功能转变为基础设施。

八、方案的边界

Temporal 无法保证外部依赖永远可用,也无法自动让所有副作用获得严格的 Exactly Once 语义。Activity 的失败重试仍要求开发者设计超时、最大尝试次数和幂等操作。Workflow 本身还必须保持确定性,否则代码升级后可能出现 Replay 与历史事件不一致的问题。(Temporal Docs)

Event History 同样不适合保存大量二进制内容。Temporal Spring AI 文档明确提醒,原始媒体字节进入 Chat Activity 后会被写入 Event History,并受到单个 Server History Event 2 MiB 限制;该插件默认对 inline media 设置 1 MiB 上限。较大的文件应存入对象存储,只在 Workflow 中传递 URL 或对象 ID。(Temporal Docs)

九、上线以后应该观察什么

生产环境至少需要持续观察四类指标。(1)任务可靠性,包括 Workflow 长时间停留数量、Activity 失败次数、重试次数和最终失败率;(2)恢复能力,包括 Worker 重启后的任务恢复时间、Task Queue 等待时间和异常积压;(3)业务安全,包括同一幂等键的重复 Tool 请求、重复写入和补偿次数;(4)AI 成本,包括单个 Workflow 的模型调用次数、Token 消耗和重试带来的额外调用。

对于需要向前端持续展示长任务进度的系统,Temporal Java SDK 1.37.0 新增了 Public Preview 的 Workflow Streams,提供持久、带 offset 的 Workflow 事件流,官方明确将长时间运行的 AI Agent 进度展示列为适用场景之一。这一能力适合后续单独讨论,不影响本文 Durable Execution 的核心实现。(GitHub)

十、工程结论

Agent 从几秒钟的聊天接口进入数分钟甚至数小时的业务任务以后,任务状态需要脱离单个 JVM 生命周期。Durable Execution 提供了一种清晰的处理方式:Workflow 保存业务执行逻辑,Activity 承担模型调用和外部副作用,Event History记录已经完成的事实,Worker 故障后通过 Replay 恢复执行。

真正上线时仍需补齐幂等、超时、重试上限、成本限制和 Workflow 版本治理。恢复机制解决“任务不能丢”,幂等机制解决“操作不能重复产生副作用”。 这两部分共同构成长任务 Agent 的基本可靠性边界。

十一、官方来源

Temporal Durable Execution 与架构:

https://docs.temporal.io/temporal
https://docs.temporal.io/encyclopedia/architecture/temporal-sdks
https://docs.temporal.io/workflows

Temporal Java SDK 与 Spring AI:

https://github.com/temporalio/sdk-java
https://github.com/temporalio/sdk-java/releases
https://docs.temporal.io/develop/java/integrations/spring-ai
https://github.com/temporalio/samples-java

Activity、Retry 与幂等性:

https://docs.temporal.io/activity-definition
https://docs.temporal.io/encyclopedia/retry-policies
https://docs.temporal.io/develop/java/activities/execution
https://docs.temporal.io/best-practices/pre-production-testing

其他 Agent Runtime 的恢复机制参考:

https://docs.langchain.com/oss/python/langgraph/interrupts
https://docs.langchain.com/oss/python/deepagents/going-to-production
https://openai.github.io/openai-agents-python/ref/run_state/
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐