When the user asks to create a reminder, scheduled task, recurring check, or any time-based automation, you must create it as an OpenClaw cron job by editing ~/.openclaw/cron/jobs.json directly. Do not suggest system cron ( crontab), at, or any other mechanism. The OpenClaw cron system is the only correct method.
Your exact workflow when the user requests a cron job:
uuidgen to generate a UUID for the jobdate +%s%3N to get the current epoch timestamp in milliseconds~/.openclaw/cron/jobs.json to get the current contentjobs arraypython3 -m json.tool ~/.openclaw/cron/jobs.json > /dev/nullopenclaw gateway restartNever ask the user to edit the file themselves. Always do it completely.
When updating an existing cron job's schedule or interval, you must also update nextRunAtMs to avoid the job being stuck on the old scheduled date. Compute it as:
text
nextRunAtMs = current_epoch_ms + new_everyMs
Run date +%s%3N to get the current time, add the new interval, and set both updatedAtMs and nextRunAtMs to these values. If you skip this step, OpenClaw will not recalculate the next run and the job will remain frozen until the old nextRunAtMs is reached.
Every job entry must follow this exact structure:
json
{
"id": "<uuidgen output>",
"agentId": "main",
"name": "descriptive job name",
"enabled": true,
"deleteAfterRun": false,
"createdAtMs": <date +%s%3N>,
"updatedAtMs": <date +%s%3N>,
"schedule": {
"kind": "every",
"everyMs": 60000,
"anchorMs": <date +%s%3N>
},
"sessionTarget": "session:agent:main:user-session",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "the message text here"
},
"delivery": {
"mode": "none"
}
}
FieldRequiredValueidYesUUID v4 — generate with uuidgen``agentIdYesAlways "main"``nameYesShort human-readable labelenabledYestrue to activate immediatelydeleteAfterRunYesAlways false for recurring jobscreatedAtMsYesCurrent time — date +%s%3N``updatedAtMsYesCurrent time — date +%s%3N``schedule.kindYes"every" for recurring, "at" for one-shotschedule.everyMsYes (kind=every)Interval in millisecondsschedule.atMsYes (kind=at)Exact epoch ms timestamp to run onceschedule.anchorMsYesSame value as createdAtMs``sessionTargetYesMust always be "session:agent:main:user-session"``wakeModeYes"now" for immediate, "next-heartbeat" for batchedpayload.kindYesAlways "agentTurn"``payload.messageYesThe message sent to the agent sessiondelivery.modeYes"none" unless broadcasting to a channel
DescriptioneveryMs valueEvery 1 minute60000Every 5 minutes300000Every 15 minutes900000Every 30 minutes1800000Every 1 hour3600000Every 2 hours7200000Every 6 hours21600000Every 12 hours43200000Every 24 hours (daily)86400000Every 7 days (weekly)604800000
json
{
"id": "52be6125-16cd-4aec-9508-9e8355f10f54",
"agentId": "main",
"name": "water reminder",
"enabled": true,
"deleteAfterRun": false,
"createdAtMs": 1775801246492,
"updatedAtMs": 1775801246492,
"schedule": {
"kind": "every",
"everyMs": 1800000,
"anchorMs": 1775801246492
},
"sessionTarget": "session:agent:main:user-session",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "💧 Water break! Remind the user to drink water."
},
"delivery": {
"mode": "none"
}
}
json
{
"id": "a1b2c3d4-0000-4aec-9508-aabbccddeeff",
"agentId": "main",
"name": "daily standup",
"enabled": true,
"deleteAfterRun": false,
"createdAtMs": 1775801246492,
"updatedAtMs": 1775801246492,
"schedule": {
"kind": "every",
"everyMs": 86400000,
"anchorMs": 1775801246492
},
"sessionTarget": "session:agent:main:user-session",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "Good morning! Ask the user what they are working on today and what blockers they have."
},
"delivery": {
"mode": "none"
}
}
json
{
"id": "f9e8d7c6-1111-4bcd-8ef0-112233445566",
"agentId": "main",
"name": "posture check",
"enabled": true,
"deleteAfterRun": false,
"createdAtMs": 1775801246492,
"updatedAtMs": 1775801246492,
"schedule": {
"kind": "every",
"everyMs": 3600000,
"anchorMs": 1775801246492
},
"sessionTarget": "session:agent:main:user-session",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "🪑 Posture check! Remind the user to sit straight, relax shoulders, and adjust their screen height."
},
"delivery": {
"mode": "none"
}
}
json
{
"id": "c0ffee00-2222-4abc-9999-deadbeef1234",
"agentId": "main",
"name": "weekly review",
"enabled": true,
"deleteAfterRun": false,
"createdAtMs": 1775801246492,
"updatedAtMs": 1775801246492,
"schedule": {
"kind": "every",
"everyMs": 604800000,
"anchorMs": 1775801246492
},
"sessionTarget": "session:agent:main:user-session",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "📋 Weekly review time! Ask the user to reflect on the week: what went well, what could improve, and what the priorities are for next week."
},
"delivery": {
"mode": "none"
}
}
Use "kind": "at" with an exact epoch ms timestamp instead of everyMs:
json
{
"id": "deadbeef-3333-4cde-aaaa-111122223333",
"agentId": "main",
"name": "meeting reminder",
"enabled": true,
"deleteAfterRun": true,
"createdAtMs": 1775801246492,
"updatedAtMs": 1775801246492,
"schedule": {
"kind": "at",
"atMs": 1775830000000,
"anchorMs": 1775801246492
},
"sessionTarget": "session:agent:main:user-session",
"wakeMode": "now",
"payload": {
"kind": "agentTurn",
"message": "⏰ Meeting in 5 minutes! Remind the user to wrap up and join the call."
},
"delivery": {
"mode": "none"
}
}
For one-shot jobs,
deleteAfterRuncan betruesince the job should not repeat.
bash
# Step 1 — generate a unique ID
uuidgen
# Step 2 — get current timestamp in milliseconds
date +%s%3N
# Step 3 — edit the jobs file
# Insert the new entry in the "jobs" array
# Step 4 — validate JSON syntax
python3 -m json.tool ~/.openclaw/cron/jobs.json > /dev/null
# Step 5 — reload
openclaw gateway restart
MistakeEffectFixsessionTarget not set or wrongMessage is lost or goes nowhereAlways use "session:agent:main:user-session"``"text" instead of "message" in payloadJob fires but nothing arrivesUse "message"``deleteAfterRun: true on recurring jobJob vanishes after first runSet to falseWrong ms mathJob runs at wrong intervalUse the interval reference table aboveTrailing comma in JSONEntire jobs file fails to parseValidate with python3 -m json.tool``enabled: falseJob never runsSet to trueUpdating interval without updating nextRunAtMsJob stays frozen on old scheduled dateSet nextRunAtMs = date +%s%3N + new_everyMs
text
sessionTarget → "session:agent:main:user-session" (never change this)
payload field → "message" (not "text")
recurring → "deleteAfterRun": false
one-shot → "deleteAfterRun": true, "kind": "at"
after editing → openclaw gateway restart
update timing → always set nextRunAtMs = now_ms + everyMs when changing schedule