Skip to main content

Triggers

Give the runtime the clock, instead of holding your own scheduler. A trigger runs an agent or an orchestration on a cron, on demand, or both — and records every run.

Overview

Before triggers, the only way to run an agent was an inbound call from a caller holding its own scheduler. A trigger lets naturali hold it instead: create one against a cron expression and a target, and it fires on schedule from then on. Every fire — scheduled or manual — is recorded as a firing: what ran, when, and what came back.

A trigger runs one of two things, chosen with target_type:

  • an agent (target_type: agent, the default) — the common case, "run this agent daily at 9am UTC", and the building block behind naturali's solo-agent pattern: agent + cron, a single-role automation with one cycle;
  • an orchestration (target_type: orchestration) — the scheduled counterpart of starting a run by hand. Each fire starts a run of the graph, and the trigger's input seeds that run's input.

Reach for an orchestration target when the scheduled work is more than one step — a nightly pipeline that queries knowledge, runs an agent over each result and pauses for a human on anything unusual. Reach for an agent target when one generation is the whole job.

See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Triggers.

Data Model

Trigger

FieldTypeDescription
idstringPublic trigger ID (trg_ prefix) — the runtime trigger id.
project_idstringThe owning project.
namestring
descriptionstring, nullable
type"schedule"Fixed — the only trigger type naturali fronts today.
target_type"agent" | "orchestration"What the trigger runs. Defaults to agent on create.
target_idstringThe agent or orchestration this trigger runs, per target_type.
inputobject, nullableStatic input passed to the target on every fire, shallow-merged under any fire-time input. For an orchestration target, this is the run's initial input.
cronstring5-field cron expression, evaluated in UTC.
activebooleanWhether the trigger fires at all — on schedule and on a manual …:fire alike.
next_fire_atstring (date-time), nullableServer-computed; the next time the schedule fires.
created_atstring (date-time)
updated_atstring (date-time)

Firing

FieldTypeDescription
idstringPublic firing ID — the runtime firing id.
trigger_idstringThe trigger that ran.
project_idstringThe owning project.
source"schedule" | "manual"What caused this run.
statuspending | running | succeeded | failed
inputobject, nullableThe input this run actually used.
resultobject, nullable{ target_type, result_id, status, output } — output truncated.
errorobject, nullable{ code, message, meta }, present only when status is failed.
started_atstring (date-time), nullable
completed_atstring (date-time), nullable
created_atstring (date-time)
updated_atstring (date-time)

Key Concepts

One type, two targets — and what is still not fronted

The runtime's own trigger primitive also has manual/webhook types and a tool target, which naturali doesn't front. A webhook trigger has no naturali-side receiver to front it with; a tool target also needs an action naming which operation of the tool to call, which is a surface of its own rather than one more enum value. type is still on the wire, fixed to "schedule", rather than omitted: a client reading it today keeps reading it once a second value exists.

A trigger of a shape this surface doesn't front — created directly against the runtime, bypassing naturali — reads as 404 here, the same treatment traces gives a trace's file_id: the contract only speaks for what it fronts.

Retargeting: the kind and the id move together

target_id on its own retargets within the trigger's current kind, and the new id is validated against that kind — so retargeting an orchestration trigger never resolves the new id as an agent.

Changing target_type requires sending target_id alongside it, or the call is 400. The two are one fact: accepting the kind alone would leave the trigger pointing an orchestration target at an agent id.

Listing both kinds, and what total counts

GET /v1/projects/{project_id}/triggers returns both fronted kinds. Pass target_type to narrow to one — that filter is applied upstream, so total is exact.

Without it, total is the count of every schedule trigger in the project. It can therefore exceed the rows returned, but only when a trigger was authored directly against the runtime with a target this surface doesn't front — the same bypass the 404 above covers, and not something this API can produce. Pass target_type when an exact count matters.

Firing an inactive trigger is 409

active: false stops the schedule and blocks a manual …:fire call — both routes go through the same guard, so "off" means off regardless of how the run was requested. Flip active back to true to resume either path.

input layers, it doesn't replace

A trigger's stored input is the baseline for every fire. A manual …:fire call's input is shallow-merged over it for that one run only — the trigger's own configuration is unchanged, so a one-off override never drifts the schedule's defaults.

Examples

Schedule an agent

naturali create-trigger \
--project-id proj_V1StGXR8Z5jdHi6B \
--name health-check \
--target-id agent_V1StGXR8Z5jdHi6B \
--cron "0 9 * * *"

Schedule an orchestration

input becomes the run's initial input on every fire, so the graph's input_schema sees { "window": "24h" } here exactly as it would from POST /v1/projects/{project_id}/orchestration-runs.

naturali create-trigger \
--project-id proj_V1StGXR8Z5jdHi6B \
--name nightly-digest \
--target-type orchestration \
--target-id orch_V1StGXR8Z5jdHi6B \
--cron "0 3 * * *" \
--input '{"window":"24h"}'

Each firing's result then names the run it started — { "target_type": "orchestration", "result_id": "orch_run_…" } — which is the id to read with GET /v1/projects/{project_id}/orchestration-runs/{orchestration_run_id}.

Fire it now, and read the firing back

naturali fire-trigger \
--project-id proj_V1StGXR8Z5jdHi6B \
--trigger-id trg_V1StGXR8Z5jdHi6B

naturali list-trigger-firings \
--project-id proj_V1StGXR8Z5jdHi6B \
--trigger-id trg_V1StGXR8Z5jdHi6B

Pause a schedule, then retune it

naturali update-trigger \
--project-id proj_V1StGXR8Z5jdHi6B \
--trigger-id trg_V1StGXR8Z5jdHi6B \
--active false

naturali update-trigger \
--project-id proj_V1StGXR8Z5jdHi6B \
--trigger-id trg_V1StGXR8Z5jdHi6B \
--cron "0 */6 * * *" \
--active true