Skip to main content
Glama

agents_trigger_create

Create a new trigger for an AI agent.

Triggers determine when the agent activates.

Trigger types:

  • incoming_message: Activates on new incoming messages

  • schedule: Activates on a schedule

  • webhook: Activates on webhook events

  • event: Activates on system events

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
enabledNoWhether the trigger is enabled. OMIT to use the default (true).
agent_idYesID of the agent to create a trigger for
priorityNoTrigger priority — lower numbers run first (default: 100)
send_modeNoSend mode override for this trigger. OMIT to inherit from the agent.
conditionsNoTrigger conditions (JSON). Supported fields for incoming_message: - keywords: ["pricing","demo"] — message must contain keyword(s) (free, no LLM cost) - keyword_match: "any" (default, OR) or "all" (AND) - channel_types: ["telegram","whatsapp","livechat_voice","twilio_voice","telegram_voice","voice",...] — filter by channel. For voice, use EITHER the three per-channel keys (scoped) OR "voice" alone (wildcard matching all three) — mixing them is redundant. Per-channel keys: "livechat_voice" (web widget), "twilio_voice" (PSTN inbound), "telegram_voice" (Telegram p2p calls) - context_types: ["dm","group","channel","livechat"] — filter by chat type - group_mode: "mentions_only" or "questions" — for group chats - channel_account_ids: ["123"] — restrict to specific accounts - folder_ids: [5,10] — restrict to threads in folders - ai_tag_ids: [1,2] — restrict to threads with AI tags - ai_filter_ids: [1,2] — semantic intent filters (message matched via embedding similarity, works in noisy groups) - ai_filter_mode: "any" (default, OR) or "all" (AND) — how multiple AI filters combine - ai_filters: [{id: 1}, {name: "...", description: "..."}] — shorthand: reference existing by id or create inline (calls Voyage embedding API). If a filter with the same name already exists, it is reused by id. Prefer referencing existing filters by id when available. Use ai_filters.create + ai_filters.test for fine-tuning before assigning. - template: the reply a rule_based agent sends, with no model run. Either a string ("Hi {from_name}, we open at 9") or a card object {text, attachments, buttons} — text is required, attachments is a list of file ids (files.upload), buttons is a list of {label, value|url|web_app} carrying exactly ONE target each: value = a reply the bot receives back, url = opens a link, web_app = opens a Mini App over https. The string, and a card's text, substitute {from_name}, {message_text}, {channel_type}, {thread_id}, {raw_data}; button labels and targets are sent verbatim. A card with buttons needs send_mode auto — a draft row cannot carry a keyboard. Buttons need a channel that renders them (telegram_bot). A card needs a chat to land in, so it refuses on webhook, schedule and other thread-less triggers where a string still drafts. - handoff: {agent_id, task} — only on a rule_based agent's trigger (refused elsewhere). After the rule's deterministic part has actually happened — the template's message delivered, or the agent's script completed — the named agent runs on the same thread, in its own send mode, exactly as its own trigger would run it: the customer's message reaches it as the incoming message, and `task` reaches it as the handoff brief from this rule. The rule stays instant; the thinking — a reminder, a referral code, a personal follow-up — happens behind it. Script plus handoff is how a workspace does something deterministic the platform has no verb for (file the thread, stamp a tag, push a row) and still lets an agent answer, with the deterministic part living in that agent's own script rather than in platform code. `task` substitutes {channel_type} and {thread_id} only; {message_text}, {from_name} and {raw_data} are refused (the sender would be writing the instruction — the message and its sender already reach the target as the incoming message). The target must be an active agentic or claude_channels agent in this workspace (the engines that read the brief). The target's run is a normal trigger run: it holds the thread lock, its own safety gates apply, and its final answer is delivered like any DM reply — a follow-up that should stay silent must call agent.silence. Nothing runs when the message was drafted, refused or failed, when the script failed, when a safety gate has the rule in draft mode, or when the event carries no thread for the target to answer on. A target paused or deleted after the trigger was written does not stop the rule's message: it goes out, and the reason the follow-up did not start is recorded in the rule's activity row (error field) and in the target's activity as an execution_error. - contact_states: ["active"] — filter by contact state - cooldown_seconds: 30 — min gap between runs per thread - max_runs_per_thread_per_hour: 5 — rate limit - once_per_thread: true — after this trigger completes on a conversation it steps aside there and the next matching trigger answers later messages. For a rule that files / tags / hands off once, not for the agent that holds the conversation. - answer_delay_s: 15 — voice pickup only (incoming_call, or incoming_message scoped to a voice channel). Ring the human's own devices this long before the agent answers; if they pick up, the agent stands down. 0/absent = answer immediately. Honoured on WhatsApp and Telegram 1:1 — other voice channels have nothing ringing to wait for and still answer at once. - auto_join: true|false — voice pickup, GROUP calls only. Whether the bot enters a matching group call on its own (true) or the call becomes a pending invite an operator accepts (false). OMIT THE KEY to defer to the channel setting (channel_account.state.voice_auto_join_policy, default 'approval'); resolution is trigger-when-present, then channel, then approval, so an explicit false beats a channel set to auto. Absent and false are different answers. - chat_ids: ["-5172634473"] — voice pickup, Telegram GROUP calls only. Scopes the trigger to specific groups; omit for every group call. Any id spelling works (5172634473, -5172634473, -1005172634473 are the same group). A call whose chat is unknown never satisfies it, so do NOT set this together with 1:1 / Twilio / Telnyx / WhatsApp / Android / LiveChat voice channels or the "voice" wildcard: those calls have no chat and the trigger would stop answering them. Supported fields for job_completed (proactive callback when a delegated job finishes): - source_agent_id: <int> — fire only when this agent's job completed - source_agent_slug: <str> — alternate to source_agent_id - job_type: "agentic_session" — match a specific job type (default: any) - outcome: ["completed"] | ["escalated"] | ["completed","escalated"] — default ["completed"] - min_duration_seconds: <int> — skip very-short jobs (noise filter) - thread_filter: {thread_ids: [<int>...]} — restrict to specific threads Supported fields for calendar_event (fires N minutes before a Google Calendar event starts): - window_minutes_before: <int 1-1440> — REQUIRED, fire when an event starts within this window - channel_account_ids: [<int>...] — restrict to specific calendar accounts (default: all) - keywords: ["standup"] — word-boundary match on event title - prepare_meet_join: true — pre-invite pool bots to the event (enables unattended Meet join) incoming_message action fields: - action: "reply_text" (default, normal agent run) or "join_voice" (deterministically join the voice channel resolved from the message — requires send_mode=auto) - message_source: "real" (default) | "transcript" | "both" — real messages vs turns during a live call. Transcripts are turns that address the agent. calendar_event run-mode field (incoming_message uses `action` instead): - run_mode: "text" (default) or "voice" (join the meeting — requires send_mode=auto) - voice: {speak_first: <bool — greet immediately vs stay silent until addressed>, vision_mode: "off"|"on_demand"|"continuous_0_3fps"} — pairs with action=join_voice (incoming_message) or run_mode=voice (calendar_event)
thread_idsNoRestrict this trigger to specific threads (chats) by their numeric thread IDs. When set, the trigger only fires for messages in these threads. Only for incoming_message and job_completed triggers. Maps to conditions.thread_filter.thread_ids.
in_workspaceNoRun this one call in this workspace id instead of the session's. Nothing is stored; other sessions are not affected.
trigger_typeYesType of trigger: 'incoming_message', 'incoming_call', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', 'calendar_event', or 'lead_captured'
target_sessionNoWhich Claude Code desktop session this trigger's runs go to, by the name / client id / session id that workspace.desktops lists. Only meaningful on a claude_channels agent: that engine hands the run to a connected desktop, and without an address it can only be delivered when EXACTLY ONE is connected (a second open window turns every run into channel_target_missing). A task assigned to the agent still wins — its own target_session is per-request and more specific than the trigger's default. Stored as conditions.target_session.
blocked_sender_idsNoNever react to these senders/callers, by their channel-side id — a phone number, a WhatsApp JID, a Telegram user id, an email address. Explicit deny: it beats every allow-list, and it applies to ALL trigger types, including incoming_call, so it is how you stop an agent answering one nuisance caller without silencing it for everybody. Phone-shaped ids are matched by their digits, so '+998901234567', '998901234567' and '998901234567@s.whatsapp.net' are the same person. Maps to conditions.sender_filter.excluded_external_ids.
excluded_thread_idsNoExclude specific threads (chats) by their numeric thread IDs — the opposite of thread_ids. When set, the trigger NEVER fires for messages in these threads, even if thread_ids would otherwise allow them (explicit deny wins). Only for incoming_message and job_completed triggers. Maps to conditions.thread_filter.excluded_thread_ids.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • addedInput schema / properties / in_workspace
      Added value: +{
      +  "description": "Run this one call in this workspace id instead of the session's. Nothing is stored; other sessions are not affected.",
      +  "type": "integer"
      +}
  2. Added
  3. Removed
  4. Changed1 schema field changed
    • changedInput schema / properties / conditions / description
      Previous value: -"Trigger conditions (JSON). Supported fields for incoming_message:\n- keywords: [\"pricing\",\"demo\"] — message must contain keyword(s) (free, no LLM cost)\n- keyword_match: \"any\" (default, OR) or \"all\" (AND)\n- channel_types: [\"telegram\",\"whatsapp\",\"livechat_voice\",\"twilio_voice\",\"telegram_voice\",\"voice\",...] — filter by channel. For voice, use EITHER the three per-channel keys (scoped) OR \"voice\" alone (wildcard matching all three) — mixing them is redundant. Per-channel keys: \"livechat_voice\" (web widget), \"twilio_voice\" (PSTN inbound), \"telegram_voice\" (Telegram p2p calls)\n- context_types: [\"dm\",\"group\",\"channel\",\"livechat\"] — filter by chat type\n- group_mode: \"mentions_only\" or \"questions\" — for group chats\n- channel_account_ids: [\"123\"] — restrict to specific accounts\n- folder_ids: [5,10] — restrict to threads in folders\n- ai_tag_ids: [1,2] — restrict to threads with AI tags\n- ai_filter_ids: [1,2] — semantic intent filters (message matched via embedding similarity, works in noisy groups)\n- ai_filter_mode: \"any\" (default, OR) or \"all\" (AND) — how multiple AI filters combine\n- ai_filters: [{id: 1}, {name: \"...\", description: \"...\"}] — shorthand: reference existing by id or create inline (calls Voyage embedding API). If a filter with the same name already exists, it is reused by id. Prefer referencing existing filters by id when available. Use ai_filters.create + ai_filters.test for fine-tuning before assigning.\n- contact_states: [\"active\"] — filter by contact state\n- cooldown_seconds: 30 — min gap between runs per thread\n- max_runs_per_thread_per_hour: 5 — rate limit\n\nSupported fields for job_completed (proactive callback when a delegated job finishes):\n- source_agent_id: <int> — fire only when this agent's job completed\n- source_agent_slug: <str> — alternate to source_agent_id\n- job_type: \"agentic_session\" — match a specific job type (default: any)\n- outcome: [\"completed\"] | [\"escalated\"] | [\"completed\",\"escalated\"] — default [\"completed\"]\n- min_duration_seconds: <int> — skip very-short jobs (noise filter)\n- thread_filter: {thread_ids: [<int>...]} — restrict to specific threads\n\nSupported fields for calendar_event (fires N minutes before a Google Calendar event starts):\n- window_minutes_before: <int 1-1440> — REQUIRED, fire when an event starts within this window\n- channel_account_ids: [<int>...] — restrict to specific calendar accounts (default: all)\n- keywords: [\"standup\"] — word-boundary match on event title\n- prepare_meet_join: true — pre-invite pool bots to the event (enables unattended Meet join)\n\nGeneric run-mode fields (incoming_message AND calendar_event):\n- run_mode: \"text\" (default, normal agent run) or \"voice\" (deterministically join the meeting/call resolved from the event or message — requires send_mode=auto)\n- voice: {speak_first: <bool — greet immediately vs stay silent until addressed>, vision_mode: \"off\"|\"on_demand\"|\"continuous_0_3fps\"}"New value: +"Trigger conditions (JSON). Supported fields for incoming_message:\n- keywords: [\"pricing\",\"demo\"] — message must contain keyword(s) (free, no LLM cost)\n- keyword_match: \"any\" (default, OR) or \"all\" (AND)\n- channel_types: [\"telegram\",\"whatsapp\",\"livechat_voice\",\"twilio_voice\",\"telegram_voice\",\"voice\",...] — filter by channel. For voice, use EITHER the three per-channel keys (scoped) OR \"voice\" alone (wildcard matching all three) — mixing them is redundant. Per-channel keys: \"livechat_voice\" (web widget), \"twilio_voice\" (PSTN inbound), \"telegram_voice\" (Telegram p2p calls)\n- context_types: [\"dm\",\"group\",\"channel\",\"livechat\"] — filter by chat type\n- group_mode: \"mentions_only\" or \"questions\" — for group chats\n- channel_account_ids: [\"123\"] — restrict to specific accounts\n- folder_ids: [5,10] — restrict to threads in folders\n- ai_tag_ids: [1,2] — restrict to threads with AI tags\n- ai_filter_ids: [1,2] — semantic intent filters (message matched via embedding similarity, works in noisy groups)\n- ai_filter_mode: \"any\" (default, OR) or \"all\" (AND) — how multiple AI filters combine\n- ai_filters: [{id: 1}, {name: \"...\", description: \"...\"}] — shorthand: reference existing by id or create inline (calls Voyage embedding API). If a filter with the same name already exists, it is reused by id. Prefer referencing existing filters by id when available. Use ai_filters.create + ai_filters.test for fine-tuning before assigning.\n- contact_states: [\"active\"] — filter by contact state\n- cooldown_seconds: 30 — min gap between runs per thread\n- max_runs_per_thread_per_hour: 5 — rate limit\n- answer_delay_s: 15 — voice pickup only (incoming_call, or incoming_message scoped to a voice channel). Ring the human's own devices this long before the agent answers; if they pick up, the agent stands down. 0/absent = answer immediately. Honoured on WhatsApp and Telegram 1:1 — other voice channels have nothing ringing to wait for and still answer at once.\n\nSupported fields for job_completed (proactive callback when a delegated job finishes):\n- source_agent_id: <int> — fire only when this agent's job completed\n- source_agent_slug: <str> — alternate to source_agent_id\n- job_type: \"agentic_session\" — match a specific job type (default: any)\n- outcome: [\"completed\"] | [\"escalated\"] | [\"completed\",\"escalated\"] — default [\"completed\"]\n- min_duration_seconds: <int> — skip very-short jobs (noise filter)\n- thread_filter: {thread_ids: [<int>...]} — restrict to specific threads\n\nSupported fields for calendar_event (fires N minutes before a Google Calendar event starts):\n- window_minutes_before: <int 1-1440> — REQUIRED, fire when an event starts within this window\n- channel_account_ids: [<int>...] — restrict to specific calendar accounts (default: all)\n- keywords: [\"standup\"] — word-boundary match on event title\n- prepare_meet_join: true — pre-invite pool bots to the event (enables unattended Meet join)\n\nGeneric run-mode fields (incoming_message AND calendar_event):\n- run_mode: \"text\" (default, normal agent run) or \"voice\" (deterministically join the meeting/call resolved from the event or message — requires send_mode=auto)\n- voice: {speak_first: <bool — greet immediately vs stay silent until addressed>, vision_mode: \"off\"|\"on_demand\"|\"continuous_0_3fps\"}"
  5. Changed2 schema fields changed
    • changedInput schema / properties / trigger_type / description
      Previous value: -"Type of trigger: 'incoming_message', 'incoming_call', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', or 'calendar_event'"New value: +"Type of trigger: 'incoming_message', 'incoming_call', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', 'calendar_event', or 'lead_captured'"
    • changedInput schema / properties / trigger_type / enum
      Previous value: -[
      -  "incoming_message",
      -  "incoming_call",
      -  "schedule",
      -  "webhook",
      -  "event",
      -  "blockchain_event",
      -  "job_completed",
      -  "calendar_event"
      -]New value: +[
      +  "incoming_message",
      +  "incoming_call",
      +  "schedule",
      +  "webhook",
      +  "event",
      +  "blockchain_event",
      +  "job_completed",
      +  "calendar_event",
      +  "lead_captured"
      +]
  6. Changed2 schema fields changed
    • addedInput schema / properties / excluded_thread_ids
      Added value: +{
      +  "description": "Exclude specific threads (chats) by their numeric thread IDs — the opposite of thread_ids. When set, the trigger NEVER fires for messages in these threads, even if thread_ids would otherwise allow them (explicit deny wins). Only for incoming_message and job_completed triggers. Maps to conditions.thread_filter.excluded_thread_ids.",
      +  "items": {
      +    "type": "integer"
      +  },
      +  "type": "array"
      +}
    • changedInput schema / properties / thread_ids / description
      Previous value: -"Restrict this trigger to specific threads (chats) by their numeric thread IDs. When set, the trigger only fires for messages in these threads. Maps to conditions.thread_filter.thread_ids."New value: +"Restrict this trigger to specific threads (chats) by their numeric thread IDs. When set, the trigger only fires for messages in these threads. Only for incoming_message and job_completed triggers. Maps to conditions.thread_filter.thread_ids."
  7. Changed2 schema fields changed
    • changedInput schema / properties / trigger_type / description
      Previous value: -"Type of trigger: 'incoming_message', 'incoming_call', 'voice_transcript', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', or 'calendar_event'"New value: +"Type of trigger: 'incoming_message', 'incoming_call', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', or 'calendar_event'"
    • changedInput schema / properties / trigger_type / enum
      Previous value: -[
      -  "incoming_message",
      -  "incoming_call",
      -  "voice_transcript",
      -  "schedule",
      -  "webhook",
      -  "event",
      -  "blockchain_event",
      -  "job_completed",
      -  "calendar_event"
      -]New value: +[
      +  "incoming_message",
      +  "incoming_call",
      +  "schedule",
      +  "webhook",
      +  "event",
      +  "blockchain_event",
      +  "job_completed",
      +  "calendar_event"
      +]
  8. Changed3 schema fields changed
    • changedInput schema / properties / conditions / description
      Previous value: -"Trigger conditions (JSON). Supported fields for incoming_message:\n- keywords: [\"pricing\",\"demo\"] — message must contain keyword(s) (free, no LLM cost)\n- keyword_match: \"any\" (default, OR) or \"all\" (AND)\n- channel_types: [\"telegram\",\"whatsapp\",\"livechat_voice\",\"twilio_voice\",\"telegram_voice\",\"voice\",...] — filter by channel. For voice, use EITHER the three per-channel keys (scoped) OR \"voice\" alone (wildcard matching all three) — mixing them is redundant. Per-channel keys: \"livechat_voice\" (web widget), \"twilio_voice\" (PSTN inbound), \"telegram_voice\" (Telegram p2p calls)\n- context_types: [\"dm\",\"group\",\"channel\",\"livechat\"] — filter by chat type\n- group_mode: \"mentions_only\" or \"questions\" — for group chats\n- channel_account_ids: [\"123\"] — restrict to specific accounts\n- folder_ids: [5,10] — restrict to threads in folders\n- ai_tag_ids: [1,2] — restrict to threads with AI tags\n- ai_filter_ids: [1,2] — semantic intent filters (message matched via embedding similarity, works in noisy groups)\n- ai_filter_mode: \"any\" (default, OR) or \"all\" (AND) — how multiple AI filters combine\n- ai_filters: [{id: 1}, {name: \"...\", description: \"...\"}] — shorthand: reference existing by id or create inline (calls Voyage embedding API). If a filter with the same name already exists, it is reused by id. Prefer referencing existing filters by id when available. Use ai_filters.create + ai_filters.test for fine-tuning before assigning.\n- contact_states: [\"active\"] — filter by contact state\n- cooldown_seconds: 30 — min gap between runs per thread\n- max_runs_per_thread_per_hour: 5 — rate limit\n\nSupported fields for job_completed (proactive callback when a delegated job finishes):\n- source_agent_id: <int> — fire only when this agent's job completed\n- source_agent_slug: <str> — alternate to source_agent_id\n- job_type: \"agentic_session\" — match a specific job type (default: any)\n- outcome: [\"completed\"] | [\"escalated\"] | [\"completed\",\"escalated\"] — default [\"completed\"]\n- min_duration_seconds: <int> — skip very-short jobs (noise filter)\n- thread_filter: {thread_ids: [<int>...]} — restrict to specific threads"New value: +"Trigger conditions (JSON). Supported fields for incoming_message:\n- keywords: [\"pricing\",\"demo\"] — message must contain keyword(s) (free, no LLM cost)\n- keyword_match: \"any\" (default, OR) or \"all\" (AND)\n- channel_types: [\"telegram\",\"whatsapp\",\"livechat_voice\",\"twilio_voice\",\"telegram_voice\",\"voice\",...] — filter by channel. For voice, use EITHER the three per-channel keys (scoped) OR \"voice\" alone (wildcard matching all three) — mixing them is redundant. Per-channel keys: \"livechat_voice\" (web widget), \"twilio_voice\" (PSTN inbound), \"telegram_voice\" (Telegram p2p calls)\n- context_types: [\"dm\",\"group\",\"channel\",\"livechat\"] — filter by chat type\n- group_mode: \"mentions_only\" or \"questions\" — for group chats\n- channel_account_ids: [\"123\"] — restrict to specific accounts\n- folder_ids: [5,10] — restrict to threads in folders\n- ai_tag_ids: [1,2] — restrict to threads with AI tags\n- ai_filter_ids: [1,2] — semantic intent filters (message matched via embedding similarity, works in noisy groups)\n- ai_filter_mode: \"any\" (default, OR) or \"all\" (AND) — how multiple AI filters combine\n- ai_filters: [{id: 1}, {name: \"...\", description: \"...\"}] — shorthand: reference existing by id or create inline (calls Voyage embedding API). If a filter with the same name already exists, it is reused by id. Prefer referencing existing filters by id when available. Use ai_filters.create + ai_filters.test for fine-tuning before assigning.\n- contact_states: [\"active\"] — filter by contact state\n- cooldown_seconds: 30 — min gap between runs per thread\n- max_runs_per_thread_per_hour: 5 — rate limit\n\nSupported fields for job_completed (proactive callback when a delegated job finishes):\n- source_agent_id: <int> — fire only when this agent's job completed\n- source_agent_slug: <str> — alternate to source_agent_id\n- job_type: \"agentic_session\" — match a specific job type (default: any)\n- outcome: [\"completed\"] | [\"escalated\"] | [\"completed\",\"escalated\"] — default [\"completed\"]\n- min_duration_seconds: <int> — skip very-short jobs (noise filter)\n- thread_filter: {thread_ids: [<int>...]} — restrict to specific threads\n\nSupported fields for calendar_event (fires N minutes before a Google Calendar event starts):\n- window_minutes_before: <int 1-1440> — REQUIRED, fire when an event starts within this window\n- channel_account_ids: [<int>...] — restrict to specific calendar accounts (default: all)\n- keywords: [\"standup\"] — word-boundary match on event title\n- prepare_meet_join: true — pre-invite pool bots to the event (enables unattended Meet join)\n\nGeneric run-mode fields (incoming_message AND calendar_event):\n- run_mode: \"text\" (default, normal agent run) or \"voice\" (deterministically join the meeting/call resolved from the event or message — requires send_mode=auto)\n- voice: {speak_first: <bool — greet immediately vs stay silent until addressed>, vision_mode: \"off\"|\"on_demand\"|\"continuous_0_3fps\"}"
    • changedInput schema / properties / trigger_type / description
      Previous value: -"Type of trigger: 'incoming_message', 'incoming_call', 'voice_transcript', 'schedule', 'webhook', 'event', 'blockchain_event', or 'job_completed'"New value: +"Type of trigger: 'incoming_message', 'incoming_call', 'voice_transcript', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', or 'calendar_event'"
    • changedInput schema / properties / trigger_type / enum
      Previous value: -[
      -  "incoming_message",
      -  "incoming_call",
      -  "voice_transcript",
      -  "schedule",
      -  "webhook",
      -  "event",
      -  "blockchain_event",
      -  "job_completed"
      -]New value: +[
      +  "incoming_message",
      +  "incoming_call",
      +  "voice_transcript",
      +  "schedule",
      +  "webhook",
      +  "event",
      +  "blockchain_event",
      +  "job_completed",
      +  "calendar_event"
      +]
  9. First observed

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare the safety profile (readOnly=false, destructive=false, idempotent=false, openWorld=false), so the description's remaining burden is light. It contributes the trigger-type activation semantics, but says nothing about persistence, default enabled state, draft/send_mode interactions, or what happens on repeat calls — all of which matter for a mutating tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Six short lines, purpose front-loaded, zero filler prose. Minor waste: the trigger-type bullet list duplicates (and under-represents) the schema enum, so it earns less than a full 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 11-parameter mutating tool with a nested conditions object, the description is thin, but the schema carries essentially all parameter detail, so the gap is mostly the unstated return value (no output schema exists) and the incomplete type enumeration (4 of 9 enum values, missing incoming_call, job_completed, calendar_event, blockchain_event, lead_captured).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema fully documents all 11 parameters (including the very detailed `conditions` object); baseline 3 applies. The description adds no parameter meaning of its own, and its four-item type list is narrower than the nine-value enum, so it does not compensate further.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (create) and resource (trigger) plus the owning entity (AI agent), and adds that triggers determine when an agent activates. It is clearly distinguishable from agents_create and agents_trigger_update, though it never names a sibling explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The type list implies when each trigger fires, which gives some usage context, but there is no explicit when-to-use guidance, no mention of prerequisites (the agent must already exist), and no routing to agents_trigger_update / agents_trigger_delete for modifying or removing triggers.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.