Skip to main content

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.

Build with AI

Assistants should read the full schema in the flow authoring guide while editing trigger configuration. See also Vibe coding.

For AI assistants

If you are helping a user author JSON, use the flow authoring guide for exact shapes, CEL filter hints, and idempotency behavior.

Summary

typeWhen it firesMain config
manualSomeone starts a run from the UI, Chat, or an integrationNone in stored config
scheduleCron expressionschedule.expr, optional schedule.timezone
intervalRepeating durationinterval.duration (e.g. "6h", "30m")
activityModel activity eventsactivity.activity_ids (array); optional filter_expr
signalSignal rule firessignal.rule_ids (array); optional filter_expr
needle_moverNeedle mover recordedneedle_mover.labels and/or needle_mover.impacts (at least one value across both); optional filter_expr
conversationConversation ingestedconversation.types (e.g. meeting, ticket, email, chat, call); optional filter_expr
querySQL against semantic DB returns rowsquery.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.

Trigger data at runtime

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 LIMIT and a selective WHERE.
  • Idempotency is derived from the full row JSON — stable identical rows dedupe; changing any column changes the key.
  • Use LIMIT to 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.