
Security News
/Company News
Securing the Financial Frontier: How Capital One Uses Socket for Open Source Security
Capital One is partnering with Socket to proactively secure its open source supply chain.
Build software that thinks and does.
Persistent AI agents on Cloudflare's global network. They remember context, reason through problems, schedule their own work, and take action—all without you managing servers.
npm create cloudflare@latest -- --template cloudflare/agents-starter
Or add to an existing project:
npm install agents
LLMs can reason, plan, and use tools—but they need infrastructure that matches their capabilities. Traditional serverless is stateless and ephemeral. Agents are persistent and purposeful.
From request handlers → to autonomous entities
From stateless functions → to persistent intelligence
Traditional serverless: Request → Response → Gone
Agents: Thinking, remembering, acting — continuously
Pay only when active. Agents hibernate between requests. You can have millions of agents—one per user, per session, per game room—each costs nothing when idle.
Built on Cloudflare Durable Objects, agents run globally, close to your users, with persistent state that survives restarts.
A counter agent with real-time state sync and callable methods:
// server.ts
import { Agent, callable } from "agents";
type State = { count: number };
export class CounterAgent extends Agent<Env, State> {
initialState: State = { count: 0 };
@callable()
increment() {
this.setState({ count: this.state.count + 1 });
return this.state.count;
}
@callable()
decrement() {
this.setState({ count: this.state.count - 1 });
return this.state.count;
}
}
// client.tsx
import { useAgent } from "agents/react";
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const agent = useAgent<State>({
agent: "counter-agent",
name: "my-counter",
onStateUpdate: (state) => setCount(state.count)
});
return (
<div>
<span>{count}</span>
<button onClick={() => agent.stub.increment()}>+</button>
<button onClick={() => agent.stub.decrement()}>-</button>
</div>
);
}
State changes sync to all connected clients automatically. Call methods like they're local functions.
| Use Case | Why Agents |
|---|---|
| Multiplayer game rooms | Per-room state, real-time sync, hibernates when empty |
| Customer support bots | Remembers conversation history, escalates to humans |
| Collaborative editors | Presence, cursors, document state |
| Approval workflows | Long-running, pauses for human input, durable |
| Personal AI assistants | Per-user memory, tool access via MCP |
| Notification systems | Scheduled delivery, user preferences, retry logic |
Core State sync · Routing · HTTP & WebSockets · @callable RPC · Sub-agents (facets)
Clients React hook · Vanilla JS · Real-time state sync
Channels WebSocket · HTTP · Email · (coming: SMS, Voice, Messengers)
Background Queue · Scheduling · Managed fibers · Workflows · Human-in-the-loop
AI Chat agents · Agent tools · Tool calling · MCP servers & clients
Platform Observability · Cross-domain auth · Resumable streams
State persists across requests and syncs to all connected clients:
export class MyAgent extends Agent<Env, { items: string[] }> {
initialState = { items: [] };
@callable()
addItem(item: string) {
this.setState({ items: [...this.state.items, item] });
}
onStateChanged(state: State, source: Connection | "server") {
// Called after state is persisted and broadcast
}
}
Expose methods to clients with the @callable() decorator:
@callable()
async processOrder(orderId: string, items: Item[]) {
// Full type safety - clients call this like a local function
const result = await this.validateAndProcess(orderId, items);
return result;
}
// Client
const result = await agent.stub.processOrder("order-123", items);
Run tasks later, on intervals, or with cron expressions:
// In 60 seconds
this.schedule(60, "sendReminder", { userId: "123" });
// Every hour
this.scheduleEvery(3600, "syncData");
// Daily at 9am UTC
this.schedule("0 9 * * *", "dailyReport");
// At a specific date
this.schedule(new Date("2025-12-31"), "yearEndTask");
Queue immediate background work:
await this.queue("processUpload", { fileId: "abc" });
// Returns immediately, task runs in background
Spawn child Durable Objects (facets) from a parent agent. Each child has its own SQLite storage and runs in parallel, but is addressed under the parent's URL:
export class Inbox extends Agent {
@callable()
async createChat() {
const id = crypto.randomUUID();
await this.subAgent(Chat, id);
return id;
}
override async onBeforeSubAgent(_req, { className, name }) {
if (!this.hasSubAgent(className, name)) {
return new Response("Not found", { status: 404 });
}
}
}
export class Chat extends Agent {
async writePreview(text: string) {
const inbox = await this.parentAgent(Inbox);
await inbox.savePreview(this.name, text);
}
}
Client-side, connect to a child with useAgent({ sub: [...] }):
const inbox = useAgent({ agent: "Inbox", name: userId });
const chat = useAgent({
agent: "Inbox",
name: userId,
sub: [{ agent: "Chat", name: chatId }]
});
The routed URL becomes /agents/inbox/{userId}/sub/chat/{chatId}.
Child WebSocket clients can use the same URL shape. The parent remains the
public address, while child agents still receive onConnect, onMessage,
onClose, broadcast(), and getConnections() calls scoped to their own
clients. Parent broadcasts do not leak to child-targeted sockets, and child
connection tags, readonly state, and protocol-message settings are preserved
when a connection is resumed from hibernation.
Nested sub-agent URLs are supported using repeated /sub/{agent}/{name}
segments, subject to the platform's current facet nesting limits.
Run chat-capable sub-agents as tools from a parent chat agent. Think agents and
AIChatAgent subclasses are supported. The child keeps its own messages, tools,
SQLite storage, and resumable stream, while the parent broadcasts
agent-tool-event frames so the UI can render the child timeline inline.
import { Think } from "@cloudflare/think";
import { agentTool } from "agents/agent-tools";
import { z } from "zod";
export class Researcher extends Think<Env> {
getSystemPrompt() {
return "Research the requested topic and end with a concise summary.";
}
}
export class Assistant extends Think<Env> {
getTools() {
return {
research: agentTool(Researcher, {
description: "Research one topic in depth.",
inputSchema: z.object({ query: z.string().min(3) })
})
};
}
}
For deterministic fan-out, call this.runAgentTool(Researcher, { input })
directly. Parent recovery reconciles stale child rows after restarts and marks
unrecoverable runs interrupted instead of hanging. In React, use
useAgentToolEvents({ agent }) to render retained and replayed child timelines.
AIChatAgent children run headlessly, so browser client tools require a separate
bridge; server-side tools work normally. See the full
Agent Tools guide.
Handle real-time communication:
async onConnect(connection: Connection) {
console.log(`Client ${connection.id} connected`);
}
async onMessage(connection: Connection, message: unknown) {
// Handle incoming messages
connection.send(JSON.stringify({ received: true }));
}
async onClose(connection: Connection) {
console.log(`Client ${connection.id} disconnected`);
}
Agents can receive and respond to emails:
async onEmail(email: EmailMessage) {
const from = email.from;
const subject = email.headers.get("subject");
// Process incoming email
}
import { useAgent } from "agents/react";
import { useState } from "react";
function App() {
const [state, setState] = useState<MyState | null>(null);
const agent = useAgent<MyState>({
agent: "my-agent",
name: "instance-name",
onStateUpdate: (newState) => setState(newState)
});
return (
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
<button onClick={() => agent.stub.doSomething()}>Call Agent</button>
</div>
);
}
import { AgentClient } from "agents/client";
const client = new AgentClient({
agent: "my-agent",
name: "instance-name",
onStateUpdate: (state) => console.log("State:", state)
});
// Call methods
const result = await client.call("processData", [payload]);
// Or use the stub
const result = await client.stub.processData(payload);
For durable, multi-step tasks that survive failures and can pause for human approval, integrate with Cloudflare Workflows:
import { AgentWorkflow } from "agents";
export class OrderWorkflow extends AgentWorkflow<OrderAgent, OrderParams> {
async run(event, step) {
// Step 1: Validate (retries automatically on failure)
const validated = await step.do("validate", async () => {
return validateOrder(event.payload);
});
// Step 2: Wait for human approval
await this.reportProgress({ step: "approval", status: "pending" });
const approval = await this.waitForApproval(step, { timeout: "7 days" });
// Step 3: Process the approved order
await step.do("process", async () => {
return processOrder(validated, approval);
});
}
}
Workflows provide:
waitForApproval()See Workflows and Human in the Loop.
For AI-powered chat experiences with persistent conversations, streaming responses, and tool support, see @cloudflare/ai-chat.
import { AIChatAgent } from "@cloudflare/ai-chat";
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish) {
return streamText({
model: openai("gpt-4o"),
messages: this.messages,
tools: this.tools,
onFinish
});
}
}
// Client
import { useAgentChat } from "@cloudflare/ai-chat/react";
const { messages, input, handleSubmit } = useAgentChat({
agent: useAgent({ agent: "chat-agent" })
});
Features:
Agents integrate with MCP to act as servers (providing tools to AI assistants) or clients (using tools from other services).
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpAgent } from "agents/mcp";
export class MyMCP extends McpAgent<Env, State, {}> {
server = new McpServer({ name: "my-tools", version: "1.0.0" });
async init() {
this.server.registerTool(
"lookup",
{ description: "Look up data", inputSchema: { query: z.string() } },
async ({ query }) => {
const result = await this.search(query);
return { content: [{ type: "text", text: result }] };
}
);
}
}
export default MyMCP.serve("/mcp", { binding: "MyMCP" });
// Connect to external MCP servers
await this.addMcpServer(
"weather-service",
"https://weather-mcp.example.com/mcp",
{
transport: { type: "streamable-http" }
}
);
// Use with AI SDK
const result = await generateText({
model: openai("gpt-4o"),
tools: this.mcp.getTools(),
prompt: "What's the weather in Tokyo?"
});
Add your agent to wrangler.jsonc:
{
"durable_objects": {
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }]
}
Route requests to your agent:
import { routeAgentRequest } from "agents";
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
}
};
The published package includes the complete documentation tree at
docs/index.md.
Getting Started · State Management · Scheduling · Callable Methods · MCP Integration · Full Documentation
Contributions are welcome, especially when:
Small fixes, type bugs, and documentation improvements can be raised directly as PRs.
MIT licensed. See the LICENSE file for details.
Build something that thinks. Ship something that does.
FAQs
A home for your AI agents
The npm package agents receives a total of 1,415,651 weekly downloads. As such, agents popularity was classified as popular.
We found that agents demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.

Security News
/Company News
Capital One is partnering with Socket to proactively secure its open source supply chain.

Security News
Socket CTO Ahmad Nassri discusses how to keep AI agents from bypassing package blocks, limit credential access, and monitor their actions.

Security News
GPT-6 Astra tried to plant malicious code in simulated open source projects using fake GitHub accounts and deceptive PRs during an assigned CTF challenge.