Skip to content

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


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.

PropertyDescription
AutonomousAgents perform tasks without waiting for each instruction
StatefulBuilt on Durable Objects with SQLite persistence
Real-timeWebSocket support for streaming updates
SchedulableCron expressions and delayed execution
Tool-enabledMCP 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.


z0 PrincipleAgents SDK Alignment
Principle 10: AI as ToolAgents are just another tool—they invoke APIs, record Facts, and have measurable costs
Principle 8: Errors Are First-ClassAgent decisions and failures create Facts for full audit trail
Principle 6: Configs Are VersionedAutonomy Config governs what agents can do; version recorded on every Decision Fact
Principle 7: Derived State Is DisposableAgent state is persisted but not authoritative—Facts are

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.

┌─────────────────────────────────────────────────────────────┐
│ 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 ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘

The Agent class is the core building block. It extends Durable Objects with AI-specific capabilities.

class Agent<Env = unknown, State = unknown> {
// Env: environment variables and service bindings
// State: agent-specific data structure, automatically synced
}
HookPurposez0 Usage
onStart()Triggered when Agent initializes or resumesLoad Autonomy Config, initialize state
onRequest(request)Handles incoming HTTP requestsAPI calls triggering agent actions
onConnect(connection, ctx)WebSocket connection establishedReal-time dashboard connections
onMessage(connection, message)Incoming WebSocket messageUser commands, approval responses
onStateUpdate(state, source)State changedTrigger guardrail checks
onError(connection, error)Connection errorRecord error Facts
onClose(connection, code, reason)WebSocket closedClean up, persist state

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)
});
}
}

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) }
});
}
}
}

Each Agent has access to SQLite via tagged template literals:

// Execute SQL query
const decisions = await this.sql<Decision[]>`
SELECT * FROM decisions
WHERE timestamp > ${oneDayAgo}
ORDER BY timestamp DESC
LIMIT 100
`;

Agents are globally unique instances identified by name:

// Same name always returns same instance
const 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.

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.md
if (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);
}

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).

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 escalation
Fact {
id: 'dec_pending_001',
type: 'decision',
subtype: 'escalation_triggered',
data: {
action: 'reallocate_budget',
pending_approval: true,
requested_at: timestamp
}
}
// Days later: human response
Fact {
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'
}
}

Here’s how a z0 optimization agent would be implemented using the Agents SDK:

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.toml
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”
ScenarioUseWhy
Per-entity ledger (Facts)Plain DONo AI needed, just storage
Real-time budget checksPlain DOHot path, no reasoning
Budget optimizationAgents SDKAI analysis, decision making
Anomaly detectionAgents SDKPattern recognition, alerts
Human approval workflowsAgents SDKState management, WebSockets
Scheduled reconciliationEitherAlarms work in both
Cross-entity reportingNeitherUse 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”

Agents SDK automatically syncs state between server and clients. z0 could adopt this for:

  • Real-time dashboard updates
  • Approval workflow UIs
  • Live budget monitoring

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

Built-in cron and delayed execution simplifies:

  • Periodic optimization runs
  • Scheduled reconciliation
  • Delayed notifications

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

LimitValueMitigation
Agent instance countTens of millionsOne per entity is fine
State sizeDO limits (10 GB SQLite)Archive old state
Scheduled tasksOne active alarm per DOChain tasks if needed
WebSocket connectionsThousands per agentShard if hit

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 invocation
await 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 }
}
});

ConceptDescription
What Agents SDK isFramework for AI-powered autonomous agents on Durable Objects
How it relates to z0Implementation infrastructure for our autonomy-guardrails pattern
Key patternsAgent addressing, human-in-the-loop, MCP tools, state sync
When to useDecision-making agents, approval workflows, AI analysis
When not to useSimple 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.