a13nDocs

状态与恢复

保存 HarnessState,以跨进程继续、fork 或恢复 Thread。

HarnessState 是单个独立推进 Thread 的可移植续接值。它保留模型历史、带版本的 Capability 命名空间和可选的可移植 Environment 数据,有意不保留权限或 Host 生命周期状态。

状态内容

状态结构包含:

  • schema_version;
  • 稳定 thread_id;
  • 公共 Pydantic AI 消息历史;
  • 由 Capability ID 管理、带版本的独立 JSON 值;
  • 为已选择兼容挂载提供的可选 provider 专用可移植 Environment 状态。

不包含:

  • Model、Toolset、Capability、插件、可调用对象或活动客户端;
  • 身份验证、策略、授权、凭据或审批;
  • 期望 Environment 挂载定义、运行时修改权限、provider 会话或启动状态;
  • Host 执行记录、尝试、租约、队列或终态提交;
  • 持久异步子级、长期记忆、交付、计费或统计状态。

状态恢复数据,不恢复权限。

继续 Thread

已完成、挂起或安全失败的结果可能携带候选状态:

Python
from a13n_harness import HarnessState

initial_state = HarnessState.new(thread_id="thr_productthread1")
first = await executable.run(
    "Draft the plan",
    bindings=fresh_bindings(),
    previous_state=initial_state,
)

if first.state is None:
    raise RuntimeError("No continuation state is available")

second = await executable.run(
    "Review it",
    bindings=fresh_bindings(),
    previous_state=first.state,
)

first.thread_id == second.thread_id,而 first.run_id != second.run_id。Host 提供新的绑定,新 Run 创建新的上下文、EnvironmentRuntime、Run 绑定插件和用量累加器。

序列化状态

HarnessState 是冻结的 Pydantic 模型:

Python
from a13n_harness import HarnessState

payload = state.model_dump_json()
restored = HarnessState.model_validate_json(payload)

持久化完整、经过验证的状态结构,不保存私有编码字段或原始模型增量。Host 应在 Harness 载荷之外,将它与自身定义版本、provider 生命周期状态、检查点来源及 Host 验证元数据关联。

Fork Thread

将续接数据复制为独立推进的历史时,用 fork()。省略 ID 让 Harness 生成,或传入不同的 Host 自选 ID:

Python
branch_state = state.fork(thread_id="thr_productbranch1")

assert branch_state.thread_id != state.thread_id
assert branch_state.message_history == state.message_history

不要手动编辑序列化状态。显式初始身份用 HarnessState.new(thread_id=...);改变身份并保留可移植续接数据用 state.fork(thread_id=...)。fork 会按设计清除可移植 Environment 状态。Harness 用 ID 关联一份历史和模型会话亲和性,不把它视为权限。

流式期间导出

活动流可在最新安全公共边界导出候选状态:

Python
async with executable.stream("Work", bindings=fresh_bindings()) as stream:
    async for item in stream:
        checkpoint_candidate = await stream.export_state()

export_state() 不持久化内容,也不暴露任意 token 增量。Host 决定候选是否完整、当前有效且可安全选择。终态结果仍是最简单的完整检查点边界。

恢复未回答工具调用

检查点可能包含尚未记录结果的工具调用。崩溃后,即使结果缺失,操作也可能已运行。在将调用视为未知前,先提供 Host 保留的已接受延迟输入。外部结果、失败和显式拒绝在所有恢复模式下仍有效。其余未解决调用由 run() 和 stream() 的单 Run tool_recovery 决定是否重新执行:

模式恢复后未回答的调用
"declared"(默认)执行显式声明可恢复重试的工具;其他调用填入未知结果。
"never"对每个未回答调用填入未知结果 ToolReturnPart。
"always"执行当前工具集合中仍可用的每个未回答调用。

已记录工具返回和参数重试结果仍有效。恢复保留原调用 ID 和部分结果,保存进度边界中断时也如此。不再可用工具得到未知结果。已有未知结果返回本身已是结果,即使 "always" 也不重新打开。新生成调用正常执行。

在来源声明可重试工具

用 recovery_retryable() 装饰器,或包装已有函数/原生 Tool:

Python
from a13n_harness.tools import recovery_retryable
from pydantic_ai import Tool
from pydantic_ai.capabilities import Capability

@recovery_retryable
def lookup_record(record_id: str) -> str:
    return read_record(record_id)

catalog = Capability(
    id="acme.catalog",
    tools=[lookup_record, recovery_retryable(Tool(search_records, name="search"))],
)

辅助函数返回原生 Tool,只添加元数据,不包装执行;已有 Tool 会被复制,保留设置和其他元数据。实例方法可在构造 Capability 时用 recovery_retryable(self.lookup_record)。插件可如此声明各工具,无须 Host 知道工具名。

动态 Toolset 构建当前工具时也可使用此函数。已有 ToolDefinition 元数据的集成,将 a13n_harness.tools 的 RECOVERY_RETRY_SAFE_METADATA_KEY 设为布尔 True。也支持原生 SetToolMetadata。声明从新准备的定义读取,不从已保存消息读取。

声明表示即使之前结果未知,重复操作仍可接受。它与提供方派发重试和 HarnessToolMetadata.idempotency 独立:仅有 provider 幂等键不能证明恢复调用会复用原上游操作。

按策略恢复

Python
result = await executable.run(
    bindings=fresh_bindings(),
    previous_state=checkpoint,
    tool_recovery="declared",  # The default; use "never" to disable all replay.
)

也可提供新输入。原生 Pydantic AI 续接在下次模型请求前处理保留调用和部分结果。恢复使用原生 ToolApproved 作为重放所选调用的程序许可。仍执行原生参数验证;当前需审批或外部执行的工具仍挂起;受管理调用按新策略和资源检查。恢复重放许可不满足当前审批要求。新的 DeferredToolResume 独立于 tool_recovery 使用提供的审批/结果批次;中断恢复使用 recovery=True 和当前恢复策略。提供方挂起响应保留原生续接路径。

此选项属于 Run,不属于序列化状态或模型重试策略。不保证恰好一次副作用,也不阻止后续模型请求再次调用。

在默认的 "declared" 模式下,未标记工具收到未知结果。

结构化挂起

原生延迟工具和审批会以 status="suspended" 结束根逻辑 Run。结果包含:

  • state:已接受历史和 Capability 数据;
  • deferred:确切的原生待处理请求结构;
  • suspend_reason="deferred"。

Host 在 Run 关闭后处理外部交互,再启动新 Run:

Python
from a13n_harness import DeferredToolResume

first = await executable.run(
    "Ask for confirmation",
    bindings=fresh_bindings(),
)

if first.status != "suspended" or first.state is None or first.deferred is None:
    raise RuntimeError("Expected a suspended run")

results = first.deferred.build_results(approve_all=True)

second = await executable.run(
    bindings=fresh_bindings(),
    previous_state=first.state,
    deferred_resume=DeferredToolResume(first.deferred, results),
)

确切结果构造 API 取决于延迟项是审批、外部调用还是结构化用户问题。对全部待处理项各回答一次,并保留类别。客户端工具示例展示完整离线声明、挂起、关联外部结果和恢复。受管理工具策略说明审批元数据和新授权。

恢复会验证:

  • 存在之前状态;
  • 延迟请求与提供结果精确关联;
  • 待处理调用符合合法消息历史和当前工具类别;
  • 覆盖参数再次通过验证;
  • 新策略和提供方约束仍允许派发。

之前审批不绕过当前策略。Host 可构造合法历史,并提供原生 bool、ToolApproved 或 ToolDenied,无须私有 Harness 元数据。不要求历史参数或 schema 的指纹、资源版本或受管理身份声明证明。保持待处理请求与历史一致;替换参数使用 ToolApproved.override_args。当前 schema 验证、审查、策略和 Environment 约束仍适用。审批不预留底层目标,也不授权未知结果后的自动重放。

当前绑定支持延迟工具时,此原生流程也适用于 Host 管理的子级。内置内联子级关闭延迟工具,只支持普通提示续接。没有延迟生命周期的 Host 显式设置 deferred_tools_supported=False;仅有父子血缘不会关闭交互。

恢复中断的延迟续接

外部结果可能在原生历史记录前就已接受。例如批次同时包含外部结果和本地批准动作,执行可能在动作运行前停在检查点。中间 HarnessState 本身不是已接受批次的完整记录。Harness 不在 Capability 状态中保留第二份延迟结果表示。

Host 将关联的原生 DeferredToolRequests 和 DeferredToolResults 与所选检查点一起保留,或放入已有持久输入记录,直到历史纳入它们。发布新检查点时,accepted.remaining(checkpoint.message_history) 返回未纳入批次;已纳入或被后续响应替代时返回 None。按 Host 普通发布规则将此值与检查点持久化;收到输入或仅调用 export_state() 不会保存。

从中断的恢复 Run 继续时,提供保留批次和 recovery=True:

Python
from a13n_harness import DeferredToolResume

# Loaded from the Host's checkpoint and accepted-input storage.
recovery_input = (
    DeferredToolResume(accepted.requests, accepted.results, recovery=True)
    if accepted is not None
    else None
)
result = await executable.run(
    bindings=fresh_bindings(),
    previous_state=checkpoint,
    deferred_resume=recovery_input,
    tool_recovery="declared",
)

恢复过滤历史已有结果,验证剩余关联和当前工具集合;即使 tool_recovery="never",也消费保留外部事实和显式拒绝。绝不复用正向审批作为重放许可。未解决本地任务遵循当前恢复策略,适用时需新审批。Host 管理的子检查点也如此;这不保证恰好一次副作用,也不能恢复 Host 未保存输入。

安全的失败状态候选

Harness 能规范化中断历史时,失败结果可包含安全候选状态。例如保留完整可见文本和已完成的思考内容,排除未完成思考片段。

候选不是持久恢复决策。选择前,Host 必须判断外部修改是否可能已派发但无权威结果。重放不确定操作前,应核对提供方状态,或复用操作专用幂等契约。

RunCleanupError.outcome 也只是结果不确定的候选,因为未完成正常终态交付。

Capability 状态

有状态 Capability 使用稳定命名空间和确切版本:

Python
from pydantic import BaseModel


class CounterState(BaseModel):
    value: int


current = await context.state.read(
    "acme.counter",
    CounterState,
    version="1",
)
await context.state.write(
    "acme.counter",
    CounterState(value=(current.value if current else 0) + 1),
    version="1",
)

未知命名空间可在 Run 中保持不透明。只有所属 Capability 解释载荷和版本。不要在 Capability 命名空间存储凭据、客户端、锁或 EnvironmentState。

Run 本地 shell 观测

DynamicEnvironmentCapability 按各挂载实际动作组合 shell 执行、shell_info 发现/检查、显式偏移 shell_wait 和支持的 stdin/控制工具。shell 进程引用属于一个 Harness Run,不属于持久进程服务。查询不重置输出;原生完成不代表输出已完整捕获。

Run 关闭释放观测,不会统一终止全部进程。根据 Provider 状态恢复的新 adapter 可能发现后端保留的命令;新 Run 分配新引用,不能使用历史旧引用。进程引用、缓冲、watcher 和输出游标不进入 Harness Capability 状态。Provider 状态保存原生恢复证据;Host 负责目标生命周期和状态发布。

用法和 Provider 专用限制见 Environment 工具。不要求 Host 进程管理器、进程数据库或持久 Run 后唤醒集成。

Host 检查点

持久 Host 应将这些事实分开管理:

事实负责方
可移植 Thread 续接HarnessState 候选
所选检查点和来源Host
定义版本和产物锁定Host
当前身份、策略和凭据Host 重新构造
期望挂载定义和权威 EnvironmentStateHost/Provider 集成
执行尝试、generation、fence 和租约Host
持久完成与输出交付Host/产品

完整权限边界见嵌入 Host。可运行的 Agent Application 示例展示更简单的单应用场景:流式执行一轮、提交返回状态、重建应用,然后继续同一 Thread。

本页目录