Cron jobs (Gateway scheduler)

Cron hay Heartbeat? Xem Cron vs Heartbeat để biết khi nào nên dùng cái nào.

Cron là scheduler tích hợp sẵn của Gateway. Nó lưu trữ các jobs, đánh thức agent đúng thời điểm, và có thể gửi kết quả về chat.

Nếu các bạn muốn “chạy cái này mỗi sáng” hoặc “nhắc agent sau 20 phút”, thì cron chính là cơ chế để làm việc đó.

TL;DR

  • Cron chạy bên trong Gateway (không chạy trong model).
  • Jobs được lưu dưới ~/.openclaw/cron/ nên khi restart sẽ không mất lịch.
  • Hai kiểu thực thi:
    • Main session: đưa system event vào hàng đợi, rồi chạy ở heartbeat tiếp theo.
    • Isolated: chạy một agent turn riêng trong cron:<jobId>, có thể gửi output ra ngoài.
  • Wakeups là tính năng chính thức: một job có thể yêu cầu “wake now” hoặc “next heartbeat”.

Bắt đầu nhanh (thực hành)

Tạo một reminder chạy một lần, kiểm tra nó tồn tại, và chạy ngay:

openclaw cron add \
  --name "Reminder" \
  --at "2026-02-01T16:00:00Z" \
  --session main \
  --system-event "Reminder: check the cron docs draft" \
  --wake now \
  --delete-after-run

openclaw cron list
openclaw cron run <job-id> --force
openclaw cron runs --id <job-id>

Lên lịch một isolated job lặp lại với delivery:

openclaw cron add \
  --name "Morning brief" \
  --cron "0 7 * * *" \
  --tz "America/Los_Angeles" \
  --session isolated \
  --message "Summarize overnight updates." \
  --deliver \
  --channel slack \
  --to "channel:C1234567890"

Tool-call tương đương (Gateway cron tool)

Để xem các JSON shapes chuẩn và ví dụ, xem JSON schema for tool calls.

Cron jobs được lưu ở đâu

Cron jobs được lưu trên Gateway host tại ~/.openclaw/cron/jobs.json theo mặc định. Gateway load file vào bộ nhớ và ghi lại khi có thay đổi, nên việc chỉnh sửa thủ công chỉ an toàn khi Gateway đã dừng. Nên dùng openclaw cron add/edit hoặc cron tool call API để thay đổi.

Tổng quan dễ hiểu

Hãy nghĩ về một cron job như: khi nào chạy + làm gì.

  1. Chọn lịch trình

    • Reminder chạy một lần → schedule.kind = "at" (CLI: --at)
    • Job lặp lại → schedule.kind = "every" hoặc schedule.kind = "cron"
    • Nếu ISO timestamp không có timezone, nó sẽ được coi là UTC.
  2. Chọn nơi chạy

    • sessionTarget: "main" → chạy trong heartbeat tiếp theo với main context.
    • sessionTarget: "isolated" → chạy một agent turn riêng trong cron:<jobId>.
  3. Chọn payload

    • Main session → payload.kind = "systemEvent"
    • Isolated session → payload.kind = "agentTurn"

Tùy chọn: deleteAfterRun: true sẽ xóa các one-shot jobs thành công khỏi store.

Các khái niệm

Jobs

Một cron job là một bản ghi được lưu trữ với:

  • một schedule (khi nào nó chạy),
  • một payload (nó làm gì),
  • delivery tùy chọn (output sẽ được gửi đến đâu).
  • agent binding tùy chọn (agentId): chạy job dưới một agent cụ thể; nếu thiếu hoặc không tìm thấy, gateway sẽ dùng agent mặc định.

Jobs được định danh bằng một jobId ổn định (dùng bởi CLI/Gateway APIs). Trong agent tool calls, jobId là chuẩn; id cũ vẫn được chấp nhận để tương thích. Jobs có thể tự động xóa sau khi chạy thành công một lần thông qua deleteAfterRun: true.

Schedules

Cron hỗ trợ ba loại schedule:

  • at: timestamp chạy một lần (ms kể từ epoch). Gateway chấp nhận ISO 8601 và chuyển sang UTC.
  • every: khoảng thời gian cố định (ms).
  • cron: biểu thức cron 5 trường với timezone IANA tùy chọn.

Biểu thức cron dùng croner. Nếu timezone bị bỏ qua, timezone local của Gateway host sẽ được dùng.

Main vs isolated execution

Main session jobs (system events)

Main jobs đưa một system event vào hàng đợi và có thể đánh thức heartbeat runner. Chúng phải dùng payload.kind = "systemEvent".

  • wakeMode: "next-heartbeat" (mặc định): event chờ heartbeat được lên lịch tiếp theo.
  • wakeMode: "now": event kích hoạt heartbeat chạy ngay lập tức.

Đây là lựa chọn tốt nhất khi các bạn muốn heartbeat prompt bình thường + main-session context. Xem Heartbeat.

Isolated jobs (dedicated cron sessions)

Isolated jobs chạy một agent turn riêng trong session cron:<jobId>.

Các hành vi chính:

  • Prompt được thêm prefix [cron:<jobId> <job name>] để dễ theo dõi.
  • Mỗi lần chạy bắt đầu một session id mới (không có lịch sử hội thoại trước đó).
  • Một tóm tắt được đăng vào main session (prefix Cron, có thể cấu hình).
  • wakeMode: "now" kích hoạt heartbeat ngay sau khi đăng tóm tắt.
  • Nếu payload.deliver: true, output được gửi đến một channel; nếu không nó sẽ ở nội bộ.

Dùng isolated jobs cho các tác vụ ồn ào, thường xuyên, hoặc “công việc nền” mà không nên làm spam lịch sử chat chính.

Payload shapes (cái gì chạy)

Hai loại payload được hỗ trợ:

  • systemEvent: chỉ dành cho main-session, được định tuyến qua heartbeat prompt.
  • agentTurn: chỉ dành cho isolated-session, chạy một agent turn riêng.

Các trường agentTurn phổ biến:

  • message: text prompt bắt buộc.
  • model / thinking: overrides tùy chọn (xem bên dưới).
  • timeoutSeconds: timeout override tùy chọn.
  • deliver: true để gửi output đến một channel target.
  • channel: last hoặc một channel cụ thể.
  • to: target cụ thể của channel (phone/chat/channel id).
  • bestEffortDeliver: tránh làm job thất bại nếu delivery thất bại.

Các tùy chọn isolation (chỉ cho session=isolated):

  • postToMainPrefix (CLI: --post-prefix): prefix cho system event trong main.
  • postToMainMode: summary (mặc định) hoặc full.
  • postToMainMaxChars: số ký tự tối đa khi postToMainMode=full (mặc định 8000).

Model và thinking overrides

Isolated jobs (agentTurn) có thể override model và thinking level:

  • model: Provider/model string (ví dụ: anthropic/claude-sonnet-4-20250514) hoặc alias (ví dụ: opus)
  • thinking: Thinking level (off, minimal, low, medium, high, xhigh; chỉ GPT-5.2 + Codex models)

Lưu ý: Các bạn có thể set model trên main-session jobs, nhưng nó sẽ thay đổi model của main session chung. Mình khuyên chỉ dùng model overrides cho isolated jobs để tránh context shifts không mong muốn.

Thứ tự ưu tiên:

  1. Job payload override (cao nhất)
  2. Hook-specific defaults (ví dụ: hooks.gmail.model)
  3. Agent config default

Delivery (channel + target)

Isolated jobs có thể gửi output đến một channel. Job payload có thể chỉ định:

  • channel: whatsapp / telegram / discord / slack / mattermost (plugin) / signal / imessage / last
  • to: recipient target cụ thể của channel

Nếu channel hoặc to bị bỏ qua, cron có thể fallback về “last route” của main session (nơi cuối cùng agent đã trả lời).

Lưu ý về delivery:

  • Nếu to được set, cron tự động gửi output cuối cùng của agent ngay cả khi deliver bị bỏ qua.
  • Dùng deliver: true khi các bạn muốn last-route delivery mà không có to rõ ràng.
  • Dùng deliver: false để giữ output nội bộ ngay cả khi có to.

Nhắc nhở về định dạng target:

  • Slack/Discord/Mattermost (plugin) targets nên dùng prefixes rõ ràng (ví dụ: channel:<id>, user:<id>) để tránh nhầm lẫn.
  • Telegram topics nên dùng dạng :topic: (xem bên dưới).

Telegram delivery targets (topics / forum threads)

Telegram hỗ trợ forum topics qua message_thread_id. Để cron delivery, các bạn có thể encode topic/thread vào trường to:

  • -1001234567890 (chỉ chat id)
  • -1001234567890:topic:123 (khuyên dùng: topic marker rõ ràng)
  • -1001234567890:123 (viết tắt: numeric suffix)

Prefixed targets như telegram:... / telegram:group:... cũng được chấp nhận:

  • telegram:group:-1001234567890:topic:123

JSON schema for tool calls

Dùng các shapes này khi gọi Gateway cron.* tools trực tiếp (agent tool calls hoặc RPC). CLI flags chấp nhận human durations như 20m, nhưng tool calls dùng epoch milliseconds cho atMseveryMs (ISO timestamps được chấp nhận cho at times).

cron.add params

One-shot, main session job (system event):

{
  "name": "Reminder",
  "schedule": { "kind": "at", "atMs": 1738262400000 },
  "sessionTarget": "main",
  "wakeMode": "now",
  "payload": { "kind": "systemEvent", "text": "Reminder text" },
  "deleteAfterRun": true
}

Recurring, isolated job với delivery:

{
  "name": "Morning brief",
  "schedule": { "kind": "cron", "expr": "0 7 * * *", "tz": "America/Los_Angeles" },
  "sessionTarget": "isolated",
  "wakeMode": "next-heartbeat",
  "payload": {
    "kind": "agentTurn",
    "message": "Summarize overnight updates.",
    "deliver": true,
    "channel": "slack",
    "to": "channel:C1234567890",
    "bestEffortDeliver": true
  },
  "isolation": { "postToMainPrefix": "Cron", "postToMainMode": "summary" }
}

Lưu ý:

  • schedule.kind: at (atMs), every (everyMs), hoặc cron (expr, tz tùy chọn).
  • atMseveryMs là epoch milliseconds.
  • sessionTarget phải là "main" hoặc "isolated" và phải khớp với payload.kind.
  • Các trường tùy chọn: agentId, description, enabled, deleteAfterRun, isolation.
  • wakeMode mặc định là "next-heartbeat" khi bị bỏ qua.

cron.update params

{
  "jobId": "job-123",
  "patch": {
    "enabled": false,
    "schedule": { "kind": "every", "everyMs": 3600000 }
  }
}

Lưu ý:

  • jobId là chuẩn; id được chấp nhận để tương thích.
  • Dùng agentId: null trong patch để xóa agent binding.

cron.run và cron.remove params

{ "jobId": "job-123", "mode": "force" }
{ "jobId": "job-123" }

Storage & history

  • Job store: ~/.openclaw/cron/jobs.json (Gateway-managed JSON).
  • Run history: ~/.openclaw/cron/runs/<jobId>.jsonl (JSONL, tự động dọn dẹp).
  • Override store path: cron.store trong config.

Configuration

{
  cron: {
    enabled: true, // mặc định true
    store: "~/.openclaw/cron/jobs.json",
    maxConcurrentRuns: 1, // mặc định 1
  },
}

Tắt cron hoàn toàn:

  • cron.enabled: false (config)
  • OPENCLAW_SKIP_CRON=1 (env)

CLI quickstart

One-shot reminder (UTC ISO, tự động xóa sau khi thành công):

openclaw cron add \
  --name "Send reminder" \
  --at "2026-01-12T18:00:00Z" \
  --session main \
  --system-event "Reminder: submit expense report." \
  --wake now \
  --delete-after-run

One-shot reminder (main session, wake ngay lập tức):

openclaw cron add \
  --name "Calendar check" \
  --at "20m" \
  --session main \
  --system-event "Next heartbeat: check calendar." \
  --wake now

Recurring isolated job (gửi đến WhatsApp):

openclaw cron add \
  --name "Morning status" \
  --cron "0 7 * * *" \
  --tz "America/Los_Angeles" \
  --session isolated \
  --message "Summarize inbox + calendar for today." \
  --deliver \
  --channel whatsapp \
  --to "+15551234567"

Recurring isolated job (gửi đến Telegram topic):

openclaw cron add \
  --name "Nightly summary (topic)" \
  --cron "0 22 * * *" \
  --tz "America/Los_Angeles" \
  --session isolated \
  --message "Summarize today; send to the nightly topic." \
  --deliver \
  --channel telegram \
  --to "-1001234567890:topic:123"

Isolated job với model và thinking override:

openclaw cron add \
  --name "Deep analysis" \
  --cron "0 6 * * 1" \
  --tz "America/Los_Angeles" \
  --session isolated \
  --message "Weekly deep analysis of project progress." \
  --model "opus" \
  --thinking high \
  --deliver \
  --channel whatsapp \
  --to "+15551234567"

Agent selection (multi-agent setups):

# Gắn job vào agent "ops" (fallback về default nếu agent đó thiếu)
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops

# Chuyển hoặc xóa agent trên một job hiện có
openclaw cron edit <jobId> --agent ops
openclaw cron edit <jobId> --clear-agent

Manual run (debug):

openclaw cron run <jobId> --force

Chỉnh sửa một job hiện có (patch fields):

openclaw cron edit <jobId> \
  --message "Updated prompt" \
  --model "opus" \
  --thinking low

Run history:

openclaw cron runs --id <jobId> --limit 50

System event ngay lập tức mà không tạo job:

openclaw system event --mode now --text "Next heartbeat: check battery."

Gateway API surface

  • cron.list, cron.status, cron.add, cron.update, cron.remove
  • cron.run (force hoặc due), cron.runs Để tạo system events ngay lập tức mà không cần job, dùng openclaw system event.

Troubleshooting

”Không có gì chạy”

  • Kiểm tra cron đã được bật: cron.enabledOPENCLAW_SKIP_CRON.
  • Kiểm tra Gateway đang chạy liên tục (cron chạy bên trong Gateway process).
  • Với cron schedules: xác nhận timezone (--tz) so với host timezone.

Telegram gửi sai chỗ

  • Với forum topics, dùng -100…:topic:<id> để rõ ràng và không nhầm lẫn.
  • Nếu các bạn thấy prefixes telegram:... trong logs hoặc “last route” targets được lưu, đó là bình thường; cron delivery chấp nhận chúng và vẫn parse topic IDs đúng.