Cloudflare Agents SDK
Building AI-powered autonomous agents on Cloudflare infrastructure.
Prerequisites: PRINCIPLES.md (Principle 10: AI as Tool), autonomy-guardrails.md, durable-objects.md
Overview
Section titled “Overview”Cloudflare’s Agents SDK is a framework for building AI-powered agents that operate autonomously on Cloudflare’s edge infrastructure. Agents are implemented as classes extending a base Agent class and run on Durable Objects—stateful micro-servers that can scale to tens of millions of instances.
| Property | Description |
|---|---|
| Autonomous | Agents perform tasks without waiting for each instruction |
| Stateful | Built on Durable Objects with SQLite persistence |
| Real-time | WebSocket support for streaming updates |
| Schedulable | Cron expressions and delayed execution |
| Tool-enabled | MCP integration for external capabilities |
Key Insight: The Agents SDK builds on the same Durable Objects infrastructure that z0 uses for per-entity ledgers. This means agents can share the architectural patterns we already have—single-writer semantics, SQLite storage, and alarm-based background work.
How It Relates to z0
Section titled “How It Relates to z0”Alignment with z0 Principles
Section titled “Alignment with z0 Principles”| z0 Principle | Agents SDK Alignment |
|---|---|
| Principle 10: AI as Tool | Agents are just another tool—they invoke APIs, record Facts, and have measurable costs |
| Principle 8: Errors Are First-Class | Agent decisions and failures create Facts for full audit trail |
| Principle 6: Configs Are Versioned | Autonomy Config governs what agents can do; version recorded on every Decision Fact |
| Principle 7: Derived State Is Disposable | Agent state is persisted but not authoritative—Facts are |
What z0 Already Has
Section titled “What z0 Already Has”z0’s autonomy-guardrails.md and autonomous-optimization.md already define:
- Autonomy Config — What actions an agent is allowed to take
- Decision Facts — Immutable record of every autonomous decision
- Guardrail evaluation — Check before every action
- Human-in-the-loop — Escalation when confidence is low
The Agents SDK is implementation infrastructure, not new patterns. It provides the runtime environment for executing what z0 has already designed.
Architectural Fit
Section titled “Architectural Fit”┌─────────────────────────────────────────────────────────────┐│ z0 Agent Architecture │├─────────────────────────────────────────────────────────────┤│ z0 Layer (Our patterns) ││ ┌─────────────────────────────────────────────────────────┐││ │ Autonomy Config: allowed_actions, thresholds, limits │││ │ Decision Facts: action, reasoning, confidence, audit │││ │ Guardrail evaluation: before every action │││ │ Human escalation: when blocked or uncertain │││ └─────────────────────────────────────────────────────────┘│├─────────────────────────────────────────────────────────────┤│ Cloudflare Agents SDK (Their infrastructure) ││ ┌─────────────────────────────────────────────────────────┐││ │ Agent class: lifecycle hooks, state management │││ │ Durable Objects: persistence, single-writer │││ │ WebSockets: real-time communication │││ │ Scheduling: cron, delayed execution │││ │ MCP: tool discovery, external capabilities │││ └─────────────────────────────────────────────────────────┘│└─────────────────────────────────────────────────────────────┘Agent Class API
Section titled “Agent Class API”The Agent class is the core building block. It extends Durable Objects with AI-specific capabilities.
Type Parameters
Section titled “Type Parameters”class Agent<Env = unknown, State = unknown> { // Env: environment variables and service bindings // State: agent-specific data structure, automatically synced}Lifecycle Hooks
Section titled “Lifecycle Hooks”| Hook | Purpose | z0 Usage |
|---|---|---|
onStart() | Triggered when Agent initializes or resumes | Load Autonomy Config, initialize state |
onRequest(request) | Handles incoming HTTP requests | API calls triggering agent actions |
onConnect(connection, ctx) | WebSocket connection established | Real-time dashboard connections |
onMessage(connection, message) | Incoming WebSocket message | User commands, approval responses |
onStateUpdate(state, source) | State changed | Trigger guardrail checks |
onError(connection, error) | Connection error | Record error Facts |
onClose(connection, code, reason) | WebSocket closed | Clean up, persist state |
State Management
Section titled “State Management”Agents have built-in state management that automatically syncs between server and clients:
class OptimizationAgent extends Agent<Env, OptimizerState> { // Initial state initialState: OptimizerState = { lastDecision: null, pendingActions: [], metrics: {} };
// Read state get currentState() { return this.state; }
// Update state (persists and notifies clients) async recordDecision(decision: Decision) { this.setState({ ...this.state, lastDecision: decision, pendingActions: this.state.pendingActions.filter(a => a.id !== decision.actionId) }); }}Scheduling
Section titled “Scheduling”Agents can schedule tasks using dates or cron expressions:
class ReconciliationAgent extends Agent<Env, State> { async onStart() { // Schedule reconciliation every 5 minutes await this.schedule( "*/5 * * * *", // cron expression this.runReconciliation, { entity_id: this.entityId } ); }
async runReconciliation(payload: { entity_id: string }) { // Verify cached state matches ledger const cached = await this.getCachedState('BudgetState'); const calculated = await this.calculateFromLedger();
if (!this.statesMatch(cached, calculated)) { // Record reconciliation Fact await this.appendDecisionFact({ type: 'reconciliation', subtype: 'mismatch_corrected', data: { cached, calculated, delta: this.diff(cached, calculated) } }); } }}SQL Storage
Section titled “SQL Storage”Each Agent has access to SQLite via tagged template literals:
// Execute SQL queryconst decisions = await this.sql<Decision[]>` SELECT * FROM decisions WHERE timestamp > ${oneDayAgo} ORDER BY timestamp DESC LIMIT 100`;Key Patterns from Cloudflare
Section titled “Key Patterns from Cloudflare”1. Agent Addressing
Section titled “1. Agent Addressing”Agents are globally unique instances identified by name:
// Same name always returns same instanceconst agent = env.OPTIMIZER_AGENT.get( env.OPTIMIZER_AGENT.idFromName(`optimizer_${accountId}`));z0 mapping: This matches our DO ID convention: {namespace}_{entity_id}. An optimization agent for account acct_123 would be optimizer_acct_123.
2. Human-in-the-Loop
Section titled “2. Human-in-the-Loop”Cloudflare emphasizes human oversight for autonomous systems:
“Human-in-the-Loop (HITL) workflows integrate human judgment and oversight into automated processes. These systems pause at critical decision points rather than automating everything end-to-end.”
z0 implementation: Our escalation patterns already define this:
// From autonomy-guardrails.mdif (confidence < config.thresholds.min_confidence || config.permissions.require_human_review.includes(action)) { // Create escalation Fact await this.appendFact({ type: 'decision', subtype: 'escalation_triggered', data: { action, reasoning, confidence, human_escalation: true, escalation_reason: 'confidence_below_threshold' } });
// Notify humans await this.triggerEscalation(config.escalation);}3. Tool Integration via MCP
Section titled “3. Tool Integration via MCP”Model Context Protocol (MCP) provides standardized tool discovery:
class InvoiceAgent extends Agent<Env, State> { async onStart() { // Connect to payment processing MCP server await this.addMcpServer( 'stripe', 'https://mcp.stripe.com/v1', { auth: this.env.STRIPE_MCP_TOKEN } );
// Connect to notification MCP server await this.addMcpServer( 'notifications', 'https://mcp.internal/notifications' ); }
async processInvoice(invoice: Invoice) { // Agent can now discover and use tools from connected servers // Payment processing, email sending, etc. }}z0 usage: MCP servers could wrap Twilio (for notifications), Stripe (for payments), and internal z0 APIs (for Fact recording).
4. Long-Running State Persistence
Section titled “4. Long-Running State Persistence”Agents maintain state across extended periods—critical for approval workflows:
“Extended review periods—potentially spanning weeks—require persistent state storage. The documentation recommends Durable Objects for maintaining state consistency during these pauses.”
z0 implementation: Decision Facts with pending_approval: true persist in the ledger. When a human approves/rejects, a new Fact records the resolution:
// Initial escalationFact { id: 'dec_pending_001', type: 'decision', subtype: 'escalation_triggered', data: { action: 'reallocate_budget', pending_approval: true, requested_at: timestamp }}
// Days later: human responseFact { id: 'dec_response_001', type: 'decision', subtype: 'human_review_completed', source_id: 'dec_pending_001', user_id: 'user_jane', data: { review_action: 'approved', reviewer_notes: 'Approved with modification to $500 max' }}z0 Agent Implementation Pattern
Section titled “z0 Agent Implementation Pattern”Here’s how a z0 optimization agent would be implemented using the Agents SDK:
Agent Class Structure
Section titled “Agent Class Structure”import { Agent } from '@cloudflare/agents';
interface OptimizationState { lastAnalysis: number | null; pendingDecisions: PendingDecision[]; metrics: PerformanceMetrics;}
interface Env { ACCOUNT_LEDGER: DurableObjectNamespace; AUTONOMY_CONFIG: KVNamespace; AI: Fetcher; // Workers AI binding NOTIFICATION_QUEUE: Queue;}
export class BudgetOptimizationAgent extends Agent<Env, OptimizationState> { initialState: OptimizationState = { lastAnalysis: null, pendingDecisions: [], metrics: {} };
// Entity this agent optimizes private entityId: string; private autonomyConfig: AutonomyConfig | null = null;
async onStart() { // Extract entity ID from agent name this.entityId = this.name.replace('optimizer_', '');
// Load Autonomy Config this.autonomyConfig = await this.loadAutonomyConfig();
// Schedule periodic optimization (per Config) const interval = this.autonomyConfig?.settings.analysis_interval_minutes || 60; await this.schedule( `*/${interval} * * * *`, this.analyzeAndOptimize, { entity_id: this.entityId } ); }
async analyzeAndOptimize(payload: { entity_id: string }) { // 1. Gather performance data const metrics = await this.gatherMetrics(payload.entity_id);
// 2. AI analysis (Principle 10: AI as tool) const analysis = await this.analyzeWithAI(metrics);
// 3. Propose actions const proposals = this.generateProposals(analysis);
// 4. Evaluate each proposal against guardrails for (const proposal of proposals) { await this.evaluateAndExecute(proposal); }
// 5. Update state this.setState({ ...this.state, lastAnalysis: Date.now(), metrics }); }
private async evaluateAndExecute(proposal: ActionProposal) { const config = this.autonomyConfig!;
// Check 1: Is action allowed? if (!config.settings.allowed_actions.includes(proposal.action)) { await this.recordBlockedDecision(proposal, 'action_not_allowed'); return; }
// Check 2: Is action forbidden? if (config.settings.forbidden_actions.includes(proposal.action)) { await this.recordBlockedDecision(proposal, 'action_forbidden'); return; }
// Check 3: Within limits? const limitCheck = this.checkLimits(proposal, config.settings.limits); if (!limitCheck.passed) { if (limitCheck.severity === 'hard') { await this.escalate(proposal, 'hard_threshold_exceeded'); return; } // Soft threshold: log warning, proceed console.warn(`Soft threshold exceeded: ${limitCheck.reason}`); }
// Check 4: Confidence above threshold? if (proposal.confidence < config.settings.thresholds.min_confidence) { await this.escalate(proposal, 'confidence_below_threshold'); return; }
// Check 5: Requires human review? if (config.settings.escalation.require_approval_for.includes(proposal.action)) { await this.escalate(proposal, 'action_requires_review'); return; }
// All checks passed: execute await this.executeAction(proposal); await this.recordDecisionFact(proposal, false); }
private async escalate(proposal: ActionProposal, reason: string) { // Record escalation Fact const decisionFact = await this.recordDecisionFact(proposal, true, reason);
// Trigger notification (within 60s per invariant) await this.env.NOTIFICATION_QUEUE.send({ type: 'escalation', decision_id: decisionFact.id, channel: this.autonomyConfig!.settings.escalation.channel, recipients: this.autonomyConfig!.settings.escalation.notify_users, message: `Agent requests approval: ${proposal.action} for ${this.entityId}`, action_url: `https://app.web1.co/review/${decisionFact.id}` }); }
private async recordDecisionFact( proposal: ActionProposal, escalated: boolean, escalationReason?: string ): Promise<Fact> { const fact: Fact = { id: `dec_${crypto.randomUUID()}`, type: 'decision', subtype: escalated ? 'escalation_triggered' : proposal.action, timestamp: Date.now(), tenant_id: this.entityId.split('_')[0], // Extract tenant from entity entity_id: this.entityId, config_id: this.autonomyConfig!.id, config_version: this.autonomyConfig!.version, data: { action: proposal.action, action_details: proposal.details, alternatives: proposal.alternatives, reasoning: proposal.reasoning, confidence: proposal.confidence, constraints_checked: proposal.constraintsChecked, human_escalation: escalated, escalation_reason: escalationReason } };
// Append to entity's DO ledger const entityDO = this.env.ACCOUNT_LEDGER.get( this.env.ACCOUNT_LEDGER.idFromName(`account_${this.entityId}`) ); await entityDO.fetch(new Request('https://internal/facts', { method: 'POST', body: JSON.stringify(fact) }));
return fact; }
private async analyzeWithAI(metrics: PerformanceMetrics): Promise<AIAnalysis> { // Record AI invocation as Fact (Principle 10) const startTime = Date.now();
const response = await this.env.AI.fetch(new Request('https://workers-ai/llm', { method: 'POST', body: JSON.stringify({ model: '@cf/meta/llama-2-7b-chat-int8', messages: [ { role: 'system', content: OPTIMIZATION_SYSTEM_PROMPT }, { role: 'user', content: JSON.stringify(metrics) } ] }) }));
const result = await response.json(); const latency = Date.now() - startTime;
// Record AI invocation Fact await this.appendFact({ type: 'invocation', subtype: 'ai_analysis', data: { model_id: '@cf/meta/llama-2-7b-chat-int8', purpose: 'optimization_analysis', input_tokens: result.usage?.input_tokens, output_tokens: result.usage?.output_tokens, latency_ms: latency } });
return this.parseAnalysis(result); }}Wrangler Configuration
Section titled “Wrangler Configuration”name = "z0-optimization-agents"main = "src/agents/optimization.ts"
[[durable_objects.bindings]]name = "OPTIMIZER_AGENT"class_name = "BudgetOptimizationAgent"
[[durable_objects.bindings]]name = "ACCOUNT_LEDGER"class_name = "AccountDO"script_name = "z0-core"
[[durable_objects.migrations]]tag = "v1"new_classes = ["BudgetOptimizationAgent"]
[ai]binding = "AI"
[[queues.producers]]binding = "NOTIFICATION_QUEUE"queue = "z0-notifications"When to Use Agents SDK vs Plain Durable Objects
Section titled “When to Use Agents SDK vs Plain Durable Objects”| Scenario | Use | Why |
|---|---|---|
| Per-entity ledger (Facts) | Plain DO | No AI needed, just storage |
| Real-time budget checks | Plain DO | Hot path, no reasoning |
| Budget optimization | Agents SDK | AI analysis, decision making |
| Anomaly detection | Agents SDK | Pattern recognition, alerts |
| Human approval workflows | Agents SDK | State management, WebSockets |
| Scheduled reconciliation | Either | Alarms work in both |
| Cross-entity reporting | Neither | Use D1 |
Rule of thumb: If the DO needs to make decisions based on AI analysis or interact with humans in real-time, use Agents SDK. If it’s just storing and serving data, use plain DOs.
What We Can Learn from Cloudflare’s Approach
Section titled “What We Can Learn from Cloudflare’s Approach”1. State Sync is Valuable
Section titled “1. State Sync is Valuable”Agents SDK automatically syncs state between server and clients. z0 could adopt this for:
- Real-time dashboard updates
- Approval workflow UIs
- Live budget monitoring
2. MCP for Tool Integration
Section titled “2. MCP for Tool Integration”Model Context Protocol provides a standard way to discover and invoke tools. z0 could:
- Wrap Twilio as an MCP server
- Wrap the Interpretation Engine as an MCP server
- Enable agents to discover available tools dynamically
3. Scheduling as First-Class
Section titled “3. Scheduling as First-Class”Built-in cron and delayed execution simplifies:
- Periodic optimization runs
- Scheduled reconciliation
- Delayed notifications
4. Human-in-the-Loop is Not Optional
Section titled “4. Human-in-the-Loop is Not Optional”Cloudflare emphasizes that autonomous systems must have human oversight. This aligns perfectly with z0’s guardrails pattern:
- Pause at critical decision points
- Maintain context for long review periods
- Learn from human overrides
Constraints and Considerations
Section titled “Constraints and Considerations”Agent Limits
Section titled “Agent Limits”| Limit | Value | Mitigation |
|---|---|---|
| Agent instance count | Tens of millions | One per entity is fine |
| State size | DO limits (10 GB SQLite) | Archive old state |
| Scheduled tasks | One active alarm per DO | Chain tasks if needed |
| WebSocket connections | Thousands per agent | Shard if hit |
Cost Considerations
Section titled “Cost Considerations”Agents incur costs for:
- DO compute time
- AI model invocations (Workers AI or external)
- State storage
- Network egress
Principle 1 (Economics Must Close the Loop): All agent costs must be recorded as Facts and attributed to the entities they serve.
// After AI invocationawait this.appendFact({ type: 'cost', subtype: 'ai_inference', source_id: invocationFactId, amount: calculateAICost(inputTokens, outputTokens), currency: 'USD', data: { model_id: modelId, tokens: { input: inputTokens, output: outputTokens } }});Summary
Section titled “Summary”| Concept | Description |
|---|---|
| What Agents SDK is | Framework for AI-powered autonomous agents on Durable Objects |
| How it relates to z0 | Implementation infrastructure for our autonomy-guardrails pattern |
| Key patterns | Agent addressing, human-in-the-loop, MCP tools, state sync |
| When to use | Decision-making agents, approval workflows, AI analysis |
| When not to use | Simple storage, hot path reads, cross-entity queries |
The Agents SDK doesn’t change z0’s architecture—it provides a runtime for executing the autonomous optimization patterns we’ve already designed. Our Autonomy Config, Decision Facts, and guardrail evaluation remain the same. The SDK just makes it easier to build the agents that implement those patterns.
Further Reading
Section titled “Further Reading”- autonomy-guardrails.md — z0’s guardrail patterns
- autonomous-optimization.md — Product specification
- durable-objects.md — DO fundamentals
- Cloudflare Agents Documentation — Official docs