缓存时间:
2026/10/21 18:20
# 让每一个 Harness 都具备持久性
Source: https://vercel.com/blog/making-every-harness-durable
开发者们已经习惯于通过 Pi、Claude Code 等 harness 在本地运行编码 agent。它们是日常生成代码的可靠工具,但要让 agent 全天候运行,就需要你的电脑一直开着、不休眠——于是就有了那些"合上笔记本盖子"的表情包,以及 Mac mini 需求量的暴涨。
Cormac (https://x.com/cormachayden_/status/2050773588521992642)https://x.com/cormachayden_/status/2050773588521992642
软件工程师使用 agent 前 vs 使用 agent 后
Harness 现在可以在云端运行,所以你可以放心合上盖子了。
#### 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#running-harnesses-in-the-cloud)在云端运行 Harness
Harness 的工作负载正在向云端迁移。独立开发者正在部署始终在线的编码 agent 工作流,像 OpenWork 和 HanaAgent 这样的项目将开源 harness 用作其应用的引擎,工程师们也在打造自己的 harness 产品。
这些 harness 是为长驻进程设计的,会将会话保存在内存中,agent 任务通常运行几分钟到几小时。在本地,harness 可以直接、持续地访问同一磁盘上的工作区和会话历史,因此恢复被中断的工作只需一个简单的提示词。
在云端,你可以用一台 VM 和持久化存储来复制这种配置,但 VM 不会自动记录哪些模型调用和工具调用已经完成,也不会在崩溃后恢复被中断的轮次。一个失败的轮次或网络错误就意味着工作丢失。
另外,并非通过 harness 执行的每个 agent 任务都需要沙箱。数据库查询和 API 调用可以在函数中安全运行,这也意味着所需的凭据永远不必进入沙箱。
### 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#making-every-harness-durable)让每一个 Harness 都具备持久性
我们构建了 eve (https://eve.dev/) 和 fx (https://fx.sh/),它们开箱即用地在云端持久运行。今天,我们推出 `durable-harness`,它将这种持久性扩展到任何 harness,包括 Pi (https://github.com/earendil-works/pi)、OpenCode (https://opencode.ai/)、Codex (https://github.com/openai/codex) 和 Claude Code (https://docs.claude.com/en/docs/claude-code/overview)。它使用与 Workflow SDK (https://workflow-sdk.dev/) 相同的机制,能够在崩溃和函数超时后存活下来。
有了 `durable-harness`,你可以:
- 在 Vercel 上持久运行任何这些 harness
- 用自己的 Postgres 实例自托管 harness
- 让你本地的 harness 工作流具备持久性
- 构建属于你自己的持久 harness
## 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#sessions-that-survive-a-crash-mid-turn)能在轮次中途崩溃中幸存的会话
以一个编码 agent 为现有应用添加定价页面为例。agent 读取现有页面,编写新页面,发起一个 pull request,然后等待 CI 完成后再报告结果。
如果它的函数在 CI 还在运行时超时了,会发生什么?
在下面的演示中,终止函数或断开连接,比较两个 agent 如何恢复以及哪些工作被重复执行。
没有持久性时,agent 会停止工作,直到手动重启,已完成的工作也会被不必要地重复。
有了 `durable-harness`,一个新的函数会从日志中读取已完成的结果并恢复该轮次。如果浏览器断开连接,agent 会继续运行,重新连接时会从日志中追赶进度。
## 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#logs-and-queues)日志与队列
`durable-harness` 是我们开源产品组合中最新的包。它是一组适配器和工具,让你已经在使用的 harness 将会话保存在 World(https://workflow-sdk.dev/worlds)中——Workflow SDK 的持久化后端。
它将每一轮的每个步骤写入持久日志。在模型调用或工具调用运行之前,它记录一个意图,当调用完成时,它记录结果。该轮次本身由一条队列消息兜底,因此如果它的函数挂掉,队列会将该轮次交给一个新的函数,该函数读取日志并从中断处继续。
崩溃之后,日志会告诉你什么完成了、什么在运行中、什么从未开始:
- 已完成的调用不会再次运行,因为它们的结果是从日志中读取的,所以你永远不会为已完成的模型调用的 token 重复付费。
- 模型会记住之前的调用,而不是重复执行它们。
- 被标记为 `replay: 'safe'` 的中断工具会重新运行。任何其他工具会以"可能已执行"的状态返回给模型,由模型决定是否进行检查。
那些保存自己的会话文件的 harness,比如 Claude Code 的 transcript、Codex 的 rollout 和 OpenCode 的 event store,它们的每条记录都会被镜像到日志中,这样一个新的函数或 Sandbox 就能从中重建这些文件。
## 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#pick-your-harness)选择你的 Harness
fx (https://fx.sh/) 和 eve (https://eve.dev/) 本身就具备持久性。对于其他 harness,`durable-harness` 从外部为其添加持久性,无需修改 harness 本身。
每个 harness 的完整设置指南,请查看 文档(https://vercel.com/docs/agent-harnesses/harnesses)。
## 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#runs-everywhere)随处运行
持久化 agent 需要每个步骤的日志、在进程死亡时恢复轮次的方法,以及一个供观察者使用的流。
Workflow SDK 将这三者封装在一个接口背后:World(https://workflow-sdk.dev/worlds)。
```
import type { Queue, Storage, Streamer } from '@workflow/world'
interface World extends Storage, Queue, Streamer {}
```
一个 World 就是同一个接口背后的日志、兜底和流。
`durable-harness` 写入这个接口,所以你可以将同一个 agent 从一个 World 迁移到另一个 World,而无需修改其代码:
- 在 Vercel 上,它使用 Vercel World(https://workflow-sdk.dev/worlds/vercel)。
- 在开发环境中,它使用你机器上的文件(https://workflow-sdk.dev/worlds/local)。
- 在你自己的服务器上,它使用 Postgres(https://workflow-sdk.dev/worlds/postgres)、MongoDB、Redis 或 Turso。
两个变量即可让任何使用 `durable-harness` 的 agent 迁移到 Postgres。
```
WORKFLOW_TARGET_WORLD="@workflow/world-postgres"
WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
```
将 durable-harness 重定向到使用 Postgres World 以获取持久性。
如果需要更多控制,比如设置命名空间或在服务器启动时恢复会话,可以在代码中创建 World 并将其传递给你的 agent:
更多详情请查看 自托管文档(https://vercel.com/docs/agent-harnesses/self-hosting)。
## 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#see-every-session-in-agent-runs)在 Agent Runs 中查看每个会话
Vercel 上的 Agent Runs(https://vercel.com/docs/agent-harnesses/observability)会显示每个会话的追踪信息,包括每次模型调用和工具调用。崩溃后恢复的轮次会出现在同一个追踪中,因此你可以准确看到轮次在哪里停止以及在哪里恢复。
一个 Pi 会话在 Agent Runs 标签页中,其中有一次模型调用被中断并重试。
Agent 只拥有你赋予它的工具,它们运行在你的函数中。每个工具都会说明如果崩溃中断了它应该发生什么。标记为 `'safe'` 的工具(比如一次查询)可以再次运行。标记为 `'never'` 的工具(比如一次退款)永远不会运行两次:模型会被告知该调用可能已经发生,因此它可以在再次尝试之前先进行检查。
下面是上面演示中的编码 agent 可能如何定义其工具的示例:
- 读取文件或检查 CI 状态的工具被标记为 `'safe'`,因此可以自动重新运行。
- 发布 PR 评论的工具被标记为 `'never'`,以避免评论重复发布。
以下示例以一个处理退款的 agent 为例,为每种策略定义了一个工具:
```
import { pi } from 'durable-harness/pi';
import type { HostTool } from 'durable-harness';
import { getOrder, refundOrder } from './orders'; // 你自己的代码
const tools: HostTool[] = [
{
name: 'get_order',
description: 'Looks up an order',
replay: 'safe', // 崩溃后可以安全地重新运行
inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
execute: async ({ id }) => JSON.stringify(await getOrder(id)),
},
{
name: 'refund_order',
description: 'Refunds an order',
replay: 'never', // 永远不会运行两次
inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
execute: async ({ id }) => JSON.stringify(await refundOrder(id)),
},
];
export const agent = pi({ model: 'anthropic/claude-sonnet-5.5', tools });
```
一次查询可以在崩溃后重新运行,而一次退款永远不会运行两次。
关于每个 harness 的内置工具,比如它的 shell 和文件编辑,请继续往下读。
## 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#files)文件
当 harness 需要 shell 和文件时,直接在共享的无服务器函数主机上运行并不安全。多个会话可能共享同一个函数,而它的文件系统在调用之间不保证持久。
为了解决这个问题,`durable-harness` 提供了几个选项:
- `virtual()` 将文件保存在会话的日志中,配备一个与 bash 兼容的 shell,它无法访问你函数的文件或网络。它适合短任务和小型工作区,无需启动整个沙箱。
- `virtual({ durable: false })` 将文件保存在内存中用于临时工作,并在每个轮次结束时清除它们。
- `sandbox()` 为会话提供其专属的 Vercel Sandbox(https://vercel.com/docs/sandbox),`/workspace` 位于一个 Drive(https://vercel.com/kb/guide/vercel-drives)上,该 Drive 比任何沙箱和任何函数都活得更久。它适合真实项目和长时间运行的工作。
在文档(https://vercel.com/docs/agent-harnesses/tools#files)中可以并排比较这三种方式。
每个 harness 的内置工具都运行在你选择的工作区上。对于 Pi,`CodingTools` 会安装 Pi 自己的 `read`、`write`、`edit` 和 `bash`,而 `virtualEnv()` 或 `sandboxEnv()` 为它们提供一个工作区:
```
import { BACKGROUND_CONTEXT as ctx } from '@earendil-works/chord/context';
import { createRegistry, Harness, type Storage } from '@earendil-works/pi-durable';
import { CodingTools, gatewayModels, resumeWith, virtualEnv } from 'durable-harness/pi-durable';
const models = gatewayModels();
const registry = createRegistry();
registry.install(CodingTools); // Pi 的 read、write、edit 和 bash
// 文件保存在会话的日志中,或使用 sandboxEnv() 获取会话专属的 Vercel Sandbox
const env = virtualEnv();
export const open = resumeWith((storage: Storage) => Harness.open(storage, { models, registry, env }, ctx));
```
在虚拟工作区或 Vercel Sandbox 中为 Pi 提供其编码工具。
对于 OpenCode,将工作区作为 `workspace` 传入;对于 fx,将其传给 `fxTools()`。Claude Code 和 Codex 始终在 Vercel Sandbox 中运行,因为它们需要真实的 shell 和文件系统,不适合多租户使用。一旦你通过 `builtins: true` 启用它们,它们的内置工具就可以在会话的 Drive 上工作。每个 harness 指南(https://vercel.com/docs/agent-harnesses/harnesses)都列出了它的内置工具。
## 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#documents)文档
有些状态属于会话但不属于对话本身,比如待办清单、计划或 agent 填写的表单。文档是带类型的 JSON,存在于会话的日志中,因此它能在崩溃后幸存并随会话一起迁移。
工具可以通过 `io.docs` 编辑文档,每次编辑都会与工具的结果一起提交到日志中,因此文档的持久状态与工具调用本身保持一致:
```
import { defineDoc, type HostTool } from 'durable-harness';
export const Todos = defineDoc<{ items: string[] }>({
kind: 'app.todos',
version: 1,
initial: () => ({ items: [] }),
});
export const addTodo: HostTool<{ item: string }> = {
name: 'add_todo',
description: 'Adds a todo',
replay: 'safe',
inputSchema: { type: 'object', properties: { item: { type: 'string' } }, required: ['item'] },
async execute({ item }, io) {
const todos = io.docs!.update(Todos, (draft) => {
draft.items.push(item);
});
return `${todos.items.length} todos`;
},
};
```
定义了一个待办清单文档和一个向其中添加条目的工具。
你的应用可以读取和编辑同一个文档,而无需自建数据库,聊天路由甚至可以将每次变更流式传输到你的 UI:
```
import { agent } from '@/lib/agent';
import { Todos } from '@/lib/todos';
// 在路由或 webhook 中:无需自建数据库即可读取列表
const { value } = await agent.sessions.doc(sessionId, Todos);
// 在聊天路由中:将每次变更流式传输到浏览器
export const { GET, POST } = agent.chatRoute({ authorize, docs: [Todos] }); // 你的鉴权检查
```
从你的应用中读取待办清单并将其变更流式传输到你的 UI。
实际上,前面提到的 `virtual()` 工作区就是建立在文档之上的:它的文件树是会话日志中的一个文档,每次工具调用的变更都与结果一起提交。
了解更多关于 工具、文件和文档(https://vercel.com/docs/agent-harnesses/tools)以及 如何保护你的 agent 安全(https://vercel.com/docs/agent-harnesses/security)的信息。
## 复制标题链接 (https://vercel.com/blog/making-every-harness-durable#wrapping-up)总结
开发者选择一个 harness,看中的是它对提示词、工具和上下文的处理方式。它如何在崩溃中存活,不应是需要操心的事情。有了 `durable-harness`,一个在中途失去函数的轮次会在新的函数上从日志继续,无需手动重试,也不会重复已完成的工作。自托管和可移植性开箱即用,因为它们构建在 Workflow SDK 的 World 接口之上。
`durable-harness` 已在 GitHub 上 开源(https://github.com/vercel-labs/durable-harness)。
**让你的 Harness 具备持久性** 使 Pi、OpenCode、Claude Code 和 Codex 在 Vercel、Postgres 或你的笔记本电脑上都具备持久性的适配器。阅读文档(https://vercel.com/docs/agent-harnesses)