Install
$ agentstack add skill-jackspace-claudeskillz-cloudflare-agents Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Dangerous shell/eval execution.
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Cloudflare Agents SDK
Status: Production Ready ✅ Last Updated: 2025-10-21 Dependencies: cloudflare-worker-base (recommended) Latest Versions: agents@latest, @modelcontextprotocol/sdk@latest Production Tested: Cloudflare's own MCP servers (https://github.com/cloudflare/mcp-server-cloudflare)
What is Cloudflare Agents?
The Cloudflare Agents SDK enables building AI-powered autonomous agents that run on Cloudflare Workers + Durable Objects. Agents can:
- Communicate in real-time via WebSockets and Server-Sent Events
- Persist state with built-in SQLite database (up to 1GB per agent)
- Schedule tasks using delays, specific dates, or cron expressions
- Run workflows by triggering asynchronous Cloudflare Workflows
- Browse the web using Browser Rendering API + Puppeteer
- Implement RAG with Vectorize vector database + Workers AI embeddings
- Build MCP servers implementing the Model Context Protocol
- Support human-in-the-loop patterns for review and approval
- Scale to millions of independent agent instances globally
Each agent instance is a globally unique, stateful micro-server that can run for seconds, minutes, or hours.
Quick Start (10 Minutes)
1. Scaffold Project with Template
npm create cloudflare@latest my-agent -- \
--template=cloudflare/agents-starter \
--ts \
--git \
--deploy false
What this creates:
- Complete Agent project structure
- TypeScript configuration
- wrangler.jsonc with Durable Objects bindings
- Example chat agent implementation
- React client with useAgent hook
2. Or Add to Existing Worker
cd my-existing-worker
npm install agents
Then create an Agent class:
// src/index.ts
import { Agent, AgentNamespace } from "agents";
export class MyAgent extends Agent {
async onRequest(request: Request): Promise {
return new Response("Hello from Agent!");
}
}
export default MyAgent;
3. Configure Durable Objects Binding
Create or update wrangler.jsonc:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-agent",
"main": "src/index.ts",
"compatibility_date": "2025-10-21",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{
"name": "MyAgent", // MUST match class name
"class_name": "MyAgent" // MUST match exported class
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyAgent"] // CRITICAL: Enables SQLite storage
}
]
}
CRITICAL Configuration Rules:
- ✅
nameandclass_nameMUST be identical - ✅
new_sqlite_classesMUST be in first migration (cannot add later) - ✅ Agent class MUST be exported (or binding will fail)
- ✅ Migration tags CANNOT be reused (each migration needs unique tag)
4. Deploy
npx wrangler@latest deploy
Your agent is now running at: https://my-agent..workers.dev
Configuration Deep Dive
Complete wrangler.jsonc Example
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-agent",
"main": "src/index.ts",
"account_id": "YOUR_ACCOUNT_ID",
"compatibility_date": "2025-10-21",
"compatibility_flags": ["nodejs_compat"],
// Durable Objects configuration (REQUIRED)
"durable_objects": {
"bindings": [
{
"name": "MyAgent",
"class_name": "MyAgent"
}
]
},
// Migrations (REQUIRED)
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyAgent"] // Enables state persistence
}
],
// Optional: Workers AI binding (for AI model calls)
"ai": {
"binding": "AI"
},
// Optional: Vectorize binding (for RAG)
"vectorize": {
"bindings": [
{
"binding": "VECTORIZE",
"index_name": "my-agent-vectors"
}
]
},
// Optional: Browser Rendering binding (for web browsing)
"browser": {
"binding": "BROWSER"
},
// Optional: Workflows binding (for async workflows)
"workflows": [
{
"name": "MY_WORKFLOW",
"class_name": "MyWorkflow",
"script_name": "my-workflow-script" // If in different project
}
],
// Optional: D1 binding (for additional persistent data)
"d1_databases": [
{
"binding": "DB",
"database_name": "my-agent-db",
"database_id": "your-database-id"
}
],
// Optional: R2 binding (for file storage)
"r2_buckets": [
{
"binding": "BUCKET",
"bucket_name": "my-agent-files"
}
],
// Optional: Environment variables
"vars": {
"ENVIRONMENT": "production"
},
// Optional: Secrets (set with: wrangler secret put KEY)
// OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.
// Observability
"observability": {
"enabled": true
}
}
Migrations Best Practices
Atomic Deployments: Migrations are atomic operations - they cannot be gradually deployed.
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyAgent"] // Initial: enable SQLite
},
{
"tag": "v2",
"renamed_classes": [
{"from": "MyAgent", "to": "MyRenamedAgent"}
]
},
{
"tag": "v3",
"deleted_classes": ["OldAgent"]
},
{
"tag": "v4",
"transferred_classes": [
{
"from": "AgentInOldScript",
"from_script": "old-worker",
"to": "AgentInNewScript"
}
]
}
]
}
Migration Rules:
- ✅ Each migration needs a unique
tag - ✅ Cannot enable SQLite on existing deployed class (must be in first migration)
- ✅ Migrations apply in order during deployment
- ✅ Cannot edit or remove previous migration tags
- ❌ Never deploy new migrations gradually (atomic only)
Environment-Specific Migrations
{
"migrations": [{"tag": "v1", "new_sqlite_classes": ["MyAgent"]}],
"env": {
"staging": {
"migrations": [
{"tag": "v1", "new_sqlite_classes": ["MyAgent"]},
{"tag": "v2-staging", "renamed_classes": [{"from": "MyAgent", "to": "StagingAgent"}]}
]
}
}
}
Agent Class API
The Agent class is the foundation of the Agents SDK. Extend it to create your agent.
Basic Agent Structure
import { Agent } from "agents";
interface Env {
// Environment variables and bindings
OPENAI_API_KEY: string;
AI: Ai;
VECTORIZE: Vectorize;
DB: D1Database;
}
interface State {
// Your agent's persistent state
counter: number;
messages: string[];
lastUpdated: Date | null;
}
export class MyAgent extends Agent {
// Optional: Set initial state (first time agent is created)
initialState: State = {
counter: 0,
messages: [],
lastUpdated: null
};
// Optional: Called when agent instance starts or wakes from hibernation
async onStart() {
console.log('Agent started:', this.name, 'State:', this.state);
}
// Handle HTTP requests
async onRequest(request: Request): Promise {
return Response.json({ message: "Hello from Agent", state: this.state });
}
// Handle WebSocket connections (optional)
async onConnect(connection: Connection, ctx: ConnectionContext) {
console.log('Client connected:', connection.id);
// Connections are automatically accepted
}
// Handle WebSocket messages (optional)
async onMessage(connection: Connection, message: WSMessage) {
if (typeof message === 'string') {
connection.send(`Echo: ${message}`);
}
}
// Handle WebSocket errors (optional)
async onError(connection: Connection, error: unknown): Promise {
console.error('Connection error:', error);
}
// Handle WebSocket close (optional)
async onClose(connection: Connection, code: number, reason: string, wasClean: boolean): Promise {
console.log('Connection closed:', code, reason);
}
// Called when state is updated from any source (optional)
onStateUpdate(state: State, source: "server" | Connection) {
console.log('State updated:', state, 'Source:', source);
}
// Custom methods (call from any handler)
async customMethod(data: any) {
this.setState({
...this.state,
counter: this.state.counter + 1,
lastUpdated: new Date()
});
}
}
Accessing Agent Properties
Within any Agent method:
export class MyAgent extends Agent {
async someMethod() {
// Access environment variables and bindings
const apiKey = this.env.OPENAI_API_KEY;
const ai = this.env.AI;
// Access current state (read-only)
const counter = this.state.counter;
// Update state (persisted automatically)
this.setState({ ...this.state, counter: counter + 1 });
// Access SQL database
const results = await this.sql`SELECT * FROM users`;
// Get agent instance name
const instanceName = this.name; // e.g., "user-123"
// Schedule tasks
await this.schedule(60, "runLater", { data: "example" });
// Call other methods
await this.customMethod({ foo: "bar" });
}
}
HTTP & Server-Sent Events
HTTP Request Handling
export class MyAgent extends Agent {
async onRequest(request: Request): Promise {
const url = new URL(request.url);
const method = request.method;
if (method === "POST" && url.pathname === "/increment") {
const counter = (this.state.counter || 0) + 1;
this.setState({ ...this.state, counter });
return Response.json({ counter });
}
if (method === "GET" && url.pathname === "/status") {
return Response.json({ state: this.state, name: this.name });
}
return new Response("Not Found", { status: 404 });
}
}
Server-Sent Events (SSE) Streaming
export class MyAgent extends Agent {
async onRequest(request: Request): Promise {
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
// Send events to client
controller.enqueue(encoder.encode('data: {"message": "Starting"}\n\n'));
await new Promise(resolve => setTimeout(resolve, 1000));
controller.enqueue(encoder.encode('data: {"message": "Processing"}\n\n'));
await new Promise(resolve => setTimeout(resolve, 1000));
controller.enqueue(encoder.encode('data: {"message": "Complete"}\n\n'));
controller.close();
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
}
SSE vs WebSockets:
| Feature | SSE | WebSockets | |---------|-----|------------| | Direction | Server → Client only | Bi-directional | | Protocol | HTTP | ws:// or wss:// | | Reconnection | Automatic | Manual | | Binary Data | Limited | Full support | | Use Case | Streaming responses, notifications | Chat, real-time collaboration |
Recommendation: Use WebSockets for most agent applications (full duplex, better for long sessions).
WebSockets
Complete WebSocket Example
import { Agent, Connection, ConnectionContext, WSMessage } from "agents";
interface ChatState {
messages: Array;
participants: string[];
}
export class ChatAgent extends Agent {
initialState: ChatState = {
messages: [],
participants: []
};
async onConnect(connection: Connection, ctx: ConnectionContext) {
// Access original HTTP request for auth
const authHeader = ctx.request.headers.get('Authorization');
const userId = ctx.request.headers.get('X-User-ID') || 'anonymous';
// Connections are automatically accepted
// Optionally close connection if unauthorized:
// if (!authHeader) {
// connection.close(401, "Unauthorized");
// return;
// }
// Add to participants
this.setState({
...this.state,
participants: [...this.state.participants, userId]
});
// Send welcome message
connection.send(JSON.stringify({
type: 'welcome',
message: 'Connected to chat',
participants: this.state.participants
}));
}
async onMessage(connection: Connection, message: WSMessage) {
if (typeof message === 'string') {
try {
const data = JSON.parse(message);
if (data.type === 'chat') {
// Add message to state
const newMessage = {
id: crypto.randomUUID(),
text: data.text,
sender: data.sender || 'anonymous',
timestamp: Date.now()
};
this.setState({
...this.state,
messages: [...this.state.messages, newMessage]
});
// Broadcast to this connection (state sync will broadcast to all)
connection.send(JSON.stringify({
type: 'message_added',
message: newMessage
}));
}
} catch (e) {
connection.send(JSON.stringify({ type: 'error', message: 'Invalid message format' }));
}
}
}
async onError(connection: Connection, error: unknown): Promise {
console.error('WebSocket error:', error);
// Optionally log to external monitoring
}
async onClose(connection: Connection, code: number, reason: string, wasClean: boolean): Promise {
console.log(`Connection ${connection.id} closed:`, code, reason, wasClean);
// Clean up connection-specific state if needed
}
}
Connection Management
export class MyAgent extends Agent {
async onMessage(connection: Connection, message: WSMessage) {
// Connection properties
const connId = connection.id; // Unique connection ID
const connState = connection.state; // Connection-specific state
// Update connection state (not agent state)
connection.setState({ ...connection.state, lastActive: Date.now() });
// Send to this connection only
connection.send("Message to this client");
// Close connection programmatically
connection.close(1000, "Goodbye");
}
}
State Management
Using setState()
interface UserState {
name: string;
email: string;
preferences: { theme: string; notifications: boolean };
loginCount: number;
lastLogin: Date | null;
}
export class UserAgent extends Agent {
initialState: UserState = {
name: "",
email: "",
preferences: { theme: "dark", notifications: true },
loginCount: 0,
lastLogin: null
};
async onRequest(request: Request): Promise {
if (request.method === "POST" && new URL(request.url).pathname === "/login") {
// Update state
this.setState({
...this.state,
loginCount: this.state.loginCount + 1,
lastLogin: new Date()
});
// State is automatically persisted and synced to connected clients
return Response.json({ success: true, state: this.state });
}
return Response.json({ state: this.state });
}
onStateUpdate(state: UserState, source: "server" | Connection) {
console.log('State updated:', state);
console.log('Source:', source); // "server" or Connection object
// React to state changes
if (state.loginCount > 10) {
console.log('Frequent user!');
}
}
}
State Rules:
- ✅ State is JSON-serializable (objects, arrays, strings, numbers, booleans, null)
- ✅ State persists across agent restarts
- ✅ State is immediately consistent within the agent
- ✅ State automatically syncs to connected WebSocket clients
- ❌ State cannot contain functions or circular references
- ❌ Total state size limited by database size (1GB max per agent)
Using SQL Database
Each agent has a built-in SQLite database accessible via this.sql:
export class MyAgent extends Agent {
async onStart() {
// Create tables on first start
await this.sql`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [jackspace](https://github.com/jackspace)
- **Source:** [jackspace/ClaudeSkillz](https://github.com/jackspace/ClaudeSkillz)
- **License:** MIT
- **Homepage:** http://claudeskillz.jackspace.com/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.