All docs

Docs · Reference

Cron

Start work at regular intervals

The cron head turns the clock into events: every --cron-period seconds (default 60), subscribed stacks receive a tick envelope and their rules decide what the hour demands.

You may want to process work in a batch, several times a day, hour, week. The cron channel allows you to have your agents work while you sleep.

Subscribing is implicit

Define a stack named _cron in your tenant and you’re subscribed: each tick, the chassis queries for tenants with a _cron stack and delivers one envelope per tenant (@cron.job = "_cron", @cron.tenant = <slug>). No registration, no YAML — the stack’s existence is the subscription.

The tick envelope

Rules match on wall-clock fields rather than cron syntax. All @cron.* clock fields are UTC — identical on every node, regardless of the chassis box’s local zone:

WHEN @cron.hour == 9 && @cron.minute == 0     # every day at 09:00 UTC
EXEC "https://ops.example.com/morning-sweep"

Set a timezone for the tenant. Most cron runs in one zone — set it once and every @cron.* field for this tenant’s _cron stack is localized to it, so the rule above (@cron.hour == 9) fires at 09:00 there. No per-rule syntax:

txco cron config set timezone Asia/Tokyo   # @cron.* now in Tokyo wall-clock
txco cron config show                      # → cron timezone: Asia/Tokyo
txco cron config set timezone ""           # clear, back to UTC

Fractional offsets work too: with Asia/Kolkata (+05:30), @cron.hour == 9 && @cron.minute == 0 fires at 09:00 IST. The @cron.bucket dedup key stays UTC, so fleet scheduling is unaffected.

Per-rule override. To target a zone in a single rule — handy when one stack mixes zones — leave the tenant on UTC and convert inline with &tz(zone, "hour"|"minute", h [, m]), which maps a local time to the UTC @cron.* value (DST-aware):

WHEN @cron.hour == &tz("Asia/Kolkata", "hour", 9)
  && @cron.minute == &tz("Asia/Kolkata", "minute", 9)            # 09:00 in India
EXEC "https://ops.example.com/morning-sweep"
Warning

&tz assumes @cron.* is UTC, so use it or a tenant timezone — not both. A tenant timezone already localizes the fields, so combining them double-converts.

FieldMeaning
@cron.{minute,hour,dom,dow,month,year}Wall-clock at the tick, in UTC
@cron.mod5 / mod10 / mod15 / mod30Precomputed buckets — WHEN @cron.mod15 == 0 fires every 15 minutes
@cron.bucketCanonical period-boundary timestamp — the fleet dedup key
@cron.tickMonotonic counter since boot
@cron.job / @cron.tenant / @cron.node_cron or default / subscriber / firing chassis

Tenant deliveries are smeared across the period (a deterministic per-tenant offset) so a thousand tenants don’t stampede at second zero — but the envelope’s clock fields are frozen at the tick instant, so WHEN @cron.minute == 0 still means minute zero regardless of when your delivery lands.

Flags

FlagDefaultMeaning
--cron-period60Seconds between ticks (min 1)
--cron-max-inflight32Concurrent dispatches per tick
--cron-system-tickfalseEnable the tenant-less system tick
--cron-queuelocalQueue backend (in-process; the interface is pluggable for brokers)

The local queue is single-node, at-most-once — no retries, no cross-node dedup. Fleet-grade scheduling rides on @cron.bucket as the dedup key.

Edit this page · View as markdown