packages/docs/guides/background-tasks.mdx
Some operations take too long for a request/response cycle:
Blocking the main thread kills responsiveness. Polling wastes resources.
<Tip> **Task workers handle the heavy lifting.** Register a worker, create tasks, and ElizaOS executes them in the background with built-in persistence and scheduling. </Tip>import type { TaskWorker } from "@elizaos/core";
const dataSync: TaskWorker = {
name: "SYNC_EXTERNAL_DATA",
async execute(runtime, options, task) {
const { sourceUrl, batchSize = 100 } = options;
// Fetch data from external source
const response = await fetch(sourceUrl);
const data = await response.json();
// Process in batches
for (let i = 0; i < data.length; i += batchSize) {
const batch = data.slice(i, i + batchSize);
await processRecords(runtime, batch);
}
console.log(`Synced ${data.length} records from ${sourceUrl}`);
},
// Optional: validate before creating task
async validate(runtime, message, state) {
return message.content.text?.includes("sync");
},
};
// In your plugin initialization
const plugin: Plugin = {
name: "my-sync-plugin",
async init(runtime) {
// Register the task worker
runtime.registerTaskWorker(dataSync);
},
};
// One-time task
const taskId = await runtime.createTask({
name: "SYNC_EXTERNAL_DATA",
description: "Sync user data from CRM",
roomId: context.roomId,
tags: ["sync", "crm"],
metadata: {
sourceUrl: "https://api.crm.com/users",
batchSize: 50,
},
});
// Recurring task (runs every hour)
await runtime.createTask({
name: "SYNC_EXTERNAL_DATA",
description: "Hourly CRM sync",
tags: ["sync", "crm", "scheduled"],
metadata: {
sourceUrl: "https://api.crm.com/users",
updateInterval: 60 * 60 * 1000, // 1 hour in ms
},
});
flowchart TB
Create["1. CREATE
runtime.createTask()
Task persisted to database"]
Queue["2. QUEUE
Task picker selects tasks
Based on priority, schedule"]
Execute["3. EXECUTE
TaskWorker.execute()
Worker logic runs in background"]
Complete["4. COMPLETE / RESCHEDULE
One-time: Task deleted
Recurring: waits for next interval"]
Create --> Queue
Queue --> Execute
Execute --> Complete
style Create fill:#e3f2fd
style Queue fill:#fff3e0
style Execute fill:#e8f5e9
style Complete fill:#f3e5f5
interface Task {
id?: UUID; // Auto-generated if not provided
name: string; // Must match a registered TaskWorker.name
description: string; // Human-readable purpose
roomId?: UUID; // Associate with a room
worldId?: UUID; // Associate with a world
entityId?: UUID; // Associate with an entity
tags: string[]; // For filtering and organization
metadata?: TaskMetadata; // Custom options and scheduling
updatedAt?: number; // Last execution timestamp
}
type TaskMetadata = {
// Scheduling
updateInterval?: number; // Recurring interval in ms
// UI configuration (optional)
options?: {
name: string;
description: string;
}[];
// Custom data (passed to execute())
[key: string]: unknown;
};
interface TaskWorker {
name: string; // Unique identifier
execute: (
runtime: IAgentRuntime,
options: Record<string, unknown>, // From task.metadata
task: Task,
) => Promise<void>;
validate?: (
// Optional pre-creation check
runtime: IAgentRuntime,
message: Memory,
state: State,
) => Promise<boolean>;
}
// Get tasks by room
const roomTasks = await runtime.getTasks({
roomId: "room-uuid",
});
// Get tasks by tags
const syncTasks = await runtime.getTasks({
tags: ["sync"],
});
// Get tasks by entity
const userTasks = await runtime.getTasks({
entityId: "user-uuid",
});
// Get tasks by name
const allSyncWorkers = await runtime.getTasksByName("SYNC_EXTERNAL_DATA");
// Get specific task
const task = await runtime.getTask("task-uuid");
await runtime.updateTask(taskId, {
description: "Updated description",
metadata: {
...existingMetadata,
sourceUrl: "https://new-api.example.com",
},
});
// Stop a recurring task
await runtime.deleteTask(taskId);
Set metadata.updateInterval to create tasks that run periodically:
// Daily summary at midnight
await runtime.createTask({
name: "DAILY_SUMMARY",
description: "Generate daily activity summary",
tags: ["scheduled", "daily"],
metadata: {
updateInterval: 24 * 60 * 60 * 1000, // 24 hours
},
});
// Check external service every 5 minutes
await runtime.createTask({
name: "HEALTH_CHECK",
description: "Ping external service",
tags: ["scheduled", "monitoring"],
metadata: {
updateInterval: 5 * 60 * 1000, // 5 minutes
serviceUrl: "https://api.example.com/health",
},
});
Handle errors gracefully in your workers:
const robustWorker: TaskWorker = {
name: "ROBUST_SYNC",
async execute(runtime, options, task) {
try {
await performSync(options);
} catch (error) {
// Log the error
console.error(`Task ${task.id} failed:`, error);
// Optionally update task with error info
if (task.id) {
await runtime.updateTask(task.id, {
metadata: {
...task.metadata,
lastError: error.message,
lastErrorAt: Date.now(),
},
});
}
// Re-throw to mark task as failed
// Or swallow to silently continue (for recurring tasks)
throw error;
}
},
};
const scheduleAction: Action = {
name: "SCHEDULE_REPORT",
description: "Schedule a background report generation",
async handler(runtime, message, state) {
const taskId = await runtime.createTask({
name: "GENERATE_REPORT",
description: "Weekly analytics report",
roomId: message.roomId,
tags: ["report", "analytics"],
metadata: {
format: "pdf",
recipients: ["[email protected]"],
},
});
return {
text: `Report scheduled. Task ID: ${taskId}`,
action: "SCHEDULE_REPORT",
success: true,
data: { taskId },
};
},
};
const progressWorker: TaskWorker = {
name: "LONG_PROCESSING",
async execute(runtime, options, task) {
const items = options.items as string[];
let processed = 0;
for (const item of items) {
await processItem(item);
processed++;
// Update progress in metadata
if (task.id && processed % 10 === 0) {
await runtime.updateTask(task.id, {
metadata: {
...task.metadata,
progress: Math.round((processed / items.length) * 100),
},
});
}
}
},
};
const step1Worker: TaskWorker = {
name: "PIPELINE_STEP_1",
async execute(runtime, options, task) {
const result = await doStep1();
// Create next step
await runtime.createTask({
name: "PIPELINE_STEP_2",
description: "Continue pipeline",
roomId: task.roomId,
tags: task.tags,
metadata: {
previousResult: result,
pipelineId: options.pipelineId,
},
});
},
};