Skip to main content

Level Up

agents-starter gives you a chat agent — a model, a conversation, a tool call. A completion call with good manners. What makes it an agent is state that outlives the request and a shape that survives failure. Pick one primitive below for the build task — your playbook says which, and why not the other four.

1. A Workflow​

Use it when a step must survive a crash, a retry, or a human going to lunch. Every step.do() checkpoints its result — if the Workflow restarts, completed steps never re-run.

import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';

export class MyWorkflow extends WorkflowEntrypoint<Env> {
async run(event: WorkflowEvent<{ id: string }>, step: WorkflowStep) {
const data = await step.do('fetch the record', async () => {
return { id: event.payload.id, status: 'pending' };
});

await step.do(
'call an unreliable API',
{ retries: { limit: 3, delay: '5 seconds', backoff: 'exponential' } },
async () => {
// any 5xx here retries automatically, without re-doing step 1
},
);

return data;
}
}

2. A schedule​

Use it when the trigger is a clock, not an event — a nightly sweep, a delayed follow-up. The Agents SDK wraps a Durable Object alarm so you never manage timers by hand.

import { Agent } from 'agents';

export class MyAgent extends Agent<Env> {
async onStart() {
// runs once a day, survives restarts (idempotent by default for cron)
await this.schedule('0 6 * * *', 'dailySweep', { reason: 'fleet-check' });
}

async dailySweep(payload: { reason: string }) {
// do the scheduled work — this.sql and this.env are both available here
}
}

3. Browser Rendering​

Use it when the source of truth lives on a page you don't control — a competitor's price, a partner's status page. No local browser to manage; Cloudflare runs headless Chrome for you.

// wrangler.jsonc — needs compatibility_date >= 2026-03-24
{ "browser": { "binding": "BROWSER" } }
const screenshot = await env.BROWSER.quickAction('screenshot', {
url: 'https://example.com/status',
});
// also available: "markdown", "content", "json", "scrape", "links", "snapshot"

4. AI Search over your own docs​

Use it when an answer must be grounded and citable, not just plausible. Point it at your own (synthetic) policy docs or runbooks and the model retrieves before it answers, instead of guessing from training data.

// wrangler.jsonc — needs compatibility_date >= 2026-03-27
{ "ai_search": [{ "binding": "AI_SEARCH", "instance_name": "my-instance" }] }
const instance = env.AI_SEARCH.get('my-instance');
const results = await instance.search({
messages: [{ role: 'user', content: 'what is the chargeback window?' }],
});
// results include the source chunk, so your answer can cite it

5. Persistent Durable Object state​

Use it when two requests must never see two different versions of the same fact — a case file, a running total, anything where "eventually consistent" is the wrong trade. Every Agent instance is single-threaded per id, with embedded SQLite built in.

import { Agent } from 'agents';

export class MyAgent extends Agent<Env, { openCases: number }> {
initialState = { openCases: 0 };

async fileCase(id: string) {
this.sql`INSERT INTO cases (id, status) VALUES (${id}, 'open')`;
this.setState({ openCases: this.state.openCases + 1 });
}
}

Bonus: the human approval gate​

The theme of the event: agents don't fail on capability, they fail on accountability. step.waitForEvent() pauses a Workflow — up to a year, zero cost — until a human decides. Scored on its own rubric dimension.

async run(event: WorkflowEvent<{ id: string }>, step: WorkflowStep) {
const caseFile = await step.do('prepare the case', async () => {
// build the thing a human needs to see, not the thing you'd auto-execute
return { id: event.payload.id, recommendation: 'refund' };
});

const decision = await step.waitForEvent<{ approved: boolean; by: string }>(
'await human decision',
{ type: 'case-decision', timeout: '2 hours' },
);

await step.do('record the outcome', async () => {
// store BOTH caseFile.recommendation and decision — the contrast is the audit trail
});
}

Send the decision from a plain HTTP route (instance.sendEvent() — see cheat sheet). No decision before timeout → Workflow ends without acting. Safe default, not a bug.

The mentor dock (bottom-right, every page) is grounded on this event's docs — if it can't answer confidently, it escalates to a host instead of guessing.

How the mentor's escalation works

Same accountability principle as the approval gate above, aimed at you instead of your users: the mentor would rather admit it doesn't know than guess confidently and be wrong. An escalation joins a queue hosts are watching — keep building while you wait, it's not a blocker.

Ready? Head to Submit once deployed, or back to Build if the clock's still running.