Skip to content

Run on a schedule

A schedule starts a run of an agent or a flow at the times a cron expression gives. Its runs act as whoever made the schedule, checked again at every run. They land in the schedule's project, and each names the schedule that started it.

Schedules are the one way the runtime starts runs by itself today: event triggers and inbound webhooks aren't served yet. To react to something outside, have it start a run with POST /v1/runs.

Terminal window
kindgi schedules create --agent=acme.order-desk --cron="0 7 * * 1-5" --timezone=America/Toronto \
--input='{"userMessage":"List the orders still open from yesterday."}' --label="weekday digest"

It prints the schedule, with its id. kindgi schedules get <schedule-id> --upcoming=3 adds the next three times it runs.

  • What it runs: --agent runs the agent's live version (else its latest), and --agent-version pins one. --flow needs --flow-version.
  • Its input: an agent's is {"userMessage": "…"}, the message each run sends; a schedule without one is refused when you create it. A flow takes its own input ({} if you give none). --input=@digest.json reads a file.
  • When: --cron takes a cron expression (0 7 * * 1-5 is 07:00 on weekdays) or a shorthand such as @daily, in --timezone (an IANA name; UTC by default). The shortest interval is a minute, except under kindgi dev.
  • Where: --project=<project-id>; the tenant's Default project if you leave it out.

From your app, the clients have the same calls on schedules: register, list, get, update, pause, resume, runNow, fires, takeOwnership and unregister (run_now and take_ownership in Python).

Each time the schedule comes due is a fire. A fire starts one run with the schedule's input, and the run's trigger names the schedule, the fire and the time it was for. With several runtime instances on one database, each time still fires once.

  • The previous run is still going (working, or waiting for an approval): with --overlap=skip, the default, the fire is skipped-overlap. --overlap=allow starts another run.
  • The runtime was down when one or more times came due: with --catch-up=latest, the default, it fires once when it's back, for the latest of them, and says how many it stood in for (missedCount). --catch-up=skip fires none of them. A time is missed when the fire would start more than --starting-deadline seconds late (600 by default).
  • Its owner may no longer run it (they lost their role on the project, say): the fire is refused, with why. Whoever holds admin on the project can make themselves its owner: kindgi schedules take-ownership <schedule-id>.
  • The person a run would act for is being erased: the fire is skipped-erasure, and no run starts for them until the erasure completes.

After 5 fires in a row that were refused or failed, the schedule pauses itself, with the reason in its statusReason. Skipped fires don't count. kindgi schedules resume <schedule-id> clears it.

Terminal window
kindgi schedules fires <schedule-id> --table

The history is newest first, kept for 30 days. Each row has when the fire happened (FIRED), the time it was for (FOR, or run-now for one you started by hand), its OUTCOME, what it STARTED (a run's id, or pass <id> for an improve schedule), and a NOTE: why it was skipped, refused or failed, or how many missed times it stood in for. Without --table, the same fires come as JSON. The outcomes are started, skipped-overlap, skipped-erasure, skipped (an improve schedule's), refused, failed, and pending while a fire is starting.

kindgi runs list --trigger=<schedule-id> lists the runs it started.

Each fire also logs one [schedules] record, with its outcome and run (Logs).

Terminal window
kindgi schedules list --status=active
kindgi schedules list --project=<project-id>

--project (?projectId= in the API) lists one project's schedules. Each schedule's owner has the owner's name now, displayName: the person's, or the service account's. When it can't be read, only its id is there. Both need the runtime 0.1.6 or later; an earlier one lists every project's and has no names.

With authorization on, the list holds the schedules in projects you may read, all of them for a tenant admin. A page can then hold fewer than --limit and still have more after it: keep going with --cursor (the previous page's nextCursor) until there's none.

  • kindgi schedules run-now <schedule-id> runs it once now, outside its schedule. Its next time doesn't move, and a run still going doesn't stop it.
  • kindgi schedules pause <schedule-id> stops it; resume starts it again from its next time, without running the ones it missed while paused.
  • kindgi schedules update <schedule-id> changes when it runs, what it runs, its input or its policies, with the same flags as create.
  • kindgi schedules unregister <schedule-id> removes it.

A project's Schedules page lists its schedules, with a headline such as "2 schedules, 1 paused; the next fires in 19 h.". Each row says what it runs ("the agent demo.echo-agent (its live version)", "the flow acme.refunds 1.0.0"), its cron, when it next fires ("Next in 19 h (2026-10-12 02:00 UTC)") or that it's paused, its last scheduled fire, and Its runs →. Times are UTC.

The Schedules page: "2 schedules, 1 paused; the next fires in 19 h.", the paused weekly report (the flow acme.refunds 1.0.0, 0 9 * * 1) with Resume, and the active nightly digest (the agent demo.echo-agent, 0 2 * * *, next in 19 h) with Pause, each with Run now and Unregister.

A schedule's page has:

  • When: its cron in UTC and its next five times. A paused one says "It doesn’t fire while paused." instead.
  • Runs as: whose access its runs have ("Runs as you, with your access."), checked again at every fire.
  • What it fired: a summary ("Of its 1 fire, 1 started a run.") and its recent fires in words, each linking the run it started, or saying why the fire was refused.

The nightly digest's page: Active, with Pause, Run now and Unregister, its next five times from 2026-10-12 02 UTC, "Runs as you, with your access.", and one fire, "Started a run (run now)", linking its run.

Project editors and up have, on the list and on the page:

  • Pause and Resume;
  • Run now: "It starts a run now, as its owner, with its usual input. Its next scheduled fire doesn't change.";
  • Unregister: "It stops firing. The runs it started stay, with their record of what started them." It can't be brought back from the console: register it again.

On a schedule that runs as someone else, project admins also get Take over: "Its next runs start as you, with your access. Runs already started keep theirs. If it's paused, it stays paused."

The Take this schedule over? dialog on a paused schedule that runs as a service account: its next runs start as you, runs already started keep theirs, and a paused one stays paused.

A run a schedule started says so at the top of its page: "Started by the schedule “nightly digest” (0 2 * * *).", with Its other runs →, the runs list filtered to that schedule.

A run's page with "Started by the schedule “nightly digest” (0 2 * * *). Its other runs →" under its header.

A schedule can start improvement passes instead of runs, when enough new trusted judgments have come in on an agent's runs:

Terminal window
kindgi schedules create --cron="0 3 * * *" --improve=acme.rank.scorer --project=<project-id> --segment=company:acme \
--input='{"tiers": ["settings"], "threshold": {"judgments": 5, "runs": 3, "judges": 2}, "monthlyCapUsd": 20}' --label="acme improve"
{
"scheduleId": "fa6356f8-3f9f-4ab3-b713-636424a43120",
"triggerId": "fa6356f8-3f9f-4ab3-b713-636424a43120",
"improve": {
"agentId": "acme.rank.scorer",
"scope": {
"kind": "segment",
"projectId": "eea49512-700f-4c59-9f66-05ca3b820a3b",
"path": [
{
"key": "company",
"value": "acme"
}
]
}
},
…
"input": {
"tiers": [
"settings"
],
"budget": {
"maxCostUsd": 5,
"maxCandidates": 30
},
"objective": "weightedYesShare",
"threshold": {
"runs": 3,
"judges": 2,
"judgments": 5
},
"classWeights": "restricted-only",
"monthlyCapUsd": 20
},
…
}

At each fire it counts the trusted "no" judgments (recorded under a restricted judge class) on the agent's runs in its scope since the agent's last pass there (one that didn't fail or get cancelled, whoever started it). With enough of them, across enough runs, from enough judges, it starts a pass on a fresh test set of those runs (improve.<pass-id>). Otherwise the fire is skipped, and its note says which count was short. Below, that schedule's history after three run-now fires, newest first. The oldest, at the bottom, was skipped: one judge short. Then the threshold was lowered to one judge (kindgi schedules update <schedule-id> --input='{"tiers": ["settings"], "threshold": {"judgments": 5, "runs": 3, "judges": 1}, "monthlyCapUsd": 20}'), the next fire started a pass, and the newest came while that pass was still running:

Terminal window
kindgi schedules fires <schedule-id> --table
FIRED FOR OUTCOME STARTED NOTE
──────────────────────── ─────── ─────────────── ───────────────────────────────────────── ──────────────────────────────────────────────────────────────────────────────────────────────────────
2026-10-09T13:25:40.917Z run-now skipped-overlap Its previous pass 3599f4ab-8788-433f-af66-7990ddfa9f47 is still running.
2026-10-09T13:25:40.341Z run-now started pass 3599f4ab-8788-433f-af66-7990ddfa9f47
2026-10-09T13:25:39.201Z run-now skipped Not enough new trusted judgments so far: 1 of 2 judges (trusted "no" judgments met: 10, runs met: 10).
  • Its scope is the schedule's project (--project), or a segment of it (--segment=<key:value>, repeated for a path). Never the tenant or an org: a pass learns from one project's runs, so its evidence must cover the scope it changes. Promote wider by hand after review.
  • Its input (--input) takes the pass's options, threshold and monthlyCapUsd, and is kept with the defaults applied, as above: the thresholds default to 5 judgments, 3 runs and 2 judges, and the cap to 20 US dollars a month.
  • A monthly cap: once the schedule's passes this month (UTC) have cost monthlyCapUsd, its fires are skipped until the next month, and a pass's budget is never more than what's left of it.
  • One pass at a time: while the schedule's previous pass still runs, a fire is skipped-overlap.
  • At most once an hour. Registering needs publish on the agent.

A fire that started a pass names it (passId), and the pass names the schedule and the fire (trigger). When the pass proposes, it asks for the review at once, so the proposal waits in reviewers' approvals.