A trigger starts a session with no person present. A cron schedule fires it on the clock; a signed webhook fires it on an event. Either way the agent gets its own cloud computer, its own branch, and the same review on the way back.
Two types · Declared in kortix.yaml · Reviewed like everything else
A trigger is a clock or a signature. Everything else about it — which agent it runs as, what it says, which session it lands in — is the same config either way.
A 6-field cron expression — second, minute, hour, day, month, weekday — in any IANA timezone. Or a single run_at timestamp, for something that should happen once and then stay quiet.
An external service POSTs to the trigger URL. Kortix checks the signature, renders the payload into the prompt, and starts the session. A payload that fails your filter is accepted and ignored.
Every trigger in a project is one row: what it is called, when it fires, in whose timezone, as which agent, and which session that fire lands in. Nothing about it is hidden state you have to click into.
| Trigger | Cron | Timezone | Agent | Session |
|---|---|---|---|---|
| daily-digest | 0 0 9 * * 1-5Weekdays at 09:00 | America/Los_Angeles | kortix | fresh |
| invoice-sweep | 0 30 6 1 * *The 1st of the month at 06:30 | Europe/Berlin | finance | reuse |
| oncall-handoff | 0 0 17 * * 5Fridays at 17:00 | UTC | support | fresh |
| roadmap-review | 0 0 8 * * 1Mondays at 08:00 | America/New_York | planner | pinned |
Triggers live in kortix.yaml next to your agents and sandbox images. Each one names its agent, its schedule or its secret, and the prompt template that becomes the session’s first message.
# fires on the clocktriggers: - slug: daily-digest type: cron agent: kortix cron: "0 0 9 * * 1-5" timezone: America/Los_Angeles session_mode: fresh prompt: | Summarize yesterday’s commits. Open a change request against main. # fires on an event - slug: new-lead type: webhook agent: sales secret_env: WEBHOOK_SECRET prompt: >- A new lead arrived: {{ body.name }} ({{ body.email }}). Add it to the CRM.# add it, ship it, and the schedule is live$ kortix triggers add daily-digest --type cron \ --cron "0 0 9 * * 1-5" \ --timezone America/Los_Angeles \ --prompt "Summarize yesterday. Open a CR."$ kortix ship→ kortix.yaml pushed. daily-digest is scheduled. # see every trigger and when it last fired$ kortix triggers ls # do not wait for 09:00 to find out$ kortix triggers fire daily-digest→ session startedA prompt renders {{ token.dotted.path }} against the payload that fired it. A webhook fire gets {{ body.* }} and the request headers; a cron fire gets {{ cron.schedule }}, {{ cron.timezone }} and {{ cron.scheduled_for }}. A value that is not there renders as nothing — no error, no leftover braces in the message your agent reads.
Every webhook trigger names a project secret that signs it. A trigger without one is rejected at validation — there is no unauthenticated webhook to forget to lock down later.
POST /v1/webhooks/projects/{projectId}/{slug}
X-Kortix-Signature: sha256=<hmac>
HMAC-SHA256 over the raw request body, compared in constant time. The GitHub-compatible X-Hub-Signature-256 header works too, so a repo webhook needs no adapter.
A filter is a dotted path matched against the same payload the prompt sees. It exists to break loops: a source that reports both sides of a conversation would otherwise fire the agent on its own reply.
By default every fire is a clean slate. When the work is a running thread rather than a fresh errand, a trigger can re-prompt a session it already owns. Kortix tries the modes in order and falls through on failure, so a fire never simply disappears.
Re-prompt one exact session, named by id. If that session is gone or failed, fall through.
Render a key from the payload, then re-prompt the most recent healthy session stamped with that exact key. One customer, one thread. It never falls through into another key’s session.
Re-prompt the most recent healthy session this trigger created. A pinned trigger falls back here before falling further.
Cut a new branch and boot a new cloud computer. This is the default, and the last resort for every other mode.
A triggered session is visible to the whole project, not private to whoever configured the trigger. It stops itself after 5 minutes idle, so an automation that runs at 3am is not a machine billing until morning.
An automation gets no privileges a person would not get. The same isolation, the same scoped reach, the same one road back to main.
Open source and self-hostable. Any model, your keys. Kortix Cloud, your own VPC, or fully on-prem.