Agents SDK 入門:在 Workers 打造有狀態 AI Agent | Cloudflare 完整教學

2026/09/09
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.statesetState 管理狀態、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-123user-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,適合歷史/大型/可查詢資料
routeAgentRequestWorker 入口用它把請求路由到對的 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,並示範 initialStatethis.statesetState() 與生命週期方法:

// 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」這類場景特別好用——你用穩定的名字(userIdorderIdroomId)去定位實例,同一個名字永遠對到同一個實例、同一份記憶。

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 官方文件(套件、類別與方法名稱以官方文件為準)。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。

本週主打

AI 自動化入門包

你每天手動在做的那些煩事,其實 AI 可以自己跑。這份給你 10 個照著做就會的自動化工作流 + 50 個複製即用的提示詞,不用會寫程式。

看看這個產品 →