Skip to content

Triggers

A trigger decides when an agent runs. Every agent definition carries zero or more triggers; each one names a closed kind and, for message triggers, an optional filter. Triggers are part of the agent IR — you compose them with the ir.* builders, validate them in a dry run, and ship them with create_agent. At runtime the dispatcher loads a thread's installed, live agents and, for each enabled trigger, evaluates a cheap Python predicate to decide which agents to run — once each.

Dark behind a flag

The Agents platform — triggers included — is gated by apikey_auth_enabled. The endpoints below 404 until the flag is on for your workspace.

Trigger kinds

The kind must be one of a fixed, closed set. A malformed kind fails IR validation structurally — it can never reference a trigger the runtime doesn't have.

Kind Builder (Python / TS) Fires when
schedule ir.schedule(cron) / ir.schedule(cron) A cron expression matches. Requires cron.
message_arrival ir.on_message(...) / ir.onMessage(...) A new message arrives in a thread the agent watches and the filter passes.
manual ir.manual() / ir.manual() Only on demand — e.g. a test run. Never auto-fires.
intent ir.on_intent() / ir.onIntent() A matching intent is detected.
inbound_webhook ir.on_webhook() / ir.onWebhook() An inbound webhook fires.

Today's autonomous path is message_arrival

The only trigger the in-process dispatcher evaluates and auto-runs end-to-end today is message_arrival (driven by the MessageSent event). schedule, intent, and inbound_webhook are valid IR kinds you can author and persist, but their autonomous wiring is part of the broader runtime rollout. manual never auto-fires by design — use it for agents you only invoke yourself.

message_arrival filters

A message_arrival trigger carries a filter object. Only the keys below are evaluated by the dispatcher's predicate — every condition is ANDed, and an empty filter matches every message in the watched thread. Unknown keys are ignored.

Filter key Type Behavior
mention_required bool When truthy, the agent runs only if it was mentioned in the message.
content_type string Runs only when the message's content type matches exactly (e.g. "text", "voice_note").
from_user_ids list of ids Runs only when the sender is one of these user ids.
thread_ids list of ids Restricts the trigger to these threads. Omit to watch every thread the agent is installed in.
allow_agent_input bool Opts this trigger into agent-authored messages (see the feedback-loop guard). Off by default.

Builder ergonomics

The SDK builders surface the common three directly — mention_required, content_type, from_user_ids — as keyword arguments to on_message / fields of the onMessage filter. Any filter key in the table above is valid; the builders pass the filter object through verbatim.

Cron examples

schedule triggers take a standard 5-field cron expression in cron.

Expression Meaning
0 9 * * 1 Mondays at 09:00
0 9 * * * Every day at 09:00
*/15 * * * * Every 15 minutes
0 0 1 * * Midnight on the 1st of each month
from telbox import ir

ir.schedule("0 9 * * 1")  # Mondays 9am

schedule requires cron

A schedule trigger without a cron fails IR validation. The builders always set it for you, but if you hand-write the IR, include it.

Feedback-loop guard

When an agent posts a reply, that message can itself satisfy another agent's message_arrival trigger. To stop agent → message → agent cascades, the dispatcher applies two guards before it runs a matched agent:

  1. Agent-authored messages don't re-trigger. If the triggering message was authored by an agent, the trigger is skipped — unless that trigger opts in with allow_agent_input. (The dispatcher reads provenance from the message's authored_by_agent_id.)
  2. Depth cap. A re-trigger chain is capped (MAX_FEEDBACK_DEPTH = 3); a run that reaches the cap is recorded FAILED with feedback_depth_exceeded rather than fanning out further.

Why this matters

Two agents that each reply on message_arrival would otherwise ping-pong forever. Leave allow_agent_input off unless you specifically want an agent to react to other agents' messages — and even then, the depth cap bounds the chain.

Beyond matching, every run is idempotent: a deterministic dedupe key derived from the trigger id + message id means an at-least-once redelivery resolves to the same run row instead of executing twice.

Building triggers in the IR

Triggers live on the agent IR alongside persona, steps, and guards. Compose the whole definition with the builders, preview it with dry_run, then create it.

from telbox import TelboxClient, ir

tb = TelboxClient(api_key="tb_live_…")

nudge = ir.agent(
    "Nudge",
    persona="Each week, nudge whoever still owes a reply.",
    triggers=[ir.schedule("0 9 * * 1")],          # Mondays 9am
    steps=[
        ir.tool("s1", "get_tasks", status=ir.literal("open")),
        ir.tool("s2", "create_reminder", title=ir.prompt("follow up")),
    ],
    guards={"reminders": ir.guard("auto_act_limited")},
)

# Preview with no LLM and no side effects before committing.
print(tb.dry_run(nudge).effects)
agent = tb.create_agent(nudge)
import { TelboxClient, ir } from "@telbox/sdk";

const tb = new TelboxClient({ apiKey: "tb_live_…" });

const nudge = ir.agent("Nudge", {
  persona: "Each week, nudge whoever still owes a reply.",
  triggers: [ir.schedule("0 9 * * 1")],            // Mondays 9am
  steps: [
    ir.tool("s1", "get_tasks", { status: ir.literal("open") }),
    ir.tool("s2", "create_reminder", { title: ir.prompt("follow up") }),
  ],
  guards: { reminders: ir.guard("auto_act_limited") },
});

console.log((await tb.dryRun(nudge)).effects);
const agent = await tb.createAgent(nudge);
curl -X POST https://api.telbox.ai/v1/agents \
  -H "Authorization: Bearer tb_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Nudge",
    "persona": "Each week, nudge whoever still owes a reply.",
    "triggers": [{"kind": "schedule", "cron": "0 9 * * 1"}],
    "steps": [
      {"id": "s1", "type": "tool", "tool": "get_tasks",
       "args": {"status": {"literal": "open"}}},
      {"id": "s2", "type": "tool", "tool": "create_reminder",
       "args": {"title": {"prompt": "follow up"}}}
    ],
    "guards": {"capabilities": {"reminders": {"level": "auto_act_limited"}}}
  }'

A filtered message_arrival agent

This agent runs only when it is mentioned in a text message:

watcher = ir.agent(
    "Mention Watcher",
    persona="Answer when someone mentions me.",
    triggers=[ir.on_message(mention_required=True, content_type="text")],
    steps=[ir.say("s1", "On it — give me a moment.")],
)
tb.create_agent(watcher)
const watcher = ir.agent("Mention Watcher", {
  persona: "Answer when someone mentions me.",
  triggers: [ir.onMessage({ mention_required: true, content_type: "text" })],
  steps: [ir.say("s1", "On it — give me a moment.")],
});
await tb.createAgent(watcher);
curl -X POST https://api.telbox.ai/v1/agents \
  -H "Authorization: Bearer tb_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mention Watcher",
    "persona": "Answer when someone mentions me.",
    "triggers": [{
      "kind": "message_arrival",
      "filter": {"mention_required": true, "content_type": "text"}
    }],
    "steps": [{"id": "s1", "type": "say", "text": "On it — give me a moment."}]
  }'

Validate before you ship

The IR is strictly validated: an unknown trigger kind, a schedule without a cron, or a step naming a tool that isn't registered all fail at create time. Run dry_run first to see exactly what an agent would do — no LLM, no side effects.

See also

  • Agents — defining, creating, and managing agents.
  • Tools — the tool surface a trigger's steps can call.
  • Runs — what happens after a trigger fires, and how to inspect a run's trace.
  • Dry run — preview an agent's effects before creating it.
  • Authentication — API keys and OAuth for the Agents platform.