Manage scheduled reminders (crons) — list, add, delete, pause, resume, reconcile, or import from OpenClaw. Triggers on /agent:crons, /agent:reminders, "list reminders", "show crons", "recordatorios", "mis crons", "mis recordatorios", "import crons", "importar crons", "traer crons".
This workspace maintains a persistent cron registry at memory/crons.json — the source of truth for every scheduled task across sessions. The SessionStart hook reconciles it against the live harness; the PostToolUse hook captures ad-hoc CronCreate/CronDelete calls automatically.
All writes to the registry go through one script: bash ${CLAUDE_PLUGIN_ROOT}/skills/crons/writeback.sh <subcommand>. Never edit memory/crons.json by hand.
CronCreate / CronList / CronDelete are deferred tools — call ToolSearch(query="select:CronList,CronCreate,CronDelete") once per session before invoking them. The parameter name is cron, not schedule. Always pass durable: true (forward-compat for when the upstream flag is fixed).
Parse the user's phrasing and route to the right flow:
| Phrasing |
|---|
| Subcommand |
|---|
/agent:crons list, /agent:reminders, "list reminders", "show crons", "recordatorios", "mis crons", or no args (registry populated) | LIST |
/agent:crons add <cron> <prompt>, "add reminder", "agrega un recordatorio" | ADD |
| `/agent:crons delete <key | N>`, "delete reminder X", "borra el recordatorio X" |
/agent:crons pause <key>, "pausa reminder X" | PAUSE |
/agent:crons resume <key>, "reanuda X" | RESUME |
/agent:crons reconcile, "reconcile" | RECONCILE |
/agent:crons import, "importar crons", "traer crons", or no args (OpenClaw source present + registry empty) | IMPORT |
Call CronList. Output is plain text, one line per live job:
<8hex-id> — <cron-expr> (recurring|one-shot) [session-only|durable]: <prompt>
Empty state is the literal string No scheduled jobs..
Read the registry:
cat "$CLAUDE_PROJECT_DIR/memory/crons.json" | jq -c '.entries[]'
For each registry entry (skip tombstone != null unless the user asks for audit):
harnessTaskId appears in CronList.paused: true.tombstone != null (only show if user asked).Render a compact table. Example:
# STATUS KEY CRON PROMPT
1 ✅ heartbeat-default */30 * * * * Run /agent:heartbeat
2 ✅ dreaming-default 0 3 * * * Use the dream tool...
3 ⏸ harness-abc12345 0 9 * * * recordame estirarme
If the user's request is ambiguous (e.g. "remind me tomorrow" without a time), use AskUserQuestion to pin down the cron expression:
question: "A qué hora del día?",
options: [ "09:00", "14:00", "18:00", "otra" ]
Call CronCreate(cron, prompt, durable=true, recurring). The PostToolUse hook captures it automatically into the registry with source: ad-hoc and key harness-<task_id>. No extra writeback call needed.
Confirm to the user:
✅ Listo. Agendado "<prompt>" para <cron>. Va a sobrevivir cierres de sesión.
Resolve the user's target to a registry key. Accept either:
harness-abc12345)list outputRead registry to find the entry's harnessTaskId and display the cron + prompt.
Use AskUserQuestion to confirm:
question: "¿Borrar el recordatorio '<prompt>' (<cron>)?",
header: "Confirmación",
options:
- label: "Sí, borrar"
- label: "Cancelar"
If confirmed:
CronDelete(id=<harnessTaskId>). PostToolUse auto-tombstones on success.harnessTaskId (already dead / paused), manually tombstone it via:
bash "$CLAUDE_PLUGIN_ROOT/skills/crons/writeback.sh" tombstone --key <key>
Pause keeps the registry entry but removes the cron from the harness. Useful for "silence this reminder but don't lose it".
Touch the suppression marker so PostToolUse won't tombstone on the upcoming CronDelete:
touch "$CLAUDE_PROJECT_DIR/memory/.reconciling"
Read the entry's current harnessTaskId. If non-null, call CronDelete(id=<id>).
Mark paused in the registry:
bash "$CLAUDE_PLUGIN_ROOT/skills/crons/writeback.sh" pause --key <key>
Remove the marker:
rm -f "$CLAUDE_PROJECT_DIR/memory/.reconciling"
Confirm: ⏸ <key> pausado. Usa "/agent:crons resume <key>" para reactivar.
Touch the suppression marker:
touch "$CLAUDE_PROJECT_DIR/memory/.reconciling"
Read the entry's cron, prompt, recurring from the registry.
Call CronCreate(cron, prompt, durable=true, recurring). Capture the new 8hex task_id from the response.
Record the new link:
bash "$CLAUDE_PLUGIN_ROOT/skills/crons/writeback.sh" resume --key <key> --harness-task-id <new_task_id>
Remove the marker:
rm -f "$CLAUDE_PROJECT_DIR/memory/.reconciling"
Confirm: ▶️ <key> reactivado.
Run the same logic as SessionStart reconcile, on demand:
CronList.harnessTaskId is not in CronList output: CronCreate + writeback.sh set-alive.writeback.sh adopt-unknown to capture any live-but-unknown crons.Used when the user has an existing ~/.openclaw/cron/jobs.json from a previous OpenClaw agent and wants to port those reminders into this workspace.
Imports need custom registry keys (openclaw-<uuid>) and source: openclaw-import. Since PostToolUse would otherwise capture each CronCreate as source: ad-hoc with harness-<id> key, suppress it for the duration:
touch "$CLAUDE_PROJECT_DIR/memory/.reconciling"
Remove the marker at the end of the batch (or via trap on error).
cat ~/.openclaw/cron/jobs.json
The file shape is {"version": 1, "jobs": [...]}. Each job:
{
"id": "uuid",
"agentId": "main",
"name": "Job Name",
"enabled": true,
"schedule": { "kind": "cron", "expr": "0 14 * * 3,6", "tz": "America/Santiago" },
"payload": { "kind": "agentTurn", "message": "prompt here", "model": "opus" },
"delivery": { "mode": "announce", "channel": "whatsapp" }
}
Iterate data["jobs"], not data.
Use the active agent's ID (check IDENTITY.md or agent-config.json). If ambiguous, ask the user with AskUserQuestion.
HARD_RED='sessions_spawn|gateway config\.patch|http://192\.168\.|canvas\(|remindctl|wacli|openclaw gateway|HEARTBEAT_OK|NO_REPLY|peekaboo'
SOFT_YELLOW='sessions_send|message\(|~/\.openclaw/|\.openclaw/credentials'
| Tier | Criteria | Action |
|---|---|---|
| 🟢 GREEN | enabled: true, kind: cron, payload.kind: agentTurn, no HARD_RED match, delivery.channel plugin installed (if any) | Import as-is |
| 🟡 YELLOW | kind: at with future timestamp, kind: every (convertible), uninstalled channel, or SOFT_YELLOW match | Import with adapted prompt + fallback note |
| 🔴 RED | enabled: false, expired at, kind: systemEvent, or HARD_RED match | Skip. Record reason. |
Record the specific reason per item (which token matched which field).
AskUserQuestion