Overview
The TaskService schedules and executes tasks automatically based on time intervals or triggers. It provides a robust task execution system with support for recurring tasks, one-time tasks, validation, and blocking/non-blocking execution modes.
Key Features
- Recurring tasks: Execute tasks on a regular interval
- One-time tasks: Execute tasks once and auto-delete
- Task validation: Validate tasks before execution
- Blocking control: Prevent overlapping task runs
- Priority queuing: Execute tasks based on timing and priority
- Worker registration: Extensible task worker system
- Automatic cleanup: Auto-delete completed one-time tasks
Service Lifecycle
Starting the Service
import { TaskService } from "@elizaos/core";
const service = await TaskService.start(runtime);
The service automatically:
- Starts the processing timer (checks every 1 second)
- Begins monitoring for scheduled tasks
- Executes tasks when their intervals elapse
Stopping the Service
await service.stop();
// or
await TaskService.stop(runtime);
On stop, the service:
- Clears the processing interval
- Stops checking for tasks
- Clears the executing tasks set
Task Types
Recurring Tasks
Tasks that execute repeatedly at a specified interval.
await runtime.createTask({
name: "DAILY_REPORT",
description: "Generate daily report",
metadata: {
updatedAt: Date.now(),
updateInterval: 24 * 60 * 60 * 1000, // 24 hours
},
tags: ["queue", "repeat"],
});
Key characteristics:
- Must include
"repeat" tag
- Requires
updateInterval in metadata (in milliseconds)
- Updates
updatedAt timestamp after each execution
- Never auto-deleted
One-Time Tasks
Tasks that execute once and are automatically deleted.
await runtime.createTask({
name: "SEND_WELCOME_EMAIL",
description: "Send welcome email to new user",
metadata: {
updatedAt: Date.now(),
userId: "user123",
},
tags: ["queue"], // No "repeat" tag
});
Key characteristics:
- Must NOT include
"repeat" tag
- Executes as soon as possible
- Automatically deleted after execution
- Useful for deferred operations
Recurring tasks that execute immediately on first run.
await runtime.createTask({
name: "IMMEDIATE_TASK",
description: "Execute immediately, then repeat",
metadata: {
createdAt: Date.now(),
updatedAt: Date.now(),
updateInterval: 60 * 1000, // 1 minute
},
tags: ["queue", "repeat", "immediate"],
});
Key characteristics:
- Includes both
"repeat" and "immediate" tags
- Executes immediately on first check
- Then follows normal interval schedule
Creating and Managing Tasks
Create Task
const taskId = await runtime.createTask({
name: string,
description: string,
roomId?: UUID,
entityId?: UUID,
tags: string[],
metadata?: Record<string, unknown>,
});
Unique identifier for the task type (must match a registered worker)
Human-readable description of the task
Optional room context for the task
Optional entity associated with the task
Task tags. Must include "queue" for TaskService to process.Common tags:
"queue": Required for service to process
"repeat": Makes task recurring
"immediate": Executes immediately on first run
Task metadata and configurationCommon fields:
updatedAt: Timestamp of last execution (required)
updateInterval: Milliseconds between executions (for recurring tasks)
blocking: Whether to prevent overlapping runs (default: true)
- Any custom data for the task worker
Update Task
await runtime.updateTask(taskId, {
metadata: {
updatedAt: Date.now(),
// other metadata updates
},
});
Delete Task
await runtime.deleteTask(taskId);
Get Tasks
// Get all queued tasks
const tasks = await runtime.getTasks({ tags: ["queue"] });
// Get tasks by name
const tasks = await runtime.getTasksByName("MY_TASK");
// Get tasks for a specific room
const tasks = await runtime.getTasks({ roomId: "room-id" });
Task Workers
TaskWorker Interface
interface TaskWorker {
name: string;
validate?: (
runtime: IAgentRuntime,
message: Memory,
state: State,
) => Promise<boolean>;
execute: (
runtime: IAgentRuntime,
options: Record<string, JsonValue | object>,
task: Task,
) => Promise<void>;
}
Register Task Worker
runtime.registerTaskWorker({
name: "MY_TASK",
validate: async (runtime, message, state) => {
// Optional: Validate before execution
// Return false to skip execution
return true;
},
execute: async (runtime, options, task) => {
// Task execution logic
console.log("Executing task:", task.name);
console.log("Options:", options);
// Access task metadata
const interval = options.updateInterval as number;
// Perform work
await doWork();
},
});
Example: Recurring Cleanup Task
// Register worker
runtime.registerTaskWorker({
name: "CLEANUP_OLD_MESSAGES",
validate: async (runtime, message, state) => {
// Only run during off-peak hours (example)
const hour = new Date().getHours();
return hour >= 2 && hour <= 5; // 2am - 5am
},
execute: async (runtime, options, task) => {
runtime.logger.info("Starting cleanup task");
const cutoffDate = Date.now() - (30 * 24 * 60 * 60 * 1000); // 30 days
// Clean up old messages
const deleted = await runtime.deleteMemoriesOlderThan(
"messages",
cutoffDate
);
runtime.logger.info(`Deleted ${deleted} old messages`);
},
});
// Create recurring task
await runtime.createTask({
name: "CLEANUP_OLD_MESSAGES",
description: "Clean up messages older than 30 days",
metadata: {
updatedAt: Date.now(),
updateInterval: 24 * 60 * 60 * 1000, // Run daily
},
tags: ["queue", "repeat"],
});
Example: One-Time Notification
// Register worker
runtime.registerTaskWorker({
name: "SEND_NOTIFICATION",
execute: async (runtime, options, task) => {
const userId = options.userId as string;
const message = options.message as string;
await sendNotification(userId, message);
runtime.logger.info(`Notification sent to ${userId}`);
},
});
// Create one-time task
await runtime.createTask({
name: "SEND_NOTIFICATION",
description: "Send notification to user",
metadata: {
updatedAt: Date.now(),
userId: "user123",
message: "Your report is ready!",
},
tags: ["queue"], // No "repeat" - executes once and deletes
});
Blocking vs Non-Blocking
Blocking Mode (Default)
Prevents overlapping executions of the same task.
await runtime.createTask({
name: "LONG_TASK",
description: "Task that takes a while",
metadata: {
updatedAt: Date.now(),
updateInterval: 60 * 1000, // 1 minute
blocking: true, // Default
},
tags: ["queue", "repeat"],
});
Behavior:
- If task is still running when interval elapses, skip the next run
- Prevents overlapping executions
- Useful for tasks that must complete before running again
Non-Blocking Mode
Allows overlapping executions of the same task.
await runtime.createTask({
name: "CONCURRENT_TASK",
description: "Task that can run concurrently",
metadata: {
updatedAt: Date.now(),
updateInterval: 60 * 1000,
blocking: false, // Allow overlapping runs
},
tags: ["queue", "repeat"],
});
Behavior:
- Multiple instances can run simultaneously
- Useful for independent operations
- Be careful with shared resources
Task Execution Flow
Processing Loop
Every 1 second (TICK_INTERVAL):
1. Get all tasks with "queue" tag
2. Validate tasks (run validate() if defined)
3. For each valid task:
a. Check if it's a one-time task → execute immediately
b. Check if it's recurring:
- If "immediate" tag and first run → execute
- Check if interval elapsed → execute
c. For blocking tasks, check if already executing → skip
4. Execute task via registered worker
5. For recurring tasks, update timestamp
6. For one-time tasks, delete after execution
Execution Steps
For each task:
1. Check if task has ID (skip if not)
2. Get registered worker for task.name (skip if not found)
3. Run worker.validate() if defined (skip if returns false)
4. Mark task as executing (add to executingTasks set)
5. For recurring tasks:
- Update task.metadata.updatedAt to current time
6. Execute worker.execute(runtime, metadata, task)
7. For one-time tasks:
- Delete task after execution
8. Remove from executingTasks set
9. Log execution duration
Common Use Cases
Periodic Data Sync
runtime.registerTaskWorker({
name: "SYNC_DATA",
execute: async (runtime, options, task) => {
const endpoint = options.endpoint as string;
const data = await fetchExternalData(endpoint);
await runtime.updateKnowledge(data);
},
});
await runtime.createTask({
name: "SYNC_DATA",
description: "Sync data from external API",
metadata: {
updatedAt: Date.now(),
updateInterval: 15 * 60 * 1000, // 15 minutes
endpoint: "https://api.example.com/data",
},
tags: ["queue", "repeat"],
});
Scheduled Reports
runtime.registerTaskWorker({
name: "DAILY_REPORT",
execute: async (runtime, options, task) => {
const report = await generateReport(runtime);
await sendReportToAdmin(report);
},
});
await runtime.createTask({
name: "DAILY_REPORT",
description: "Generate and send daily report",
metadata: {
updatedAt: Date.now(),
updateInterval: 24 * 60 * 60 * 1000, // 24 hours
},
tags: ["queue", "repeat"],
});
Delayed Actions
runtime.registerTaskWorker({
name: "DELAYED_MESSAGE",
execute: async (runtime, options, task) => {
const roomId = options.roomId as UUID;
const message = options.message as string;
await runtime.sendMessage(roomId, message);
},
});
// Send message after 5 minutes
await runtime.createTask({
name: "DELAYED_MESSAGE",
description: "Send delayed message",
metadata: {
updatedAt: Date.now() + (5 * 60 * 1000), // 5 minutes from now
roomId: "room-id",
message: "This is a delayed message!",
},
tags: ["queue"], // One-time task
});
Resource Monitoring
runtime.registerTaskWorker({
name: "MONITOR_RESOURCES",
execute: async (runtime, options, task) => {
const usage = await getResourceUsage();
if (usage.memory > 0.9) {
await alertAdmins("High memory usage: " + usage.memory);
}
// Log metrics
await runtime.log({
entityId: runtime.agentId,
roomId: runtime.agentId,
type: "resource_usage",
body: usage,
});
},
});
await runtime.createTask({
name: "MONITOR_RESOURCES",
description: "Monitor system resources",
metadata: {
updatedAt: Date.now(),
updateInterval: 30 * 1000, // Every 30 seconds
},
tags: ["queue", "repeat", "immediate"],
});
Advanced Configuration
Custom Tick Interval
The service checks for tasks every 1 second by default:
class TaskService extends Service {
private readonly TICK_INTERVAL = 1000; // milliseconds
}
To modify, extend the service:
class CustomTaskService extends TaskService {
private readonly TICK_INTERVAL = 500; // Check every 500ms
}
Dynamic Intervals
Change task intervals dynamically:
runtime.registerTaskWorker({
name: "ADAPTIVE_TASK",
execute: async (runtime, options, task) => {
await doWork();
// Adjust interval based on conditions
const newInterval = calculateOptimalInterval();
if (task.id) {
await runtime.updateTask(task.id, {
metadata: {
...task.metadata,
updateInterval: newInterval,
},
});
}
},
});
Conditional Execution
runtime.registerTaskWorker({
name: "CONDITIONAL_TASK",
validate: async (runtime, message, state) => {
// Complex validation logic
const config = await runtime.getConfig();
// Only run if feature is enabled
if (!config.featureEnabled) {
return false;
}
// Only run during business hours
const hour = new Date().getHours();
if (hour < 9 || hour > 17) {
return false;
}
return true;
},
execute: async (runtime, options, task) => {
// Task logic
},
});
Monitoring and Debugging
Logging
The service provides detailed logs:
// Task validation
"Validating repeating test task" // { agentId }
// Task execution
"Executing task" // { agentId, taskName, taskId }
"Task execution completed" // { agentId, taskName, taskId, durationMs }
// Recurring task updates
"Updated repeating task with new timestamp" // { agentId, taskName, taskId }
// One-time task cleanup
"Deleted non-repeating task after execution" // { agentId, taskName, taskId }
// Blocking
"Skipping task - already executing (blocking enabled)" // { agentId, taskName, taskId }
// Interval elapsed
"Executing task - interval elapsed" // { agentId, taskName, intervalMs }
// Immediate execution
"Immediately running task" // { agentId, taskName }
// No worker found
"No worker found for task type" // { agentId, taskName }
Task Dashboard
async function showTaskDashboard(runtime: IAgentRuntime) {
const tasks = await runtime.getTasks({ tags: ["queue"] });
console.log(`\n=== Task Dashboard ===");
console.log(`Total queued tasks: ${tasks.length}\n`);
for (const task of tasks) {
const isRecurring = task.tags?.includes("repeat");
const interval = task.metadata?.updateInterval as number | undefined;
const lastRun = task.metadata?.updatedAt as number | undefined;
const nextRun = lastRun && interval ? lastRun + interval : undefined;
const timeUntilNext = nextRun ? nextRun - Date.now() : undefined;
console.log(`Task: ${task.name}`);
console.log(` Description: ${task.description}`);
console.log(` Type: ${isRecurring ? 'Recurring' : 'One-time'}`);
if (interval) {
console.log(` Interval: ${interval / 1000}s`);
}
if (timeUntilNext !== undefined) {
if (timeUntilNext <= 0) {
console.log(` Next run: Ready to execute`);
} else {
console.log(` Next run: in ${Math.floor(timeUntilNext / 1000)}s`);
}
} else {
console.log(` Next run: Immediate`);
}
console.log("");
}
}
Best Practices
Task Design
- Clear naming: Use descriptive, unique task names
- Idempotency: Design tasks to be safely re-run
- Error handling: Catch and log errors within workers
- Appropriate intervals: Don’t over-schedule tasks
- Use blocking mode for resource-intensive tasks
- Set reasonable intervals (avoid sub-second for most tasks)
- Validate efficiently (validation runs every tick)
- Clean up one-time tasks promptly
Reliability
- Always include error handling in workers
- Log task execution for audit trails
- Use validation to prevent execution in bad states
- Monitor task completion times
Resource Management
- Be mindful of blocking vs non-blocking
- Limit concurrent task execution if needed
- Clean up resources in finally blocks
- Monitor executingTasks set size
Troubleshooting
Task Not Executing
- Verify task has
"queue" tag
- Check if worker is registered for task name
- Review validate() function (may be returning false)
- Check if blocking is preventing execution
- Verify interval has elapsed for recurring tasks
Task Executing Too Often
- Check updateInterval value
- Verify updatedAt is being set correctly
- Ensure blocking is enabled if needed
- Review TICK_INTERVAL setting
Task Validation Always Failing
- Add logging to validate() function
- Check validation logic for errors
- Verify runtime state is correct
- Test validation conditions independently
Memory Growing
- Ensure one-time tasks are being deleted
- Check for orphaned tasks (no worker)
- Monitor executingTasks set
- Review task metadata size