OpenClaw has a built-in cron scheduler that can run tasks on a schedule without you building a queue system from scratch. The cron system is separate from the queue processor. It is simpler, more reliable for one-off scheduled tasks, and requires zero custom code. This article walks through the exact steps to set up an openclaw schedule daily task and keep it running reliably over time, from the simplest possible cron job to more advanced patterns like conditional delivery, chained jobs, and testing before you go live.
TL;DR
- Use the built-in cron tool: no external cron daemon, no scripts required.
- Two job types:
agentTurn(isolated session) orsystemEvent(injects into main chat). - Three schedule kinds:
cronexpression,everyinterval,atone-shot. - Delivery modes:
announcesends result to chat,webhookposts to a URL,noneruns silently. - Test before you wait: trigger the job immediately after creating it to verify it works before the first scheduled run.
Throughout this article you will see indented blocks like the ones below. Each one is a command you can paste directly into your OpenClaw chat. Your agent will run it and report back. You do not need to open a terminal or edit any files manually.
When to use cron vs. a queue
OpenClaw has two built-in automation systems. They solve different problems.
- Cron is for scheduled tasks that run at a specific time or interval, independent of anything else. Daily morning brief, weekly digest, monthly cleanup, hourly health check.
- Queue is for sequential tasks that need to be processed in order, with dependencies or priority. Content pipeline, email sequence, multi-step research, task backlog.
If the task is “run this every day at 8am”, cron is correct. If the task is “process these 50 items in order and each step depends on the previous one”, use a queue. Most daily tasks are cron tasks.
I want to schedule a daily task. Should I use cron or build a queue? Describe the tradeoffs and recommend which one fits a single recurring daily task better.
Anatomy of a cron job
Every cron job has four parts: a schedule, a payload, a session target, and a delivery mode. Here is the minimal working example for a daily task:
{
"name": "Daily morning brief",
"schedule": {
"kind": "cron",
"expr": "0 8 * * *",
"tz": "America/New_York"
},
"payload": {
"kind": "agentTurn",
"message": "Generate the morning brief. Check system health, list pending tasks, summarize yesterday. Under 300 words.",
"model": "ollama/phi4:latest"
},
"sessionTarget": "isolated",
"delivery": {
"mode": "announce",
"channel": "telegram",
"to": "YOUR_CHAT_ID"
},
"enabled": true
}
Each field:
- name: Label shown in the cron job list.
- schedule.kind:
"cron"for cron expression,"every"for interval,"at"for one-shot. - schedule.expr: Standard five-field cron expression (minute hour day month weekday).
- schedule.tz: Timezone for the schedule. Defaults to UTC if omitted.
- payload.kind:
"agentTurn"runs an isolated agent."systemEvent"injects text into the main session. - payload.message: The prompt the isolated agent receives.
- payload.model: Which model to use. Defaults to your session default if omitted.
- sessionTarget:
"isolated"for agentTurn."main"for systemEvent. - delivery.mode:
"announce"sends result to chat."webhook"posts to a URL."none"runs silently. - enabled:
trueto activate,falseto define but not run yet.
Show me the cron job JSON for a daily task that runs at 9am America/New_York, uses ollama/phi4:latest, sends the result to my Telegram, and asks the agent to check disk space, memory usage, and pending cron jobs. Do not create the job yet.
Three schedule types
Cron expression (most common)
Five fields: minute, hour, day-of-month, month, day-of-week. Asterisk means every.
{ "kind": "cron", "expr": "0 8 * * *", "tz": "America/New_York" }
Useful patterns:
0 8 * * *, 8am every day0 9 * * 1, 9am every Monday0 0 1 * *, midnight on the 1st of each month*/15 * * * *, every 15 minutes0 9,18 * * 1-5, 9am and 6pm on weekdays
I want a cron expression that fires at 7am and 7pm every weekday (Monday through Friday). Show me the schedule object.
Interval (every N milliseconds)
For tasks that should repeat at a fixed interval regardless of clock time.
{ "kind": "every", "everyMs": 3600000 }
Interval drift
Interval schedules are not adjusted for daylight saving time. If you need a task to stay at a consistent wall-clock time, use a cron expression instead. Use every only when you care about elapsed time, not clock time.
One-shot at a specific time
Runs once at an absolute timestamp, then stops.
{ "kind": "at", "at": "2026-03-25T09:00:00-04:00" }
Use ISO-8601 format. Include the timezone offset or it will be interpreted as UTC.
Payload types: agentTurn vs systemEvent
agentTurn (isolated session)
Spawns a fresh agent session that runs your prompt. Separate from your main conversation, cannot see your current context, and cannot interfere with anything you are working on. The result is sent via your delivery setting.
Use agentTurn when the task needs tools, may take more than a few seconds, or should run regardless of whether you are active.
Create a cron job that runs every day at 8am America/New_York using an agentTurn payload. The agent should check disk and memory usage, and send a Telegram alert if either is above 80%. Use ollama/phi4:latest. Deliver the result to my Telegram. Show the JSON and then create it.
systemEvent (main session injection)
Injects text into your main chat as a system message. Your main agent responds to it in your current context, with access to all your tools and history.
Use systemEvent for simple reminders or prompts you want handled in your normal workspace. The limitation is that it only works reliably when your session is active. If your session is idle, the injection will be queued until your next message.
{
"name": "End-of-day check-in",
"schedule": { "kind": "cron", "expr": "0 17 * * 1-5", "tz": "America/New_York" },
"payload": {
"kind": "systemEvent",
"text": "End of workday. Summarize what was accomplished today and list any open items."
},
"sessionTarget": "main",
"delivery": { "mode": "none" },
"enabled": true
}
For guaranteed execution that does not depend on session state, use agentTurn.
Delivery modes
announce
The job result is sent as a message to a chat channel. The most common mode for daily tasks where you want to see the output without being at your computer.
"delivery": {
"mode": "announce",
"channel": "telegram",
"to": "YOUR_CHAT_ID"
}
Discord also works. Replace "telegram" with "discord" and set "to" to your Discord user ID or channel ID.
webhook
Posts the result as a JSON payload to a URL. Useful for piping cron output into another system (Zapier, n8n, a custom API, a logging endpoint).
"delivery": {
"mode": "webhook",
"url": "https://your-endpoint.com/cron-result"
}
none
Runs silently. No delivery, no message, no log entry in chat. The job executes and the result goes nowhere. Use this for systemEvent jobs that inject into main session (the response appears in chat anyway), or for tasks where you only care about side effects like writing to a file or updating a database.
Testing a cron job before it goes live
Never set up a daily task and just wait until the scheduled time to find out if it works. Test it immediately after creating it.
List all my cron jobs and show me their job IDs. Then trigger the job named “Daily morning brief” immediately so I can verify the output.
If you know the job ID already:
Trigger cron job ID [your-job-id] immediately and show me the output.
What to check in the test output:
- Did the job fire without an error?
- Did the agent complete the task described in the prompt?
- Did the delivery arrive where you expected it?
- Is the output readable and formatted correctly?
- Is the model fast enough for the task, or does it time out?
Test with the model you intend to use in production
If your production job uses ollama/phi4:latest, test with that model. A job that works fine with Sonnet may time out with a local model if the prompt is too complex. The test is not valid unless the model matches.
Managing jobs after creation
Once you have jobs running, you will occasionally need to pause, update, or remove them.
List all my cron jobs with their IDs, schedules, enabled status, and the last time each one ran.
Disable cron job ID [job-id] without deleting it.
Update cron job ID [job-id]: change the schedule to run at 7am instead of 8am, keep everything else the same.
Delete cron job ID [job-id].
If you have many jobs, ask for a summary grouped by category or schedule frequency so you can audit what is running and what is not.
Writing a prompt that actually works at 8am
The quality of the cron output is determined almost entirely by the quality of the prompt. A vague prompt produces a vague output. Cron jobs run without supervision, so there is no opportunity to clarify.
Good cron prompts have four properties:
- Specific output format: Tell the agent exactly how to format the result. “Under 300 words, bullet points, plain text, no headers” is better than “summarize.”
- Explicit scope: Tell the agent what to check and what to ignore. Ambiguity costs tokens and produces noise.
- Failure condition: Tell the agent what to do if a check fails. “If disk usage is above 80%, include a warning at the top in caps.”
- No multi-step approval loops: The isolated session cannot ask you for clarification. Every decision the agent needs to make must be answered in the prompt.
Compare these two prompts:
Weak: “Generate the morning brief.”
Strong: “Check disk usage with df -h, memory usage with free -h, and list any cron jobs that failed in the last 24 hours. Format as: one line per check, WARN: prefix if anything is above 80%. Under 150 words total. If you cannot run a command, note it and continue.”
I want to set up a daily 8am cron job that checks my server health. Write a strong prompt for the agentTurn payload that covers disk, memory, and failed cron jobs, with a specific output format and failure handling instructions. Then show me the full job JSON using that prompt.
Cost optimization for recurring jobs
A daily cron job that runs 365 times per year with a paid API model adds up. The cost of a single run may look small, but multiply it by frequency and by the number of jobs you accumulate.
The cheapest reliable setup for most daily tasks is ollama/phi4:latest. It runs locally, costs nothing per call, and handles most reporting and summarization tasks without issue. The tradeoff is that it is slower than a cloud API and less capable on complex reasoning tasks.
Use a paid model only when:
- The task requires multi-step reasoning or complex tool chains.
- Output quality directly affects something you will act on.
- The local model consistently fails or times out on the task.
For everything else, use local. A health check does not need Claude Sonnet. A morning brief does not need GPT-4. Reserve paid models for tasks where the quality difference is actually visible in the output you act on.
List all my cron jobs. For each one that is using a paid API model, estimate the monthly cost based on the schedule frequency and suggest whether ollama/phi4:latest could handle the same task.
Step-by-step: creating your first daily cron job
Here is the complete sequence from nothing to a working daily job, including verification.
Step 1: Define the job
Start by writing the job JSON before asking the agent to create it. Get the definition right first.
Write a cron job definition for a daily task that runs at 8am America/New_York. The job should ask the agent to check disk usage, memory usage, and list any cron jobs that failed in the last 24 hours. Use ollama/phi4:latest. Send the result to my Telegram. Show me the full JSON without creating the job yet.
Review the JSON before proceeding. Check that the schedule timezone is correct, the model name is spelled exactly right, and the delivery channel and recipient ID match your setup.
Step 2: Create the job
Create the cron job using the JSON we just reviewed. Show me the job ID after it is created.
Save the job ID. You will need it for the test in the next step.
Step 3: Test it immediately
Trigger cron job [job-id] immediately and show me the full output including any errors.
Do not skip this step. Problems caught during testing take two minutes to fix. Problems discovered at 8am the next morning, when you wake up to a broken brief, take longer and start your day badly.
Step 4: Verify delivery
Check that the result actually arrived where you expected it. For Telegram, look at the bot’s conversation with you. For Discord, check the target channel or DM. For webhook, check your endpoint logs.
If the job ran successfully but delivery did not arrive, the problem is almost always an incorrect recipient ID or a misconfigured channel. Check the delivery settings in the job definition.
Read my openclaw.json and tell me my Telegram bot name and my configured chat IDs. Then check if cron job [job-id] delivery is configured correctly to reach me.
Step 5: Confirm the schedule
Show me cron job [job-id] details. What time will it next fire in my local timezone (America/New_York)?
This is the final sanity check. If the next fire time looks wrong, the schedule is misconfigured. Fix it before the next scheduled run.
Common mistakes and how to avoid them
Wrong timezone or no timezone
If you omit tz, the schedule runs in UTC. If you are in America/New_York during daylight saving time, that is UTC-4. A job set for 0 8 * * * with no timezone will fire at 4am your time, not 8am. Always set tz explicitly.
List all my cron jobs. For any that are missing a timezone setting, show me what time they are actually firing in America/New_York.
Prompt too vague for an isolated session
The isolated agent has no conversation history. It does not know your name, your server setup, your project names, or your preferences unless you tell it. A prompt like “check things and let me know” produces nothing useful from an isolated session.
Every detail the agent needs must be in the prompt. Server paths, thresholds, output format, what to do when something fails. Write the prompt as if you are leaving instructions for someone who has never worked with you before.
Using a paid model for simple tasks
A health check that reads disk usage and formats three lines of output does not need a frontier model. It needs a model that can run shell commands and format text. That is well within phi4:latest’s capabilities. Using Claude Sonnet for a daily status check that runs 365 times per year is a cost decision that adds up noticeably over time.
Never testing the job
Creating a job and assuming it will work at the scheduled time is the most common mistake. Always trigger the job manually immediately after creating it. If it works in the test, it will work on schedule. If it does not work in the test, you will find out before it silently fails for a week.
Scheduling too many jobs at the same time
If you have five jobs all firing at 0 8 * * *, they will all spawn isolated sessions simultaneously. On a server with a local model, concurrent sessions compete for memory. Spread them out: 8:00, 8:05, 8:10. The cost is five minutes of delay. The benefit is that each job runs with the full memory budget of the local model.
I have multiple cron jobs scheduled at the same time. Suggest a revised schedule that staggers them by 5 minutes each and show me the updated cron expressions.
Advanced patterns for daily tasks
Conditional delivery: only notify if something is wrong
By default, a daily health check sends a message every day regardless of whether anything is wrong. After a few weeks, you will start ignoring it. A better pattern is to instruct the agent to send a notification only when there is something actionable.
Create a cron job that checks disk usage every day at 6am. Use this prompt: “Check disk usage with df -h. If any filesystem is above 80% used, send me a Telegram message with the full df -h output and the heading DISK ALERT. If everything is below 80%, do nothing and exit silently.” Use delivery mode none, since the agent will send Telegram directly from the prompt.
This approach uses the agent’s judgment to filter noise. You only get a message when something needs your attention.
Daily digest that aggregates data over time
Some daily tasks are more useful when they reference what happened yesterday or last week, not just the current state. The standard pattern for this is file-based state: the daily job writes a summary to a dated file in your workspace, and the next day’s job reads yesterday’s file before generating its output.
Create a cron job that runs at 11pm every day. The prompt should: (1) Read the file at workspace/daily-log/[yesterday’s date].md if it exists. (2) Summarize the key events from that file in three bullet points. (3) Write the summary to workspace/daily-log/[today’s date].md. (4) Send the summary to my Telegram. Use ollama/phi4:latest.
Using environment variables in cron prompts
If you have sensitive values like API keys, server addresses, or recipient IDs that you do not want hardcoded in every prompt, store them in your openclaw.json environment block and reference them by name in the prompt. Ask the agent to read the config at job runtime rather than embedding the values in the job definition.
I want to create a cron job that reads a target URL from my openclaw.json config rather than hardcoding it in the prompt. How would I structure the agentTurn message to read the config value at runtime and use it in the task?
Chained jobs: one job triggers the next
OpenClaw does not have native job chaining, but you can simulate it. Job A writes an output file and sets a flag. Job B runs 10 minutes later, reads the flag, and only proceeds if Job A succeeded. This works because both jobs have access to the same workspace filesystem.
I want two jobs to run in sequence: Job A runs at 8am and writes its output to workspace/job-a-output.txt. Job B runs at 8:10am, reads that file, and sends a summary to Telegram. Create both jobs. In Job B’s prompt, include instructions to handle the case where workspace/job-a-output.txt does not exist.
Troubleshooting a daily job that stops working
Daily cron jobs are reliable when they are set up correctly. When they stop working, the failure is almost always in one of four places: the schedule, the model, the delivery, or the prompt.
The job is not firing at all
Start here:
Show me cron job [job-id] status. When did it last fire? What is its enabled status? What time is the next scheduled run in UTC and in America/New_York?
If the job shows as enabled and the next fire time is in the past, the gateway may have been restarted without recovering the cron schedule. Disable and re-enable the job to force a reschedule.
Disable cron job [job-id], then immediately re-enable it. Show me the updated next fire time.
The job fires but produces no output
The isolated agent ran but produced nothing useful. This is almost always a prompt problem. The model did not understand what was being asked, or the instructions were too vague for an isolated session to execute.
Trigger the job manually and read the raw output carefully. If the output is empty, the model may have timed out. If the output contains an apology or a clarifying question, the prompt is ambiguous. If the output is present but not reaching you, the delivery is misconfigured.
The job fires but delivery fails
The agent completed the task but the result never arrived in Telegram or Discord. Check the delivery configuration first:
Show me the full delivery config for cron job [job-id]. Then test by sending a simple Telegram message to [my-chat-id] to confirm the channel is working.
If the channel test succeeds but cron delivery does not, the job may be using an outdated chat ID from when it was first created. Update the delivery config with the correct current recipient ID.
The model is not available
If you use a local model and Ollama is down or the model is not loaded, the cron job will fail with a model unavailable error. Add a fallback model to the job definition so that if the primary model fails, the job retries with the fallback.
Update cron job [job-id] to add a fallback model. If ollama/phi4:latest is unavailable, fall back to deepseek/deepseek-chat.
Real-world examples: five daily jobs worth setting up
Here are five daily cron jobs that operators actually run in production. Each one is self-contained, uses a local model, and produces output worth receiving every day.
1. Daily server health brief
Fires at 7am. Checks disk, memory, load average, and whether the OpenClaw gateway is responding. Sends a four-line summary to Telegram only if any metric is above threshold.
Create a daily cron job at 7am America/New_York. Prompt: “Run: df -h, free -h, uptime, and curl -s -o /dev/null -w ‘%{http_code}’ http://localhost:18789/health. For each check: output a single line in format CHECK_NAME: STATUS (value). If any check shows disk above 85%, memory above 85%, or gateway not returning 200, prepend the output with ALERT. Send to Telegram. If all clear, send nothing.” Model: ollama/phi4:latest. Delivery: none (agent sends Telegram from prompt).
2. Weekly content queue review
Fires every Monday at 9am. Reads the article queue file, counts pending items, lists the next three by priority, and sends the summary to Telegram. Useful for staying oriented on where the content pipeline stands without opening the workspace.
Create a weekly cron job at 9am America/New_York every Monday. Prompt: “Read workspace/pipeline/ARTICLE-QUEUE.md. Count the total PENDING articles. List the next 3 by priority order with their ID, title, and category. Send to Telegram in this format: Queue: X pending. Next up: (list). Model: ollama/phi4:latest. Delivery: announce to Telegram.
3. Daily memory audit flag
Fires at 10pm. Lists memories stored in the last 24 hours, flags any that look like they might be duplicates of existing entries, and sends the flag list to Telegram. Catches extraction noise before it accumulates.
Create a daily cron job at 10pm America/New_York. Prompt: “Run memory_list with limit=20. Review the most recent 20 entries. Flag any that appear to be duplicates of each other (similar text, same category). List flagged pairs with both memory texts. If no duplicates, reply: No duplicates found. Send to Telegram.” Model: ollama/phi4:latest. Delivery: announce to Telegram.
4. Git commit reminder
Fires at 11pm on weekdays. Checks whether the workspace has uncommitted changes. If yes, sends a reminder to Telegram. If not, sends nothing. Prevents workspace changes from sitting uncommitted overnight.
Create a weekday cron job at 11pm America/New_York. Prompt: “Run: git -C /home/node/.openclaw/workspace status –short. If the output is empty, do nothing and exit. If there are uncommitted changes, send a Telegram message: Uncommitted workspace changes: (list the files). Model: ollama/phi4:latest. Delivery: none.
5. Daily API spend check
Fires at 8pm. Reads the session log or any cost tracking file and estimates the day’s API spend. Sends a brief spend summary to Telegram. Helps catch cost spikes before they accumulate into a large bill at the end of the month.
Create a daily cron job at 8pm America/New_York. Prompt: “Read workspace/memory/[today’s date].md if it exists. Look for any mentions of API spend, model usage, or cost. Summarize in one line: Today’s noted API activity: (summary). If no spend notes found, say: No spend notes today. Send to Telegram.” Model: ollama/phi4:latest. Delivery: announce to Telegram.
Keeping your cron setup healthy over time
Cron jobs accumulate. Operators who have been running OpenClaw for a few months often have a dozen jobs defined, half of which are disabled, three of which are duplicates of each other, and two of which reference file paths that no longer exist. A quarterly audit prevents this from becoming a source of confusion.
List all my cron jobs with their IDs, names, schedules, enabled status, last run time, and model. For any job that has not run in the last 30 days, flag it. For any disabled job, ask me if I want to re-enable or delete it.
Running this every quarter takes five minutes and keeps the job list from becoming noise. A clean cron setup is one where every job is intentional, runs reliably, and produces output you actually read. Every openclaw recurring task you set up should earn its place in the list. The jobs you ignore are not harmless. They consume model time, generate delivery messages that train you to tune out alerts, and make it harder to notice when something genuinely breaks. Trim the list to what you actually use, and the signal-to-noise ratio of your automation stays high.
Frequently asked questions
The questions below cover the gaps that most operators run into when setting up their first daily job or expanding to a larger set of scheduled tasks. Each answer is self-contained and actionable.
What happens if the cron job runs while the server is restarting?
If the gateway is down when a scheduled job fires, the job is skipped. It will not retry automatically. If you need guaranteed execution, check the job list after any restart and manually trigger any jobs that were missed.
Can I have the cron job write to a file instead of sending a message?
Yes. Use an agentTurn payload with a prompt that instructs the agent to write output to a specific file path in the workspace. Set delivery to none. The agent will write the file and exit. You can then read it on demand or have another job process it.
How do I know if a cron job failed?
Check the job status in the cron list. A failed job will show an error status and the last error message. For ongoing visibility without manual checks, set up an announce delivery so you receive a message when the job completes, and include failure handling in the prompt so errors appear in the output explicitly rather than being swallowed silently.
Can I pass output from one daily job into the next day’s run?
Not directly through the cron system. The standard pattern is to have the job write its output or state to a file in the workspace, and have the next run read that file at the start of its prompt. The article on passing output between cron runs covers this in detail.
Can I run multiple daily tasks at the same time?
Yes. Each cron job runs in its own isolated session and does not block others. Scheduling five jobs at 8am will fire all five simultaneously. The only resource constraint is the number of concurrent requests your model provider allows and the available memory for local models.
What is the difference between disabling a job and deleting it?
Disabling sets enabled: false and stops the job from firing, but keeps the definition intact. You can re-enable it later. Deleting removes it entirely. Disable when you want to pause temporarily. Delete when you are done with the job permanently.
Do cron jobs survive an OpenClaw update?
Job definitions are stored in openclaw.json. As long as your config file is preserved across updates, your jobs will be intact. Before any major update, back up openclaw.json.
