Files
dotfiles/dot_claude/skills/agents-sdk/references/state-scheduling.md
T
Prad NukalaandClaude Fable 5 20891f3910 config(git,claude,ssh): templated gitconfig; claude settings/commands/hooks/skills; ssh config
gitconfig: name/email from chezmoi data, gh credential helper by PATH
lookup (portable to darwin). claude: no credentials, history, projects,
sessions, or plugin caches. ssh config as private_ (0600).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 14:53:46 -04:00

4.0 KiB

State & Scheduling

Fetch https://developers.cloudflare.com/agents/api-reference/store-and-sync-state/ and https://developers.cloudflare.com/agents/api-reference/schedule-tasks/ for complete documentation.

State Management

State persists to SQLite and broadcasts to connected clients automatically.

Define Typed State

type State = { 
  count: number;
  items: string[];
};

export class MyAgent extends Agent<Env, State> {
  initialState: State = { count: 0, items: [] };
}

Read and Update

// Read (lazy-loaded from SQLite)
const count = this.state.count;

// Write (sync, persists, broadcasts)
this.setState({ count: this.state.count + 1 });

Validation Hook

validateStateChange() runs synchronously before state persists. Throw to reject the update.

validateStateChange(nextState: State, source: Connection | "server") {
  if (nextState.count < 0) {
    throw new Error("Count cannot be negative");
  }
}

Execution Order

  1. validateStateChange(nextState, source) - sync, gating
  2. State persisted to SQLite
  3. State broadcast to connected clients
  4. onStateUpdate(nextState, source) - async via ctx.waitUntil, non-gating

Client-Side Sync (React)

import { useAgent } from "agents/react";

function App() {
  const [state, setLocalState] = useState<State>({ count: 0 });
  
  const agent = useAgent<State>({
    agent: "MyAgent",
    name: "instance-1",
    onStateUpdate: (newState) => setLocalState(newState)
  });

  return <button onClick={() => agent.setState({ count: state.count + 1 })}>
    Count: {state.count}
  </button>;
}

SQL API

Direct SQLite access for custom queries:

// Create table
this.sql`
  CREATE TABLE IF NOT EXISTS items (
    id TEXT PRIMARY KEY,
    name TEXT,
    created_at INTEGER DEFAULT (unixepoch())
  )
`;

// Insert
this.sql`INSERT INTO items (id, name) VALUES (${id}, ${name})`;

// Query with types
const items = this.sql<{ id: string; name: string }>`
  SELECT * FROM items WHERE name LIKE ${`%${search}%`}
`;

Scheduling

Schedule Types

Mode Syntax Use Case
Delay this.schedule(60, ...) Run in 60 seconds
Date this.schedule(new Date(...), ...) Run at specific time
Cron this.schedule("0 8 * * *", ...) Recurring schedule
Interval this.scheduleEvery(30, ...) Fixed interval (every 30s)

Examples

// Delay (seconds)
await this.schedule(60, "checkStatus", { id: "abc123" });

// Specific date
await this.schedule(new Date("2025-12-25T00:00:00Z"), "sendGreeting", { to: "user" });

// Cron (recurring)
await this.schedule("0 9 * * 1-5", "weekdayReport", {});

// Fixed interval (every 30 seconds, overlap prevention built-in)
await this.scheduleEvery(30, "pollUpdates");
await this.scheduleEvery(300, "syncData", { source: "api" });

Handler

async sendGreeting(payload: { to: string }, schedule: Schedule) {
  console.log(`Sending greeting to ${payload.to}`);
  // Cron schedules auto-reschedule; one-time schedules are deleted
}

Manage Schedules

const schedules = this.getSchedules();
const crons = this.getSchedules({ type: "cron" });
await this.cancelSchedule(schedule.id);

Retry on Schedules

await this.schedule(60, "task", payload, { retry: { maxAttempts: 3 } });
await this.scheduleEvery(30, "poll", undefined, { retry: { maxAttempts: 2 } });

Lifecycle Callbacks

export class MyAgent extends Agent<Env, State> {
  async onStart() {
    // Agent started or woke from hibernation
  }

  onConnect(conn: Connection, ctx: ConnectionContext) {
    // WebSocket connected
  }

  onMessage(conn: Connection, message: WSMessage) {
    // WebSocket message (non-RPC)
  }

  onStateUpdate(state: State, source: Connection | "server") {
    // State changed (async, non-blocking)
  }

  onError(error: unknown) {
    // Error handler
    throw error; // Re-throw to propagate
  }
}