Agents SDK 入門:在 Workers 打造有狀態 AI Agent | Cloudflare 完整教學
Cloudflare Agents SDK 是建立在 Durable Objects(第 023 篇)之上的有狀態 AI Agent 框架。跟你之前用的「無狀態」Workers AI 一次性呼叫不同,一個 Agent 擁有自己專屬、持久的記憶與身份——它記得上一次的對話、能跨多次請求維持狀態、能自己排程未來的任務。這篇是我們 Agents 段落的第一站,帶你認識
agents套件、class X extends Agent<Env, State>的寫法、this.state/setState狀態管理、onRequest生命週期、getAgentByName取得實例,以及用this.schedule排程,並徹底釐清它和純 Workers AI 的根本差異。
前言
上一篇《AutoRAG 自動化 RAG》,我們讓 Cloudflare 全託管地把「知識」餵給 AI——你問一句,它根據你的 R2 知識庫回你一句。但無論是自建 RAG 還是 AutoRAG,它們本質上都還是一問一答、無記憶的:每次呼叫都是獨立的,模型不記得你上一句說了什麼,更不會「自己主動去做事」。
這一篇開始,我們要跨進一個全新的世界:讓 AI 不只是回答,而是能記住狀態、跨步驟執行、甚至自主行動。這就是 Agent(AI 代理人) 的概念,而 Cloudflare 為此推出了 Agents SDK。
先給一個一句話定義:
Cloudflare Agents SDK 是一個建立在 Durable Objects 之上的框架,讓你能在 Workers 平台上部署「有狀態(stateful)」的 AI Agent——每個 Agent 實例都有持久的記憶、獨立的身份、生命週期方法,以及內建的排程能力。
這裡最關鍵的三個字是有狀態。到目前為止,你在這個系列裡呼叫 Workers AI 的方式(env.AI.run(...))都是無狀態(stateless) 的——就像每次跟一個失憶症患者對話,他永遠不記得你們上一秒聊了什麼。而 Agent 剛好相反:它有記憶、記得你、記得整場對話。
打個比方會更清楚。純 Workers AI 呼叫像是你打去一間沒有客服系統的電話總機:每通電話都是全新的陌生人接聽,你得從頭自我介紹、重述所有背景,講完掛掉,對方立刻忘光。而 Agent 像是一位有專屬檔案夾的個人助理:你每次找他,他都翻開「你這一份」的檔案,記得你的偏好、上次交辦的事、進度到哪——因為每個 Agent 實例都對應一個獨立的 Durable Object,有自己專屬的持久儲存。
為什麼是 Durable Objects?還記得第 023 篇《Durable Objects 入門》嗎?DO 的核心特性就是「具有唯一身份 + 持久狀態的單例物件」。Agents SDK 正是站在這個基礎上——把 DO 的「有狀態、單例、可協調」能力,包裝成專為 AI Agent 設計的高階框架,再加上生命週期、狀態同步、排程、WebSocket、工具呼叫等一整套能力。
讀完這篇你會掌握:
- Agents SDK 是什麼、為什麼建在 Durable Objects 上——以及它和無狀態 Workers AI 呼叫的根本差異。
- 怎麼寫一個 Agent——
class X extends Agent<Env, State>、this.state與setState管理狀態、onRequest等生命週期方法。 - 怎麼呼叫與排程——用
getAgentByName從 Worker 拿到某個實例、用this.schedule讓 Agent 自己排程未來的任務。 - 什麼場景該用 Agent、什麼場景用純 Workers AI 就好——以及常見的觀念坑。
小提醒:Agents SDK 仍在快速演進中,以下的套件、類別、方法名稱以當下 Cloudflare 官方文件為準。若 API 名稱有出入,以官方文件為主;本篇聚焦「入門觀念與最小可用範例」,MCP Server、WebSocket 即時通訊等進階主題留給後續文章。
核心概念
一、Agent = Durable Object + AI + 持久 State
理解 Agent 最快的方法,是先把它拆開:
┌─────────────────────────────────────────┐
│ 一個 Agent 實例 │
│ (對應一個 Durable Object 實例,有唯一身份) │
│ │
│ this.state ← 持久狀態(自動存進 SQLite) │
│ this.sql ← 內嵌 SQLite(零延遲查詢) │
│ this.env ← Bindings(AI、KV、D1...) │
│ this.name ← 這個實例的名字(DO ID) │
│ │
│ 生命週期: onStart / onRequest / ... │
│ 排程: this.schedule(...) │
└─────────────────────────────────────────┘
幾個關鍵事實,一次記牢:
- 每個 Agent 類別 = 一個 Durable Objects 命名空間;每個 Agent 實例 = 一個 DO 實例。 你定義一個
class ChatAgent extends Agent,它背後就是一個 DO 類別;你用不同的名字(例如room-123、user-alice)去取用它,就會得到不同的、各自獨立的實例,每個實例有自己專屬的記憶。 - 狀態自動持久化。 你放進
this.state的東西,SDK 幫你自動存進該實例的內嵌 SQLite,存活於重啟與休眠——Agent 閒置一段時間會進入休眠(sleep)以省資源,但醒來後this.state還在。 - 零延遲的本地儲存。 每個實例都有一個內嵌 SQLite(
this.sql),資料就在實例旁邊,查詢不需跨越網路。
二、和純 Workers AI 呼叫的根本差異
這是整篇最重要的觀念,值得用一張表講清楚:
| 面向 | 純 Workers AI 呼叫 | Agents SDK |
|---|---|---|
| 狀態 | 無狀態,每次呼叫獨立 | 有狀態,實例持久保留記憶 |
| 記憶 | 自己接 KV/D1/Vectorize 手動存 | 內建 this.state + SQLite |
| 身份 | 沒有「實例」概念 | 每個實例有唯一名字/身份 |
| 生命週期 | 無(就是一次函數呼叫) | onStart/onRequest 等 |
| 排程 | 靠 Worker 層級 Cron Triggers | 實例層級 this.schedule |
| 底層 | 就是 Worker | 建在 Durable Objects 上 |
| 適合 | 一次性推論、無記憶的生成 | 跨請求記憶、自主多步驟、排程 |
一句話總結:「輸入一段文字、拿一次結果」用純 Workers AI 就好;「需要記住狀態、跨多次請求維持記憶、會自己排程做事」才需要 Agent。 Agent 沒有取代 Workers AI——事實上 Agent 內部通常還是會呼叫 Workers AI 來做推論,只是外面多包了一層「有狀態的殼」。
三、生命週期(Lifecycle)
Agent 是「活的」——它會因為各種事件被喚醒並執行對應的方法。入門階段先認識最核心的兩個:
| 方法 | 觸發時機 |
|---|---|
onStart() | Agent 實例首次啟動或從休眠喚醒時(在任何連線到達前) |
onRequest(request) | 每次 HTTP 請求到達這個實例時 |
(還有 onConnect / onMessage(WebSocket)、onStateChanged(狀態變更)、onEmail(收信)等,這些留到後續談即時通訊與工具時再深入。)
四、關鍵術語一次記牢
| 術語 | 是什麼 |
|---|---|
agents 套件 | Cloudflare Agents SDK 的 npm 套件,npm install agents |
Agent<Env, State> | 你的 Agent 要繼承的基底類別,泛型帶入環境型別與狀態型別 |
this.state | 這個實例的持久狀態(自動存進 SQLite),讀取用 |
setState() | 更新狀態:寫入 SQLite + 廣播給連線客戶端 + 觸發 onStateChanged |
this.sql | 這個實例的內嵌 SQLite,適合歷史/大型/可查詢資料 |
routeAgentRequest | Worker 入口用它把請求路由到對的 Agent 實例 |
getAgentByName | 在 Worker 端「用名字」取得某個 Agent 實例並呼叫它的方法 |
this.schedule | 讓這個實例排程未來要執行的 callback(延遲/定時/cron) |
實作範例
觀念清楚後,我們動手寫一個最小可用的 Agent。這個範例是一個「計數器 Agent」——它記得自己被增加了幾次(這正是「有狀態」最直觀的展示:換成無狀態的純 Workers AI,你根本做不到「記得次數」)。
1. 安裝與設定
先把 agents 套件裝起來:
# 安裝 Agents SDK 套件
npm install agents
wrangler.jsonc 的關鍵設定:因為 Agent 建在 Durable Objects 上,所以要綁一個 DO binding,並用 migrations 把它宣告為 SQLite-backed 的類別:
// wrangler.jsonc
{
"name": "my-agent-app",
"main": "src/index.ts",
"compatibility_date": "2026-09-01",
"compatibility_flags": ["nodejs_compat"],
// 每個 Agent 類別需要一個 Durable Objects binding
"durable_objects": {
"bindings": [
{ "name": "CounterAgent", "class_name": "CounterAgent" }
]
},
// 用 new_sqlite_classes 宣告這是 SQLite-backed 的 Agent 類別
// 注意:舊的 migration 條目永遠不要修改,只新增帶新 tag 的條目
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["CounterAgent"] }
],
// Agent 內部若要呼叫 Workers AI,綁上 AI binding
"ai": { "binding": "AI" }
}
提醒:Agents SDK 的 TypeScript 設定通常會繼承
agents/tsconfig,且不應開啟experimentalDecorators(SDK 使用標準裝飾器)。這些設定細節以官方 starter 範本與文件為準。
2. 定義一個有狀態的 Agent
這是核心。我們用 class CounterAgent extends Agent<Env, CounterState> 定義一個 Agent,並示範 initialState、this.state、setState() 與生命週期方法:
// src/index.ts
import { Agent, routeAgentRequest } from "agents";
// 這個實例要記住的狀態長什麼樣
type CounterState = {
count: number;
lastUpdated: string | null;
};
type Env = {
CounterAgent: DurableObjectNamespace;
AI: Ai;
};
export class CounterAgent extends Agent<Env, CounterState> {
// 新實例「首次啟動」時使用的初始狀態
initialState: CounterState = {
count: 0,
lastUpdated: null,
};
// 生命週期:實例首次啟動或從休眠喚醒時
async onStart() {
// this.name 就是這個實例的名字(Durable Object ID)
console.log(`CounterAgent「${this.name}」啟動,目前 count = ${this.state.count}`);
}
// 生命週期:每次 HTTP 請求到達「這個實例」時
async onRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
// GET /increment → 加一(這就是「有狀態」的威力:它記得上次的值)
if (url.pathname.endsWith("/increment")) {
this.increment();
return Response.json(this.state);
}
// 其他 → 回傳目前狀態
return Response.json(this.state);
}
// 一般方法:更新狀態
increment() {
// setState 會做三件事:寫進 SQLite、廣播給連線客戶端、觸發 onStateChanged
this.setState({
...this.state, // 展開現有狀態,只改要改的欄位
count: this.state.count + 1,
lastUpdated: new Date().toISOString(),
});
}
}
// Worker 入口:把請求路由到對的 Agent 實例
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;
這段程式碼有三個重點值得停下來看:
this.state是讀、setState()是寫。 你永遠不要直接this.state.count++去改它——那不會持久化、也不會廣播。所有變更都要透過setState(),它才會幫你存進 SQLite 並同步。initialState只在「這個實例第一次出生」時用一次。 之後這個實例的狀態就從它自己的 SQLite 載入了。也就是說,名為room-a的實例和名為room-b的實例,各自有各自的count,互不干擾。onRequest收到的請求,已經被路由到「特定實例」了。 你不用自己判斷是哪個實例——routeAgentRequest依 URL 幫你分流好了。
3. 路由:URL 怎麼對應到實例
routeAgentRequest() 的預設 URL 格式大致是:
https://your-worker.dev/agents/{agent-name}/{instance-name}
Agent 類別名稱會自動轉成 kebab-case。所以:
GET /agents/counter-agent/room-a/increment→ 對room-a這個實例加一GET /agents/counter-agent/room-b/increment→ 對room-b這個實例加一(和room-a完全獨立)
你反覆打 room-a 的 /increment,count 會一路累加下去、跨請求記得——這就是有狀態 Agent 和無狀態 Workers AI 呼叫最直觀的差別。(路徑格式細節以官方文件為準。)
4. 從 Worker 端直接呼叫實例:getAgentByName
有時候你不想走「HTTP 路由」那條路,而是想在 Worker 程式裡直接用名字拿到某個 Agent 實例、呼叫它的方法。這時用 getAgentByName:
import { getAgentByName } from "agents";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// 直接用名字拿到「global-counter」這個實例(不存在會自動建立)
const agent = await getAgentByName(env.CounterAgent, "global-counter");
// 直接呼叫它的方法,像呼叫本地物件一樣(底層是 Durable Object RPC)
await agent.increment();
// 也能讀它的狀態
const state = await agent.state;
return Response.json(state);
},
} satisfies ExportedHandler<Env>;
這在「一個全域計數器」「用 userId 當實例名、每個使用者一個 Agent」這類場景特別好用——你用穩定的名字(userId、orderId、roomId)去定位實例,同一個名字永遠對到同一個實例、同一份記憶。
5. 讓 Agent 自己排程未來的任務:this.schedule
Agent 最迷人的能力之一,是它能排程自己在未來執行某件事——而且這個排程是綁在這個實例上、持久化的,存活於休眠與重啟。這用純 Workers AI 是做不到的。
this.schedule 大致支援三種模式(以官方文件為準):
export class ReminderAgent extends Agent<Env, { note: string }> {
initialState = { note: "" };
async onRequest(request: Request): Promise<Response> {
// 模式一:延遲 N 秒後執行 —— 10 秒後呼叫 sendReminder
await this.schedule(10, "sendReminder", { message: "10 秒到了!" });
// 模式二:指定時間執行 —— 傳一個 Date 物件
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
await this.schedule(tomorrow, "sendReminder", { message: "明天的提醒" });
// 模式三:cron 表達式 —— 每天上午 8 點(UTC)執行
await this.schedule("0 8 * * *", "sendReminder", { message: "每日報告" });
return new Response("已排程");
}
// 排程時間到,SDK 會呼叫這個 callback,並把 payload 傳進來
async sendReminder(payload: { message: string }) {
console.log(`[${this.name}] 執行排程:${payload.message}`);
// 這裡可以做任何事:呼叫 Workers AI、寄 email、更新 this.state...
}
}
重點:排程的 callback 是這個實例的方法,payload 必須是 JSON 可序列化的。因為排程跟實例綁定,像「這位使用者註冊 24 小時後寄歡迎信」「這個訂單 30 分鐘未付款就取消」這類「跟某個實例綁定的定時任務」,用 this.schedule 遠比外部 cron 乾淨——你不必自己維護一張「哪個 cron 對應哪個實例」的對照表。
常見錯誤與最佳實踐
Agents SDK 的坑,大多來自「還沒轉換過來的無狀態思維」。以下是入門最高頻的幾個。
坑一:把 Agent 當成無狀態的函數用,狀態不放 this.state。
最常見的錯誤,是像寫普通 Worker 那樣,把資料存進類別的一般屬性或記憶體變數裡,以為它會一直在。但 Agent 會休眠——閒置一段時間後,記憶體中的變數、timer、本地快取全部不保證留存,醒來後只有 this.state 和 SQLite 裡的資料還在。正確做法:任何「醒來後還需要」的資料,一律放 this.state(輕量、要同步的)或 this.sql(大型、歷史的),絕不放類別屬性當記憶。
坑二:直接改 this.state,而不透過 setState()。
this.state.count++ 這種寫法看起來很自然,但它不會持久化、不會廣播、不會觸發 onStateChanged——你以為改了,實際上下次休眠醒來就消失了。正確做法:所有狀態變更都走 setState({ ...this.state, 欄位: 新值 }),這才是「有狀態」真正生效的方式。
坑三:把大型資料(尤其會不斷長大的陣列)塞進 this.state。
因為 setState() 每次都會廣播整包狀態給所有連線的客戶端,如果你把「全部訊息歷史」這種可能長到數千筆的陣列放進 state,每次更新都要序列化並廣播一大包,效能會急速惡化。正確做法:state 只放輕量、需要即時同步的資料(計數、當前狀態、最後一筆 ID);大型與歷史資料放 this.sql(內嵌 SQLite),要用時再查詢。 一句口訣:state 要瘦,SQL 存胖。
坑四:該用 this.schedule 卻硬接外部 cron。
想做「每個使用者各自的定時提醒」時,有人會去搭一個外部 cron 服務、再想辦法把每次觸發對應回某個使用者——又複雜又容易錯。既然排程需求是「綁定在某個實例上」的,正確做法就是用實例自己的 this.schedule:它持久化、跟實例綁定、存活於重啟,天生就是為這種場景設計的。反過來說,如果你的排程是「整個系統層級、跟任何特定實例都無關」的(例如每天全站掃一次),那才輪到 Worker 的 Cron Triggers 上場。
坑五:誤以為 Agent 能取代 Workers AI。
Agent 不是 Workers AI 的替代品——它是「外面那層有狀態的殼」。真正做 AI 推論的,通常還是你在 Agent 內部呼叫的 this.env.AI.run(...)(或透過 AI SDK)。正確認知:Agent 負責「記憶、狀態、生命週期、排程、協調」,Workers AI 負責「這一次的推論」,兩者是合作而非取代。
最佳實踐小結: 把「醒來後還要的東西」一律放 this.state / this.sql,別放記憶體;狀態變更一律走 setState();state 保持輕量、大型資料進 SQL;跟實例綁定的排程用 this.schedule、全站級的才用 Cron Triggers;把 Agent 理解成「有狀態的殼」,推論仍交給 Workers AI。API 名稱與細節請以官方文件為準。
小結
上一篇《AutoRAG 自動化 RAG》,我們讓 Cloudflare 全託管地把知識餵給 AI,完成了「一問一答」的知識庫問答。這一篇《Agents SDK 入門》,我們正式跨進「有狀態、會記憶、能做事」的 Agent 世界:
- Agents SDK 是什麼——建在 Durable Objects(第 023 篇)之上的有狀態 AI Agent 框架;每個 Agent 實例對應一個 DO 實例,有專屬、持久的記憶與身份。
- 和純 Workers AI 的根本差異——Workers AI 是無狀態的一次性推論;Agent 是有狀態、有生命週期、有排程的殼。一次性推論用 Workers AI,跨請求記憶與自主做事用 Agent。
- 怎麼寫——
class X extends Agent<Env, State>、initialState定初始狀態、this.state讀 /setState()寫、onStart/onRequest生命週期。 - 怎麼呼叫與排程——
routeAgentRequest依 URL 路由、getAgentByName用名字直接取實例、this.schedule讓實例排程延遲/定時/cron 任務。 - 關鍵坑——狀態要走
setState、state 要瘦 SQL 存胖、跟實例綁定的排程用this.schedule、Agent 不取代 Workers AI。
這一篇我們只搭好了 Agent 的「骨架」——有狀態、有生命週期、能排程。但一個真正強大的 Agent,還需要能和外部世界互動、被 AI 助理當成工具來呼叫。下一篇《Agents SDK:MCP Server》,我們就來認識 MCP(Model Context Protocol,模型情境協定),學會把你的 Agent 包裝成一個能被 Claude 等 AI 應用連接、呼叫的 MCP Server——讓你的 Agent 從「自己會做事」進化到「被 AI 生態系呼叫來做事」。我們下一篇見。
想深入 Agents SDK 的完整 API 與最新用法,可以參考 Cloudflare Agents 官方文件(套件、類別與方法名稱以官方文件為準)。