Cloudflare Agents SDK:使用方式、业务场景与运行机制
AI 参与说明(2026-10-23,Agent:Codex):本文由 Codex 使用 Context7 检索 Cloudflare 官方文档,结合固定版本源码、npm 依赖和本地运行结果辅助撰写、校验。模型与 reasoning effort 未取得任务级运行记录。示例基于
agents@0.30.0;源码核对基线为9c79ba12778cbb53b6c50db79282d780466c9897。
场景补充(2026-10-23,Agent:Codex):通过 Context7 与 Linear 官方文档补充 Issue 自动化场景,区分 Issue 事实、自动化运行状态与 AgentSession。执行入口 Codex Desktop;模型与 reasoning effort 未取得任务级运行记录。
一个客服工单,上午由用户提交,下午由客服处理,第二天还可能需要提醒。用户会关掉页面,多名客服会同时查看,处理过程中也可能调用模型。工单需要跨请求存在,并在这些事件之间保留自己的状态。
Cloudflare Agents SDK 把这样的业务实体组织成一个有稳定身份、持久存储、实时连接和调度能力的服务端实例。 模型可以参与其中的一步;实例的身份、状态、连接和提醒,在没有模型时也成立。
本文从一个不调用模型的工单程序开始,再解释何时加入模型、聊天框架和持久工作流。这样既能看懂 Agents SDK 自己提供了什么,也能理解为什么有的应用会再引入 AI SDK、Pi 或 TanStack AI。
术语对照
| English | 中文 | 本文含义 |
|---|---|---|
| Agent instance | Agent 实例 | 按业务名寻址、拥有独立状态与行为的服务端实体 |
| Durable Object | 持久对象 | Cloudflare 提供身份、协调执行与独立存储的运行单元 |
| namespace / binding | 命名空间/绑定 | 定位某类实例的空间,以及 Worker 访问它的配置入口 |
| state snapshot | 状态快照 | 用于当前界面的小型整体状态,更新时整体替换 |
| RPC | 远程过程调用 | 客户端或其他服务调用实例方法的通信方式 |
| hibernation | 休眠 | 保留平台管理的连接与持久数据,同时允许回收内存对象 |
| alarm / schedule | 闹钟/调度 | 到时唤醒实例,以及 SDK 保存的待执行回调记录 |
| journal / replay | 执行日志/重放 | 根据保存的步骤证据重新运行,并复用已完成结果 |
| idempotency key | 幂等键 | 让同一业务操作的重复请求能被识别和去重的稳定标识 |
| resumable streaming | 可恢复流传输 | 重连后补发已保存片段,并继续接收新的流内容 |
| model orchestration | 模型编排 | 管理模型调用、工具执行、上下文与继续条件 |
从一张工单理解它的角色
假设工单 ticket-42 有状态、处理记录和提醒时间。
普通 Worker 可以接收“修改状态”的请求,再访问数据库。后续如果需要让多个页面实时更新、按工单协调操作、断线后重新取得状态、到时提醒,还需要建立相应的连接与调度机制。
使用 Agents SDK 时,可以让每张工单对应一个命名的 Agent 实例。Worker 根据名字把请求交给它;实例读取自己的存储、处理操作、保存结果,并向正在查看这张工单的客户端发送状态更新。用户关闭页面后,存储和已登记的提醒仍由服务端管理。
即使没有聊天、prompt 或工具循环,这样的实例也能支撑多人协作和后续业务动作。
第二阶段,工单加入“总结对话”。Agent 收到命令后读取记录,调用一次模型,把摘要保存下来。模型调用可以直接使用 Workers AI binding、厂商 SDK 或 HTTP API;如果需要统一多个模型的调用方式、处理流式输出或工具调用,可以在这一步使用模型 SDK。
第三阶段,产品升级为客服助手:模型要查订单、选择工具、等待审批,再决定下一步。此时需要管理模型与工具之间的循环、上下文和中断恢复。可以采用 Cloudflare 的 Think 等上层方案,也可以把自己的循环放进 Agent。工单的业务身份和状态,仍由原来的实例承接。
An Agent owns durable identity and state. Model orchestration is an additional choice.
哪些场景值得使用
| 业务场景 | 实例按什么命名 | 实例需要协调的事情 |
|---|---|---|
| 客服工单 | 工单 ID | 多人修改状态、处理记录、超时提醒,按需生成摘要 |
| 协作房间 | 房间或文档 ID | 连接中的成员、共享状态、服务端命令;复杂协同编辑另选合适的数据算法 |
| 个人助手 | 用户或独立会话 ID | 长期偏好、任务状态、提醒,以及可选的模型交互 |
| 外部事件监测 | 被监测项目 ID | 接收 webhook、保存上次结果、周期检查、通知订阅者 |
| 报告或审批任务 | 业务任务 ID | 展示进度、接收人工操作,让持久工作流推进后续步骤 |
这些场景都需要一个跨事件存在的协调单元。只有一次无状态模型请求时,普通 Worker 加模型调用就可能足够;只有批量分发任务时,可以先考虑 Queues;流程重点是多个持久步骤和长期等待时,更适合由 Workflows 负责推进。简单应用如果已经能用 Durable Object 清楚表达,也可以直接使用 Durable Objects。
代入 Linear Issue:实例可以跟随一条 Issue
工单与 Issue 都是跨多次操作存在的业务实体。如果自己开发一个类似 Linear 的系统,示例里的 ticket-42 可以换成 Issue 的稳定 ID,实例继续管理它的状态、处理记录、订阅者和提醒。
如果接入现成的 Linear,需要先明确两边各自管理什么:
| 由谁管理 | 记录什么 |
|---|---|
| Linear | Issue 的标题、描述、负责人、状态和评论等业务事实 |
| 每条 Issue 对应的 Agent | 已处理的事件、当前自动化任务、等待用户补充的信息、执行器任务 ID,以及重试和提醒安排 |
例如,用户把一条 Issue 交给助手后,可以这样组织处理流程:
- Worker 接收并验证 Linear webhook,定位对应 Issue 的 Agent。
- Agent 记录已接收的任务,取得所需上下文,再按规则处理,或调用模型分析需求。
- 需要读仓库、改代码和运行测试时,启动具备相应权限与运行环境的执行器,并保存它的任务 ID。
- 需求不完整时保存等待状态,收到用户补充后继续;收到执行器完成的回调时,按任务 ID 关联原任务。
- 根据应用权限与业务规则,把结果或进度写回 Linear;需要长期重试或等待的步骤交给持久任务机制。
页面、webhook 请求或模型调用结束后,实例仍可保存这条 Issue 的处理进度。代码执行由执行器承担;Agents SDK 负责接收这些事件、保存状态并协调后续动作。
命名时,可以在 workspace 作用域内使用 Issue 的 UUID;ENG-42 这类 identifier 留作展示。Linear 的 GraphQL API允许部分 Issue 操作接受 UUID 或短编号,这不要求应用也用展示编号作为持久实例名。
还要区分 Issue 的生命周期与一次助手工作的生命周期。Linear 原生 Agent 集成提供用于跟踪助手一次工作的 AgentSession:提及或委派可以创建 session,后续 prompted 事件向已有 session 补充输入。
实现时可让 Issue Agent 协调该 Issue 的各次任务,再用 runId 或 AgentSession ID 隔离执行上下文;也可以直接按 session 建实例,由 Issue ID 关联业务对象。实例粒度取决于哪些事件需要共同协调,不必让一条 Issue 只对应一次执行。
因此,Agents SDK 尤其适合等待补充信息、跟踪外部执行、跨时间提醒等 Issue 自动化。只有“收到事件后调一次 API”的简单处理,普通 Worker 往往已经足够。规则催办本身不需要模型;需要理解描述、总结讨论或规划步骤时,再加入模型能力。
底层机制:名字找到实例,事件推动实例
Agents SDK 运行在 Cloudflare Workers 与 Durable Objects 上。Worker 是请求入口,Durable Object 为每个命名实例提供身份、执行位置和独立存储;Agents SDK 在此基础上组织状态同步、RPC、连接和调度等能力。
在本文核对的 agents@0.30.0 中,Agent 直接继承 DurableObject,并通过 Lifecycle 组合 State、WebSockets、Scheduler 等能力。部分官方网页仍展示经过 PartyServer 的旧继承链,当前实现应以固定版本源码与 Lifecycle 文档为准。
身份来自 namespace 与 name
同一个 Durable Object namespace 下,相同的实例名会定位同一个业务身份。它可以在内存中退出、随后重新创建,名字与持久存储的对应关系仍然存在。
routeAgentRequest() 的默认路由结构是:
/agents/<Env binding 的 kebab-case>/<实例名>
如果 binding 叫 TicketAgent,实例名叫 ticket-42,路径就是:
/agents/ticket-agent/ticket-42
这里的路由段来自 Env binding 名。示例把 binding 和类都命名为 TicketAgent,所以看起来像按类名路由;两者不同时,应按 binding 理解。
名字也决定协调范围。每个请求随机生成新名字,后续请求就无法回到原工单实例;所有工单共用 global,又会把无关业务集中在同一个实例。对跨工单搜索和统计,通常还需要独立的查询索引或共享数据库,不能把每个实例的本地 SQLite 当作一个自动支持全局查询的数据库。
客户端操作如何进入实例
下面的图省略鉴权,展示首次连接和一次状态修改。WebSocket 建立后,RPC 在客户端与 Agent 连接之间传输,不需要为每个方法调用再走一次 Worker 路由逻辑。
%%{init: {"sequence": {"actorMargin": 24, "width": 100, "wrap": true}}}%%
sequenceDiagram
participant C as 客户端
participant W as Worker
participant A as Ticket Agent
participant D as SQLite
C->>W: 连接 ticket-42
W->>A: 按 binding 和 name 路由
A->>D: 读取已存状态
D-->>A: 当前工单
A-->>C: 初始状态
C->>A: RPC updateStatus
A->>D: 保存事件与新状态
A-->>C: RPC 结果与状态广播
SDK 自带通用客户端:React 使用 useAgent,其他环境可以使用 AgentClient。它们能建立连接、同步通用 state、调用服务端方法,这些功能不依赖聊天 SDK。
浏览器经由 WebSocket 调用的方法,需要用 @callable() 暴露。Worker 或另一个 Agent 调用时,可以通过 getAgentByName(namespace, name) 获取 stub,再使用原生 Durable Object RPC;这种内部调用不以 @callable() 为前提。
实例名和 @callable() 都不提供业务授权。生产入口仍需确认当前用户是否有权访问该工单,服务端方法还要验证参数和操作权限。
一个可以本地运行的最小程序
这个程序提供修改工单状态、保存 SQL 处理记录、向连接发送状态更新,以及延迟提醒。它不调用模型,也没有安装 AI SDK 或 Pi。
示例在 Node.js 24.19.0、agents@0.30.0、wrangler@4.149.0 下实际运行。它只用于本地教学,没有应用鉴权;接入真实工单前需要补上访问控制。
1. 创建配置文件
在一个空目录中创建以下文件。package.json 固定本次验证的直接依赖,首次安装后保留生成的 lockfile:
{
"name": "agents-sdk-guide-example",
"private": true,
"type": "module",
"scripts": { "dev": "wrangler dev --local --port 8791 --persist-to .local-state" },
"dependencies": { "agents": "0.30.0", "zod": "4.3.6" },
"devDependencies": { "wrangler": "4.149.0" }
}
tsconfig.json 使用 SDK 的配置:
{
"extends": "agents/tsconfig"
}
@callable() 使用当前的 standard decorators 语义,不要另加 experimentalDecorators: true。本例由 Wrangler 编译;接入 Vite 8 时,还要按官方说明配置 agents/vite transform。
wrangler.jsonc 注册 SQLite-backed Durable Object:
{
"name": "agents-sdk-guide-example",
"main": "server.ts",
"compatibility_date": "2026-10-01",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [{ "name": "TicketAgent", "class_name": "TicketAgent" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["TicketAgent"] }]
}
compatibility_date 固定运行时兼容语义,不必与文章日期相同。本例选用 2026-10-01,在固定 Wrangler 携带的 workerd 支持范围内。
2. 实现 Agent
创建 server.ts:
import { Agent, callable, routeAgentRequest } from 'agents'
type TicketState = { status: 'open' | 'done'; updates: number; reminders: number }
type Env = { TicketAgent: DurableObjectNamespace<TicketAgent> }
export class TicketAgent extends Agent<Env, TicketState> {
initialState: TicketState = { status: 'open', updates: 0, reminders: 0 }
onStart() {
this.sql`CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT, status TEXT NOT NULL
)`
}
// All business changes go through server methods in this example.
validateStateChange(_next: TicketState, source: unknown) {
if (source !== 'server') throw new Error('Use the updateStatus RPC')
}
@callable()
updateStatus(status: 'open' | 'done') {
if (status !== 'open' && status !== 'done') throw new Error('Invalid status')
this.sql`INSERT INTO events (status) VALUES (${status})`
this.setState({ ...this.state, status, updates: this.state.updates + 1 })
return this.state
}
@callable()
async followUp(seconds = 3) {
if (!Number.isInteger(seconds) || seconds < 1 || seconds > 3600) {
throw new Error('seconds must be an integer from 1 to 3600')
}
const scheduled = await this.schedule(seconds, 'remind', {})
return scheduled.id
}
remind() {
this.setState({ ...this.state, reminders: this.state.reminders + 1 })
}
@callable()
snapshot() {
return {
state: this.state,
events: this.sql<{ id: number; status: string }>`SELECT * FROM events ORDER BY id`,
}
}
async onRequest(request: Request) {
if (request.method === 'POST') {
const body = await request.json() as { status: 'open' | 'done' }
this.updateStatus(body.status)
}
return Response.json(this.snapshot())
}
}
export default {
async fetch(request: Request, env: Env) {
return await routeAgentRequest(request, env) ?? new Response('Not found', { status: 404 })
},
} satisfies ExportedHandler<Env>
initialState 给新工单提供默认值。onStart() 创建事件表,IF NOT EXISTS 让初始化可以重复执行。
客户端调用 updateStatus(),由服务端检查参数、写入事件,再基于当前 state 更新状态和计数。validateStateChange() 拒绝客户端直接替换整个业务快照,让写入统一经过服务端方法;它仍不能替代用户鉴权。
followUp() 保存一个到时调用 remind() 的调度记录。返回的是调度 ID,表示登记了后续工作,不表示提醒已经执行。
snapshot() 同时返回小型 state 和 SQL 事件,便于观察两类存储。示例将每次处理都计为一条记录,没有把重复提交去重;真实接口如果允许客户端重试,应按业务请求 ID 设计去重。
3. 连接并调用方法
创建 client.mjs:
import { AgentClient } from 'agents/client'
const client = new AgentClient({
host: '127.0.0.1:8791',
protocol: 'ws',
agent: 'ticket-agent',
name: 'ticket-a',
onStateUpdate(state) { console.log('state update:', JSON.stringify(state)) },
})
try {
await client.ready
console.log('initial:', JSON.stringify(client.state))
console.log('updated:', JSON.stringify(await client.call('updateStatus', ['done'])))
console.log('schedule id:', await client.call('followUp', [2]))
await new Promise(resolve => setTimeout(resolve, 2500))
console.log('snapshot:', JSON.stringify(await client.call('snapshot')))
} finally {
client.close()
}
这里用 Node 客户端把 SDK 的连接与 RPC 单独展示出来。在 React 页面中,可以通过 useAgent 使用同类能力;页面是否采用 TanStack Router 或 TanStack Query,不改变服务端实例的身份。
第一个终端安装并启动:
npm install
npm run dev
Wrangler 显示 Ready on http://localhost:8791 后,在第二个终端执行:
node client.mjs
首次使用新的本地存储目录时,输出如下,调度 ID 每次生成:
state update: {"status":"open","updates":0,"reminders":0}
initial: {"status":"open","updates":0,"reminders":0}
state update: {"status":"done","updates":1,"reminders":0}
updated: {"status":"done","updates":1,"reminders":0}
schedule id: <generated id>
state update: {"status":"done","updates":1,"reminders":1}
snapshot: {"state":{"status":"done","updates":1,"reminders":1},"events":[{"id":1,"status":"done"}]}
再次运行仍访问 ticket-a,所以计数继续累加。换一个名字则是另一张工单:
curl http://127.0.0.1:8791/agents/ticket-agent/ticket-b
{"state":{"status":"open","updates":0,"reminders":0},"events":[]}
本次还使用独立的本地黑盒脚本验证了:同名实例的两个 WebSocket 客户端共享更新、另一实例隔离、SQL 记录可读;登记提醒后停止 Wrangler/workerd,再用相同持久目录启动,原状态与 SQL 记录仍在,提醒随后执行。依赖树也确认没有 AI SDK 和 Pi 包。这些是本地运行与受控重启结果,不代表生产环境的精确触发时间或外部副作用只执行一次。
state、SQL 和内存各存什么
工单程序里出现了三种容易混淆的数据位置。
| 位置 | 适合放什么 | 生命周期与通知方式 |
|---|---|---|
this.state / setState() | 当前工单状态、计数、进度、最新记录 ID | 保存到 SQLite,并通过 SDK 协议向客户端同步 |
this.sql | 完整事件历史、可查询的业务行 | 保存在该实例的 SQLite;直接修改表不会自动广播业务查询结果 |
| 普通类字段、局部变量 | 缓存、当前调用中的临时数据 | 随内存对象消失;不能承担重启后的事实来源 |
initialState 只在没有保存状态时作为默认值。实例重新创建后会读取已有状态;重新执行初始化不应把工单改回 open。
setState() 是整体替换
当前版本的主要顺序是:写入前调用 validateStateChange(),序列化并保存新状态,更新内存缓存,广播状态,随后异步调用 onStateChanged() 通知 hook。
因此,setState({ status: 'done' }) 不能理解为只合并一个字段;示例显式展开 ...this.state,保留其他字段。它也没有自动提供 CRDT 或多客户端冲突合并。
写入前的拒绝应放在校验阶段。onStateChanged() 执行时,状态已经保存并广播,不能再靠它撤销不合法写入。当前服务端 hook 叫 onStateChanged,客户端选项 onStateUpdate 则仍是正确名称。
直接执行 INSERT 或 UPDATE 也不会自动使页面中的所有 SQL 查询刷新。本例通过 state 广播更新计数,需要历史时再调用 snapshot();复杂页面应明确使用状态快照、失效通知还是专门的订阅协议。
一次 await 期间可能发生别的操作
一个实例中的同步 JavaScript 和同步 SQL 不会在语句之间任意切换;但等待模型、HTTP 等外部 I/O 时,其他事件可能推进。
例如,先读取 const before = this.state,再等待模型,最后以 before 为基础写回整个状态,就可能覆盖客服在等待期间完成的修改。可以在返回后重新读取当前状态,检查业务版本,或把模型结果作为独立记录提交,再按业务规则决定是否更新工单。
因此,示例让客户端发送“修改状态”命令,再由服务端读取当前 state。blockConcurrencyWhile() 适合初始化等短关键区,不宜用它包住整个慢模型调用来规避冲突处理。
空闲休眠之后,什么还在
实例身份持久存在,不等于一个 JavaScript 对象一直驻留内存。 当前 Agent WebSocket 使用 Hibernation API,平台可以在保留连接的同时回收内存对象;后续事件再使实例恢复运行。
flowchart TB
E["HTTP、RPC、WebSocket 消息或 alarm"] --> M{"实例仍在内存?"}
M -->|是| H["处理本次事件"]
M -->|否| R["重建实例并运行 onStart"]
R --> S["从 SQLite 读取持久状态"]
S --> H
H --> P["保存业务结果与后续调度"]
P --> I["等待后续事件"]
I -. "平台可能回收内存对象" .-> E
onStart() 每个内存对象生命周期执行一次:仍在内存时处理新请求,不会每次重跑;对象重建后会再次执行。因此建表要可重复,业务状态不能无条件清零,也不能每次启动都新增同一个延迟提醒。
| 对象重建后可以依靠什么 | 不能依靠什么 |
|---|---|
| 已提交的 SQLite 数据、state、持久调度记录 | 普通类字段和本地缓存 |
| 仍由平台保留的 WebSocket 连接及其 attachment/state | 断线后新建连接自动继承旧连接的全部状态 |
| 根据持久记录重新开展工作 | 原来的 setTimeout、悬空 Promise 和 JavaScript 调用栈 |
不能依赖固定的休眠时间,也不应照抄旧版本的 hibernate: false 配置。keepAlive() 或 keepAliveWhile() 可以减少空闲回收,但不能防止部署替换或故障,更不会把普通函数变成持久执行。
Durable storage survives object recreation. An in-flight JavaScript stack does not.
提醒、长任务和恢复分别怎么做
工单“明天提醒”和“执行到一半失败后继续”需要保存的东西不同:前者保存何时调用哪个方法,后者还要保存执行进展。
到时做一件事:Scheduler
schedule()、scheduleEvery() 和 cron 调度,把调度记录保存在实例的 SQLite 中,并通过 Durable Object alarm 唤醒实例。到时执行时,SDK 根据保存的方法名找到回调,再调用它。
示例停止本地进程后,提醒仍有机会在重启后执行,因为记录保存在存储里,不依赖原来的定时器。调度回调支持重试;发邮件、提交订单等动作要传递稳定的业务幂等键,避免外部系统已经接受操作、SDK 却还没记录成功时重复提交。
scheduleEvery() 对相同配置的注册有去重机制,这只解决重复登记。延迟或指定日期的调度默认语义不同,如果在 onStart() 中注册,应使用适当的幂等选项或单独保存注册状态,避免每次唤醒增加一条提醒。
多步骤与长期等待:Workflows
如果工单流程变成“抓取资料 → 生成报告 → 等待主管审批 → 发布”,可以让 Cloudflare Workflows 保存步骤结果、重试和等待状态;Agent 保存工单身份、展示当前进度,并接收页面上的审批操作。
SDK 提供 AgentWorkflow 与 runWorkflow() 等集成入口。Workflow 使用 step.do() 执行可重试的步骤,使用等待事件的机制接住人工反馈。主管明天才审批时,无需一直保持浏览器请求或一条 RPC 等待。
完成结果被持久记录后,重放可以复用它;外部副作用仍要按业务键去重。详细用法可继续阅读 Workflows API 与恢复机制。
实例内部的持久后台步骤:Tasks
agents@0.30.0 还发布了 agents/tasks,官方明确标记为 Experimental。它为实例内部任务提供持久接收记录、步骤 journal、休眠和重放。
它的恢复方式是重新运行 handler:已经完成的 step.do() 返回保存的结果,step.sleep() 使用保存的截止时间,未完成的步骤则可能再次执行。因此步骤之间的普通代码也会重跑,时间、随机值和外部副作用都需要按可重放的规则处理。
任务定义需要在实例构造或类字段初始化阶段建立,使唤醒后的运行时能按持久记录找到它。实验阶段应锁版本并验证迁移,本文的入门程序使用已经实际运行的基础调度,不把 Tasks 混进最小例子。
旧文章还可能提到 runFiber() 与 stash()。当前源码把旧 Fiber API 标为 Legacy;保存快照后的恢复需要应用实现 onFiberRecovered(),默认实现只记录 warning。不能把它理解成保存任意闭包、从上一个 await 原地继续。
浏览器断线与服务端中断要分开
| 发生了什么 | 需要恢复什么 | 对应机制 |
|---|---|---|
| 刷新工单页面 | 当前业务状态 | 同名实例的持久 state、SQL 与客户端重连 |
| 到了提醒时间 | 待执行回调 | 持久调度记录与 alarm |
| 多步骤任务执行中断 | 已完成步骤与后续执行 | Workflows,或实验性 Tasks |
| 聊天页面断线,服务端仍在生成 | 已产生的流片段和实时尾部 | AIChatAgent 的 resumable streaming |
| 模型生成过程中服务端实例丢失 | 保存的部分消息、工具证据及继续策略 | 框架内建聊天执行恢复;按业务配置预算与继续策略,可能重新调用模型 |
最后两行是聊天框架的能力。通用 AgentClient 重连并不会自动保存任意模型输出流;SQLite 中有数据,也不会自动恢复原来的模型网络连接。
什么时候再引入模型 SDK 或聊天框架
在工单应用中加入模型时,应根据新增的交互和执行需求选择接入方式。
只增加一个“生成摘要”按钮
Agent 负责读取记录、验证权限、保存结果和通知页面。调用模型的那一步,可以直接通过 Workers AI binding、厂商 SDK 或 HTTP API 完成。
此时需要自己处理对应 API 的输入、错误和返回值。使用 AI SDK、Pi 或 TanStack AI,则是选择相应的模型调用抽象与工具,而不是为了让基础 Agent 得以启动。
Cloudflare Models 文档中的 agents/models/ai-sdk 与 agents/models/pi-ai,是把 Cloudflare 模型能力接入对应模型库的桥接入口。它们不表示每个 Agent 都必须二选一。基础包中相关模型库的 peer dependency 是 optional,本地例子的依赖与运行结果也印证了这一点。
增加完整聊天与模型工具循环
如果页面需要聊天消息、工具输入输出、流式回答、断线续收和中断恢复,可以采用现成的聊天与模型编排方案。
| 选择 | 接手哪部分工作 | 仍需理解的边界 |
|---|---|---|
基础 Agent + 自己的模型调用 | 实例、state、SQL、连接、RPC、调度 | 模型调用和循环、消息与输出协议由应用组织 |
AIChatAgent | 在 Agent 上管理聊天消息、聊天协议、流缓冲与恢复机制 | 当前 @cloudflare/ai-chat 依赖 AI SDK;应用编写 onChatMessage() 等生成逻辑,并按业务配置恢复预算与策略 |
| Think | 在聊天能力之上进一步提供模型工具循环、上下文、session 等 | 当前是实验性上层方案,底层仍使用 AI SDK;采用它意味着选择这一套编排方式 |
| Pi 相关集成 | 接入 Pi 的模型与 Agent 循环生态 | 按所选集成使用其模型、消息和循环接口 |
| TanStack AI | 采用其模型调用、工具定义、客户端消息处理及 AG-UI 事件体系 | 需要明确与 Agent 的 RPC、业务 state 和聊天恢复机制如何衔接 |
AIChatAgent 当前默认启用 durable chat recovery;chatRecovery 用于配置恢复预算和终结策略。它的流恢复也分层:浏览器断线时,可以补发已经缓冲的 chunks,再接上正在产生的内容;服务端实例丢失后,执行恢复需要依据保存的消息与工具记录判断重试或续写,可能产生新的模型请求。它没有保存原来的 JavaScript 调用栈,恢复策略也要考虑业务副作用与模型费用。
具体依赖以 lockfile 为准。本次同时核对了 @cloudflare/ai-chat@0.12.2 与 @cloudflare/think@0.21.0,不要由“Cloudflare 自己提供上层框架”推导出它不再使用模型 SDK。
使用 TanStack 时,先区分三个问题
前端采用 TanStack Start,不代表需要 TanStack AI。 Start 负责应用路由与服务端框架;工单页面可以直接连接 useAgent,聊天也可以采用 Cloudflare 的客户端方案。
真正需要评估 TanStack AI 的场景,是希望采用它的模型抽象、工具定义、客户端状态与 AG-UI 事件体系,或已有后端产生这种事件流。Agents SDK 则可以继续承接部署在 Cloudflare 上的业务实例、持久存储和调度。组合时要明确谁拥有聊天消息、谁管理工具循环、谁负责恢复,避免两套机制同时接管同一件事。
AG-UI 描述 Agent 与界面之间的事件契约;通用 useAgent state/RPC 是 Cloudflare 实例通信机制;Generative UI 是根据模型结果呈现组件的产品方式。三者可以协作,层次不同。AI SDK 在界面通信层的对应方案是 UI Message Stream Protocol、UIMessage、ChatTransport 与客户端消息状态处理这一套机制。
相关协议映射与 Cloudflare 当前 TanStack 集成范围,见 TanStack AI 与 AI SDK 对比。局部适配不等于整套聊天框架可以直接互换。
落到业务设计时,先确定这些边界
工单应用可以由一个 TicketAgent 承接同一张工单的命令、快照和提醒。随着规模增加,要继续明确以下边界:
- 身份与授权:由可信入口验证用户及工单权限,再路由到实例。知道名字不等于有访问权;同步给客户端的 state 也不能夹带内部密钥或其他用户的私有数据。
- 权威写入:让客户端发送“完成工单”“新增备注”等命令;服务端校验运行时参数与业务条件。TypeScript 类型本身不会验证收到的 JSON。
- 重试与幂等:为用户命令、webhook 和外部副作用设计稳定的业务 ID。持久化状态、调度登记和外部操作完成,是不同的确认阶段。
- 实例范围与查询:把需要共同协调的业务放在同一个实例;全局检索、统计和跨实例关系另行设计,避免所有用户共用一个热点实例。
- 数据与执行证据:保存当前状态、历史记录、任务 ID 与终态错误。页面重开后应能区分等待、运行、完成和失败,而不是只恢复一个永远转动的图标。
这些边界确定以后,模型才能作为一个可替换、可失败、可重试的业务步骤加入;是否引入某个模型 SDK,也就能按具体工作量判断。
关联阅读
- Durable Objects:理解 Agent 实例的身份、存储和并发基础。
- Workflows API 与恢复机制:处理多步骤、重试和长期等待。
- TanStack AI 与 AI SDK 对比:模型库、AG-UI、界面通信协议与 Generative UI 的分工。
- Convex Agent 与 Cloudflare Agents SDK 的持久聊天:进一步理解发送确认、后台生成和聊天恢复;其中版本信息以各文核验日期为准。
一手资料与核验范围
本文先通过 Context7 检索官方资料,再对照发布包、固定源码与本地运行结果。官方站点部分页面与新实现存在时间差,因此涉及继承链、Lifecycle、Tasks 和恢复机制的描述,优先采用以下固定源码快照。
- Cloudflare Agents SDK 官方仓库与发布包依赖。
- Routing、State、Callable Methods。
- Lifecycle、HTTP、WebSocket 与休眠。
- Scheduling、Workflows Integration、Tasks(Experimental)。
- Resumable Streaming、Chat Agents、当前 AIChatAgent 恢复实现。
- Cloudflare Models、Think 与 Workflows。
- Durable Objects 并发与存储规则与
blockConcurrencyWhile。
运行验证覆盖本地 Wrangler 编译、独立客户端调用、状态广播、实例隔离、SQL 持久化,以及进程重启后的调度执行;不包含 Cloudflare 生产休眠时序或真实模型服务调用。