Cron Jobs & Sub-Agents

+15 Mana ✨

Introduction

Beyond event-driven hooks, OpenClaw supports time-based automation through cron jobs and multi-agent coordination through sub-agents. These features let you schedule recurring tasks and spawn child sessions that communicate with each other, enabling sophisticated automation workflows.

Key Concepts

  • Cron Tool: A built-in tool that schedules recurring jobs using cron syntax, with jobs persisting across sessions.
  • Job Persistence: Each cron job is stored with a unique key in the format cron:<job.id>, ensuring it survives session restarts.
  • sessions_spawn: A function that creates a child session (sub-agent) that operates independently but can communicate with its parent.
  • sessions_send: A function for sending messages between sessions, enabling inter-agent communication and coordination.

Real World Context

Cron jobs are a cornerstone of Unix system administration, running everything from log rotation to database backups on a schedule. Sub-agents mirror patterns found in microservice architectures and actor systems like Erlang/OTP, where independent processes communicate via message passing. OpenClaw brings both patterns together for AI-powered automation.

Deep Dive

The cron tool lets you define scheduled jobs that run at specified intervals:

typescript
// Creating a cron job using the cron tool
// This job runs every hour and checks for outdated dependencies
await cron.create({
  id: 'dependency-check',
  schedule: '0 * * * *',  // every hour at minute 0
  command: 'check-dependencies',
  context: {
    workspace: '/path/to/project',
    notify: true
  }
});

// The job is persisted with key: cron:dependency-check

The cron.create call registers a job with a unique id, a standard cron schedule expression, the command to execute, and any context the job needs. The job is stored under the key cron:dependency-check and will persist across sessions, meaning it continues to run even after you close and reopen OpenClaw.

You can manage cron jobs throughout their lifecycle:

typescript
// List all registered cron jobs
const jobs = await cron.list();
// Returns: [{ id: 'dependency-check', schedule: '0 * * * *', ... }]

// Get details of a specific job
const job = await cron.get('dependency-check');
// Returns: { id, schedule, command, context, lastRun, nextRun }

// Update a job's schedule
await cron.update('dependency-check', {
  schedule: '0 */6 * * *'  // changed to every 6 hours
});

// Remove a job
await cron.delete('dependency-check');

These management functions give you full control over scheduled jobs. The get method returns metadata including lastRun and nextRun timestamps, which are useful for monitoring. The persistence key format cron:<job.id> means each job has a unique, predictable identifier.

Cron jobs can run in two modes: in the main session (where the job's output is added to the next heartbeat turn) or in an isolated session (where the job gets its own fresh context window). Isolated sessions are useful for heavy or long-running tasks that should not pollute the main conversation context. You configure this per-job when creating the cron entry.

For multi-agent coordination, sessions_spawn creates child sessions:

typescript
// Spawn a sub-agent for a specific task
const childSession = await sessions_spawn({
  name: 'code-reviewer',
  instructions: 'Review the staged changes for code quality issues',
  context: {
    files: stagedFiles,
    rules: codingStandards
  }
});

// The child session runs independently with its own context
console.log(`Spawned session: ${childSession.sessionKey}`);

The sessions_spawn function creates a new independent session with its own name, instructions, and context. The child agent operates autonomously but returns a sessionKey that the parent can use to communicate with it. This is powerful for delegating specialized tasks like code review, testing, or documentation generation.

To coordinate between sessions, use sessions_send:

typescript
// Parent sends a message to the child session
await sessions_send({
  targetSession: childSession.sessionKey,
  message: {
    type: 'task',
    payload: {
      action: 'review-file',
      filePath: '/src/utils/parser.ts'
    }
  }
});

// Child session can send results back to parent
// (inside the child's handler)
await sessions_send({
  targetSession: parentSessionKey,
  message: {
    type: 'result',
    payload: {
      status: 'complete',
      issues: reviewFindings
    }
  }
});

The sessions_send function takes a targetSession key and a message object. Messages can flow in both directions: parent to child and child back to parent. The message structure is flexible, using type and payload fields that you define. This bidirectional communication enables patterns like fan-out/fan-in where a parent spawns multiple children, collects their results, and synthesizes a final output.

Combining cron and sub-agents creates powerful automated workflows:

typescript
// A cron job that spawns a sub-agent for nightly analysis
await cron.create({
  id: 'nightly-analysis',
  schedule: '0 2 * * *',  // 2 AM daily
  command: 'run-analysis',
  context: {
    async handler(event) {
      const analyst = await sessions_spawn({
        name: 'nightly-analyst',
        instructions: 'Analyze codebase health metrics'
      });
      await sessions_send({
        targetSession: analyst.sessionKey,
        message: { type: 'task', payload: { scope: 'full' } }
      });
    }
  }
});

This example creates a daily cron job at 2 AM that spawns a sub-agent to perform codebase analysis. The cron job handles the scheduling, while the sub-agent handles the specialized work. This separation of concerns keeps each component simple and focused.

Common Pitfalls

  • Orphaned sub-agents: Spawning child sessions without tracking their session keys can lead to abandoned sessions that consume resources.
  • Cron schedule confusion: Forgetting that cron expressions use minute-hour-day order (not hour-minute-day) leads to jobs running at unexpected times.
  • Missing message handlers: Sending messages to a session that does not have a handler for that message type will silently drop the message.

Best Practices

  • Always store child session keys and implement cleanup logic to terminate sub-agents when their work is complete.
  • Use descriptive cron job IDs that reflect their purpose, making cron.list() output self-documenting.
  • Design message types as a clear contract between parent and child sessions, documenting expected payloads in both directions.

Summary

  • The cron tool schedules recurring jobs with standard cron syntax, persisted under cron:<job.id> keys.
  • Cron jobs survive session restarts and provide lastRun/nextRun tracking.
  • sessions_spawn creates independent child sessions with their own context and instructions.
  • sessions_send enables bidirectional message passing between parent and child sessions.
  • Combining cron jobs with sub-agents enables sophisticated automated workflows like nightly analysis or scheduled code reviews.
✓ Completed