Overview
Triggers are automated reactions to system events. When an event fires on a NATS topic that matches a trigger’s pattern, the trigger builds a prompt with the event data and sends it to an agent session for processing.Architecture
The trigger system works through these components:- TriggerRunner — A singleton that runs inside the daemon. It subscribes to NATS topics for all enabled triggers and manages the subscription lifecycle.
- Topic subscriptions — Triggers sharing the same topic share a single NATS subscription. When an event arrives, each matching trigger is evaluated independently.
- Filter evaluator — An optional expression that gates trigger firing based on event data fields.
- Template engine — Resolves
{{variable}}placeholders in the trigger message using event data before sending to the agent. - Config refresh — When triggers are added, removed, or modified via CLI, a
ravi.triggers.refreshsignal is emitted. The runner tears down all subscriptions and rebuilds them.
Creating a Trigger
Required options
--topic— NATS topic pattern to subscribe to (supports*wildcards)--message— Prompt the agent receives when the trigger fires
Optional settings
--agent— Target agent (defaults to the caller’s agent or the system default)--cooldown— Minimum time between fires (default:5s). Supports:5s,30s,1m,5m,1h--session—main(shared session) orisolated(dedicated session per trigger, default)--filter— Expression that must match for the trigger to fire--account— Account for channel delivery (auto-detected from agent if not set)
Topic Catalog
Useravi triggers topics to inspect the trigger-ready topic catalog with payload schemas, examples, and notes. Triggers may still subscribe to custom subjects published by your own code, but Ravi-maintained subjects should be registered in the catalog before being used in docs, skills, or examples.
Inbound Channel Events
Channel message aliases such as
whatsapp.*.inbound and channel reaction aliases such as whatsapp.*.reaction are not trigger-ready subjects. Inbound channel messages are consumed by the session router from Omni streams; reaction triggers use ravi.inbound.reaction.
Reaction payloads are correlation events, not full message records. ravi.inbound.reaction currently carries targetMessageId, emoji, and senderId; it does not guarantee chatId, caption, media metadata, or business/domain state. If a routine needs that state, persist a mapping keyed by the outbound message id before waiting for the reaction.
Contacts and Approvals
CLI, Watch, and Task Events
Delivery / Receipts
Audit
Anti-Loop Protection
The trigger system includes multiple layers of protection against infinite loops:1. Blocked Topic Prefixes
Topics starting withravi.session. are accepted by the CLI for persistence compatibility, but the runner refuses to arm them. This prevents triggers from reacting to internal session events that could create prompt-trigger-prompt loops.
2. Session Filter
Events originating from trigger sessions (topics containing:trigger:) are automatically skipped. This prevents a trigger’s own actions from firing other triggers.
3. Data Flag
Events with_trigger: true in their data payload are skipped. This flag is set on all prompts emitted by the trigger runner.
4. Cooldown
Each trigger has a configurable cooldown (default: 5 seconds). After a trigger fires, it will not fire again until the cooldown period elapses, regardless of how many matching events arrive.Topic Alias Warnings
The CLI accepts custom NATS subjects, but warns when a topic looks like an inferred alias rather than a Ravi publisher. Prefer the canonical built-in subjects in maintained examples:Filter Syntax
Filters are optional expressions that evaluate against the event data. If the filter does not match, the trigger is silently skipped.Format
&&, ||, unary !, and parentheses for boolean composition. Precedence is !, then &&, then ||.
Operators
Path Resolution
Thedata.<path> notation uses dot-separated keys to traverse into the event data object. For example, data.cwd resolves the cwd field in the event payload, and data.hook_event_name resolves the hook_event_name field.
Values are coerced to strings for comparison. If the path does not exist in the event data, the filter evaluates to false (trigger does not fire).
Invalid Syntax
ravi triggers add --filter ... and ravi triggers set <id> filter ... reject invalid syntax before saving it. Existing legacy filters that are already persisted still fail open at runtime and log a warning, which prevents old bad filters from silently disabling triggers.
Filter Examples
Message Templates
Trigger messages support{{variable}} placeholders that are resolved with event data before being sent to the agent.
Available Variables
Unresolved variables (paths that do not exist in the event data) are left as-is in the message. String values longer than 300 characters are automatically truncated with
....
Template Examples
Prompt Format
When a trigger fires, the agent receives a prompt with this structure:Data section contains the full event payload as JSON, giving the agent context about what happened.
Session Types
Isolated (Default)
Each trigger gets its own session:agent:<agentId>:trigger:<triggerId>. This isolates trigger conversations from the agent’s main context.
Main
The trigger fires into the agent’s shared main session. Use this when you want the agent to have full context of ongoing conversations.replySession (captured from the caller context when creating the trigger), responses are routed back to that session’s channel, even in main mode.
Cooldown
Cooldown prevents a trigger from firing too rapidly. The default is 5 seconds.5s, 10s, 30s, 1m, 5m, 10m, 30m, 1h.
The cooldown is tracked in memory with immediate update. When an event passes the cooldown check, the trigger’s lastFiredAt is set immediately (before the prompt is even published) to prevent race conditions where multiple events arriving in rapid succession all pass the cooldown check.
CLI Reference
List triggers
List trigger topics
Show trigger details
Create a trigger
Enable / Disable
Update properties
Test a trigger
Fires the trigger with synthetic event data, bypassing the normal event flow:ravi daemon logs -f.
Delete a trigger
Practical Examples
Permission Denied Alert
Monitor permission denials and alert when something needs review:Monitor Contact Changes
Track when contacts are added or modified:Permission Denied Monitor
Alert when an agent gets a permission denied error:Directory-Scoped Watcher with Filter
Only fire for events in a specific project directory:Reaction Approval
Trigger on approved reactions:{{data.targetMessageId}}, publish at most once, mark the item processed, and stay silent when there is no match.
Debugging Triggers
Check if triggers are active
ENABLED: yes and that the topic pattern is correct.
Watch for trigger events in real time
Subscribe to the trigger’s topic directly with NATS:Check daemon logs
Trigger fires and errors are logged by the daemon:triggers:runner:
"Firing trigger"— trigger matched and is being processed"Trigger cooldown active, skipping"— event matched but cooldown is active"Trigger filter did not match, skipping"— event matched but filter excluded it"Trigger filter: invalid syntax, failing open"— filter has bad syntax (trigger fires anyway)
Force subscription refresh
If triggers seem stuck, force a refresh:ravi.triggers.refresh, causing the runner to tear down and rebuild all subscriptions.
Test without real events
Use the test command to fire a trigger with synthetic data:Verify filter syntax
The filter must match the restricted parser syntax. Double or single quotes around the value are both accepted, and boolean expressions can be composed with&&, ||, !, and parentheses: