Skip to main content

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.
Triggers are stored in the database and managed via CLI. The daemon’s TriggerRunner subscribes to all enabled trigger topics and handles firing automatically.

Architecture

The trigger system works through these components:
  1. TriggerRunner — A singleton that runs inside the daemon. It subscribes to NATS topics for all enabled triggers and manages the subscription lifecycle.
  2. Topic subscriptions — Triggers sharing the same topic share a single NATS subscription. When an event arrives, each matching trigger is evaluated independently.
  3. Filter evaluator — An optional expression that gates trigger firing based on event data fields.
  4. Template engine — Resolves {{variable}} placeholders in the trigger message using event data before sending to the agent.
  5. Config refresh — When triggers are added, removed, or modified via CLI, a ravi.triggers.refresh signal 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
  • --sessionmain (shared session) or isolated (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

Use ravi 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 with ravi.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

Use &&, ||, unary !, and parentheses for boolean composition. Precedence is !, then &&, then ||.

Operators

Path Resolution

The data.<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:
The 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.
When a trigger has a 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.
Supported durations: 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

Shows all triggers with their ID, name, enabled status, topic, and fire count.

List trigger topics

Shows the catalog of trigger-ready topics with payload schemas, examples, filters, and notes.

Show trigger details

Displays full trigger configuration including message, filter, cooldown, fire count, and last fired time.

Create a trigger

Enable / Disable

Disabled triggers keep their configuration but stop subscribing to events.

Update properties

Test a trigger

Fires the trigger with synthetic event data, bypassing the normal event flow:
The test event contains:
Check results with 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:
For approval-by-reaction workflows, store enough state before posting the review message:
The trigger agent should load that state by {{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

Verify that the trigger shows 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:
Look for log entries from 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:
This emits 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: