Workflow Triggers
Triggers define when a workflow runs. The Agent owns the schedule; the workflow owns the procedure. The same workflow could run at 7am in one Agent and 9am in another -- the trigger is part of the binding, not the workflow itself.
Trigger types
Every workflow binding must include a trigger object with a type field. Five trigger types are available:
| Type | Fields | Description |
|---|---|---|
schedule | cron | Fires on a cron schedule (standard 5-field expression) |
heartbeat | interval, window (optional) | Fires at a recurring interval, optionally limited to a time window |
event | sources | Fires when a matching event occurs |
watch | plugin, event, command, restart_delay_secs | Long-running plugin process that emits NDJSON events |
manual | -- | Only fires by explicit user request or API call |
Schedule
Schedule triggers fire on a standard 5-field cron expression. The expression is evaluated every 60 seconds against the agent's configured timezone.
{ "type": "schedule", "cron": "0 7 * * 1-5" }The example above fires at 7:00 AM every weekday (Monday through Friday). Any valid 5-field cron expression is accepted:
| Field | Values | Example |
|---|---|---|
| Minute | 0-59 | 0 |
| Hour | 0-23 | 7 |
| Day of month | 1-31 | * |
| Month | 1-12 | * |
| Day of week | 0-6 (Sun-Sat) | 1-5 |
A full workflow binding with a schedule trigger:
{
"workflows": {
"morning-briefing": {
"trigger": { "type": "schedule", "cron": "0 7 * * *" },
"description": "Daily morning briefing",
"activities": [
{
"id": "gather",
"intent": "Gather today's priorities from calendar and email",
"skills": ["@nebo/skills/briefing-writer"],
"steps": ["Check calendar for today", "Scan inbox for urgent items", "Compose briefing"],
"token_budget": { "max": 4096 }
},
{
"id": "deliver",
"intent": "Send the briefing to the user",
"steps": ["Format as concise bullet points", "Post to chat"],
"token_budget": { "max": 1024 }
}
],
"budget": { "total_per_run": 6000 },
"emit": "briefing.ready"
}
}
}Heartbeat
Heartbeat triggers fire at a recurring interval. They can optionally be constrained to a time window -- outside the window, heartbeats are silently skipped.
{ "type": "heartbeat", "interval": "30m", "window": "08:00-18:00" }Heartbeat fields
| Field | Type | Required | Description |
|---|---|---|---|
interval | string | yes | Duration string: "30m", "1h", "2h30m" |
window | string | no | Active hours ("HH:MM-HH:MM"). Outside the window, heartbeats are skipped. |
The interval is measured from the end of the previous run, not the start. This prevents overlapping runs when activities take longer than the interval.
Event
Event triggers fire when a matching event occurs in the EventBus. Events can originate from other workflows (via the emit tool), watch triggers, platform capabilities, or system lifecycle events.
{ "type": "event", "sources": ["email.urgent", "calendar.*"] }Event fields
| Field | Type | Description |
|---|---|---|
sources | string[] | Event source patterns to match. Supports exact (email.urgent) and wildcard (email.*). |
Source matching
| Pattern | Matches |
|---|---|
email.urgent | Exact match only |
email.* | Any event starting with email. |
sources array is valid JSON but the trigger will never fire -- always include at least one source.The event payload becomes the triggered workflow's inputs, available as _event_payload. For more details on the event system, event sources, and event envelopes, see Event System.
Event trigger example
A workflow that triggers on customer service emails emitted by an upstream triage workflow:
{
"handle-cs": {
"trigger": { "type": "event", "sources": ["email.customer-service"] },
"description": "Handle customer service emails",
"activities": [
{
"id": "respond",
"intent": "Draft a helpful response to the customer service email",
"skills": ["@acme/skills/cs-templates"],
"token_budget": { "max": 3000 }
}
],
"budget": { "total_per_run": 3000 }
}
}Watch
Watch triggers spawn a long-running plugin process that outputs NDJSON (one JSON object per line) to stdout. Each line triggers the bound activities with the parsed JSON as input.
{
"type": "watch",
"plugin": "gws",
"event": "email.new",
"command": "gmail +watch --format ndjson",
"restart_delay_secs": 5
}Watch fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
plugin | string | yes | -- | Plugin slug |
event | string | no | -- | Plugin event name. Enables auto-emission into EventBus. |
command | string | no | "" | CLI args appended to plugin binary. Required if event is not set. |
restart_delay_secs | u64 | no | 5 | Seconds to wait before restarting on crash |
How it works
- Spawns
<plugin-binary> <command>as a long-running subprocess - Plugin outputs NDJSON (one JSON object per line) to stdout
- Each line triggers the bound activities with the parsed JSON as
_watch_payloadinput - If
eventis set, each line also auto-emits into the EventBus as{plugin}.{event} - On crash, restarts after
restart_delay_secswith exponential backoff (max 300s)
command fallback alongside event. If the plugin manifest doesn't declare the event, resolution fails silently. command ensures the watcher starts regardless.Event-only watches
A watch with event set but no activities is valid. It relays plugin output into the EventBus without processing anything inline:
{
"email-relay": {
"trigger": {
"type": "watch",
"plugin": "gws",
"event": "email.new",
"command": "gmail +watch --format ndjson"
},
"description": "Relay new email events into the EventBus"
}
}Other agents can then subscribe to these events:
{
"handle-emails": {
"trigger": { "type": "event", "sources": ["gws.email.new"] },
"activities": [...]
}
}Manual
Manual triggers only fire via an API call or explicit user request. They are useful for workflows that should not run automatically but need to be available on demand.
{ "type": "manual" }Trigger a manual workflow via:
POST /agents/{id}/workflows/{binding}/runTemplate substitution in commands
Watch trigger commands support {{key}} placeholders, which are substituted from the agent's input values at runtime:
"command": "gmail +watch --format ndjson --project {{gcp_project}}"The placeholder name must exactly match an input key. Unmatched placeholders are left as literal text (which will cause failures).
Input values & template substitution in triggers
All trigger configs -- not just watch commands -- support {{key}} template substitution. Values are resolved from agents.input_values, which users set via the Settings UI or the API.
{
"trigger": { "type": "schedule", "cron": "{{sync_time}}" }
}If the agent's input_values contains { "sync_time": "0 9 * * 1-5" }, the cron expression resolves to 0 9 * * 1-5 at trigger evaluation time. This lets end users customize workflow timing and parameters without editing the agent package.
Full example: email triage pipeline
This example demonstrates how triggers chain together. A watch trigger monitors Gmail, a triage activity classifies each email and emits events, and event triggers handle each category independently:
{
"workflows": {
"email-watcher": {
"trigger": {
"type": "watch",
"plugin": "gws",
"event": "email.new",
"command": "gmail +watch --format ndjson"
},
"description": "React to new emails in real-time",
"activities": [
{
"id": "triage",
"intent": "Classify the incoming email and route it",
"steps": [
"Read the email content from _watch_payload",
"Classify as: customer-service, sales-inquiry, or ignore",
"If not ignore, emit the appropriate event with email data"
],
"token_budget": { "max": 2048 }
}
],
"budget": { "total_per_run": 2048 }
},
"handle-cs": {
"trigger": { "type": "event", "sources": ["email.customer-service"] },
"description": "Handle customer service emails",
"activities": [
{
"id": "respond",
"intent": "Draft a helpful response to the customer service email",
"skills": ["@acme/skills/cs-templates"],
"token_budget": { "max": 3000 }
}
],
"budget": { "total_per_run": 3000 }
},
"handle-sales": {
"trigger": { "type": "event", "sources": ["email.sales-inquiry"] },
"description": "Handle inbound sales inquiries",
"activities": [
{
"id": "qualify",
"intent": "Qualify the lead and add to CRM",
"skills": ["@acme/skills/lead-qualification"],
"token_budget": { "max": 3000 }
}
],
"budget": { "total_per_run": 3000 }
}
}
}Read top to bottom: the watcher monitors Gmail. For each email, triage classifies and emits. Each emission triggers the appropriate handler with the email data as inputs.
Validation rules
Triggers are validated when the agent loads. Invalid triggers cause the individual binding to be skipped (not the entire agent):
- Trigger
typemust be one of:schedule,heartbeat,event,watch,manual - Schedule triggers must have a valid 5-field
cronexpression - Heartbeat triggers must have a valid
interval(e.g.,"30m","1h") - Event triggers should have at least one entry in
sources - Watch triggers must have a
pluginand eithereventorcommand - All
{{key}}placeholders in commands must match an inputkeyexactly
Related
- Workflows Overview -- workflow bindings, budget math, and the full binding schema
- Activities -- what happens after a trigger fires: sequential activity execution, context chaining, and error handling
- Event System -- event sources, event envelopes, source matching, and system events