选择任务边界
当任务需要跨越 HTTP 请求、观察连接或执行进程的生命周期时,使用 Durable。 如果调用方自行管理执行和状态持久化,仍可直接调用ReActAgent 或 HarnessAgent。
恢复保留 Session、Thread 和 Turn 身份。替换宿主发起调用时创建新的 Run 和 Step,
不会新建用户任务。普通应用使用 Session 的主 Thread 即可;需要独立上下文或持久子 Agent
时再创建其他 Thread,不要为每次重试或重连创建 Thread。
Java 模块与部署边界
库 JAR 位于durable-runtime、durable-worker 和 durable-adapters。adapter 聚合模块包含
AgentScope Harness adapter、Claude Managed Agents 事件 adapter 与 AG-UI 事件 adapter;
agentscope-examples/durable 是独立示例聚合模块。库由宿主打包,引入依赖不会自动启动服务。
AgentScope adapter 依赖 runtime、Core 和 Harness;
worker 仅依赖 runtime(以上不含测试依赖)。不再有独立的 codec 制品。
- Brain 使用
AgentScopeAdapter驱动原生 Agent,DurableRuntime管理 Turn 调度与恢复。 - 只处理 checkpoint 的后端使用
agentscope-durable-adapter-harness-agentscope中的io.agentscope.durable.codec.agentscope.AgentScopeCheckpoints和AgentScopeCheckpointCodec.STATE_VERSION。这些 codec 操作不构造或启动 Agent, 但引入制品会带入 Harness 依赖。codec 准备并校验原生 baseline,SessionBackend负责事务安装及目标空闲/位置检查。 agentscope-durable-worker同时提供调用侧可靠交付和 Hands 执行管理。QueuedExecutor部署在 Brain 或 Backend 服务中,持久化命令、接收结果并交给 Backend;WorkerRuntime配合ExecutionStore部署在 Hands,管理工具执行、恢复、取消和输出。BatchExecutor组合子工具执行。引入 worker JAR 不等于启动 Hands。- 本地宿主可将这些对象放在同一 JVM。独立部署时,由 HTTP/RocketMQ 扩展连接边界;
RocketMQ 扩展两侧都依赖 worker,分别装配
RocketMqRemoteExecutor和RocketMqWorker。 不需要为了区分部署角色再拆分库模块。
嵌入宿主
使用与 Core/Harness 同版本的io.agentscope:agentscope-durable-adapter-harness-agentscope 和
io.agentscope:agentscope-extensions-durable-jdbc。
SQLite 是默认本地持久后端;PostgreSQL 为多个兼容宿主提供共享存储。
两种方式都不要求部署 Worker 或消息队列。
model。在宿主生命周期内保持 Runtime 运行,关闭宿主时调用
close()。相同请求 ID 与输入再次提交时返回原 Turn;改变输入会发生冲突。
通过 receipt.status() 查询状态。
使用 AgentScopeAdapter.forHarness 提供返回全新 HarnessAgent.Builder 的工厂,并配置工作区。
两种工厂都返回 Builder;adapter 注入本次调用的 Thread Journal 后构建 Agent。
公开 Binding 只提供定义、运行时上下文和 Durable workspace。Runtime 持有 Durable 租约和任务调度;
ThreadJournal 负责 Agent 原生历史,Durable 调度仍由 Durable Runtime 负责。
namespace 映射为 RuntimeContext.userId,身份限定在每次调用内。
重连与控制任务
重新打开原任务
将SessionKey、Thread ID 和 Turn ID 保存到应用的任务记录中。刷新页面或应用重启后,
用这些身份查询原任务。以下 runtime 已连接原 Backend 或公共 HTTP 端点:
排队跟进任务或修改当前任务
以故障调查为例,将后续总结提交为新 Turn,用steer 向正在执行的调查补充约束:
steer 和 inject 在安全边界向原 Turn 加入输入,不会在模型输出一半时强行打断,
也不会撤销已发出的工具调用。withdraw 只能撤回尚未应用的输入。
控制与任务完成发生竞争时,应重新读取 Turn,不能假定控制已生效。
控制回执表示已受理;通过 signal.applied 或最终 Turn 状态确认后续处理。
排队中的任务可以立即取消;活动任务需要结清外部操作,受理取消不代表远端进程已经停止。
主动暂停和恢复
调用turn.pause("pause-1") 后,观察到 PAUSED 才能向用户确认 Agent 已停止推进。
在之后的请求中,使用观察到的版本恢复:
用另一个 Thread 执行独立工作
统一用户 API
DurableRuntime 是线程安全的装配、生命周期和业务入口。会话和任务句柄只保存身份,任意业务实例
都能重新查找;权威状态在 Backend 中,查询不会启动 Agent。
一个 DurableTurn 对应一次持久逻辑任务。原生 AgentRun 是一次进程内调用,与 Durable 的
RunView 使用同一个 runId;StepView 记录该 Run 内的步骤。恢复保留 turnId,创建新 Run/Step。
通过
turn.listRuns(cursor, limit)、turn.getRun(runId)、turn.listSteps(runId, cursor, limit)
和 turn.getStep(runId, stepId) 查询执行历史;这些查询不启动 Agent。
模型明细通过 turn.listModelCalls(runId, stepId, cursor, limit) 与
turn.getModelCall(runId, modelCallId) 查询;stepId 为 null 时列出 Run 的全部调用。
每条调用包含独立请求、重试次数、来源步骤、耗时和已知 token 用量,恢复回放不重复计量。
usageComplete=false 表示只知道部分用量,缺失计数保留 null;details 是总 token 的子集。
PROVIDER_ATTEMPTS 表示共用模型请求边界的观测;自定义模型若只暴露外层调用,则标记
MODEL_CALL,不推断隐藏重试。没有结束事实的失联请求保留 LOST 和已知部分,不伪造完整用量。
同一 Turn 的累计推理位置随原生 checkpoint 保存,重建 Runtime 不会重置 maxIters。
应用已有工具结果的恢复 Step 不额外消耗一次推理次数;新 Turn 重新计算预算。
恢复推理、工具结果应用和 finalization 都会记录属于新 Run 的 Step,并可通过
resumedFromStepId 关联中断前的步骤。Step 阶段可以推进到 FINALIZATION,阶段变化可从事件中查看。
当前原生格式为 agentscope-thread-journal-v2;旧开发格式不自动转换。
Core/Harness 升级保留 AgentScope 2.0 的状态契约。缺少 thread_id 的状态映射到 main。
空的主 Thread 首次执行时,会从配置的 AgentStateStore 导入已有 agent_state,先提交
migration/baseline 再继续运行。命名 Thread 不会导入该状态。主 Thread 已提交状态会
镜像回同一个 agent_state,因此回滚后 2.0 runtime 可以读取最近一次已落稳的状态。
2.1.0-BETA1 的 SessionLog API 和存储格式不属于升级兼容契约。
Session 创建时原子创建主 Thread;每个 Thread 固定自己的 Agent 定义版本。
同 Thread 按持久化受理顺序执行;当前任务等待时,后续任务不能越过它。同 Session 的不同 Thread 可并行执行。
turn.pause(requestId) 先受理暂停意图,活动调用在安全边界从 PAUSING 进入 PAUSED。
暂停也会阻塞同 Thread 的后续任务;外部结果和交互答案可继续落库,但不自动推进。
原生日志通过 run/output、run/end 报告调用结果,任务终态只由 Durable 提交。
排队任务可立即取消,活动任务的取消需等待实际执行结果核实。
submit 返回持久 Turn 句柄。相同 requestId/输入重试返回原 Turn,内容不同返回 CONFLICT。
steer/inject/respond/cancel/pause/resume/withdraw 返回 CommandReceipt,表示命令已原子受理;
signal.applied、interaction.resolved 或 Turn 终态才表示后续处理结果。
submit/steer/inject/respond 同时写入公开用户消息,history 只读公开投影,
不需要私有原生日志;消息携带 requestId/inputKind,响应额外携带 interactionId。
事件清理不删除已投影的历史消息。
同一 Turn 可以产生多条 assistant 消息,不同推理轮次分别保留。消息的逻辑 ID 不随
Run/Step 更换而改变;重新调用模型时增加 attempt,重放已提交模型结果时用 revision
替换未完成文本,避免重连后重复拼接。message.completed 是权威持久事实;
message.delta 与 tool.arguments.delta 是由 middleware 处理后展示事实产生的尽力而为
LiveEvent 预览,没有 sequence/cursor,也不进入 history、snapshot 或重放。模型私有推理
仍不公开。history 按首次公共 sequence 排序;直接读取 snapshot 的 messages 映射时也应
使用 sequence,不能依赖对象字段顺序。已有存储中的 message.delta 和
tool.call.arguments 仍可读取,但 AgentScope 的新写入不再创建它们。
工具调用统一保存在 snapshot 的 tools 投影中,按 Thread、Turn 和 callId 关联参数分片、
进度与结果。参数分片可以省略尚未知晓的工具名称;最终完整参数到达后停止拼接。
本地工具与远程 Hands 共用调用视图,只有真实外部执行携带 executionId。工具执行完成、
结果接收和结果应用分别记录;恢复后仍展示真实工具输出,UNKNOWN 保留到结果被明确核实。
session.call(requestId, input, timeout) 是 submit + await,返回 CompletionStage。消费事件时应先
订阅或打开事件流,再调用 submit,这样快速完成的 Agent 也不会漏掉实时预览。旧的
submit-and-observe stream(...) 快捷方法因为名称含混且只能在受理后连接,现已 deprecated。
同一 requestId 可安全重试;重复观察原 Turn 不重复提交。超时错误包含 Thread/Turn 身份。
关闭订阅、取消 Future 或关闭 Runtime 都不会取消持久任务,只有显式 cancel 才会发起取消。
这些短操作可能访问数据库/网络,Web 宿主须在非事件循环线程调用。
独立业务 API 与 HTTP
API-only Runtime 只需访问后端,创建新会话时注册定义元数据或 definitionResolver, 无需本地模型、Agent 工厂或 Executor。已有会话的查询不要求重新注册定义。agentscope-durable-adapter-harness-agentscope 内的 codec,不构造 Agent,
但制品仍依赖 Harness。原生状态在内部校验和复制,恢复目标必须空闲且位置匹配;
fork 指向同 namespace 下的其他 Thread,并继承源定义版本;目标 Session 必须已存在。
目标 Thread 的恢复事件进入目标 Session 的时间线;继承的原生对话历史属于私有恢复状态,不伪造历史公开事件。
HTTP 服务可以挂载 DurableRuntimeHandler(api, authorizer) 和
SessionEventHandler(api, authorizer)。authorizer 必须校验可信身份、namespace 和 Session;
fork 分别授权源与目标。公开 handler 只接受用户操作,拒绝 claim/commit/原生状态等内部操作。
其他 Web 框架可直接调用 Runtime;HTTP 并非必选组件。
HttpSessionBackend 是受信任执行宿主使用的
完整内部 Backend 适配,不应作为公网用户操作面。无执行配置的 Runtime 调用 start 会报错。
Brain 另行装配 Harness/Executor 并 start,持续扫描、认领和恢复;MQ 提示可以降低延迟,
提示缺失仍靠持久状态扫描推进。关闭 API 或 SSE 不影响其他 Brain。
Java 协议与后端选择
公共 Java 契约定义在DurableTypes、DurableMessages 和 DurableProtocol 中。
Backend/Executor 的 HTTP 调用使用 Runtime JAR 内同一套带版本 JSON Schema 校验,
无效请求在执行前拒绝。损坏的成功响应仍视为结果不确定,使用原请求或提交回执恢复。
私有 Frame 携带检查点元数据:Harness、定义/状态版本、独立字符串位置和规范化摘要。
适配器在原生恢复前校验绑定;公共快照和事件不包含该状态。
已经提交的原生检查点不能通过 null 丢弃。
runtime.executionCapabilities() 返回后端保证、Harness 恢复/事件边界及 Executor 查询/取消/输出能力。
动态解析器列在 resolvedHarnesses;使用 runtime.capabilities(definition) 查询冻结定义的能力,
不会认领任务或调用模型。静态宿主按定义/状态版本索引扫描,其他版本的积压不会阻挡当前任务。
AgentScope 声明原生 TOOL_BOUNDARY 恢复和 DELTA 交付。
它不重放任意 Java 调用栈,也不保证无法查询的本地副作用恰好一次。
可提交字符串,或使用通用 Java 类型构造结构化内容:
x.<namespace>.<name>;保留的生命周期、工具、消息和制品事件有载荷约束。
展示事件与协议 adapter
Durable 内部区分两类事件存储语义,对 SDK 用户提供一条强类型事件流:
应用统一消费 sealed
SessionStreamEvent:Committed 包装持久 PublicEvent,
PreviewStarted 表示预览开始,EventDelta 携带显式订阅的增量,SnapshotReset 用于游标
失效后的状态替换。只有 Committed 包含 Durable cursor。每条连接通过
EventStreamOptions.deltas(...) 显式启用消息或工具参数预览,默认只交付持久事实。
listEvents(cursor, limit) 永远只返回持久 PublicEvent。Durable 应用不需要消费
Core/Harness 的 AgentEvent、JournalEvent 或内部实时传输类型。
agentscope-durable-adapter-claude 将完成消息和显式订阅的实时消息增量映射为 Claude
Managed Agents 事件格式。为当前连接启用 EventDeltaType.MESSAGE 后,可将
ClaudeEventStreamSink 传给 SessionEventStream.forEach。agentscope-durable-adapter-agui 通过
DurableAguiEventStreamSink 将同一规范化事件流映射到现有 AG-UI 1.0 AguiEvent 类型。
这两个制品是事件 adapter,不是完整托管 HTTP API;认证、资源路由与传输封装仍由应用负责。
默认 LiveEvents.local() 只连接同一个 Runtime 装配内的生产者与观察者。Brain/API 分离部署
而且需要实时预览时,两侧必须配置同一个共享 LiveEvents 传输。即使该传输缺失或丢事件,
Durable 执行、历史和最终结果仍保持正确。
Hands Worker 与 RocketMQ 执行
Hands 请求的call.origin 保留最初产生工具调用的 Run/Step,重投、换实例和结果回传不会改变它。
ToolExecution.appliedAt 与公共工具视图的 data.appliedAt 则记录结果进入 Agent 状态的位置,
恢复后可以属于新的 Run/Step;该位置随 checkpoint 一起提交。调用结束后的宿主结清使用空位置,
不会额外创建 Run。视图的 data.origin 保留原始来源。
批量子工具和 Sandbox 路由继续携带父请求来源;AgentScope Hands 工具可从 RuntimeContext 的
durable.origin 取得 RunStepRef,getRunId() 对应原始 Brain Run。
Hands 是可嵌入的独立组件,不要求每个应用部署一个工具服务:
WorkerRuntime 负责 Hands 的调度、恢复和生命周期,本身不再实现 Executor。Brain 与
Hands 同 JVM 部署时,把借入的 worker.localExecutor() 注册到 DurableRuntime;该端点把
执行协议调用委托给其所属的 WorkerRuntime。
默认本地装配直接接收 Hands 的 RecordStore,并在内部创建事务执行账本:
executionStore(customStore)。Brain 继续接收
SessionBackend,底层 JDBC RecordStore 不负责创建该领域对象。
所有 Worker 形态复用相同执行内核。进程内线程隔离不能代替进程或 Sandbox 隔离。
Brain 会话状态与 Hands 执行账本可以使用不同数据库;同一池的副本必须共享其账本,
不能使用每个容器独立的 SQLite 文件冒充跨主机共享存储。
引入同版本
agentscope-extensions-durable-rocketmq,在 Brain 侧装配:
"hands"。在独立 Hands 进程中装配:
durable 与 route hands 自动派生 commands、controls、updates 三个普通
Topic 和对应消费组;两端无需分别配置物理名称。控制 Topic 独立,取消和查询不会被新执行
积压阻塞。
每个 Worker 池使用独立命令路由及消费组;同池副本共用该组与执行账本。
Brain 结果消费者使用另一个组,同组副本必须共享 Brain 交付记录和 SessionBackend。
不同部署若使用不同数据库,应使用独立 namespace,避免互相消费无法识别的任务。
派生后的 Topic、消费组、ACL、TLS、凭证和消息保留期由应用部署管理;Worker 的 authorize
在账本访问前执行。
接入使用 RocketMQ SimpleConsumer:
Worker 持久受理后才确认命令;Brain 持久去重并向 SessionBackend 提交后才确认事件。
Broker ACK 不代表工具完成。控制命令乱序、重复消息和发布响应丢失不改变执行身份。
取消先于执行到达会留下墓碑;查询只重发原状态,不创建工具副作用。
已保存结果的补发不会重跑工具,后台扫描修复交付中断和会话提交失败。
执行消息使用 ExecutionMessages 和随 Runtime 打包的 JSON Schema,包含版本、请求摘要、
命令/事件 ID 和源序号。收件箱只应用连续前缀,重复消息幂等,缺失事件补发后再推进。
输出页携带字节偏移和明确缺口,终态发布前先封闭输出。MQ Executor 支持输出查询,
实际字节是否可用取决于 Provider 能力;公共 SSE 仍由 SessionBackend 投影。
不承诺任意外部副作用 exactly-once,不可查询的未知结果保持 UNKNOWN。
信封上限为 1 MiB。Brain 自动使用 SessionBackend 的 ObjectStore,Worker 可通过
objects(...) 配置同一可访问的 ObjectStore,将大型信封转为对象引用;引用校验作用域和
摘要,不可访问或无效载荷不会被确认。
RocketMqWakeups 仍单独用于 Session/订阅唤醒,不承载执行命令或结果;
SSE 继续读取 SessionBackend 的公共事件。MQ 执行不要求同时配置 RocketMqWakeups:
单 Brain 进程可使用本地通知,多进程也保留持久扫描恢复。
SessionBackend 部署适配
应用选择一套完整 Backend,自定义集成才需要了解底层存储 SPI。LocalBackend.open(directory) 创建 directory/durable.db,完成 SQLite 初始化和进程内
通知装配。重启时打开同一目录即可恢复原 Session,无需数据库服务或消息队列。
生产 PostgreSQL 接入已有 DataSource:
initializeSchema(true)。
数据库/schema 选择和连接池由 DataSource 管理。两种适配均返回 SessionBackend,
使用相同 Runtime、AgentScope 工厂和事件回调。切换部署适配不会自动迁移已有数据。
关闭时先关闭订阅和 Runtime,再关闭 Backend。使用 try-with-resources 时,
按 backend、Runtime、subscription 的顺序声明,资源会按相反顺序关闭。
Backend 关闭自己拥有的记录存储访问;PostgreSQL 不关闭传入的 DataSource 或通知传输。
所有使用者停止后,由宿主关闭 RocketMqWakeups 和对应 SDK 客户端。
扩展基础设施
完整适配负责组合这些组件。高级集成仍可使用
TransactionalSessionBackend(recordStore)
和 backend.withWakeups(transport),或为不同物理布局实现完整 SessionBackend。
更换存储时,必须保持检查点与意图、结果与恢复条件之间的原子关系。
所有写入口、Runtime 和 HTTP handler 都使用装配后的 Backend。
远程写入也需配置权威服务端;只给某个 HTTP 客户端配置通知,无法观察其他客户端的写入。
wakeups() 是本地装配能力,不是 HTTP 操作。
通知在提交成功后发布,通知或错误回调失败不改变已确认的写入。
轮询补偿提交后、发布前丢失的提示,以及被消费组交给其他宿主的提示。
唤醒回调只调度或唤醒工作。RocketMQ 在本机监听者均成功返回后 ACK;
失败允许重投,之前已经成功的监听者也可能再次收到提示。
组合方式参考
durable-harness
的存储/队列边界,Java 继续使用现有事务与重放契约。
恢复工具与等待
将外部工具注册为只有 Schema 的原生工具,并将工具名映射到 Runtime 注册的 Executor。 原生权限检查通过后才提交工具意图。原生恢复点与意图一起保存; 外部结果接收后唤醒原 Turn,应用确认与包含该结果的原生恢复点一起提交。agentscope-extensions-durable-sandbox 提供 DirectSandboxExecutor,
使用已存在且固定身份的 HarnessSandbox 连接。POSIX/Python 操作日志让 Agent
进程退出后仍能查询独立运行的命令。agentscope-durable-worker 提供独立执行账本
和 Worker,适合需要独立容量或隔离的场景。DelegationExecutor 使用同一边界实现可恢复的异步子 Agent。
无法核实的远程结果或取消状态保留为 UNKNOWN。普通本地工具保留原生恢复边界。
凭证、环境创建和保留策略由应用负责。
通过 turn.pendingInteractions() 获取交互,再调用
turn.respond(requestId, interactionId, response);审批 response 为 boolean,手动外部工具为 JSON。
框架原子校验 Turn、交互状态及响应类型,并拒绝代替自动 Hands 写入结果。
turn.steer/inject 在原生推理安全边界应用,withdraw 与应用原子竞争。
自定义适配器可提交 WaitCondition(key, absoluteDeadlineMillis);零表示只等待信号。
观察到 PAUSED 后使用 turn.resume(requestId, expectedRevision),修订号取自该暂停状态。
它只解除显式暂停;未完成的审批、工具和信号条件仍需满足,也不会重启终态任务。
通用扩展信号仅允许 x.* key,不能冒充框架内部控制。
调用失败与自动恢复
需要宿主替换时,让兼容的 Brain 实例连接同一持久 Backend。原所有者租约到期后, 另一实例可以认领未完成的 Thread,从已提交状态继续。宿主必须能解析 Thread 固定的 Agent 定义和状态版本、工具绑定,并访问原工作区与外部操作。 PostgreSQL 可提供跨宿主共享存储,各容器独立的 SQLite 文件不能。 Fencing 拒绝过期所有者提交,定期扫描补偿丢失的唤醒通知,无需用户重新提交任务。 如果所有 Brain 都已停止,需要进程管理器重新启动实例。 调用失败由 Runtime 分类并交给 Backend 记录。默认对 I/O、超时及内部 UNAVAILABLE/CONFLICT 进行有界重试,未知程序错误不自动重试;AgentScope adapter 使用 Core 的模型错误分类, 429/5xx 和网络故障可重试,明确的客户端请求错误不可重试。模型自身的请求重试与这里的新 Run 恢复是两个层次,各自记录实际执行。turn.status().recovery() 和 turn.recovery 事件提供恢复信息;
没有失败记录时 recovery 为 null。必要时通过 builder 的 retryableFailure 配置宿主错误分类,
该判断不能授权重做 UNKNOWN 操作。
重试不依赖用户连接或手工信号。不可重试或预算耗尽后停止新 Agent 调用,已发出的工具仍按原
executionId 查询或取消;UNKNOWN 保持 WAITING,效果结清后才 FAILED。HarnessAdapter.settle 统一失败与取消结清;
AgentScope 在同一提交中保存原生清理状态、确认结果应用和 Turn 终态,不启动新的 Run,
同 Thread 的后续任务可使用已清理的上下文。暂停仍优先,取消可
越过退避进入结清。宿主关闭、所有权失效和其他宿主可能提供的 INCOMPATIBLE 定义不消耗此失败
预算。Backend 无法提交失败记录时不能声称已持久化,原租约到期后由新宿主核实最后有效状态。
独立观察任务
普通应用直接使用 Runtime 管理的强类型事件订阅:EventStreamOptions.builder().after(savedCursor).build();游标过期时交付
SnapshotReset。只有当前连接需要渲染预览时才在同一个 options 中启用 delta。
创建和关闭 Session/Thread 会产生持久的 session.created/closed、thread.created/closed
事件。创建 Session 同时创建主 Thread;重复相同操作不重复追加生命周期事件。
snapshot.projection.session 和 snapshot.projection.threads 提供同一游标下的资源状态,
事件回放与直接查询一致,日志裁剪后仍可通过快照恢复。
按 Thread、Turn 或 Run 观察时,使用同一 EventFilter 读取事件和快照:
sessionHandle.listEvents(filter, cursor, limit) 和 snapshot(filter) 使用相同范围;
Thread/Turn 句柄的快捷方法也遵守这些规则,Turn 快照不包含其他任务。
Thread 过滤只保留该 Thread 的资源视图;Turn/Run 过滤不包含资源生命周期事件或资源投影。
过滤快照中的 projection.session 为 null,需要父资源当前状态时显式调用 view()。
Run 快照仍带所属 Turn 的逻辑状态,投影只保留当前关联该 Run 的记录。
limit 限制扫描的 Session 事件数:空的过滤页仍可能推进游标,应比较 cursor 与
highWatermark 判断本次是否追平。过滤流的 sequence 可以不连续;
EventProjection(session, filter) 接受这种稀疏流,完整 Session 投影仍检查序号连续性。
每个订阅的回调串行运行,使用独立于 Agent 执行的观察线程。重复提示合并,
轮询补偿丢失提示;回调或后端失败后按 Runtime 的 pollInterval 重试。
只有回调成功返回才推进游标。错误交给 Runtime 的 onError,
订阅提供 cursor()、lastFailure() 和 isClosed();
错误报告失败不会阻断重试。observerParallelism 配置共享回调容量,默认为 2。
容量耗尽时慢回调可能延迟其他观察者,但不占用 Agent 执行线程。
关闭单个订阅只解除该监听,不影响其他观察者,也不取消 Turn。
关闭 Runtime 会关闭全部订阅;已经执行中的回调可能完成,Runtime 关闭会协作式中断观察线程。
订阅立即启动,独立于启动 Agent 调度的 Runtime start()。
跨消费进程重启恢复时,在回调中将事件/快照游标与自己的投影一起保存。
交付语义为至少一次,外部副作用需要幂等。
需要自行管理轮询或执行器的高级宿主仍可使用 SessionEventClient。
turn.subscribeEvents(options, listener) 会在同一串行观察线程交付持久事件和显式订阅的 delta。
只有 Committed 会推进 subscription.cursor();delta 断线后不重试,也不能用于更新持久
投影。客户端即使遗漏部分或全部预览,最终 message.completed 仍是权威结果。
通过 runtime.getSession(session) 重新取得会话句柄,再使用
mainThread().getTurn(turnId) 获取主 Thread 的任务;查询、历史和事件均不认领或执行任务。
快照包含 Turn 视图和公共投影,私有原生恢复点保留在后端内部。
HTTP 扩展提供 Backend/Executor 客户端与可挂载 handler。
SessionEventHandler 复用同一套重放/确认客户端;
SseEventWriter(OutputStream) 可用于其他 Java HTTP 服务器。
写入/flush 失败不确认该事件。初始游标过期返回 HTTP 410,
流中途过期发送 resync,客户端重新获取同一过滤范围的快照后连接。
SSE 查询支持 threadId/turnId/runId,Last-Event-ID 优先于 cursor 参数。过滤后的
扫描位置通过 cursor 控制帧续传。durable 帧是可重放公共事件,可交给 EventProjection;
live 帧只携带预览且刻意不设置 SSE id,因此 Last-Event-ID 不会确认或请求它。
认证、操作/Session 授权和阻塞 I/O 线程由宿主提供。
租约、原生恢复点、结果接收和全局发现属于特权操作。
排查任务为什么没有继续
先查看turn.status()、turn.pendingInteractions() 和 Thread 的有序 listTurns 结果。
通过 turn.listRuns 和 turn.listSteps 查看调用明细;Run 停止并不一定代表 Turn 已结束。
恢复边界
Thread 固定 Harness 和原生状态版本,重启需要匹配的工厂、工具绑定与执行环境。 共享后端不意味着可以切换原生内核。当前提供 Java 实现;其他语言接入需要自己的适配器和协议一致性检查。 恢复需要活动 Runtime 和可访问的存储。关闭 Runtime 不会取消持久任务, 也不会关闭调用方拥有的后端和 Provider。全部宿主停止后,需要外部进程管理器重新启动宿主。 参考后端通过 Session 级事务保存分段记录。不同 Thread 可以并行执行,但短暂的状态提交 仍共享这一事务范围。历史量较大或 Session 较繁忙时,需要测量事务竞争和存储增长。 恢复不能重建已删除的工作区,也不能确认不可查询的外部效果。应在实际部署环境中验证 Provider 的查询、取消能力和存储持久性。Java 平台接入能力
Agent 工厂返回全新的ReActAgent.Builder 或 HarnessAgent.Builder;adapter 自动注入本次调用的 Thread Journal 后构建 Agent。公开的 Binding 只提供定义、RuntimeContext 和 Durable workspace,不向应用暴露日志存储。adapter 同时在 RuntimeContext 中绑定 AgentExecutionHost,统一执行边界检查、输入持久化应用和 finalization 恢复,不再要求安装额外输入 middleware。工具映射会将对应原生工具转为外部执行工具,保留原生权限校验。使用 DelegationExecutor 将已声明的子 Agent 工具路由到独立子 Turn;目标解析器必须按父 Thread 冻结的定义版本解析配置。
同一 Harness 名称可注册多个定义版本,或使用 resolver 按 ThreadSpec 动态解析。已有 Thread 始终使用其固定版本。Runtime 通过就绪索引发现工作,parallelism 限制接纳;drain 停止接纳并等待当前执行,超时停止本宿主后由新宿主恢复。
状态采用分段事务记录:租约、原生日志增量、检查点、工具意图和输入回执在同一 Session 事务中提交。CommitPolicy 是事务内的宿主授权扩展;平台可基于同一事务中的策略记录拒绝提交。不要在该回调内调用外部服务。
LocalBackend 同时装配 SQLite、文件对象存储和本地通知。生产环境可向 PostgreSQL Backend 提供 ObjectStore。agentscope-extensions-durable-objects 提供 S3/MinIO 与 OSS,采用不可变、按会话隔离的摘要引用并在读取时校验。SDK 依赖为 optional,应用显式选择所用 SDK;客户端由宿主管理。大检查点、原生日志和私有资源自动转为对象引用。HTTP 支持有界二进制对象读写;MQ 的大消息可配置两侧共享的对象存储,原始内容不进入公共事件。
binding.workspace() 实现 Harness BaseStore,通过 Backend 执行带版本的 GET/SEARCH/PUT/CAS/DELETE;Brain 不需要数据库凭据。可使用 new RemoteFilesystem(binding.workspace(), List.of("workspace")) 接入 Harness。该存储已按 Session 隔离,内部命名空间使用稳定相对名称。外部共享文件或 Sandbox 内容由相应挂载负责,不自动成为私有检查点的一部分。
AgentScopeCheckpoints.restore 仅允许恢复已结束执行的原生 baseline,同时复制该检查点的私有资源;fork 重绑定原生身份,要求相同 namespace、Harness 和定义/状态版本。目标必须空闲,使用位置校验防止覆盖并发写入。
模型完成结果在状态应用前持久化。恢复时可重放已完成结果;辅助模型调用、Finalization 阶段和该阶段的工作区 IO 都有稳定回执。工具意图/结果应用继续沿原生 ThreadJournal 边界恢复,不保证任意自定义回调或不可查询外部副作用恰好一次。
Hands 使用命令、控制、执行事件三条独立通道。每个执行事件有递增序号;接收方只推进连续前缀,重复不重复应用,缺口通过查询重放。输出按字节游标提交,先封闭输出再发布终态;裁剪用显式 gap 表示。SSE 通知仍只是读取持久事件的提示,与工具执行链路分离。
BatchExecutor 持久化子调用清单与稳定身份,恢复时查询原操作,取消后不接纳新子调用。agentscope-extensions-durable-mcp 的 McpExecutor 保留 MCP 内容块和结构化结果;普通 MCP 协议没有可靠的操作查询/取消确认,因此故障时返回 UNKNOWN,由 Worker 账本防止盲目重放。
当前源码树仅提供 Java 实现,没有 TypeScript/Pi 客户端。语言中立协议不代表已经实现其他语言客户端。
UNKNOWN 的受控核实
ExecutionStore 为运维宿主提供核实入口。先查询 reconciliation(request, requestId),
无回执时通过 claim(request, owner, lease) 认领原执行,再提交
ExecutionReconciliation(requestId, verifiedTerminalResult, evidenceRef)。
reconcile(work, command) 仅接受 UNKNOWN,原子保存审计回执并写入原 outbox;
缺失输出以显式 gap 封口,Brain 仍通过原结果链路继续。核实失败或取消请求不等于已经停止。
宿主负责授权和外部证据核实;此入口没有自动开放为 HTTP/MQ 命令或模型工具。
通用协议拒绝重复 JSON 字段、多根值正文和规范化后会丢精度的数字,精确大数用字符串。
当前 Java Session 存储格式为 8;开发版不提供旧格式自动迁移。
Brain 与 Hands 共用冻结工具
AgentScope Adapter 与 Worker 使用同一份ToolBinding 目录。绑定固定 schema、
定义版本、路由和私有目标,调用携带其摘要。目标保存凭据引用;宿主解析密钥并授权 Worker 边界。
原生工具通过 AgentScopeToolExecutor.binding(tool, version, route, target) 生成绑定,
交给 AgentScopeAdapter.forReAct(factory, bindings) 或 forHarness;
WorkerRuntime 注册 new AgentScopeToolExecutor(toolkit, bindings, timeout)。
Brain 工厂可使用空 Toolkit,由 Adapter 注册 schema。Hands 保留 Toolkit 参数校验、
预设参数和默认上下文;Executor 使用期间保持工具注册不变。Brain 权限和宿主 Worker 授权均须生效。
原生内容、元数据和显式结果状态在交接中保留;普通已完成返回值遵循 AgentScope 终态判定。
调用异常、空返回或挂起仍表示结果未确认。ToolCallParam.propagateErrors(true) 使 Toolkit
和注解工具透传异常,不启用重试,默认行为不变。普通 Java 方法不能跨 Worker 进程存活,
也不会自动获得原操作查询、确认取消或持久进度流。
BatchExecutor 支持 ORDERED 与有界 PARALLEL,受理时固定模式、并发上限及子调用身份;
替换实例继续原批次,可回放输出关联原子调用及执行。宿主逐项授权冻结清单。
MCP 连接与显式恢复
McpConnections 管理有界连接池,初始化中和退休中的连接也计入容量。发现工具或执行调用
期间持有 Lease;配置轮换后,旧连接等待最后一个使用者释放再关闭,空闲连接自动淘汰。
关闭池后拒绝新借用,已有 Lease 可完成调用。连接身份按租户和服务器隔离,凭据解析由宿主负责。
通过 McpExecutor.binding(alias, version, route, server, tool, catalog) 冻结发现结果,
使用 new McpExecutor(binding, resolver, timeout)。解析器可以返回
McpExecutor.Target.leased(pool.borrow(identity, resolvedConfiguration), remoteTool, arguments)。
连接配置支持 streamable_http 或 sse、headers/queryParams、ISO-8601 格式的 timeout
及 initializationTimeout,以及 propagateMeta。不要在绑定中持久化解析后的凭据。
普通 MCP 不保证原操作查询或取消确认。远端工具可通过
_meta["io.agentscope.durable/execution"] 显式声明 Durable 扩展:version 为 1,
queryTool、可选 cancelTool 及明确的 idempotentStart。查询工具须声明 readOnlyHint。
发现时冻结主工具与控制工具描述。调用携带稳定执行身份及请求摘要,控制响应须匹配身份
并通过共用 JSON Schema 校验;无效观察保持 UNKNOWN。取消受理与确认停止分别表达。
主工具发生变化后,原操作仍可使用未变化的控制描述。完整协议位于源码中的
agentscope-durable/protocol/README.md。
持久 Sandbox 绑定
agentscope-extensions-durable-sandbox 的可选组件 SandboxBindings 管理现有
SandboxClient / Sandbox。绑定包含 namespace、稳定资源标识、定义版本和冻结配置引用。
同一 namespace 的 Session 可经宿主授权共享绑定;调用仍保留独立执行身份和冻结 ToolBinding 摘要。
records 是宿主的 RecordStore;shellBinding 是私有目标包含 operation: "sandbox.exec"
的冻结工具。其他操作包括 fs.upload、fs.download 和 artifact.publish。
Executor 可直接注册,也可接入 WorkerRuntime/RocketMqWorker。resolver 只解析配置、凭据并构造
Target,不能创建资源。云供应商可使用 Target.using(client, workspace, options, physicalIdentity),
按需提供 withKeepAlive 和 withDestroy。resume 必须连接原实例且不能重建工作区;
destroy 必须幂等并确认资源已不存在。
创建身份在调用供应商前持久化,回包丢失不会自动新建替代环境。初始化失败仍会保存已知连接状态;
重连同时校验物理身份和工作区标记。创建结果无法确认时,需要宿主授权的 reconcileCreation,
提交原状态及独立核实的证据;该操作不会创建资源或重写身份标记。
protect(encrypt, decrypt) 用于连接状态加密,默认直接保存。绑定配置中不要放入明文凭据。
requestDeletion(binding) 先持久化删除意图并拒绝新启动。清理等待宿主确认
所有共享绑定的 Session 和执行均已收尾,再持有租约进行幂等删除和重试。
默认屏障拒绝删除;等待收尾期间仍可查询与取消原操作。close() 仅停止维护并清空连接缓存,
不会销毁资源。保活待办持久化,因此缓存淘汰或宿主替换不会遗弃已有环境。
LocalSandboxClient 是显式选择的开发适配,需要 POSIX shell 和 Python 3,命令具有宿主权限。
内联 FileEntry/DirEntry 和环境变量只初始化一次,副本进程必须共享原文件系统。
它拒绝快照恢复、不支持的条目以及重建已丢失目录。生产隔离与独立资源生命周期应使用云 Sandbox。
Worker 的 drain 停止受理并等待当前控制调用结束,不等待所有远程工具结束。
RocketMqWorker 还会等待已受理的 MQ 回调和待发布事件。宿主应先 drain,再 close,最后关闭
借用的 SDK 客户端和存储。强制 close 中断自有线程、释放排队容量,并最多等待五秒,不会取消
外部副作用。宿主回调需要响应中断或使用有界 IO;回调关闭自身宿主时不能等待自身线程。
运行示例
Java 示例位于agentscope-examples/durable,归属 Examples Maven 聚合模块。
Durable 的 durable-standalone profile 也显式纳入这些示例,用于独立构建和检查。
每个场景的启动器位于 agentscope-examples/durable/{integrated|brain-hands}/{scenario}/run.sh。
integrated 包含 quickstart、http-server 和 tool-in-sandbox;brain-hands 包含 echo-tool 与 tool-in-sandbox。
从仓库根目录运行:
start、resume 两个 JVM,每个 JVM 内同时装配 Brain 和 Hands。
每个 brain-hands 模块打包 ${artifact}-brain.jar 与 ${artifact}-hands.jar,分别使用独立 Java main 和 runner,
Java 入口不接受 role 参数,也不使用角色枚举。启动器接受 <brain|hands> <endpoint> [data-directory] 来选择 JAR,
不支持 worker 别名;先启动 Hands,再在另一终端启动 Brain。
需要 Java 17+ 与 Maven。正常检查使用 make -C agentscope-durable check;隔离依赖检查使用
make -C agentscope-durable independent,另需 Python 3。真实队列执行需要 RocketMQ 5 服务及
Topic/消费组配置;--compile-only 只打包应用,不连接 Broker 或执行工具。
Durable 子委派
子任务需要跨重启恢复、独立观察或接受持续上下文中的后续请求时,使用持久委派。 普通 Core/Harness 子 Agent 调用不会自动创建 Durable 子 Thread; 将声明的工具路由到DelegationExecutor,才能获得持久子任务调度和结果交接。
DelegationExecutor 默认按父 executionId 创建同 Session 的独立子 Thread。需要连续复用子 Agent
上下文时,用目标解析器显式指定已有 Thread;其 ThreadBinding 必须一致:
TurnView.parent 记录父 Thread/Turn/executionId 与原始 Run/Step;
父工具投影 data.child、内部 ToolExecution.delegation 指向对应子 Thread/Turn。
父任务暂停不会暂停子任务;父任务取消只取消映射中的子 Turn,不影响复用 Thread 中的其他任务。
取消先于受理时只保存取消记录,后续迟到的 start 不会创建子任务。事务内拒绝跨 Session、父 Thread
自身及未完成委派形成的 Thread 依赖环。turn.delegated、tool.delegated 和
tool.delegation.cancel_requested 是统一公开事件;它们不替代真实子任务结果及父任务的结果应用。