Skip to content
KortixKortix
Esc
↑↓navigate↵open⌘Jpreview
On this page

Triggers & reminders

Start sessions on a schedule, from a signed webhook, from an app event or from a monitor, and have a session re-prompt itself later.

A trigger starts a session with no person present. A reminder re-prompts one existing session later, once or on repeat. This page is for project owners who automate agent work and debug a fire that failed.

How triggers work

A trigger has a type, a prompt, and the agent that runs it. Each fire renders the prompt against the fire’s payload and sends it to the session that session_mode picks: a new session by default.

Type Fires when Needs
cron A schedule slot arrives, or once at run_at. Nothing else.
webhook A signed POST reaches the trigger’s URL. A project secret that signs requests.
event Something happens in a connected app, such as a new pull request or a new email. The project flag event_triggers and a connector with a shared account.
monitor A command from your repository prints a line on stdout. The project flag monitors, on an instance whose sandbox provider is Platinum.

The shortest path is a schedule:

kortix triggers add daily-digest --type cron \
  --cron "0 0 9 * * 1-5" --timezone Europe/Berlin \
  --prompt "Summarize yesterday's commits and open change requests." --apply
kortix triggers fire daily-digest

--apply creates the trigger now. fire runs it once, waits up to 90 seconds for the outcome, and exits non-zero with the failure text when the run fails.

Where a trigger lives

A trigger is an entry under triggers: in kortix.yaml, or in a file listed under imports:. Runtime state, such as the last fire, the status and the last error, lives in the database. A fire creates no commit.

You change a trigger with Effect
kortix triggers add, rm, enable, disable Edits the local kortix.yaml. kortix ship pushes it. The trigger goes live when the change reaches the default branch. rm, enable and disable edit the file that declares the trigger.
The same commands with --apply, and every kortix triggers set Commits to the default branch now. No change request.
The web app, the API or the SDK Commits to the default branch now. No change request.
kortix.yaml edited in a session Follows the session branch and its change request.

In the web app, open the project, then Triggers. New trigger creates one. Run now fires one.

From code, the SDK exports listProjectTriggers, createProjectTrigger, updateProjectTrigger, deleteProjectTrigger, fireProjectTrigger and setProjectTriggersActivation. The REST routes are under /v1/projects/<project_id>/triggers in the API reference.

Commands

Command Effect
kortix triggers ls [--type cron|webhook|event|monitor] [--connector <slug>] [--json] Lists triggers, their state, last fire and manifest errors.
kortix triggers info <slug> [--json] Shows one trigger in full: the webhook URL and a signed sample request, or an app event’s status and next step.
kortix triggers add <slug> [--apply] Adds a trigger. --type defaults to cron.
kortix triggers set <slug> Changes a live trigger. Writes only the flags you pass.
kortix triggers rm|enable|disable <slug> [--apply] Removes, enables or disables a trigger.
kortix triggers fire <slug> [--wait <dur>] Fires now. --wait 0 returns when the fire is accepted.
kortix triggers pause|resume Stops or restarts every automatic fire in the project. See Pause and limits.
kortix triggers events … Lists apps and events for app event triggers. See App events.

--model, --session-mode, --session-key, --session-id, --session-access, --member, --group and --filter apply only with --apply on add, and on set.

Reading triggers needs project.trigger.read. Creating, editing and deleting need project.trigger.create, project.trigger.update and project.trigger.delete. Firing needs project.trigger.fire, which the built-in member role holds.

Schedules

A cron trigger takes a 6-field expression: second, minute, hour, day of month, month, weekday. The seconds field comes first.

triggers:
  - slug: daily-digest
    type: cron
    cron: "0 0 9 * * 1-5"
    timezone: Europe/Berlin
    prompt: Summarize yesterday's commits and open change requests.
  • A trigger fires at most once a minute. 0 */30 * * * * is every 30 minutes. */30 * * * * * is every 30 seconds, and kortix validate and the API reject it.
  • run_at: 2026-11-02T09:00:00Z (or --run-at) fires once, in place of cron. Set one of the two, never both.
  • timezone takes an IANA name. The default is UTC.
  • Kortix offsets each run by a fixed amount: up to a quarter of the period, at most 30 minutes. This spreads triggers on the same minute. next_fire_at in GET /v1/projects/<project_id>/triggers shows the real time of the next run.
  • Each fire is keyed on its schedule slot. A retried fire does not run the slot twice.

Webhooks

A webhook trigger fires when a signed request reaches its URL. Every webhook trigger needs a signing secret. There is no unsigned webhook.

Store the signing secret

kortix secrets set WEBHOOK_SECRET=- --scope connector   # reads the value from stdin; Kortix keeps it server-side for the webhook receiver

For a secret that already exists, run kortix secrets delivery WEBHOOK_SECRET broker --consumer connector. Without this delivery, the API refuses to create the trigger (409) and every request answers 401. See Secrets.

List the name under env.optional or env.required in kortix.yaml. Otherwise kortix validate and kortix ship report triggers[n].secret_env as an error.

env:
  optional: [WEBHOOK_SECRET]

Add the trigger

kortix triggers add new-lead --type webhook --secret-env WEBHOOK_SECRET \
  --prompt "A new lead arrived: {{ body.name }} ({{ body.email }}). Add it to the CRM." --apply

Point the sender at the URL

kortix triggers info new-lead

info prints the URL and a sample request signed with a $SECRET placeholder. The URL is https://api.kortix.com/v1/webhooks/projects/<project_id>/<slug>. A self-hosted instance uses its own API host.

Sign a request

Kortix authenticates a request in this order, with a constant-time comparison:

  1. HMAC signature. X-Kortix-Signature: sha256=<hex> or the GitHub header X-Hub-Signature-256. The value is HMAC-SHA256 of the raw request body, keyed with the secret. The sha256= prefix is optional.
  2. Replay guard, optional. Add X-Kortix-Timestamp: <epoch seconds> and sign <timestamp>.<body> in place of the body. Kortix rejects a timestamp more than 300 seconds from its clock.
  3. Static token, only when no signature header is present, for senders that cannot sign a body: X-Kortix-Token: <secret>, Authorization: Bearer <secret>, or Authorization: Basic with the secret as the password.
URL='https://api.kortix.com/v1/webhooks/projects/<project_id>/new-lead'   # from kortix triggers info
SECRET='<the WEBHOOK_SECRET value>'
BODY='{"name":"Ada","email":"ada@example.com"}'
curl -X POST "$URL" \
  -H "Content-Type: application/json" \
  -H "X-Kortix-Delivery-Id: $(uuidgen)" \
  -H "X-Kortix-Signature: sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')" \
  -d "$BODY"

Responses

Status Meaning
202 Accepted. Body status is fired, queued or deduped, with session_id and command_id.
200 Accepted and skipped. Body { "status": "skipped", "reason": … }: the project is paused, or the body did not match filter.
400 The project id or the slug in the URL is malformed.
401 Any miss before authentication: a bad signature or token, a stale timestamp, an unknown project or trigger, a disabled trigger, or a secret that is missing or not delivered to connector. The response does not say which.
402 The account cannot run the fire: it has no usable model, or a billing gate blocks it. The body code is no_usable_model, insufficient_credits, subscription_required or no_account.
429 More than 120 requests per minute from one client to one project.
500 Authentication passed and the fire failed.

One run per event

Kortix runs a delivery once per event. It reads the event id from the first of these headers that is present: X-Kortix-Delivery-Id, X-GitHub-Delivery, X-Gitlab-Event-UUID, Linear-Delivery, webhook-id, svix-id. A retry with the same id answers 202 with status: deduped and runs nothing. Without an id, an identical body and signature within 10 minutes count as one event. Send a new X-Kortix-Delivery-Id for each event.

App events

An event trigger runs an agent when something happens in a connected app: a pull request opens, an email arrives. You do not sign anything. Kortix subscribes to the event for you.

App event triggers need the project flag event_triggers, off by default. Turn it on in Customize → Settings → Feature flags → App event triggers, or run kortix projects features enable event_triggers. While it is off, the event routes answer 403 with code: feature_disabled. The CLI prints the command that turns it on. An event trigger in kortix.yaml reads status error. Turning it on subscribes those triggers without another save.

Set up in the web app

  1. Open Triggers, then New trigger and Set up manually.
  2. Under When, choose App event. Pick the app and the event. Fill in the event’s settings, such as the repository.
  3. Under Connection, pick a connector and one of its shared accounts, or connect one. If the project has no connector for the app, Kortix adds one when you create the trigger.
  4. Under Then, write the instruction and pick the agent and the model.
  5. Choose Create trigger. The trigger works when its badge reads Live.

Set up with the CLI

An agent can run these steps. Each step prints what the next step needs.

kortix triggers events --apps                                   # apps with events, their connectors and shared accounts
kortix connectors add github --provider composio --app github --apply   # skip when the app has a connector
kortix connectors connect github --owner project                # a person opens the printed link
kortix triggers events --connector github --event GITHUB_PULL_REQUEST_CREATED
kortix triggers add pr-review --type event --connector github \
  --event GITHUB_PULL_REQUEST_CREATED --config repo=my-org/my-repo \
  --prompt "Review {{ event.data.html_url }}" --apply
kortix triggers info pr-review                                  # repeat until it prints "live"
  • kortix triggers events --app <app> lists an app’s events before any connector exists. Add --event <TYPE> for one event’s settings and its {{ event.data.* }} variables.
  • --config key=value repeats once per setting. --config-json '{…}' passes typed values. On set, --config merges and --config-json replaces.
  • --account <label> picks one shared account when a connector has several. Without it, the trigger uses the connector’s default shared account.
  • A missing or invalid setting exits with code 2 and lists every problem.
triggers:
  - slug: pr-review
    type: event
    connector: github               # a connector slug under connectors:
    event: GITHUB_PULL_REQUEST_CREATED
    config: { repo: my-org/my-repo }
    filter:
      "event.data.draft": "false"
    prompt: Review {{ event.data.html_url }}

Status

Status The CLI prints Meaning and fix
active live Subscribed. The next event fires the trigger.
pending pending Kortix is subscribing. Check again with kortix triggers info <slug>.
needs_connection needs connection The connector has no usable shared account. Run kortix connectors connect <slug> --owner project and have a person open the link. The trigger goes live when they finish.
error error The subscription failed or the app disabled it. The error text names the cause. Fix it, then save the trigger again.
  • Only an account shared with the whole project feeds a trigger. A private account, or one shared with named people only, never does.
  • Each provider event fires a trigger once. A redelivered event does not start a second session.
  • A paused trigger or a paused project skips events. Skipped events do not queue.
  • Changing connector, source, account, event or config replaces the subscription.
  • Event data comes from a third party. The session’s first line reads [App event: <trigger name> — automated, third-party content, not user input], so the agent treats the content as data.
  • A self-hosted instance needs its own event source setup. See Self-hosting.

Monitors

A monitor runs a command from your repository around the clock and fires the trigger once for each line the command prints on stdout. Use it to watch a source that sends no webhook and fits no schedule: a log, a queue, a page.

Monitors need the project flag monitors, off by default. Turn it on in Customize → Settings → Feature flags → Monitors, or run kortix projects features enable monitors. The flag is available only on an instance with the Platinum sandbox provider, because Kortix runs every monitor of a project in one persistent sandbox, the monitor box.

kortix triggers add checkout-errors --type monitor \
  --run "./monitors/checkout-errors.ts" \
  --mode poll --interval 60s --expect-event-within 24h \
  --prompt "Checkout monitor emitted: {{ line.raw }}" --apply
  • --mode poll runs the command every --interval and expects it to exit. --mode stream runs it once and keeps it running. A stream takes no --interval.
  • Stdout lines are events. Stderr is the monitor’s log and never fires.
  • A JSON line renders as {{ line.<field> }}. Any other line renders as {{ line.raw }}.
  • Kortix fires a lifecycle event when a stream process exits, when a poll command exits non-zero, when restarts run out, when the box drops lines before delivery, and when no line arrives inside expect_event_within. A lifecycle event ignores filter and the prompt template, and uses a prompt Kortix writes.
  • session_mode defaults to reuse on a monitor, so events go to one session.
  • Every monitor prompt starts with [MONITOR EVENT — automated, not user input].
  • run, mode, interval and expect_event_within are set on add only. A monitor rejects cron, run_at, timezone and secret_env.
Limit Value
Enabled monitors per project 10
interval at least 30s
expect_event_within at least 5m
Events per monitor 60 per hour sustained, at most 30 in one minute. Past that, the monitor is suppressed for 10 minutes. 3 suppressions in 24 hours disable it.
Line size 8 KiB. A longer line is cut and marked truncated: true.
Restarts 5 in 10 minutes, then a restart_budget_exhausted lifecycle event and a retry every 15 minutes.
Event retention 30 days
Compute A monthly budget per project. When it is spent, the box stops and a budget_exceeded lifecycle event fires.

Reminders

A reminder sends a prompt into one existing session at a time you set: “in 24 hours, check whether the vendor replied”. It fires once, or it repeats until someone removes it. A reminder lives in the database, not in kortix.yaml. It makes no commit, and a fire never starts a new session.

Reminders need the project flag reminders, off by default. Turn it on in Customize → Settings → Feature flags → Reminders, or run kortix projects features enable reminders. While it is off, reminder routes answer 403 with code: feature_disabled and no reminder fires.

kortix remind "Check whether the vendor replied to the contract email." --in 24h
kortix remind "Did the deploy go green? If yes, report it and remove this reminder." --in 10m --every 1h
kortix remind "Post the launch checklist." --at 2026-11-02T09:00:00Z
kortix remind "Post the standup summary." --cron "0 0 9 * * 1-5" --timezone Europe/Berlin

kortix remind is kortix reminders add. Inside a session it targets that session. Outside one, pass --session <session_id>; a unique prefix works. --name sets a label. The command prints the reminder id, such as reminder.3f9a1c2e7b04, and the next fire.

Flag Rule
--in <dur>, --at <iso> Set the first fire. Alone, the reminder fires once. At most 366 days ahead.
--every <dur> Repeats. At least 5m, at most 366 days. Each period starts when the previous fire is claimed.
--cron "<expr>" Repeats on a 6-field cron, at most once every 5 minutes. Takes no --in or --at.
--timezone <tz> IANA zone for --cron. Default UTC.

Durations look like 30m, 24h or 2d. A session holds at most 20 active reminders and a project 200; past that, the API answers 409. A prompt holds at most 10,000 characters.

What a fire does

  • The fire queues the text into the session after a header line: [REMINDER <id> — … scheduled check-in on this session, not a new user message.]. A recurring reminder’s header adds the kortix reminders rm <id> command.
  • A parked session wakes with its full history and workspace.
  • If the session is deleted or failed, the reminder pauses itself and records last_error.
  • Anyone who can prompt a session can set a reminder on it. An agent can set one only on its own session; another session answers 403.
  • kortix triggers pause also stops reminders.
  • Each fire is a model turn. Remove a recurring reminder when its condition is met.

Manage reminders

Command Effect
kortix reminders ls [--json] Lists the session’s reminders: id, state, schedule, next fire, text.
kortix reminders pause <id> Stops firing and keeps the reminder.
kortix reminders resume <id> Fires again, re-armed from now. A one-time reminder that fired stays done.
kortix reminders rm <id> Deletes the reminder. Aliases: stop, delete.

A reminder is active, paused, or done (a one-time reminder that fired). In the web app, the project’s Reminders page lists every reminder on sessions you can open, and the reminder chip on a session shows that session’s reminders.

From code, use the session handle:

const reminders = kortix.session(projectId, sessionId).reminders;
const reminder = await reminders.create({ prompt: 'Did the vendor reply?', in: '24h', every: '1h' });
await reminders.update(reminder.id, { enabled: false });
await reminders.remove(reminder.id);

Fields

Field Applies to Required Default Rule
slug all yes — [a-z0-9][a-z0-9_-]{0,127}, unique per project. Part of the webhook URL.
type all yes — cron, webhook, event or monitor.
prompt all yes — Template. The message each fire sends.
name all no slug Display name.
agent all no default_agent Must name an agent under agents:. Omit it to use default_agent.
model all no the default model A model id. See How a session gets a model.
enabled all no true false stops every automatic fire of this trigger.
session_mode all no fresh; reuse on a monitor fresh, reuse, pinned or keyed. See Session strategy.
session_id pinned yes — The session to re-prompt.
session_key keyed yes — Template. Setting it alone implies keyed.
filter all no — Map of dotted path to expected string. See Templates and filters.
cron cron one of cron, run_at — 6-field expression, at most once a minute.
run_at cron one of cron, run_at — ISO-8601 instant. Fires once.
timezone cron no UTC IANA name.
secret_env webhook yes — The name of a project secret stored with --scope connector. Declare it under env.
connector event yes — A connector slug under connectors:.
event event yes — The event type, from kortix triggers events.
config event no — The event’s settings.
account event no the connector’s default shared account Label of one shared account.
source event no the connector’s provider The event source. composio is the only one.
run monitor yes — Repository-relative command. One line, at most 1,024 characters.
mode monitor yes — poll or stream.
interval monitor with mode: poll yes — Duration such as 30s, 5m, 24h, 7d. At least 30s.
expect_event_within monitor no — Duration, at least 5m.

A field that belongs to another type is an error: an event trigger rejects cron, run_at, timezone, secret_env, run, mode, interval and expect_event_within, and the other types reject the event fields.

Session strategy

session_mode picks the session a fire prompts. Kortix tries the steps in this order and falls through when a step finds no usable session:

  1. pinned prompts session_id. If that session is gone or failed, it falls through to reuse.
  2. keyed renders session_key against the payload and prompts the latest non-failed session with that exact key. An empty key or no match starts a fresh session. It never uses another key’s session.
  3. reuse prompts the latest non-failed session this trigger created.
  4. fresh creates a new session on its own sandbox and branch. It is the default and the last fallback. The new session becomes the trigger’s session for later reuse and keyed fires.

A reuse or keyed session whose history does not fit the model even after compaction is retired. The next fire starts a fresh session.

A frequent trigger can roll its session on a calendar boundary so its history stays short. session_key: "digest-{{ cron.scheduled_date }}" keeps one session per UTC day. {{ cron.scheduled_hour }} keeps one per UTC hour.

Templates and filters

prompt and session_key use {{ path }} placeholders. A missing value renders as an empty string. An object renders as JSON. A rendered session_key is trimmed and cut to 512 characters.

Fired by Variables
Every fire trigger.slug, trigger.type
Schedule cron.schedule, cron.timezone, cron.scheduled_for (the slot), cron.claimed_at, cron.last_scheduled_for (empty on the first fire), cron.scheduled_date (UTC YYYY-MM-DD), cron.scheduled_hour (UTC YYYY-MM-DDTHH)
Webhook fired_at, body.* (the parsed JSON body; body.raw when it does not parse), headers.content_type, headers.user_agent, headers.forwarded_for
App event fired_at, event.data.*, event.id, event.type, event.app, event.connector, event.occurred_at
Monitor line.* (line.raw for a line that is not JSON), monitor.slug, monitor.seq, monitor.emitted_at, monitor.kind (event or lifecycle)
Manual fire fired_at, source (manual), actor. An event trigger gets an empty event.data.

filter maps dotted paths to expected strings. A fire runs only when every path matches; a missing path does not match. A webhook that does not match answers 200 with status: skipped. Use a filter to break loops, for example when a source reports the agent’s own reply as a new event:

filter:
  "body.data.direction": "inbound"

Session access

--session-access sets who can open the sessions a trigger creates:

Value Web label Who can open
private (default) Trigger managers only The trigger’s agent, people whose project role holds project.trigger.update, and account owners and admins.
members Selected teammates The above, plus the members and groups you pick with --member <uuid> and --group <uuid>.
project Whole project Every project member.

Pass it with kortix triggers add --apply or kortix triggers set, or set session_access in the web app, the API or the SDK. It is stored in the database, not in kortix.yaml, and changing it makes no commit. Saving it also updates the sessions the trigger created before. A pinned session keeps its own sharing. For the access model, see Accounts & access.

Failures and alerts

When a fire or a run fails, Kortix sets the trigger’s last_status to failed and stores the reason in last_error. kortix triggers ls marks the row last run failed, and the web app shows Last run didn’t finish. The next fire or run that succeeds clears it.

With the project flag notification_center on, Kortix sends one automation_failed alert when a trigger starts failing and one automation_recovered alert when it works again. Failures in between send nothing new.

  • Recipients: the people who created or edited the trigger through the web app, the API, the SDK or the CLI. Without one, the project managers. A reminder’s alert goes to the person who set it.
  • Timing: schedules, reminders and monitors alert after their retries run out. Manual and webhook fires alert on the first failure. An app event alerts on the third failed delivery of one event, or on the first failure that a retry cannot fix.
  • Flag off: an account owner’s phone gets one push when a trigger starts failing.
  • Turn alerts off: see Notifications.

Pause and limits

kortix triggers pause stops every automatic fire in the project: schedules, webhooks, app events, monitors and reminders. Webhooks answer 200 with status: skipped. A manual fire still runs. kortix triggers resume turns fires back on. The API route is PATCH /v1/projects/<project_id>/triggers/activation with { "paused": true }. Pause one of two instances that run the same repository, so that schedules do not fire twice.

  • At most 3 trigger-created sessions provision at once in a project. A fire past that answers queued and runs when a slot frees.
  • The webhook URLs of one project accept 120 requests per minute from one client.
  • A project runs at most 10 enabled monitors, and a session holds at most 20 active reminders.

Was this page helpful?