Field notes

Autonomous agents run on a number you set on the trigger

You have read a page of definitions of autonomous AI agents this week and none of them helps you compare two products

The Kortix Team
Kortix··6 min read

You have read a page of definitions of autonomous AI agents this week and none of them helps you compare two products. Each describes what a model decides inside a run, then offers a link to the vendor's own platform. The sentence that would settle the purchase is missing: how often the system may act while nobody is watching. Kortix is the open-source AI Operating System, and the triggers that start an unattended session are rows in kortix.yaml with a cap beside them.

A cap on the trigger is what makes autonomy a quantity you can read before you buy. What follows is the arithmetic of an unattended run: fires an hour, fires a day, and what stops a loop.

The definitions describe one run and leave the rate out

Microsoft says autonomous agents are "systems that can independently analyze situations, make decisions, and act," and that "they don't just follow preset instructions" (Microsoft). Salesforce says "autonomous agents can execute on tasks and workflows independently" (Salesforce). NVIDIA calls them "advanced AI systems that reason, plan, and execute multi-step tasks to meet a goal, with built-in privacy and policy controls for secure usage" (NVIDIA).

Snowflake is the most careful of the four. Autonomous agents there are "expected to stay engaged with an outcome," and they "track progress and adapt over time" instead of completing one action and waiting (Snowflake). Read all four and one absence stands out: how many runs the system may start in an hour, what it does at the ceiling, who reads the record afterwards.

Snowflake comes closest to a bound. It describes a loop that keeps going until the objective is reached, a time limit expires or a governance boundary forces an escalation. A time limit is named, with no number and nobody assigned to set it. A limit nobody can choose is a limit nobody can check.

So compare on quantity. Every autonomy claim in a scheduled system reduces to three numbers and a default. All of them sit beside the trigger, outside the model.

The hourly cap has a default and the daily cap does not

The triggers documentation states the first figure plainly: "A trigger starts at most max_runs_per_hour runs in any 60 minutes, 60 when unset, and at most max_runs_per_day runs in a UTC day, no cap when unset" (Kortix triggers).

Every trigger starts life with a ceiling of 60 fires an hour whether or not anyone chose it. No trigger starts life with a daily ceiling, so the day stays open until someone writes one.

The hourly default matters most for the trigger types with no clock. A webhook or an app event arrives when the outside world sends it. A retry storm from one broken sender is the case that ceiling exists for. A fire past the limit starts no session and sends no prompt. It is recorded as a skipped run whose reason names the limit. A webhook answers 200 with status: skipped, so a sender that retries on errors stops retrying. Each cap is one key in the file or one flag in the CLI, and the file is the version you can diff.

triggers:
  - slug: nightly-triage
    type: cron
    cron: "0 0 2 * * *"
    timezone: Europe/Berlin
    prompt: "Triage new issues. Open a change request for the fixes you are certain about."
    max_runs_per_hour: 4
    max_runs_per_day: 4

From a terminal, the same trigger is kortix triggers add nightly-triage --type cron --cron "0 0 2 * * *" --max-runs-per-day 4 --apply. --max-runs-per-hour and --max-runs-per-day apply on every add and set, while the session flags apply only with --apply.

A runaway repeats, so the caps count fires

The expensive failure of an unattended agent is rarely one catastrophic run. It is the same mediocre run two hundred times, each paying for a sandbox and a model call. The unit that repeats is the fire, so the fire is what a cap counts.

The trigger also stops itself when it keeps hitting its hourly limit, on a threshold you can predict. "When max_runs_per_hour holds a fire and the trigger also ran at that limit through the hour before, the trigger pauses itself. Two full hours at the limit is the threshold." The daily cap never does this, and the docs say why: holding the rest of the day is what that limit is for. An hour at the ceiling is a busy hour, two hours in a row is a loop, a day at the ceiling is a budget.

Overlap is the third cap and the one buyers forget. overlap: skip is the default for a cron trigger, so "a fire waits out the previous run". Without it, a schedule that fires faster than its runs finish stacks sessions. Each session is a real machine. Provisioning is bounded past that as well: at most three trigger-created sessions provision at once in a project, and a fire past that answers queued and runs when a slot frees.

The schedule cannot ask for less than a minute

A cron trigger takes six fields, seconds first, and "a trigger fires at most once a minute." The docs state the consequence: 0 */30 * * * * fires every 30 minutes, and */30 * * * * * asks for every 30 seconds, which kortix validate and the API reject.

The floor decides what a schedule can express. Anything you would want to poll more often than once a minute belongs to a webhook, because the event already exists and a schedule would only guess at it. For work that does belong on a clock, the interval gets the attention and the drift decides what actually happens: "Kortix offsets each run by a fixed amount: up to a quarter of the period, at most 30 minutes." A hundred projects that all ask for nine in the morning do not all fire at nine. The offset stays bounded, and next_fire_at shows the real time of the next run.

Two more details keep the count honest across failures. Each fire is keyed on its schedule slot, so a retried fire does not run the same slot twice. A slot missed while automations are paused fires once on resume, rather than replaying the backlog. The cap counts runs, not intentions, and a restart does not double it.

What the caps do not decide

A cap governs how much work arrives. It says nothing about what lands. A trigger starts a session with nobody present. The session's work still leaves as a change request: "The agent commits, pushes the session branch, and opens a change request against the project's default branch" (Kortix docs). Merge is default-deny for agents.

That boundary is what makes a high cap defensible. Work reaches main through a change request a person approves, unless an admin granted the merge capability. Raise the fires and you get more proposed work on branches, waiting on whoever holds project.gitops.merge. The starter agent ships kortix_permissions: all, which lets a session merge its own change request, so a high cap is only as safe as that grant.

Six questions to put to any autonomy claim

  • How many fires an hour, and was that number chosen? A published default tells you what happens on day one.
  • Is there a daily cap, and what is the value when it is unset? An uncapped day is a decision the vendor made on your behalf.
  • What happens at the ceiling? A skipped run with a named reason is a control. A silent retry, or a queue that grows without a bound, is not.
  • Does a runaway stop itself, and on which counter? A trigger that pauses after two hours at its limit differs from one that only holds the rest of the day.
  • What is the fastest interval the schedule accepts? A minute floor with a bounded offset is a design choice. "Real time" is a phrase from a brochure.
  • Does a missed slot replay as a backlog? A restart that fires the last three thousand slots is an outage with a schedule attached.

Two more posts go deeper on the ends of this: where an agentic workflow ends covers the review a run lands in, and human in the loop vs human on the loop covers the per-call policy that sits underneath the caps.

If you want a system whose autonomy you can read off a file, try Kortix. Free to start, free to self-host.

Run your whole company from one repo you own.

Start with one job and grow from there.

Get started