Triggers
Triggers define when FunnelStory starts a new run for an agent. A published (non-draft) agent with a configured trigger runs automatically; draft agents and agents with no trigger only run when someone starts them manually.
Assistants should read the full schema in the flow authoring guide while editing trigger configuration. See also Vibe coding.
If you are helping a user author JSON, use the flow authoring guide for exact shapes, CEL filter hints, and idempotency behavior.
Summary
type | When it fires | Main config |
|---|---|---|
manual | Someone starts a run from the UI, Chat, or an integration | None in stored config |
schedule | Cron expression | schedule.expr, optional schedule.timezone |
interval | Repeating duration | interval.duration (e.g. "6h", "30m") |
activity | Model activity events | activity.activity_ids (array); optional filter_expr |
signal | Signal rule fires | signal.rule_ids (array); optional filter_expr |
needle_mover | Needle mover recorded | needle_mover.labels and/or needle_mover.impacts (at least one value across both); optional filter_expr |
conversation | Conversation ingested | conversation.types (e.g. meeting, ticket, email, chat, call); optional filter_expr |
query | SQL against semantic DB returns rows | query.query; each row starts one run |
Optional on several types: account_filter — restricts which accounts the trigger applies to. If a trigger "never fires" for certain accounts, check the account filter and trigger rules first. Uses the same filter-group concept as elsewhere in FunnelStory.
Each trigger type exposes different fields under $.trigger. For example, query triggers provide $.trigger.row.* while activity triggers provide $.trigger.activity.*. Referencing $.trigger.row on a non-query run produces empty or missing values. See Variables and data — trigger data by type for the full table.
Manual
Use manual while building. Runs start only when someone triggers them — from the UI, Chat, or an integration. Nothing schedules automatically.
{
"type": "manual"
}
Schedule
Runs on a cron expression, optionally in a named timezone.
{
"type": "schedule",
"schedule": {
"expr": "0 9 * * 1-5",
"timezone": "America/Los_Angeles"
}
}
Interval
Runs every duration after the previous eligible tick (e.g. "30s", "15m", "6h", "24h").
{
"type": "interval",
"interval": {
"duration": "6h"
}
}
Activity
Fires when configured activity IDs appear in your modeled activity stream.
{
"type": "activity",
"activity": {
"activity_ids": ["019bacd1-e737-7bef-a310-c35ff896febd"]
},
"filter_expr": "true"
}
Replace IDs with values from your workspace configuration.
Signal
Fires when specific signal rules emit.
{
"type": "signal",
"signal": {
"rule_ids": ["019c0a12-3456-7890-abcd-ef1234567890"]
}
}
Needle mover
Fires when a needle mover matches configured labels and/or impacts.
{
"type": "needle_mover",
"needle_mover": {
"labels": ["pricing"],
"impacts": ["positive"]
}
}
Conversation
Fires when a conversation of given types is ingested.
{
"type": "conversation",
"conversation": {
"types": ["ticket", "meeting"]
}
}
The trigger payload provides key, metadata, and timestamp for the conversation — not the full message body. To load the full content, use a semantic.query step referencing ids from $.trigger.conversation.key.
Query
Query triggers run SQL against the semantic workspace database. Each row returned becomes one run; row columns are exposed as @.trigger.row.<column> in steps and templates.
Important product rules:
- Evaluation runs at most once per UTC day for the workspace (first successful pass that day), so design SQL with a
LIMITand a selectiveWHERE. - Idempotency is derived from the full row JSON — stable identical rows dedupe; changing any column changes the key.
- Use
LIMITto cap how many new runs enqueue per cycle.
{
"type": "query",
"query": {
"query": "SELECT account_id, name FROM accounts WHERE subscription_remaining_days < 90 ORDER BY subscription_remaining_days ASC LIMIT 50"
}
}
In downstream steps, reference {{ $.trigger.row.account_id }} or the global trigger payload patterns described in the flow authoring guide.
Testing without waiting for the trigger
When running from the builder or starting a manual test run, you can supply sample trigger data so the run behaves as though a real trigger started it. The sample JSON must match the trigger type you are building for.
Query-shaped sample (columns from your SQL):
{
"trigger": {
"row": {
"account_id": "acct_123",
"name": "Example Corp"
}
}
}
Activity-shaped sample:
{
"trigger": {
"activity": {
"activity_id": "019bacd1-e737-7bef-a310-c35ff896febd",
"account_id": "acct_456",
"timestamp": "2026-04-01T09:00:00Z",
"count": 1
}
}
}
The same shapes apply when an assistant runs a flow with a simulated trigger via MCP.