Agents SDK foundations

Create one agent instance per user or session

Build stateful agent boundaries on Durable Objects and route stable names without creating one global agent.

8 minute lesson

~~~

Workers are stateless by design. An agent that remembers a conversation needs somewhere for that memory to live, and it should not be a global singleton shared by every user.

Cloudflare Agents SDK builds on Durable Objects. Each named agent instance has persistent SQLite state and real-time WebSocket connections. Ask for the same name twice and you reach the same object with the same storage; ask for a different name and you get a completely separate instance.

The configuration is a Durable Object binding plus a SQLite migration in wrangler.jsonc:

{
  "durable_objects": {
    "bindings": [{ "name": "ChatAgent", "class_name": "ChatAgent" }]
  },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["ChatAgent"] }]
}

The new_sqlite_classes migration is what gives each instance its embedded SQLite database. Forget it and state calls fail at runtime.

Pick the boundary first

Choose an instance boundary such as one user, workspace, or chat session. The name you route to is the boundary: name instances by user ID and each user gets isolated memory; name them by chat session and one user can hold several independent conversations.

Route requests to an instance with routeAgentRequest, or address one directly by name:

import { routeAgentRequest, getAgentByName } from 'agents'

export default {
  async fetch(request, env) {
    const session = await authenticate(request)
    if (!session) return new Response('unauthorized', { status: 401 })

    return (await routeAgentRequest(request, env))
      ?? new Response('not found', { status: 404 })
  },
}

The mistake to avoid is one global agent for everyone. A single instance serializes all users’ work through one object, and every user’s data sits in the same SQLite database one prompt-injection away from another user’s conversation.

Names are routing, not security

Authenticate before accepting a connection because the instance name is not a secret. If instance names are user IDs and you skip the auth check, anyone who guesses user-42 talks to that user’s agent. Authorization decides who may reach a name; the name only decides where the request goes.

Create two counter-agent instances and prove state persists and stays isolated. Increment counter-a three times and counter-b once, wait, then read both again: 3 and 1, or your boundary is imaginary.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →