先说一个很常见的场景你在 A 平台上调试了半小时的 Agent 工作流刚把工具调用链路跑通却发现团队另一个成员用的是 B 平台的 Agent或者公司采购的 RAG 服务只支持另一套 API。这时你想把这段对话继续下去往往只能复制粘贴最后的输出甚至重新从零开始喂上下文。这种“换一个工具就相当于换一个脑子”的体验就是 AI Agent 领域越来越被关注的lock-in供应商锁定问题。这篇文章不讨论哲学意义上的“Agent 是否应该有意识”而是聚焦一个非常工程化的问题如何让一个 Agent 开始的对话能无缝交给另一个 Agent 继续。全文会从概念拆解、互操作原理、协议设计、代码实现到踩坑排查给出一个可直接落地的思路。如果你正在做 Agent 应用开发、RAG 系统集成或者刚接触 AI Agent 开发并想避开未来的迁移陷阱这篇文章应该能帮你省下不少时间。1. 背景与核心概念1.1 什么是 AI Agent Lock-in传统软件里的 lock-in往往体现在数据库选型、云厂商绑定、私有协议这几个方面。到了 AI Agent 时代lock-in 问题变得更加隐蔽也更难处理。一个 AI Agent 的核心能力不只是“调用大模型接口”而是围绕一次目标任务维护一套完整的交互状态。这套状态包括用户的原始意图和多次对话记录。Agent 自己规划出来的执行步骤Plan。每一步执行时用到的工具名称、入参、返回值。已经收集到的中间结果比如查到的订单号、生成的报告草稿。Agent 内部维护的短期记忆和长期记忆。如果这套状态只存在于某一个 Agent 产品内部无法导出、无法标准化、无法被其他 Agent 理解那么你一旦切换平台之前的“思考过程”和“执行进度”就会全部丢失。从这个角度看AI Agent 的 lock-in 是比 API 接口锁定更严重的锁定。API 锁定只是换一个 SDK、改几行请求而 Agent 状态锁定意味着你辛辛苦苦构建的上下文资产全部作废。那为什么过去很少有人解决这件事原因是 AI Agent 本身还在快速演化几乎每三个月就会有一批新的 Agent 框架和协议出现。在标准还没成型的时候谈互操作确实容易被看成“过早设计”。但从工程经验来看有些基础性的抽象现在就可以做而且做了之后收益非常明显。1.2 对话连续性的本质标题里有一句话很关键Start conversation with one agent – continue with another。这句话看起来像产品口号但它背后其实是一个严格的技术命题给定两个不同的 Agent 运行时 A 和 B如果 A 已经与用户完成了一轮或多轮对话那么 B 能否在完全不丢失上下文的前提下从 A 的最后一轮输出继续工作要实现这个目标仅仅把 A 的最终回答文本传给 B 是不够的。B 需要知道的不只是“刚才说了什么”还包括用户的目标是什么。已经完成了哪些步骤。当前处于哪个阶段。有哪些约束条件和偏好。后续还有哪些待办步骤。也就是说连续性的本质是状态迁移而不是文本复制。一次成功的 Agent 会话应该像一份工程文档一样不仅包含结论还包含可追溯的中间过程。1.3 为什么需要跨 Agent 会话延续有人可能会问我直接在一个平台里把 Agent 用完不就行了为什么非要跨 Agent 继续对话结合我自己的实践经历跨 Agent 会话延续在下面几类场景中是刚需场景一同任务多工具协作一个复杂业务往往需要多个专业 Agent 配合。比如一个 Agent 负责对话理解另一个 Agent 负责代码生成还有一个 Agent 负责调用内部 API。用户希望只在对话层聊一次后台由多个 Agent 接力完成。此时如果这些 Agent 来自不同厂商或者由不同团队开发它们之间必须能交换上下文。场景二平台迁移与升级公司早期为了快速上线选择了一款轻量 Agent 产品。等用户量和数据量上来后发现它在权限控制、审计日志、私有化部署等方面满足不了要求需要换到另一个平台。如果会话状态不能平滑迁移老用户的每个会话都会断掉重来这对体验来说几乎是灾难。场景三多端无缝切换用户在 Web 端与 Agent 交流到一半切换到手机 App 上希望接着聊。如果 Web 端和 App 端底层接入的是不同 Agent 产品就需要一个统一的会话中间层。场景四容灾与高可用主 Agent 服务出现故障时流量切换到备用 Agent。如果两个 Agent 的对话状态不互通降级体验就会变成“从头再来”。2. 跨 Agent 会话延续的技术难点理解了目标之后再来看看实现这件事到底难在哪里。2.1 上下文表示不一致不同 Agent 框架对 Context上下文的建模方式差别很大。有的把上下文理解成纯文本数组每条消息只有 role 和 content有的则把上下文构建成结构化的消息对象包含 tool_call_id、tool_calls、name 等字段还有一些 Agent 框架为了支持复杂工作流会维护一个 DAG 图多个节点之间共享不同的上下文片段。上下文表示不一致带来的直接后果是即使你知道需要传递数据也不知道对方期望的数据格式是什么。就好比中国人和德国人用中文和德语各自写了一本技术手册互相之间不能直接阅读对方的目录。2.2 执行状态无法通过 API 获取很多商业化 Agent 平台只开放了“发起对话”和“获取回答结果”两个接口。对于 Agent 内部的中间步骤——比如它调用了哪几个工具、每个工具返回了什么、它已经执行到哪一步——平台并不提供导出能力。没有中间步骤信息就无法构建可恢复的会话状态。你能拿到的只是一个最终回答文本这就像看了一场魔术表演只知道结果但不清楚中间的手法。2.3 工具调用映射困难Agent 与普通聊天机器人最大的区别之一是它可以调用工具。在 Agent A 里一个查询订单的 API 叫queryOrder参数是orderId。在 Agent B 里同样的能力可能叫get_order_info参数是order_no。会话延续时如果 A 产生的工具调用记录直接传给 BB 可能完全无法理解。更麻烦的是有些工具返回的数据里包含了大量内部数据结构的细节这些细节在 B 的上下文模型里可能没有对应字段。2.4 记忆机制无法互通现代 Agent 产品普遍加入了短期记忆和长期记忆。短期记忆负责把最近的对话压缩成摘要长期记忆会把关键信息存入向量数据库或知识图谱。问题在于每家产品的记忆存储格式和检索策略都是私有的。A 平台生成的记忆摘要B 平台检索不到B 平台的知识向量A 平台也用不了。这导致会话延续时能传递的往往只有最近几轮的原始文本而更早的有价值信息被压缩后留在了原平台内部。2.5 会话安全边界跨 Agent 会话延续还涉及安全边界问题。Agent A 在会话过程中可能获取了用户的私有数据这些数据能否传递给 Agent B如果可以传递传输过程如何加密传递过去后Agent B 的日志系统是否会记录这些数据这些都是在设计跨 Agent 延续方案时必须提前考虑的合规问题。3. 核心设计思路让 Agent 对话变成可携带的会话状态面对上面这些难点一个可行的工程思路是不要把 Agent 切换理解为“文本交接”而是把会话整体抽象为一种可导出、可导入、可校验的标准化状态对象。3.1 会话状态数据模型在设计跨 Agent 会话延续方案时我通常会定义一个统一的会话状态结构。这个结构不是某个 Agent 平台私有格式的简单复制而是综合考虑了大部分 Agent 产品都具备的公共属性。结构上至少需要包含以下部分状态字段说明session_id会话唯一标识agent_id当前负责该会话的 Agent 标识user_id用户标识用于权限控制message_history多轮对话消息列表current_goal当前会话的核心目标plan_steps已经规划好的执行步骤包括已完成和待执行tool_executions已经执行过的工具调用记录和返回结果memory_context短期摘要与关键事实信息metadata自定义扩展字段兼容各平台专有信息这个结构的关键点在于它把对话文本和 Agent 执行状态拆开了。对话文本只是状态的一部分更重要的是执行记录和计划。这样即使换了一个 Agent 产品新 Agent 也能快速理解“现在进行到哪一步”。3.2 标准化上下文交换格式有了状态模型还需要定义交换格式。在实践中我推荐使用 JSON 作为基础序列化格式因为它的通用性和可读性最好。一个标准的会话状态导出文件可以这样设计{ schema_version: 1.0, session_id: sess_20250115_001, agent_id: agent-a, user_id: user_123, current_goal: 查询本月销售数据并生成分析报告, message_history: [ { role: user, content: 我想看一下本月销售情况, timestamp: 2025-01-15T10:00:00Z }, { role: assistant, content: 好的我来查询本月的销售数据。, timestamp: 2025-01-15T10:00:05Z }, { role: tool_call, name: query_sales, arguments: { month: 2025-01 }, timestamp: 2025-01-15T10:00:06Z }, { role: tool_result, name: query_sales, content: { total_amount: 1280000, order_count: 3200 }, timestamp: 2025-01-15T10:00:08Z } ], plan_steps: [ { step_id: step_1, description: 查询本月销售数据, status: completed, result: 总销售额 128 万订单数 3200 }, { step_id: step_2, description: 按区域拆分销售数据, status: pending }, { step_id: step_3, description: 生成销售分析报告, status: pending } ], tool_executions: [ { tool_name: query_sales, input: { month: 2025-01 }, output: { total_amount: 1280000, order_count: 3200 }, status: success } ], memory_context: { summary: 用户关注本月销售数据需要按区域拆解并生成报告, key_facts: { period: 2025-01, currency: CNY } }, metadata: { source_platform: agent-a, exported_at: 2025-01-15T10:05:00Z, custom_fields: { agent_a_specific_flag: true } } }这份 JSON 结构解决了一个核心问题任何 Agent 平台只要实现一个标准的导入解析器就能读取另一个平台导出的会话状态。即使某个平台的私有信息无法被对方理解也会保存在metadata.custom_fields中不会因为格式不兼容而丢失。3.3 兼容层与适配器模式虽然标准化格式很理想但现实世界里的 Agent 产品不会主动遵循你的格式。所以工程上需要一个适配层把每个 Agent 平台的私有格式转换成统一格式。这个适配层的设计模式比较接近企业集成中常用的消息适配器。每个 Agent 平台对应一个适配器类负责两件事导出时从平台私有对象转换为统一会话状态对象。导入时从统一会话状态对象转换为目标平台能接受的输入格式。适配层带来的最大收益是当新增一个 Agent 平台时只需要开发一个新适配器不需要修改业务逻辑和上层调用。3.4 上下文摘要与压缩有一个现实情况需要接受并不是所有平台都支持导入完整的工具调用记录。有些平台的 API 只允许你设置一段 system prompt 或者一个初始用户消息。这种情况下为了保证更高程度的兼容性我们需要一个摘要生成器。它把结构化的会话状态压缩成一段自然语言描述供目标 Agent 理解你是本次任务的接续执行者。 当前用户目标是查询本月销售数据并生成分析报告。 已完成步骤 1. 已查询本月销售总额结果为 128 万订单数 3200。 下一步需要执行 1. 按区域拆分销售数据。 2. 基于拆分结果生成销售分析报告。 用户偏好报告需要包含同比和环比对比。这段摘要看起来简单但在实际跨 Agent 场景中非常实用。它不需要目标 Agent 理解复杂的数据模型只需要它有足够的阅读能力和执行能力。对于目前主流的大语言模型来说这种文本引导足以让新 Agent 从断点继续工作。4. 完整实战构建一个可延续会话的 Agent 消息中间层理论部分讲完了下面进入代码实战。这个项目的目标是构建一个轻量级的Agent 会话消息中间层Agent Conversation Broker它不直接与大模型交互而是负责接收不同 Agent 的会话状态、统一转换格式、存储、并支持在其他 Agent 中恢复会话。为了便于演示我会用 Java Spring Boot 实现一个简化版本。如果你熟悉 Python FastAPI也可以参考同样的思路重写。4.1 项目结构设计先创建项目基础结构agent-conversation-broker/ ├── pom.xml ├── src/main/java/com/example/agentbroker/ │ ├── AgentBrokerApplication.java │ ├── controller/ │ │ └── ConversationController.java │ ├── model/ │ │ ├── SessionState.java │ │ ├── MessageRecord.java │ │ └── PlanStep.java │ ├── service/ │ │ └── ConversationTransferService.java │ ├── adapter/ │ │ ├── AgentAdapter.java │ │ ├── AgentAAdapter.java │ │ └── AgentBAdapter.java │ └── storage/ │ └── SessionStateStore.java └── src/main/resources/ └── application.yml这个项目结构本身也体现了一种设计思想controller负责 API 入口service负责业务逻辑adapter负责对接不同的 Agent 平台storage负责状态存储。每一层的职责都比较单一后续扩展新 Agent 时不需要修改 controller 和 service 的代码。4.2 Maven 依赖配置在pom.xml中加入核心依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.1/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies说明一下为什么引入 Redis会话状态在跨 Agent 切换时需要被多个节点访问Redis 在这里作为共享状态存储保存序列化后的会话状态对象。如果你的项目没有 Redis 环境也可以先用 ConcurrentHashMap 做内存版实现。本文示例可以先用内存存储方便你快速运行验证。Redis 存储可以等确认逻辑正确后再接上。4.3 定义统一会话状态模型新建SessionState.javapackage com.example.agentbroker.model; import lombok.Data; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; Data public class SessionState { private String schemaVersion 1.0; private String sessionId; private String agentId; private String userId; private String currentGoal; private ListMessageRecord messageHistory new ArrayList(); private ListPlanStep planSteps new ArrayList(); private MapString, Object memoryContext new HashMap(); private MapString, Object metadata new HashMap(); }MessageRecord.java用来表示统一的消息记录package com.example.agentbroker.model; import lombok.Data; import java.util.Map; Data public class MessageRecord { private String role; private String content; private String name; private MapString, Object arguments; private MapString, Object toolResult; private String timestamp; }PlanStep.java用来描述执行步骤package com.example.agentbroker.model; import lombok.Data; Data public class PlanStep { private String stepId; private String description; private String status; private String result; }模型设计里有几个细节值得注意使用MapString, Object而不是强类型字段来保存工具调用参数和返回值是为了兼容不同 Agent 平台的异构数据。schemaVersion字段预留在版本升级时使用后续如果模型结构变化可以通过它判断兼容性。所有字段尽量使用包装类型和默认值避免空指针问题。4.4 适配器接口与实现定义一个基础适配器接口AgentAdapter.javapackage com.example.agentbroker.adapter; import com.example.agentbroker.model.SessionState; public interface AgentAdapter { /** * 获取当前适配器支持的 Agent 标识 */ String getAgentType(); /** * 从平台私有格式导出为统一 SessionState */ SessionState exportSession(String platformSessionId); /** * 将统一 SessionState 导入目标 Agent */ String importSession(SessionState state); }然后实现两个模拟适配器。AgentAAdapter.javapackage com.example.agentbroker.adapter; import com.example.agentbroker.model.SessionState; import org.springframework.stereotype.Component; Component public class AgentAAdapter implements AgentAdapter { Override public String getAgentType() { return agent-a; } Override public SessionState exportSession(String platformSessionId) { // 模拟从 Agent A 平台加载会话状态并转换为统一格式 // 实际项目中这里需要调用 Agent A 提供的 REST API SessionState state new SessionState(); state.setSessionId(platformSessionId); state.setAgentId(agent-a); state.setCurrentGoal(查询本月销售数据并生成分析报告); return state; } Override public String importSession(SessionState state) { // 模拟向 Agent A 平台创建新会话传入统一状态 // 实际项目中这里需要调用 Agent A 的创建会话接口 return agent-a-session- state.getSessionId(); } }AgentBAdapter.java的结构类似只是getAgentType()返回agent-b。可以这样写package com.example.agentbroker.adapter; import com.example.agentbroker.model.SessionState; import org.springframework.stereotype.Component; Component public class AgentBAdapter implements AgentAdapter { Override public String getAgentType() { return agent-b; } Override public SessionState exportSession(String platformSessionId) { SessionState state new SessionState(); state.setSessionId(platformSessionId); state.setAgentId(agent-b); state.setCurrentGoal(查询本月销售数据并生成分析报告); return state; } Override public String importSession(SessionState state) { return agent-b-session- state.getSessionId(); } }这两个适配器目前是模拟实现但接口的方向已经确定。真实对接时只需要在exportSession中调用平台提供的查询接口在importSession中调用创建会话接口并把字段做一个映射即可。4.5 核心服务会话转移逻辑核心服务ConversationTransferService.java是整套机制的中枢package com.example.agentbroker.service; import com.example.agentbroker.adapter.AgentAdapter; import com.example.agentbroker.model.SessionState; import com.example.agentbroker.storage.SessionStateStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; import java.util.Map; Service public class ConversationTransferService { Autowired private SessionStateStore stateStore; Autowired private ListAgentAdapter adapters; private MapString, AgentAdapter adapterMap new HashMap(); /** * 初始化时构建 adapter 映射表 */ jakarta.annotation.PostConstruct public void init() { for (AgentAdapter adapter : adapters) { adapterMap.put(adapter.getAgentType(), adapter); } } /** * 从源 Agent 导出会话并保存到统一存储 */ public String exportConversation(String sourceAgentType, String platformSessionId) { AgentAdapter sourceAdapter adapterMap.get(sourceAgentType); if (sourceAdapter null) { throw new IllegalArgumentException(Unsupported agent type: sourceAgentType); } // 调用适配器导出 SessionState state sourceAdapter.exportSession(platformSessionId); // 保存到统一存储 stateStore.save(state); // 返回统一会话 ID用于后续导入 return state.getSessionId(); } /** * 将统一会话导入目标 Agent */ public String importConversation(String targetAgentType, String sessionId) { AgentAdapter targetAdapter adapterMap.get(targetAgentType); if (targetAdapter null) { throw new IllegalArgumentException(Unsupported agent type: targetAgentType); } // 从统一存储加载会话状态 SessionState state stateStore.load(sessionId); if (state null) { throw new RuntimeException(Session not found: sessionId); } // 修改当前负责的 agent state.setAgentId(targetAgentType); // 调用适配器导入 return targetAdapter.importSession(state); } /** * 追加一条人工消息并更新会话状态 */ public void appendMessage(String sessionId, String role, String content) { SessionState state stateStore.load(sessionId); if (state null) { throw new RuntimeException(Session not found: sessionId); } MapString, Object message new HashMap(); message.put(role, role); message.put(content, content); message.put(timestamp, java.time.Instant.now().toString()); // 这里简化处理实际项目中需要创建 MessageRecord // state.getMessageHistory().add(message); stateStore.save(state); } }init()方法中用PostConstruct在服务启动时构建适配器映射这样后续只需按代码注入即可。SessionStateStore.java先提供一个内存版存储package com.example.agentbroker.storage; import com.example.agentbroker.model.SessionState; import org.springframework.stereotype.Component; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; Component public class SessionStateStore { private MapString, SessionState store new ConcurrentHashMap(); public String save(SessionState state) { if (state.getSessionId() null || state.getSessionId().isEmpty()) { state.setSessionId(UUID.randomUUID().toString()); } store.put(state.getSessionId(), state); return state.getSessionId(); } public SessionState load(String sessionId) { return store.get(sessionId); } public void delete(String sessionId) { store.remove(sessionId); } }接入 Redis 时可以把save和load方法改为操作 Redis 字符串序列化工具可以用 Jackson 的 ObjectMapper。这里的内存版足够展示业务逻辑。4.6 编写控制器接口有了服务层还需要对外暴露 API。新建ConversationController.javapackage com.example.agentbroker.controller; import com.example.agentbroker.model.SessionState; import com.example.agentbroker.service.ConversationTransferService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Map; RestController RequestMapping(/api/conversation) public class ConversationController { Autowired private ConversationTransferService transferService; /** * 导出会话从源 Agent 获取状态返回统一会话 ID * 请求示例 * POST /api/conversation/export * { * sourceAgentType: agent-a, * platformSessionId: a-12345 * } */ PostMapping(/export) public MapString, String exportConversation(RequestBody MapString, String request) { String sessionId transferService.exportConversation( request.get(sourceAgentType), request.get(platformSessionId) ); return Map.of(sessionId, sessionId); } /** * 导入会话把统一会话状态导入到目标 Agent * 请求示例 * POST /api/conversation/import * { * targetAgentType: agent-b, * sessionId: uuid * } */ PostMapping(/import) public MapString, String importConversation(RequestBody MapString, String request) { String platformSessionId transferService.importConversation( request.get(targetAgentType), request.get(sessionId) ); return Map.of(platformSessionId, platformSessionId); } /** * 查看当前统一会话状态 */ GetMapping(/{sessionId}) public SessionState getSession(PathVariable String sessionId) { return transferService.getSession(sessionId); } }为了支持getSession需要给ConversationTransferService加一个查询方法public SessionState getSession(String sessionId) { return stateStore.load(sessionId); }这样一个最小可运行的会话中间层就完成了。它的执行流程是用户在 Agent A 中开始对话。如果想切换到 Agent B先调用/api/conversation/export把 Agent A 的会话状态导出为统一格式并保存到统一存储。接着调用/api/conversation/import指定目标 Agent B 和统一会话 ID。Agent B 通过适配器加载状态并在自己的平台上创建新会话继续执行。4.7 运行与验证启动 Spring Boot 应用后可以通过 curl 来模拟一次完整流程。第一步从 Agent A 导出会话curl -X POST http://localhost:8080/api/conversation/export \ -H Content-Type: application/json \ -d {sourceAgentType: agent-a, platformSessionId: a-12345}预期返回{sessionId:生成的-uuid}第二步查询导出的会话状态curl http://localhost:8080/api/conversation/生成的-uuid预期返回的是一个 SessionState JSON包含agentId、currentGoal等字段。第三步将会话导入 Agent Bcurl -X POST http://localhost:8080/api/conversation/import \ -H Content-Type: application/json \ -d {targetAgentType: agent-b, sessionId: 生成的-uuid}预期返回{platformSessionId:agent-b-session-生成的-uuid}到这里一次完整的“Agent A 开始对话Agent B 继续对话”就完成了。5. 进阶方案不用代码也能延续会话上面的中间层方案适合有开发资源的团队。如果你暂时不想引入一套中间层系统只是希望在日常使用或小型项目中减少 Agent lock-in 的影响也可以采用一种更轻量的思路把会话状态导出为人类可读的 Markdown / Text 文件再作为新 Agent 的初始提示词导入。我这里整理了一份导出模板# Agent 会话状态导出 ## 会话信息 - 原平台Agent A - 原始会话IDxxx - 导出时间2025-01-15 10:05:00 ## 用户目标 查询本月销售数据并生成分析报告 ## 已完成步骤 1. [完成] 查询本月销售总额结果为 128 万订单数 3200 - 工具query_sales - 入参{month: 2025-01} - 返回{total_amount: 1280000, order_count: 3200} ## 待执行步骤 1. [待执行] 按区域拆分销售数据 2. [待执行] 基于拆分结果生成分析报告 ## 关键对话记录 用户我想看一下本月销售情况 助手好的我来查询本月的销售数据。 助手本月销售总额 128 万订单数 3200接下来继续按区域拆分。 ## 用户偏好 - 报告需要包含同比和环比对比使用这份 Markdown 文件时直接把它作为 system prompt 或第一轮用户消息粘贴到新 Agent 中再追加一句“请从中断的地方继续执行”。对于现代大模型来说它通常能理解这种结构化文本并在新会话中继续工作。这种方法虽然不如标准协议精确但在没有开发资源的情况下非常实用。它的价值在于让“会话状态”成为用户可以自己掌握的一等公民而不是被锁定在某个产品内部。6. 常见问题与排查思路在实现跨 Agent 会话延续时有几个高频问题。下面整理成表格方便排查。问题现象常见原因解决思路从 Agent A 导出的会话状态导入 Agent B 后B 不认上下文两个平台的消息格式不一致直接传原始 JSON 无法解析增加适配器做字段映射必要时转换成目标平台的私有格式会话中工具调用记录丢失平台 API 不提供中间步骤查询只返回最终结果在每次工具调用后由中间层主动记录工具调用和返回结果而不是依赖平台提供导入新 Agent 后它重新执行已完成的步骤没有把“已完成步骤”和“待执行步骤”区分清楚在摘要或状态中明确标记每个步骤的完成状态并告诉新 Agent 不要重复执行已完成步骤长期记忆无法恢复各平台的记忆存储格式和检索机制不互通提前设计统一记忆摘要在导出时把记忆整理成自然语言文本写入 memory_context切换后涉及用户隐私数据外泄会话状态中包含敏感字段传输和存储未加密对敏感字段做脱敏或加密传输使用 HTTPS统一存储增加访问控制新增 Agent 平台时需要改业务代码没有使用适配器模式业务逻辑与新平台耦合引入适配器模式新增平台时只实现 AgentAdapter 接口还有一个很容易踩的坑不要试图把一个平台的完整内部数据结构原样塞给另一个平台。你觉得自己很努力在做兼容但对方根本不知道你的custom_xxx字段是什么意思。更好的做法是导出时只保留通用语义私有字段全部放进 metadata导入时按需解析。7. 最佳实践与工程建议7.1 从第一天就引入会话 ID如果你想在未来支持跨 Agent 迁移现在就应该在系统中引入全局唯一的会话 ID。不要依赖各平台的原始会话 ID而是自己生成 UUID并在每次调用 Agent 时把会话 ID 透传进去。这样后续做状态同步时就能建立映射关系。7.2 状态导出要及时不要等最后Agent 执行过程中的中间产物往往比最终结果更有价值。比如代码生成场景中每一步的修改记录、测试反馈、报错信息都是恢复会话的关键。建议在每个工具调用完成后就立即把状态增量同步到统一存储中。不要等到用户手动点击“导出”时才去拉取。7.3 摘要与结构化数据并存给新 Agent 传递上下文时结构化的 JSON 适合程序解析但自然语言摘要更适合大模型理解。推荐两套都保留raw_state完整的结构化状态供程序自动化处理。summary_prompt压缩后的自然语言描述供新 Agent 快速理解任务背景。7.4 安全边界与最小权限跨 Agent 会话延续时尤其要注意权限边界。新 Agent 应该只获得完成当前任务所需的最小上下文而不是原始会话的全部敏感信息。建议在导出接口中增加字段过滤配置只导出允许跨平台共享的字段。从工程上可以这样实现public SessionState exportSession(String platformSessionId, ListString allowedFields) { // 先导出全部状态 SessionState fullState agentAdapter.exportSession(platformSessionId); // 按 allowedFields 过滤敏感字段 return filterState(fullState, allowedFields); }7.5 日志与审计跨 Agent 切换本质上是数据在不同系统之间流动一定要记录完整的审计日志。至少包括哪个用户发起了切换。从哪个 Agent 切换到哪个 Agent。导出和导入的会话 ID。切换前后的状态版本。是否有敏感字段被过滤。有了这些日志不仅便于排查问题也能满足合规审计要求。7.6 不要过度设计最后想提一点Agent 互操作还处于早期阶段不要一上来就搞非常重量级的协议和标准那是标准组织的工作。对于业务团队来说先实现一个简单的 JSON 状态导出和导入适配器已经能解决绝大多数问题。协议的字段设计可以保持精简后续按实际需要慢慢扩展。8. 总结与下一步学习方向回到开头的问题End AI Agent lock-in不代表消灭某个平台也不是要求所有 Agent 产品使用同一套 API。真正的解药是让会话状态成为用户可携带、可理解、可迁移的资产。本文从概念上拆解了 Agent lock-in 的本质指出会话连续性不是文本复制而是状态迁移然后给出了一个标准化的会话状态模型并结合 Java Spring Boot 实现了一个支持跨 Agent 会话延续的中间层最后还提供了一个无需开发也能操作的 Markdown 导出方案以及一些工程上的最佳实践。如果你准备把这个方案落到自己的项目里我建议下一步按这个顺序深入先梳理自己当前使用的 Agent 平台支持哪些 API确认能否获取到中间执行步骤。设计一个最小可用的 SessionState 结构只包含你真实需要的字段。实现一个导出适配器和一个导入适配器跑通一条路径。把统一存储从内存换到 Redis解决多实例共享问题。逐步补充安全过滤、审计、摘要生成等能力。随着 AI Coding Agent、Agent Workflow 编排工具的快速迭代未来一定会有更多场景需要我们像管理数据库事务一样管理 Agent 上下文状态。在标准成熟之前先从自己的项目里建立一套可交换的会话状态模型是一件投资回报非常高的工程决策。
