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 |
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:
- 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'sauthored_by_agent_id.) - Depth cap. A re-trigger chain is capped (
MAX_FEEDBACK_DEPTH = 3); a run that reaches the cap is recordedFAILEDwithfeedback_depth_exceededrather 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:
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.