Triggers
A trigger starts an agent without anyone calling it: every weekday at 09:00, once tomorrow morning, in an hour, when someone presses Run now, or when another system posts an event to its webhook URL. Each trigger belongs to one agent and carries the message to send (a prompt, plus optional variables). Every time a trigger fires it creates an ordinary agent run, so it has an id, a status, a result, usage, tracing and a session, and shows up in the runs list.
One trigger has one type, and an agent can have as many triggers as it needs. A trigger is a schedule, a one-time start, a manual button or a webhook, never two of those at once. To run the same agent on a schedule and when a webhook arrives, add two triggers: a schedule trigger and a webhook trigger, each with its own prompt, variables, class and history.
When to use it: a nightly report, a morning digest, a periodic data check, a reminder that should arrive "in 30 minutes", a manually triggered job with fixed parameters, an agent that reacts to a pull request or a failed build (webhook triggers). For a one-off start time from your own code, you can also send run_at or delay_seconds on a run request.
Trigger types
A trigger has a type and, for the types that need settings, one sub-object named after the type (schedule, once or webhook):
| Type | Settings | Fires |
|---|---|---|
schedule | schedule: { cron, timezone }: cron has 5 fields, timezone is an IANA name (default UTC) | On every cron match, in that timezone. Daylight saving is handled by the timezone. |
once | once: { run_at } (ISO time or unix seconds) or once: { delay_seconds } | One time. Afterwards the trigger has no next fire (next_fire_at: null) and keeps its history. |
manual | none | Only when you call Run (or press the button). |
webhook | webhook: { preset, auth, events, filter, include_payload } | When an authenticated delivery reaches the trigger's URL and passes its events and filter. Sources are GitHub, GitLab, Azure DevOps and any sender of JSON. See Webhook triggers. |
Only the sub-object of the trigger's own type is present when you read a trigger; the others are omitted. A once time must not be in the past (run_at is ISO-8601 or unix seconds). Cron examples: 0 9 * * 1-5 weekdays at 09:00, */15 * * * * every 15 minutes, 0 2 * * * daily at 02:00, 0 8 1 * * the first of every month. Cron uses the 5-field form; with "timezone": "Europe/Istanbul" the hour fields are Istanbul time.
What a trigger carries
| Field | Meaning |
|---|---|
name, description | Labels. |
enabled | A disabled trigger never fires on its own; manual Run still works. |
type and its sub-object | What starts it: see trigger types. |
prompt | The message each fire sends, up to 32 000 characters. Required, unless the agent declares variables and the trigger provides them. For a webhook trigger it is a template filled from each delivery. |
variables | Values for the agent's variables. For a webhook trigger, a mapping from the delivery. |
version | A published version number. Omitted = the published version at fire time (the draft only if the agent was never published). |
class | default or deferred. A nightly job usually belongs in deferred (run classes). |
metadata | String key/values copied onto every run it creates. |
callback | { "url", "secret" }: POSTed when an async run finishes (callbacks). |
response_format | A JSON schema for the answer, as on a normal request. |
Variables suit triggers. A program, not a person, starts the run, so the values can be written down once: fixed values on a schedule, once or manual trigger ({ "team": "ops" }), and on a webhook trigger a mapping that picks them out of each event ({ "pr_number": "{{payload.pull_request.number}}" }, see the mapping). They are checked when you save the trigger and again at every fire, against the variables the agent declares at that moment: if a later version of the agent adds a required variable the trigger does not give, the fire is skipped and the trigger records last_status: "failed" with last_error: "invalid variables: …". A missing required value or a wrong type never starts a run, so nothing is spent. A trigger needs a prompt or the variables the agent declares (a webhook trigger can also rely on the event payload), so the prompt may be empty when variables carry the request.
Create one
curl -X POST "$CONSOLE_URL/api/client/v1/agents/support-bot/triggers" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
-d '{
"name": "Morning ticket digest",
"type": "schedule",
"schedule": { "cron": "0 9 * * 1-5", "timezone": "Europe/Istanbul" },
"prompt": "Summarise the tickets opened since yesterday 09:00, grouped by product.",
"class": "deferred",
"metadata": { "report": "morning-digest" },
"callback": { "url": "https://hooks.example.com/digest", "secret": "a-shared-secret-of-16-plus-chars" }
}'curl -X POST "$CONSOLE_URL/api/client/v1/agents/support-bot/triggers" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
-d '{
"name": "Follow up on ticket OPS-12",
"type": "once",
"once": { "delay_seconds": 3600 },
"prompt": "Check whether ticket OPS-12 has an answer and nudge the owner if not."
}'curl -X POST "$CONSOLE_URL/api/client/v1/agents/data-checker/triggers" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
-d '{
"name": "Check a customer",
"type": "manual",
"variables": { "customer_id": "c-1042" }
}'The response is the trigger (timestamps are unix seconds):
{
"id": "6720b4c3d2e1f0a9b8c7d6e5",
"object": "agent.trigger",
"agent": "support-bot",
"name": "Morning ticket digest",
"description": null,
"enabled": true,
"type": "schedule",
"schedule": { "cron": "0 9 * * 1-5", "timezone": "Europe/Istanbul" },
"prompt": "Summarise the tickets opened since yesterday 09:00, grouped by product.",
"variables": null,
"version": null,
"class": "deferred",
"metadata": { "report": "morning-digest" },
"callback": { "url": "https://hooks.example.com/digest", "signed": true },
"response_format": null,
"next_fire_at": 1792389600,
"last_fire_at": null,
"last_run_id": null,
"last_status": null,
"last_error": null,
"created_at": 1792314000,
"updated_at": 1792314000
}A trigger is rejected with 400 invalid_trigger when the type is missing or is not one of the four, the cron is not a valid 5-field expression, the timezone is not an IANA name, a once time is in the past, or neither a prompt nor variables are given; error.errors lists each field and why.
Several triggers on one agent
An agent's triggers are independent: add another with POST …/triggers, and GET /agents/{agentKey}/triggers lists them all. A nightly schedule plus a webhook for the same agent, for example:
# 1. every night at 02:00
curl -X POST "$CONSOLE_URL/api/client/v1/agents/report-writer/triggers" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "Nightly report", "type": "schedule",
"schedule": { "cron": "0 2 * * *", "timezone": "Europe/Istanbul" },
"prompt": "Write the nightly operations report.", "class": "deferred" }'
# 2. whenever the deploy pipeline posts a failed build
curl -X POST "$CONSOLE_URL/api/client/v1/agents/report-writer/triggers" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "Failed deploy", "type": "webhook",
"webhook": { "preset": "generic", "auth": "bearer", "filter": [ { "path": "payload.status", "op": "eq", "value": "failed" } ] },
"prompt": "A deploy failed. Find the cause and write a short report." }'Each trigger starts its own runs and has its own history, so you can disable, edit or delete one without touching the others. A trigger cannot be two types at once: use two triggers.
Run now
POST /agents/{agentKey}/triggers/{triggerId}/run fires the trigger immediately, whatever its type or enabled flag. The body may override variables and metadata for this one run, and wait / Prefer: wait=N behave as on the runs API: the answer is the run (202, or 200 if it finished inside the wait; the wait stops if the client disconnects, the run carries on). The run counts against the caller and the API token that fired it, not the trigger's last updater. A webhook trigger also takes a sample payload and headers, and dry_run: true to see what a delivery would do without starting anything (how).
curl -X POST "$CONSOLE_URL/api/client/v1/agents/data-checker/triggers/$TRIGGER_ID/run" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
-d '{ "variables": { "customer_id": "c-2087" }, "wait": 60 }'History
Every run a trigger creates carries trigger_id, channel: "trigger" and a trigger field that says what started it: schedule, once, manual or webhook. List them:
curl "$CONSOLE_URL/api/client/v1/agents/runs?trigger_id=$TRIGGER_ID&limit=20" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN" | jq '.data[] | {id, status, trigger, scheduled_for, completed_at}'The trigger itself shows last_fire_at, last_run_id, last_status (started or failed) and last_error. last_status says whether the run could be created; the outcome of the agent's work is the run's own status.
Reliability
- Exactly once across replicas. Several Console nodes can run the scheduler. Each fire first claims its slot in the database (one claim per trigger and fire time) and uses the idempotency key
trigger:<triggerId>:<fireTime>. Whichever node wins starts the run; the others do nothing. A restart in the middle of a fire cannot double it. - Misfire policy. The scheduler ticks every 30 seconds. If the system was down and a slot is late by up to one hour, the trigger fires once to catch up, however many slots were missed. A slot older than one hour is skipped, and the trigger moves on to its next slot. (
AGENT_TRIGGER_MISFIRE_GRACE_MS, default one hour.) - Queued behind capacity. A fire is a run in the trigger's class. A
deferredtrigger can wait for capacity; if it has not started withinAGENT_DEFERRED_MAX_WAIT_MS(24 hours) it endsfailedwithexpired. See run classes. - Each fire is its own run in its own session, so runs of the same trigger do not block each other. The session is owned by the synthetic actor
system:trigger:<id>. - Whose limits a fire counts against. The session belongs to the trigger, but a fire is accountable to a person: the spend limits, quota, run limits and usage of a
schedule,onceorwebhookfire are counted against the trigger's last updater (whoever last changed it), or its creator until somebody changes it. Run now counts against the person who pressed it and the API token they used. To move a trigger's spend to someone else, save the trigger as that person. - Fire failures. If a run cannot be created (the agent was deleted or deactivated, a cap is reached), the trigger records
last_status: "failed"withlast_error, and atrigger.failedevent is sent.
Events
Subscribe a webhook to follow triggers:
| Event | Sent when | data |
|---|---|---|
trigger.fired | A fire created a run | triggerId, agentKey, trigger (what started it), runId, and event for a webhook delivery |
trigger.failed | A fire could not create a run | triggerId, agentKey, trigger, reason, and event for a webhook delivery |
trigger.failed is sent at most once an hour for each trigger and reason, so a schedule that is refused on every tick does not flood you. A webhook delivery that is filtered out, repeated or not authenticated is not a failure and sends no event; it shows in the trigger's Deliveries.
The run's own outcome arrives as agent_run.succeeded, agent_run.failed, … with triggerId and your metadata in data. These events are sent for async runs, which a trigger fire always is.
Studio
Open Build → Agents → your agent → Ship → Triggers. The list shows each trigger with its type, next fire time, last result and an enabled switch. Add trigger opens a full-screen form with the same fields as the API: name, type (Schedule, Once, Manual, Webhook), the settings of that type, prompt, variables, class, version, callback and output schema. A webhook trigger has Settings, Test and Deliveries tabs and a Connect the sender card (details). A trigger can be run on demand with Run now, edited, opened to see its runs, or deleted. An agent can have several triggers, one row each. Deleting an agent deletes its triggers.
API
| Method | Path | |
|---|---|---|
GET, POST | /api/client/v1/agents/{agentKey}/triggers | List, create |
GET, PATCH, DELETE | /api/client/v1/agents/{agentKey}/triggers/{triggerId} | Read, update (any field above, including enabled), delete |
POST | /api/client/v1/agents/{agentKey}/triggers/{triggerId}/run | Fire now (webhook triggers: with a sample payload, or dry_run) |
POST | /api/client/v1/agents/{agentKey}/triggers/{triggerId}/webhook/rotate-secret | Replace a webhook secret, optionally with a grace period |
GET | /api/client/v1/agents/{agentKey}/triggers/{triggerId}/deliveries | The delivery log of a webhook trigger (7 days) |
POST | /api/public/hooks/{tenantId}/{hookId} | The URL senders post to (no API token) |
GET | /api/client/v1/agents/runs?trigger_id=… | History |
Field-level reference: Triggers API.
A one-off run without a trigger
For a single delayed start from your own code you do not need a trigger: send run_at or delay_seconds on a run request. The run is scheduled until due (at most 30 days ahead) and then starts like any queued run.
curl -X POST "$CONSOLE_URL/api/client/v1/agents/support-bot/runs" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: followup-OPS-12" \
-d '{ "input": "Check OPS-12 for an answer", "delay_seconds": 3600, "class": "deferred" }'Cancel a scheduled run with POST /agents/runs/{id}/cancel before it starts.
Replaces agent schedules
Triggers replace the older per-agent schedules and the schedule.fired / schedule.failed events. Old schedules are not migrated: re-create them as triggers of type schedule. A trigger adds timezones, one-shot, manual and webhook types, several triggers per agent, a client API, variables, classes and exactly-once firing.

