Skip to content

Schedule functions

Fire a function-mode AgentRuntime on a cron schedule with fixed input, for recurring work that has no caller: a nightly digest, an hourly sync, a periodic sweep of a queue.

Every spec field, status field, skip reason, condition and metric is in the ScheduledFunction reference.

  • A mode: function AgentRuntime in the workspace namespace, with spec.facades[].managementPlane enabled. The operator dispatches to the function’s management-plane endpoint, so a schedule on a function without one reports SpecValid=False with reason ManagementEndpointMissing.
  1. Apply a ScheduledFunction in the same namespace as the function:

    apiVersion: omnia.altairalabs.ai/v1alpha1
    kind: ScheduledFunction
    metadata:
    name: daily-digest
    namespace: ws-acme
    spec:
    functionRef:
    name: daily-digest
    schedule:
    cron: "0 6 * * *"
    timezone: "Europe/London"
    concurrencyPolicy: Forbid
    input:
    region: emea
    runAs:
    systemUser: {}

    input is validated against the function’s inputSchema before any fire. Each fire is recorded as an ordinary sessions row tagged function.

  2. Set suspend: false if the operator may run with --hold-unreviewed-schedules. See Holding schedules nobody has reviewed.

Skip this section if each run is independent. Otherwise declare a cursor: a slice of one fire’s output that Omnia splices into the next fire’s input.

  1. Add spec.state:

    spec:
    input:
    region: emea
    state:
    cursorFrom: "/cursor" # JSON Pointer into the fire's output
    cursorInto: "/cursor" # JSON Pointer into the next fire's input
    initial: # used before the first successful fire
    since: "2026-01-01T00:00:00Z"
    store:
    configMap: {} # defaults to a ConfigMap named <schedule>-state
  2. Make the function read cursor from its input and return a new cursor in its output. With the example above it receives {"region":"emea","cursor":{"since":"..."}}.

Keep the cursor to a resumption pointer: a watermark timestamp, a last-processed id or a page token. If the store is lost, the next fire reprocesses a window, so never keep the only copy of a business fact in it. A cursor over 64 KiB fails the fire.

The cursor advances only when the fire returns HTTP 200 with output that validates against the function’s outputSchema. After any other outcome the stored cursor is unchanged and the next fire retries the same window. A fire whose cursor could not be saved is recorded as Failed.

Use Forbid for any schedule that carries a cursor. Under Allow, overlapping fires can move the cursor backwards.

If the function has memory enabled, each schedule runs as the stable principal system:schedule:<workspace>/<name>, so what one fire writes the next can recall.

Make the function repeatable within a fire slot

Section titled “Make the function repeatable within a fire slot”

In a DST-observing timezone, a schedule whose slot falls in the repeated hour fires twice on fall-back day, and one whose slot falls in the skipped hour does not fire on spring-forward day. A watermark-driven function absorbs the second fire as an empty window. A function with non-idempotent side effects, such as sending email or writing to a CRM, must be made idempotent within a fire slot, or the schedule must use timezone: UTC.

An unset suspend fires by default. When the operator runs with --hold-unreviewed-schedules, set from the chart value scheduledFunctions.holdUnreviewed: true, schedules with suspend unset are held with the skip reason SuspendedByDefault. Enable it where applications are installed and tried out, so an install does not start recurring spend against a paid provider unnoticed. State that a schedule should run:

spec:
suspend: false

Set the trigger annotation to any new value. The dashboard’s Run now does the same:

Terminal window
kubectl annotate scheduledfunction daily-digest -n ws-acme \
omnia.altairalabs.ai/trigger-now="$(date -u +%FT%TZ)" --overwrite

A manual fire is a real fire: it reads and advances the cursor, writes the same session row and obeys concurrencyPolicy. It runs once per annotation value. A suspended schedule consumes the value without firing, so unsuspending does not replay it. If the operator’s dispatch cap is full (DispatchCapReached), the request stays pending and is retried on the next reconcile; see the dispatch cap.

To invoke the function without touching stored state, use the function’s test panel in the dashboard.

Terminal window
kubectl get scheduledfunction -n ws-acme

The columns include READY, LAST (the last fire’s outcome) and NEXT. For detail, read the status:

Terminal window
kubectl get scheduledfunction daily-digest -n ws-acme -o yaml
  • status.lastOutcome and status.consecutiveFailures say how fires are going.
  • status.lastSkipReason says why the last due tick did not fire.
  • status.state points at where the cursor lives. It never holds the cursor value; read the ConfigMap for that.

The reference describes the status fields, including the SpecValid and Ready conditions and the timestamps. The omnia_scheduled_function_* metrics are listed under Metrics.

Read status.conditions first.

  • SpecValid=False names the spec problem: a missing functionRef, input that fails the function’s inputSchema, a malformed cursor pointer, an outputSchema that will not compile, or ManagementEndpointMissing. The last one means the function’s facade has managementPlane disabled.
  • Ready=False with DispatchStartFailed means the tick was due but the dispatch could not start, for example because the cursor ConfigMap is unreadable. The cause is in the condition message and the skip event.

If both conditions are True, check status.nextFireTime and status.lastSkipReason. The reasons are listed in the reference.

The controller parses spec.schedule.cron. It accepts standard five-field expressions and the descriptors @hourly, @daily, @weekly, @monthly, @yearly and @every <duration>.

The operator restarted while the fire was in flight, so its result was never observed. The cursor did not advance, so the next fire retries the same window. Unknown does not increment consecutiveFailures.