Skip to content

Native cron

Kata creates recurring child issues and inbox requests from enabled native notification jobs. The owning hub daemon handles delivery; consumers read the ordinary inbox and decide what to do.

Kata also stores portable job and workflow definitions and independent run observations. External adapters schedule other jobs, resolve checkout and provider configuration, create or update ordinary issues, deliver notifications, and launch processes. An observation records evidence; its status does not control whether anything runs.

Vocabulary

Name Meaning
Job Configured work: its trigger, action, and execution settings.
Workflow Reusable command or prompt steps and their dependencies.
Run One execution and its reported status and results.

An execution job can invoke a workflow or supply a single prompt. Each execution has its own run UID. A workflow can also be referenced directly by a run.

Issue responsibility uses Kata's existing claim, assign, unassign, and owner vocabulary. A federation lease provides exclusive coordination on an issue. Recording a run changes neither ownership nor leases. See federation leases for that mechanism.

Definitions

kata cron job and kata cron workflow support create, list, show, update, delete, and restore. Commands return JSON. Creation retains a ULID:

kata cron job create --uid <job-uid> --file job.json --json
kata cron job list --json
kata cron workflow create --uid <workflow-uid> --file workflow.json --json
kata cron workflow list --json
kata cron job update <job-uid> --expected-event-uid <event-uid> --file replacement.json --json
kata cron job delete <job-uid> --expected-event-uid <event-uid> --json

Creation requires no expected event UID. Updates, deletion, and restoration require the current expected_event_uid, either as the flag or in the request body; when both are present they must match. Conflicts require inspecting the current definition. A lost create response can be inspected using the retained UID. A repeated create does not silently become an update. Tombstones remain readable, and list --include-deleted includes them. Deleting a workflow is refused while a live job in the project uses it; update or delete that job first.

Enabling a native recurring issue job activates delivery by the owning daemon. Other jobs require an adapter installation to schedule them. No job launches a process inside Kata. Secret references are names, and checkout keys are portable names. Credentials, host paths, and runtime handles are not part of this shared contract. Opaque option objects retain exact JSON numbers.

Request files

The --file body for create and update is a JSON object with a name and a definition. Kata trims name, which must then be 1–256 bytes. Optional actor must match --as when both are given; optional expected_event_uid applies to update only. A job request file looks like this:

{
  "name": "Weekday review",
  "definition": {
    "version": 1,
    "kind": "job",
    "enabled": true,
    "trigger": {"kind": "cron", "cron": "0 9 * * 1-5", "timezone": "America/New_York"},
    "action": {"kind": "execute", "prompt": "Review open pull requests and summarize risks."},
    "issue": {"kind": "per-run", "title": "Weekday review", "deadline_offset_seconds": 28800},
    "overlap": "forbid",
    "catchup": "skip",
    "checkout_key": "main-repo",
    "timeout_seconds": 3600,
    "secret_refs": {"GITHUB_TOKEN": "github-review"}
  }
}

Definitions are strictly decoded: unknown fields are rejected, and each definition is bounded to 256 KiB. A portable name is 1–256 bytes of letters, digits, -, _, and ., and is neither . nor ...

Job definitions

Field Rule
version Required; must be 1.
kind Required; must be "job".
enabled Boolean; defaults to false.
trigger Required; see triggers.
action Required; see actions.
issue Issue policy; see issue policies. Required for execute and native recurring issues.
overlap Required; forbid or allow.
catchup Required; skip, latest, or all.
checkout_key Optional portable name. Not allowed with notify.
grace_seconds, timeout_seconds Optional; zero or greater.
secret_refs Optional object mapping portable names to portable names.
options Optional JSON object for the adapter.

options objects, at any depth, cannot contain these keys (compared without case): token, api_token, api_key, password, secret, credentials, authorization, cwd, path, checkout_path, and local_path.

Triggers

Every trigger has a kind and accepts an optional IANA timezone, such as America/New_York. A trigger cannot carry fields that belong to another kind.

kind Required fields
manual None.
cron cron: a five-field expression (minute, hour, day of month, month, day of week) or a descriptor such as @daily.
interval interval_seconds: a positive integer.
once at: an RFC 3339 instant.
issue-scheduled issue_uid: the ULID of the issue whose scheduled_on date fires the job.
issue-deadline issue_uid: the ULID of the issue whose deadline_on date fires the job. Optional lead_seconds (zero or greater) is how long before the deadline to fire.

Actions

kind Fields and rules
execute Exactly one of prompt (not blank) or workflow_uid (a workflow ULID). Requires an issue policy. Cannot use an issue-deadline trigger. Cannot carry recipient or message.
notify recipient: an exact inbox address of at most 128 bytes, such as worker or worker/adapter, or current-owner-or-author. message: at most 1024 bytes; required unless recipient is current-owner-or-author or this is a native recurring issue job. Cannot carry prompt or workflow_uid, and the job cannot set checkout_key.

A notify job with an issue-scheduled or issue-deadline trigger targets that issue and may omit issue. Any other notify job requires an issue policy of kind existing or the native recurring per-run policy below.

Issue policies

kind Fields and rules
existing uid: the ULID of an existing issue. No other fields.
per-run title (not blank) and optional body. Optional scheduled_offset_seconds and deadline_offset_seconds are relative date offsets, in seconds, for the issue the adapter creates. No uid.

For notify, per-run instead requires parent_uid and a nonblank title. It accepts an optional body, but no uid or date offsets. This selects native recurring child delivery. parent_uid is not accepted on execution jobs.

Recurring child issues

Keep an ordinary parent issue open for the continuing commitment, then create a job with --parent <ref>. For example, save this request as job.json:

{
  "name": "Monthly service review",
  "definition": {
    "version": 1,
    "kind": "job",
    "enabled": true,
    "trigger": {"kind": "cron", "cron": "0 9 1 * *", "timezone": "UTC"},
    "action": {"kind": "notify", "recipient": "current-owner-or-author"},
    "issue": {
      "kind": "per-run",
      "title": "Review service",
      "body": "Check health and record any follow-up work."
    },
    "overlap": "allow",
    "catchup": "all"
  }
}

Create the job with the parent's bare short ID, qualified ref, or full UID:

kata cron job create --parent example-project#abc4 --file job.json --json

The CLI resolves the ref and stores its full UID in definition.issue.parent_uid. The stored definition stays UID-only, so short ID growth and project renaming do not change its parent identity. API clients can still supply parent_uid in the request. If the file supplies it alongside --parent, both must name the same UID. --parent requires issue.kind: "per-run"; execution jobs cannot carry a parent binding.

The job uses the parent's project when no project is otherwise selected. An explicit --project or workspace binding must match that project. Job updates also accept --parent, together with the current --expected-event-uid; a native job's existing parent binding remains immutable.

Each due occurrence creates one ordinary child with a parent link and an inbox request. Earlier children may remain unfinished. The child copies the parent's current owner at delivery; an unowned child remains unowned. Routing follows that owner, or the parent author when unowned. Set an exact recipient such as worker/consumer to keep routing fixed. An omitted message uses Kata's default recurring notification message. Read with kata inbox --for worker/consumer, then clear the request with kata notify <child-ref> --to worker/consumer --clear after handling it. Reading does not acknowledge it.

The title gains a UTC occurrence timestamp. The configured title can be at most 991 characters, so the child title stays within 1024 characters. Children carry cron.occurrence provenance and no scheduled_on, so the ordinary date sweeper does not request the same occurrence again. Clearing a request, closing a child, or restarting the daemon never rewinds delivery. Native delivery creates no run observation; a consumer may record its own execution evidence separately.

Native jobs accept only cron and interval triggers and require overlap: "allow". Calendar expressions use the trigger's IANA timezone, with UTC as the default. They follow that timezone's daylight-saving transitions. An embedded TZ= or CRON_TZ= must agree with timezone. Use {"kind":"interval","interval_seconds":7200} for an elapsed two-hour cadence; intervals must be at least 60 seconds; @every is not supported for native delivery. Intervals start at the first binding time plus one interval. Calendar schedules start strictly after that binding time, without historical backfill.

catchup After downtime or a pause
all Create every missed child, at most 1000 per job per sweep. Later sweeps continue from durable progress.
latest Create only the newest missed occurrence. Calendar scans examine at most 20,000 occurrences per sweep, retain a fixed scan horizon, and deliver when that scan finishes. Intervals find the newest occurrence directly.
skip Require positive grace_seconds; discard older occurrences and deliver those within the grace window, with the same bounded sweep limits.

The existing daemon sweep runs at startup and every 30 seconds by default. Keep the authoritative hub running for unattended delivery. Local projects deliver locally; federated projects deliver only on their enabled owning hub. Spokes never deliver, even with push writes enabled, and cannot create or edit native recurring definitions. An old peer that lacks cron_notify_children_v1 cannot receive a project containing native recurring state. Capability refusal happens before a baseline or cursor change. Projects with native recurring delivery history cannot be adopted into a different hub through spoke snapshots; the refusal preserves their existing local authority and history.

Create, update, delete, and restore retain ordinary authorization and expected revision checks. Once bound, a job's parent cannot change and the job cannot become an adapter job. Edits preserve committed progress and the original interval anchor; an unfinished latest scan restarts against the new definition. Disable the job or close its parent to pause delivery. An archived project or a deleted parent also pauses it. Re-enabling, reopening, or restoring resumes the configured catch-up policy. A pause does not skip occurrences: with catchup: "all", resuming creates every child missed while paused. This includes the time since creation for a job created disabled. Use latest or skip when a pause must not backfill. Deleting the job stops delivery until restoration. Keep the parent in the job's project. Kata rejects moving the parent alone while its native job exists, including when the job is disabled or deleted; restoring the job can resume its binding. Merge projects to move the job and parent together.

Child creation, its initial notification, and the parent's cron.progress.<job-uid> checkpoint commit together. Each delivery therefore changes the parent's revision and update time; re-read the parent before a conditional edit. Backup, restore, federation, and project merge preserve this metadata. Delivery fails closed if progress is missing or malformed; ordinary issue writes and spoke ingest cannot change or delete progress or occurrence provenance. Recurrence templates also reject these system keys, including when older templates are materialized. Inspect that state before recovery; there is no implicit reset that could recreate already delivered children. Native jobs reject execution timeouts, secrets, adapter options, checkout keys, and issue date offsets.

Workflow definitions

A workflow definition has version 1 and a non-empty steps array. It may also carry about, input, and options (with the same key rules as job options). Each step has these fields:

Field Rule
key Required portable name, unique within the workflow.
kind command (requires command, no prompt) or prompt (requires prompt, no command).
after Optional keys of earlier steps this step depends on.
retries Optional; 0–100.
options Optional JSON object for the adapter.
{
  "name": "Review then test",
  "definition": {
    "version": 1,
    "about": "Review a change, then run the tests.",
    "steps": [
      {"key": "review", "kind": "prompt", "prompt": "Review the change."},
      {"key": "test", "kind": "command", "command": "make test", "after": ["review"], "retries": 1}
    ]
  }
}

Capabilities

kata cron capabilities --json reads the project's UID and advertised event_features without changing state. The corresponding GET is /api/v1/projects/{project_id}/cron/capabilities; the X-Kata-Event-Features header advertises cron_v1 and cron_notify_children_v1. Missing, unknown, or failed advertisements remain distinct outcomes. This is an ordinary project read and creates no permissions. Ordinary authentication, project isolation, and issue-scoped route restrictions apply.

Independent run evidence

An adapter creates one independent run UID and retains it across retries:

kata cron run observe <run-uid> --json-input observation.json --json
kata cron run list --limit 100 --json
kata cron run show <run-uid> --json

The HTTP operation is PUT /api/v1/projects/{project_id}/cron/runs/{run_uid} (observeCronRun). An omitted JSON teammate inherits --teammate or KATA_TEAMMATE; an explicit empty flag suppresses that inheritance. An explicit JSON string or null freezes attribution across retries and ignores the environment. An explicit flag must match the frozen value (null matches an empty flag). An empty JSON string remains invalid; it is never replaced by the environment. For example:

{
  "job_uid": "01ARZ3NDEKTSV4RRFFQ69G5FAA",
  "definition_event_uid": "01ARZ3NDEKTSV4RRFFQ69G5FAB",
  "occurrence_key": "daily:2026-10-06",
  "actor": "worker",
  "teammate": "adapter",
  "executor_label": "local-worker",
  "status": "running",
  "summary": {"version": 1, "message": "Review started"},
  "expected_revision": 0
}

At least one paired job/event or workflow/event reference is required; both pairs may be supplied. References identify historical definitions, including earlier versions and tombstones, within the project. An optional issue UID is an ordinary existing issue in that project. Distinct run UIDs may share the same occurrence key and issue. No occurrence is reserved by logging a run.

UID, project, definition references, occurrence key, issue UID, attributed actor, teammate, executor label, and server-created time are immutable. Status, summary, start time, and end time are mutable evidence. Supported statuses are running, succeeded, failed, cancelled, and unknown; they impose no transition or launch protocol. Summary version 1 permits a message and nonnegative integer input/output token counts and is bounded to 64 KiB. The whole observation and its event are bounded to 96 KiB. Unknown fields are rejected. Times are RFC3339 instants; an end cannot precede a start. An occurrence key is at most 1024 characters; actor and optional executor label are at most 256 bytes. Teammates use Kata's existing 1–64 character ASCII names.

A new observation uses expected revision 0. A changed observation requires its current revision. An identical retry of the same UID and canonical evidence returns replayed: true, emits no event, and changes no revision even when its expected revision is stale. A different immutable identity always conflicts. Authenticated actor attribution overrides an untrusted request actor, and revocation is rechecked in the write transaction. Logging does not change an issue's owner, dates, lease, status, or readiness.

Responses contain run, the exact committed events, and replayed. Run listing is project-scoped, accepts --job-uid to list one job's runs, and orders by creation time and UID descending. Use next_before_uid with --before-uid for another page. Ordinary federation publishes observations from spokes, folds evidence by original event provenance per run UID, and preserves independent runs in baselines and backup. Run identity conflicts fail rather than replacing a run.

Native issue planning dates

kata show <issue-ref> --planning-dates --json and GET /api/v1/projects/{project_id}/issues/{ref}/planning-dates read one consistent issue revision with required nullable scheduled_on and deadline_on objects. Each present object contains field, raw value, resolved timezone, and a UTC instant. The flat response also contains project_id, issue_uid, and ordinary revision.

Native date parsing applies the issue timezone first, then the recurrence fallback for scheduled dates, then daemon default timezone and UTC. Deadline dates have no recurrence fallback. Explicit UTC values resolve to UTC. The read uses native civil-time and DST behavior; it evaluates no clock and changes no readiness or persisted state. Normal project and issue-subtree read policy applies.

Adapter responsibilities

Adapters must exclude notify jobs with issue.kind: "per-run" from their schedulers; those jobs belong to the native hub. Existing strict decoders reject the new parent_uid shape instead of scheduling it.

Shared enabled configuration does not activate an adapter installation. Each adapter owns its local activation, scheduler, overlap and catchup handling, checkout and secret mappings, process lifecycle, and conversation recovery. A second adapter may independently execute the same occurrence. Each execution keeps a separate run UID; no shared observation reserves the issue or occurrence. Local transcripts, process IDs, session handles, credentials, and delivery buffers are not native run evidence. An adapter may buffer a failed observation write and retry it later without blocking its own work.

The adapter must refresh current definitions before starting new scheduled work; offline cached data is for labeled reads. Portable cron triggers use the configured trigger timezone. Issue-date triggers use the planning-date instant above: issue timezone, recurrence fallback, daemon default and UTC remain authoritative. Future scheduled_on and someday defer ordinary execute readiness; deadline_on never does. Deadline actions are notification-only.

Kata's default-date sweeper continues to request the current owner's attention, or the author's when unowned, for reached native schedules and deadlines. The same-source, zero-lead current-owner-or-author alias must not produce a second adapter notification. Custom offsets and recipients use ordinary notifications. Inbox addresses are exact: worker does not aggregate worker/adapter. Reads do not clear requests; the recipient handler clears only after handling and reads back. Attention is a replaceable per-issue/recipient slot, so replacement and clear can race.

Shared definitions and run evidence inherit ordinary authentication, project/ issue-scoped roles, actor attribution, token revocation and credential-origin checks. Capability discovery advertises compatibility only. Shared federation and backup do not transfer local activation, checkout credentials, execution buffers or conversation handles; preserve those separately under the adapter's backup workflow. PostgreSQL runtime roles still cannot perform owner-only migrations.

Herdr Kata is one adapter that implements this contract; its documentation covers setup.

Storage and backup

SQLite and PostgreSQL schema 31 store native cron data in cron_jobs, cron_workflows, and cron_runs. JSONL export and import retain shared definitions, including tombstones, and bounded run evidence. Native recurring delivery reuses ordinary issue metadata and parent links; it adds no tables or schema version. Keep parent checkpoints and child provenance in the same backup. Runs with running or unknown status do not prevent an explicitly requested restore, federation reset, adoption, or project merge; those operations keep their ordinary identity, revision and credential guards. See PostgreSQL migrations for owner-only migration, backup, runtime grants, and rollback requirements.

Routes, events, and records

Workflow routes are /api/v1/projects/{project_id}/cron/workflows and /api/v1/projects/{project_id}/cron/workflows/{cron_uid}. Responses use workflow or workflows; job actions and run observations use workflow_uid, and run observations pair it with workflow_definition_event_uid. Events use cron.workflow.*, and JSONL workflow records use cron_workflow.