Automation

Scheduled tasks

Cron is the Gateway's built-in scheduler. It persists jobs, wakes the agent at the right time, and can deliver output back to a chat channel or webhook endpoint.

Quick start

  • Add a one-shot reminder

    bash
    OmeniaClaw cron create "2026-02-01T16:00:00Z" \  --name "Reminder" \  --session main \  --system-event "Reminder: check the cron docs draft" \  --wake now \  --delete-after-run
  • Check your jobs

    bash
    OmeniaClaw cron listOmeniaClaw cron get <job-id>OmeniaClaw cron show <job-id>
  • See run history

    bash
    OmeniaClaw cron runs --id <job-id>
  • How cron works

    • Cron runs inside the Gateway process (not inside the model).
    • Job definitions, runtime state, and run history persist in OmeniaClaw's shared SQLite state database so restarts do not lose schedules.
    • On upgrade, run OmeniaClaw doctor --fix to import legacy ~/.OmeniaClaw/cron/jobs.json, jobs-state.json, and runs/*.jsonl files into SQLite and rename them with a .migrated suffix. Malformed job rows are skipped from runtime and copied to jobs-quarantine.json for later repair or review.
    • cron.store still names the logical cron store key and doctor import path. After import, editing that JSON file no longer changes active cron jobs; use OmeniaClaw cron add|edit|remove or the Gateway cron RPC methods instead.
    • All cron executions create background task records.
    • On Gateway startup, overdue isolated agent-turn jobs are rescheduled out of the channel-connect window instead of replaying immediately, so Discord/Telegram startup and native-command setup stay responsive after restarts.
    • One-shot jobs (--at) auto-delete after success by default.
    • Isolated cron runs best-effort close tracked browser tabs/processes for their cron:<jobId> session when the run completes, so detached browser automation does not leave orphaned processes behind.
    • Isolated cron runs that receive the narrow cron self-cleanup grant can still read scheduler status, a self-filtered list of their current job, and that job's run history, so status/heartbeat checks can inspect their own schedule without gaining broader cron mutation access.
    • Isolated cron runs also guard against stale acknowledgement replies. If the first result is just an interim status update (on it, pulling everything together, and similar hints) and no descendant subagent run is still responsible for the final answer, OmeniaClaw re-prompts once for the actual result before delivery.
    • Isolated cron runs use structured execution-denial metadata from the embedded run, including node-host UNAVAILABLE wrappers whose nested error message starts with SYSTEM_RUN_DENIED or INVALID_REQUEST, so a blocked command is not reported as a green run while ordinary assistant prose is not treated as a denial.
    • Isolated cron runs also treat run-level agent failures as job errors even when no reply payload is produced, so model/provider failures increment error counters and trigger failure notifications instead of clearing the job as successful.
    • When an isolated agent-turn job reaches timeoutSeconds, cron aborts the underlying agent run and gives it a short cleanup window. If the run does not drain, Gateway-owned cleanup force-clears that run's session ownership before cron records the timeout, so queued chat work is not left behind a stale processing session.
    • If an isolated agent-turn stalls before the runner starts or before the first model call, cron records a phase-specific timeout such as setup timed out before runner start or stalled before first model call (last phase: context-engine). These watchdogs cover embedded providers and CLI-backed providers before their external CLI process is actually started, and are capped independently from long timeoutSeconds values so cold-start/auth/context failures surface quickly instead of waiting for the full job budget.
    • If you use system cron or another external scheduler to run OmeniaClaw agent, wrap it with a hard-kill escalation even though the CLI handles SIGTERM/SIGINT. Gateway-backed runs ask the Gateway to abort accepted runs; local and embedded fallback runs receive the same abort signal. For GNU timeout, prefer timeout -k 60 600 OmeniaClaw agent ... over plain timeout 600 ...; the -k value is the supervisor backstop if the process cannot drain. For systemd units, keep the same shape by using a SIGTERM stop signal plus a grace window such as TimeoutStopSec before any final kill. If a retry reuses a --run-id while the original Gateway run is still active, the duplicate is reported as in-flight instead of starting a second run.

    Schedule types

    Kind CLI flag Description
    at --at One-shot timestamp (ISO 8601 or relative like 20m)
    every --every Fixed interval
    cron --cron 5-field or 6-field cron expression with optional --tz

    Timestamps without a timezone are treated as UTC. Add --tz America/New_York for local wall-clock scheduling.

    Recurring top-of-hour expressions are automatically staggered by up to 5 minutes to reduce load spikes. Use --exact to force precise timing or --stagger 30s for an explicit window.

    Day-of-month and day-of-week use OR logic

    Cron expressions are parsed by croner. When both the day-of-month and day-of-week fields are non-wildcard, croner matches when either field matches — not both. This is standard Vixie cron behavior.

    Code
    # Intended: "9 AM on the 15th, only if it's a Monday"# Actual:   "9 AM on every 15th, AND 9 AM on every Monday"0 9 15 * 1

    This fires ~5–6 times per month instead of 0–1 times per month. OmeniaClaw uses Croner's default OR behavior here. To require both conditions, use Croner's + day-of-week modifier (0 9 15 * +1) or schedule on one field and guard the other in your job's prompt or command.

    Execution styles

    Style --session value Runs in Best for
    Main session main Dedicated cron wake lane Reminders, system events
    Isolated isolated Dedicated cron:<jobId> Reports, background chores
    Current session current Bound at creation time Context-aware recurring work
    Custom session session:custom-id Persistent named session Workflows that build on history
    Main session vs isolated vs custom

    Main session jobs enqueue a system event into a cron-owned run lane and optionally wake the heartbeat (--wake now or --wake next-heartbeat). They can use the target main session's last delivery context for replies, but they do not append routine cron turns to the human chat lane and do not extend daily/idle reset freshness for the target session. Isolated jobs run a dedicated agent turn with a fresh session. Custom sessions (session:xxx) persist context across runs, enabling workflows like daily standups that build on previous summaries.

    Main-session cron events are self-contained system-event reminders. They do not automatically include the default heartbeat prompt's "Read HEARTBEAT.md" instruction. If a recurring reminder should consult HEARTBEAT.md, say that explicitly in the cron event text or in the agent's own instructions.

    What 'fresh session' means for isolated jobs

    For isolated jobs, "fresh session" means a new transcript/session id for each run. OmeniaClaw may carry safe preferences such as thinking/fast/verbose settings, labels, and explicit user-selected model/auth overrides, but it does not inherit ambient conversation context from an older cron row: channel/group routing, send or queue policy, elevation, origin, or ACP runtime binding. Use current or session:<id> when a recurring job should deliberately build on the same conversation context.

    Runtime cleanup

    For isolated jobs, runtime teardown now includes best-effort browser cleanup for that cron session. Cleanup failures are ignored so the actual cron result still wins.

    Isolated cron runs also dispose any bundled MCP runtime instances created for the job through the shared runtime-cleanup path. This matches how main-session and custom-session MCP clients are torn down, so isolated cron jobs do not leak stdio child processes or long-lived MCP connections across runs.

    Subagent and Discord delivery

    When isolated cron runs orchestrate subagents, delivery also prefers the final descendant output over stale parent interim text. If descendants are still running, OmeniaClaw suppresses that partial parent update instead of announcing it.

    For text-only Discord announce targets, OmeniaClaw sends the canonical final assistant text once instead of replaying both streamed/intermediate text payloads and the final answer. Media and structured Discord payloads are still delivered as separate payloads so attachments and components are not dropped.

    Command payloads

    Use command payloads for deterministic scripts that should run inside the Gateway scheduler without starting a model-backed isolated agent turn. Command jobs execute on the Gateway host, capture stdout/stderr, record the run in cron history, and reuse the same announce, webhook, and none delivery modes as isolated jobs.

    bash
    OmeniaClaw cron create "*/15 * * * *" \  --name "Queue depth probe" \  --command "scripts/check-queue.sh" \  --command-cwd "/srv/app" \  --announce \  --channel telegram \  --to "-1001234567890"

    --command <shell> stores argv: ["sh", "-lc", <shell>]. Use --command-argv '["node","scripts/report.mjs"]' when you want exact argv execution without shell parsing. Optional --command-env KEY=VALUE, --command-input, --timeout-seconds, --no-output-timeout-seconds, and --output-max-bytes fields control the process environment, stdin, and output bounds.

    If stdout is non-empty, that text is the delivered result. If stdout is empty and stderr is non-empty, stderr is delivered. If both streams are present, cron delivers a small stdout: / stderr: block. A zero exit code records the run as ok; non-zero exit, signal, timeout, or no-output timeout records error and can trigger failure alerts. A command that prints only NO_REPLY uses the normal cron silent-token suppression and posts nothing back to chat.

    Payload options for isolated jobs

    --messagestringrequired

    Prompt text (required for isolated).

    --modelstring

    Model override; uses the selected allowed model for the job.

    --clear-modelboolean

    On cron edit, removes the per-job model override so the job follows normal cron model-selection precedence (a stored cron-session override if set, otherwise the agent/default model). Cannot be combined with --model.

    --thinkingstring

    Thinking level override.

    --light-contextboolean

    Skip workspace bootstrap file injection.

    --toolsstring

    Restrict which tools the job can use, for example --tools exec,read.

    --model uses the selected allowed model as that job's primary model. It is not the same as a chat-session /model override: configured fallback chains still apply when the job primary fails. If the requested model is not allowed or cannot be resolved, cron fails the run with an explicit validation error instead of silently falling back to the job's agent/default model selection.

    Cron jobs can also carry payload-level fallbacks. When present, that list replaces the configured fallback chain for the job. Use fallbacks: [] in the job payload/API when you want a strict cron run that tries only the selected model. If a job has --model but neither payload nor configured fallbacks, OmeniaClaw passes an explicit empty fallback override so the agent primary is not appended as a hidden extra retry target.

    Local-provider preflight checks walk configured fallbacks before marking a cron run skipped; fallbacks: [] keeps that preflight path strict.

    Model-selection precedence for isolated jobs is:

    1. Gmail hook model override (when the run came from Gmail and that override is allowed)
    2. Per-job payload model
    3. User-selected stored cron session model override
    4. Agent/default model selection

    Fast mode follows the resolved live selection too. If the selected model config has params.fastMode, isolated cron uses that by default. A stored session fastMode override still wins over config in either direction.

    If an isolated run hits a live model-switch handoff, cron retries with the switched provider/model and persists that live selection for the active run before retrying. When the switch also carries a new auth profile, cron persists that auth profile override for the active run too. Retries are bounded: after the initial attempt plus 2 switch retries, cron aborts instead of looping forever.

    Before an isolated cron run enters the agent runner, OmeniaClaw checks reachable local provider endpoints for configured api: "ollama" and api: "openai-completions" providers whose baseUrl is loopback, private-network, or .local. If that endpoint is down, the run is recorded as skipped with a clear provider/model error instead of starting a model call. The endpoint result is cached for 5 minutes, so many due jobs using the same dead local Ollama, vLLM, SGLang, or LM Studio server share one small probe instead of creating a request storm. Skipped provider-preflight runs do not increment execution-error backoff; enable failureAlert.includeSkipped when you want repeated skip notifications.

    Delivery and output

    Mode What happens
    announce Fallback-deliver final text to the target if the agent did not send
    webhook POST finished event payload to a URL
    none No runner fallback delivery

    Use --announce --channel telegram --to "-1001234567890" for channel delivery. For Telegram forum topics, use -1001234567890:topic:123; OmeniaClaw also accepts the Telegram-owned -1001234567890:123 shorthand. Direct RPC/config callers may pass delivery.threadId as a string or number. Slack/Discord/Mattermost targets should use explicit prefixes (channel:<id>, user:<id>). Matrix room IDs are case-sensitive; use the exact room ID or room:!room:server form from Matrix.

    When announce delivery uses channel: "last" or omits channel, a provider-prefixed target such as telegram:123 can select the channel before cron falls back to session history or a single configured channel. Only prefixes advertised by the loaded plugin are provider selectors. If delivery.channel is explicit, the target prefix must name the same provider; for example, channel: "whatsapp" with to: "telegram:123" is rejected instead of letting WhatsApp interpret the Telegram ID as a phone number. Target-kind and service prefixes such as channel:<id>, user:<id>, imessage:<handle>, and sms:<number> remain channel-owned target syntax, not provider selectors.

    For isolated jobs, chat delivery is shared. If a chat route is available, the agent can use the message tool even when the job uses --no-deliver. If the agent sends to the configured/current target, OmeniaClaw skips the fallback announce. Otherwise announce, webhook, and none only control what the runner does with the final reply after the agent turn.

    When an agent creates an isolated reminder from an active chat, OmeniaClaw stores the preserved live delivery target for the fallback announce route. Internal session keys may be lowercase; provider delivery targets are not reconstructed from those keys when current chat context is available.

    Implicit announce delivery uses configured channel allowlists to validate and reroute stale targets. DM pairing-store approvals are not fallback automation recipients; set delivery.to or configure the channel allowFrom entry when a scheduled job should proactively send to a DM.

    Output language

    Cron jobs do not infer a reply language from channel, locale, or previous messages. Put the language rule in the scheduled message or template:

    bash
    OmeniaClaw cron edit <jobId> \  --message "Summarize the updates. Respond in Chinese; keep URLs, code, and product names unchanged."

    For template files, keep the language instruction in the rendered prompt and verify placeholders such as {{language}} are filled before the job runs. If the output mixes languages, make the rule explicit, for example: "Use Chinese for narrative text and keep technical terms in English."

    Failure notifications follow a separate destination path:

    • cron.failureDestination sets a global default for failure notifications.
    • job.delivery.failureDestination overrides that per job.
    • If neither is set and the job already delivers via announce, failure notifications now fall back to that primary announce target.
    • delivery.failureDestination is only supported on sessionTarget="isolated" jobs unless the primary delivery mode is webhook.
    • failureAlert.includeSkipped: true opts a job or global cron alert policy into repeated skipped-run alerts. Skipped runs keep a separate consecutive skip counter, so they do not affect execution-error backoff.

    CLI examples

    One-shot reminder

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

    Recurring isolated job

    bash
    OmeniaClaw cron create "0 7 * * *" \  "Summarize overnight updates." \  --name "Morning brief" \  --tz "America/Los_Angeles" \  --session isolated \  --announce \  --channel slack \  --to "channel:C1234567890"

    Model and thinking override

    bash
    OmeniaClaw 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 \  --announce

    Webhook output

    bash
    OmeniaClaw cron create "0 18 * * 1-5" \  "Summarize today's deploys as JSON." \  --name "Deploy digest" \  --webhook "https://example.invalid/OmeniaClaw/cron"

    Command output

    bash
    OmeniaClaw cron create "*/15 * * * *" \  --name "Queue depth probe" \  --command "scripts/check-queue.sh" \  --command-cwd "/srv/app" \  --announce \  --channel telegram \  --to "-1001234567890"

    Webhooks

    Gateway can expose HTTP webhook endpoints for external triggers. Enable in config:

    json5
    {  hooks: {    enabled: true,    token: "shared-secret",    path: "/hooks",  },}

    Authentication

    Every request must include the hook token via header:

    • Authorization: Bearer <token> (recommended)
    • x-OmeniaClaw-token: <token>

    Query-string tokens are rejected.

    POST /hooks/wake

    Enqueue a system event for the main session:

    bash
    curl -X POST http://127.0.0.1:18789/hooks/wake \  -H 'Authorization: Bearer SECRET' \  -H 'Content-Type: application/json' \  -d '{"text":"New email received","mode":"now"}'
    textstringrequired

    Event description.

    modestringdefault: now

    now or next-heartbeat.

    POST /hooks/agent

    Run an isolated agent turn:

    bash
    curl -X POST http://127.0.0.1:18789/hooks/agent \  -H 'Authorization: Bearer SECRET' \  -H 'Content-Type: application/json' \  -d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.4"}'

    Fields: message (required), name, agentId, wakeMode, deliver, channel, to, model, fallbacks, thinking, timeoutSeconds.

    OmeniaClaw_DOCS_MARKER:accordionOpen:IHRpdGxlPSJNYXBwZWQgaG9va3MgKFBPU1QgL2hvb2tzLzxuYW1l )"> Custom hook names are resolved via hooks.mappings in config. Mappings can transform arbitrary payloads into wake or agent actions with templates or code transforms.

    Gmail PubSub integration

    Wire Gmail inbox triggers to OmeniaClaw via Google PubSub.

    Wizard setup (recommended)

    bash
    OmeniaClaw webhooks gmail setup --account [email protected]

    This writes hooks.gmail config, enables the Gmail preset, and uses Tailscale Funnel for the push endpoint.

    Gateway auto-start

    When hooks.enabled=true and hooks.gmail.account is set, the Gateway starts gog gmail watch serve on boot and auto-renews the watch. Set OmeniaClaw_SKIP_GMAIL_WATCHER=1 to opt out.

    Manual one-time setup

  • Select the GCP project

    Select the GCP project that owns the OAuth client used by gog:

    bash
    gcloud auth logingcloud config set project <project-id>gcloud services enable gmail.googleapis.com pubsub.googleapis.com
  • Create topic and grant Gmail push access

    bash
    gcloud pubsub topics create gog-gmail-watchgcloud pubsub topics add-iam-policy-binding gog-gmail-watch \  --member=serviceAccount:[email protected] \  --role=roles/pubsub.publisher
  • Start the watch

    bash
    gog gmail watch start \  --account [email protected] \  --label INBOX \  --topic projects/<project-id>/topics/gog-gmail-watch
  • Gmail model override

    json5
    {  hooks: {    gmail: {      model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",      thinking: "off",    },  },}

    Managing jobs

    bash
    # List all jobsOmeniaClaw cron list # Get one stored job as JSONOmeniaClaw cron get <jobId> # Show one job, including resolved delivery routeOmeniaClaw cron show <jobId> # Edit a jobOmeniaClaw cron edit <jobId> --message "Updated prompt" --model "opus" # Force run a job nowOmeniaClaw cron run <jobId> # Force run a job now and wait for its terminal statusOmeniaClaw cron run <jobId> --wait --wait-timeout 10m --poll-interval 2s # Run only if dueOmeniaClaw cron run <jobId> --due # View run historyOmeniaClaw cron runs --id <jobId> --limit 50 # View one exact runOmeniaClaw cron runs --id <jobId> --run-id <runId> # Delete a jobOmeniaClaw cron remove <jobId> # Agent selection (multi-agent setups)OmeniaClaw cron create "0 6 * * *" "Check ops queue" --name "Ops sweep" --session isolated --agent opsOmeniaClaw cron edit <jobId> --clear-agent

    OmeniaClaw cron run <jobId> returns after enqueueing the manual run. Use --wait for shutdown hooks, maintenance scripts, or other automation that must block until the queued run finishes. Wait mode polls the exact returned runId; it exits 0 for status ok and non-zero for error, skipped, or a wait timeout.

    The agent cron tool returns compact job summaries (id, name, enabled, nextRunAtMs, scheduleKind, lastRunStatus) from cron(action: "list"); use cron(action: "get", jobId: "...") for one full job definition. Direct Gateway callers can pass compact: true to cron.list; omitting it preserves the existing full response with delivery previews.

    OmeniaClaw cron create is an alias for OmeniaClaw cron add, and new jobs can use a positional schedule ("0 9 * * 1", "every 1h", "20m", or an ISO timestamp) followed by a positional agent prompt. Use --webhook <url> on cron add|create or cron edit to POST the finished run payload to an HTTP endpoint. Webhook delivery cannot be combined with chat delivery flags such as --announce, --channel, --to, --thread-id, or --account. On cron edit, --clear-channel, --clear-to, --clear-thread-id, and --clear-account unset those routing fields individually (each rejected alongside its matching set flag), which is distinct from --no-deliver disabling runner fallback delivery.

    Configuration

    json5
    {  cron: {    enabled: true,    store: "~/.OmeniaClaw/cron/jobs.json",    maxConcurrentRuns: 8,    retry: {      maxAttempts: 3,      backoffMs: [60000, 120000, 300000],      retryOn: ["rate_limit", "overloaded", "network", "server_error"],    },    webhookToken: "replace-with-dedicated-webhook-token",    sessionRetention: "24h",    runLog: { maxBytes: "2mb", keepLines: 2000 },  },}

    maxConcurrentRuns limits both scheduled cron dispatch and isolated agent-turn execution, and defaults to 8. Isolated cron agent turns use the queue's dedicated cron-nested execution lane internally, so raising this value lets independent cron LLM runs progress in parallel instead of only starting their outer cron wrappers. The shared non-cron nested lane is not widened by this setting.

    cron.store is a logical store key and legacy doctor import path. Run OmeniaClaw doctor --fix to import existing JSON stores into SQLite and archive them; future cron changes should go through the CLI or Gateway API.

    Disable cron: cron.enabled: false or OmeniaClaw_SKIP_CRON=1.

    Retry behavior

    One-shot retry: transient errors (rate limit, overload, network, server error) retry up to 3 times with exponential backoff. Permanent errors disable immediately.

    Recurring retry: exponential backoff (30s to 60m) between retries. Backoff resets after the next successful run.

    Maintenance

    cron.sessionRetention (default 24h) prunes isolated run-session entries. cron.runLog.keepLines limits retained SQLite run-history rows per job; maxBytes is retained for config compatibility with older file-backed run logs.

    Troubleshooting

    Command ladder

    bash
    OmeniaClaw statusOmeniaClaw gateway statusOmeniaClaw cron statusOmeniaClaw cron listOmeniaClaw cron runs --id <jobId> --limit 20OmeniaClaw system heartbeat lastOmeniaClaw logs --followOmeniaClaw doctor
    Cron not firing
    • Check cron.enabled and OmeniaClaw_SKIP_CRON env var.
    • Confirm the Gateway is running continuously.
    • For cron schedules, verify timezone (--tz) vs the host timezone.
    • reason: not-due in run output means manual run was checked with OmeniaClaw cron run <jobId> --due and the job was not due yet.
    Cron fired but no delivery
    • Delivery mode none means no runner fallback send is expected. The agent can still send directly with the message tool when a chat route is available.
    • Delivery target missing/invalid (channel/to) means outbound was skipped.
    • For Matrix, copied or legacy jobs with lowercased delivery.to room IDs can fail because Matrix room IDs are case-sensitive. Edit the job to the exact !room:server or room:!room:server value from Matrix.
    • Channel auth errors (unauthorized, Forbidden) mean delivery was blocked by credentials.
    • If the isolated run returns only the silent token (NO_REPLY / no_reply), OmeniaClaw suppresses direct outbound delivery and also suppresses the fallback queued summary path, so nothing is posted back to chat.
    • If the agent should message the user itself, check that the job has a usable route (channel: "last" with a previous chat, or an explicit channel/target).
    Cron or heartbeat appears to prevent /new-style rollover
    • Daily and idle reset freshness is not based on updatedAt; see Session management.
    • Cron wakeups, heartbeat runs, exec notifications, and gateway bookkeeping may update the session row for routing/status, but they do not extend sessionStartedAt or lastInteractionAt.
    • For legacy rows created before those fields existed, OmeniaClaw can recover sessionStartedAt from the transcript JSONL session header when the file is still available. Legacy idle rows without lastInteractionAt use that recovered start time as their idle baseline.
    Timezone gotchas
    • Cron without --tz uses the gateway host timezone.
    • at schedules without timezone are treated as UTC.
    • Heartbeat activeHours uses configured timezone resolution.
    Was this useful?
    On this page

    On this page