Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions examples/task-runner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Task Runner Example

Demonstrates the Agents SDK task system with two execution modes:

1. **Quick Analysis** (`@task()`) - Runs in the Agent (Durable Object), good for < 30s operations
2. **Deep Analysis** (`workflow()`) - Runs in Cloudflare Workflows, durable for hours/days

## Key Feature: Same Client API

Both modes use the identical client-side API:

```tsx
// Client code - works for both @task() and workflow()
const task = await agent.task<ResultType>("methodName", input);

// Task is reactive - updates automatically
task.status; // "pending" | "running" | "completed" | "failed"
task.progress; // 0-100
task.events; // Real-time events from the task
task.result; // Available when completed
task.abort(); // Cancel the task
```

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│ Client (React) │
│ │
│ const task = await agent.task("quickAnalysis", { repoUrl }) │
│ const task = await agent.task("deepAnalysis", { repoUrl }) │
│ │ │
│ Same API! ↓ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Agent (Durable Object) │
│ │
│ @task() │ @callable() │
│ quickAnalysis() │ deepAnalysis() { │
│ - Runs in DO │ return this.workflow("ANALYSIS_WORKFLOW")│
│ - Fast │ } │
│ - < 30s tasks │ │
│ │ │ │ │
└─────────┼──────────┴───────────┼────────────────────────────────┘
│ │
▼ ▼
[Executes [Dispatches to
in Agent] Workflow]
┌─────────────────────────────────────────────────────────────────┐
│ Cloudflare Workflow │
│ │
│ class AnalysisWorkflow extends WorkflowEntrypoint { │
│ async run(event, step) { │
│ await step.do("fetch", ...); // Durable step │
│ await step.sleep("1 hour"); // Survives restarts │
│ await step.do("analyze", ...); // Auto-retry on failure │
│ } │
│ } │
│ │
│ // Sends updates back to Agent via HTTP callback │
│ notifyAgent({ progress: 50, event: { type: "phase" } }) │
└─────────────────────────────────────────────────────────────────┘
```

## When to Use Each

| Feature | @task() | workflow() |
| ---------- | ------------------- | ------------------------ |
| Duration | Seconds to minutes | Minutes to days |
| Execution | In Durable Object | Separate Workflow engine |
| Durability | Lost on DO eviction | Survives restarts |
| Retries | Manual | Automatic per-step |
| Sleep | Not durable | Durable (can wait hours) |
| Cost | DO compute time | Workflow compute time |

## Setup

1. Install dependencies:

```bash
npm install
```

2. Create `.dev.vars`:

```
OPENAI_API_KEY=sk-...
```

3. Run development server:

```bash
npm run dev
```

4. Open http://localhost:5173

## Server Implementation

```typescript
// Quick task - runs in Agent
@task({ timeout: "5m" })
async quickAnalysis(input: Input, ctx: TaskContext) {
ctx.emit("phase", { name: "fetching" });
ctx.setProgress(10);

// Your logic here...

return result;
}

// Durable task - runs in Workflow
@callable()
async deepAnalysis(input: Input) {
return this.workflow("ANALYSIS_WORKFLOW", input);
}
```

## Workflow Implementation

```typescript
// src/workflows/analysis.ts
import {
WorkflowEntrypoint,
WorkflowStep,
WorkflowEvent
} from "cloudflare:workers";

export class AnalysisWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// Durable step - persisted, auto-retry on failure
const files = await step.do("fetch-repo", async () => {
// This survives worker restarts
return await fetchFiles(event.payload.repoUrl);
});

// Durable sleep - can wait for hours
await step.sleep("rate-limit", "1 hour");

// Step with retry config
const analysis = await step.do(
"analyze",
{ retries: { limit: 3, backoff: "exponential" } },
async () => analyzeFiles(files)
);

return analysis;
}
}
```

## Configuration (wrangler.jsonc)

```jsonc
{
"durable_objects": {
"bindings": [{ "name": "TaskRunner", "class_name": "TaskRunner" }]
},
"workflows": [
{
"name": "analysis-workflow",
"binding": "ANALYSIS_WORKFLOW",
"class_name": "AnalysisWorkflow"
}
]
}
```

## Files

- `src/server.ts` - Agent with both @task() and workflow() methods
- `src/workflows/analysis.ts` - Durable workflow implementation
- `src/App.tsx` - React UI demonstrating both modes
- `wrangler.jsonc` - Cloudflare configuration
23 changes: 23 additions & 0 deletions examples/task-runner/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Task Runner - Agents SDK Example</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: #f5f5f5;
min-height: 100vh;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
26 changes: 26 additions & 0 deletions examples/task-runner/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "task-runner-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"deploy": "wrangler deploy"
},
"dependencies": {
"openai": "^4.77.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.15.3",
"@cloudflare/workers-types": "^4.20251127.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"typescript": "^5.9.3",
"vite": "^7.2.4",
"wrangler": "^4.51.0"
}
}
Loading
Loading