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

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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there - 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query; add tag: <ai_tag id> beside it to put that tag on the CONVERSATION at the tap, so for people, not groups, where any member's tap tags the whole group; add pass: "<name>" to open the url with <name>=<the chat's id on its channel, a Telegram user id for a bot chat>, so the site can name the person back in its own events), 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) Supported fields for task_assigned (a task on the workspace board was assigned to this agent): - target_session: <session_id> — for a Claude Code agent, the session the task goes to (default: the agent's own session) Supported fields for job_continuation (this agent's own call or video finished): - job_types: ["voice_call", "video_generation"] — which kinds re-invoke it (default: all) 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', 'lead_captured', 'task_assigned' (a task on the workspace board was assigned to this agent; without one, assigning a task does not start the agent), or 'job_continuation' (this agent's OWN call from calls.make or video from videos.generate finished; without one the agent is not re-invoked to carry on)
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. 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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there\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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query; add tag: <ai_tag id> beside it to put that tag on the CONVERSATION at the tap, so for people, not groups, where any member's tap tags the whole group; add pass: \"<name>\" to open the url with <name>=<the chat's id on its channel, a Telegram user id for a bot chat>, so the site can name the person back in its own events), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nSupported fields for task_assigned (a task on the workspace board was assigned to this agent):\n- target_session: <session_id> — for a Claude Code agent, the session the task goes to (default: the agent's own session)\n\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there\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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query; add tag: <ai_tag id> beside it to put that tag on the CONVERSATION at the tap, so for people, not groups, where any member's tap tags the whole group; add pass: \"<name>\" to open the url with <name>=<the chat's id on its channel, a Telegram user id for a bot chat>, so the site can name the person back in its own events), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nSupported fields for task_assigned (a task on the workspace board was assigned to this agent):\n- target_session: <session_id> — for a Claude Code agent, the session the task goes to (default: the agent's own session)\n\nSupported fields for job_continuation (this agent's own call or video finished):\n- job_types: [\"voice_call\", \"video_generation\"] — which kinds re-invoke it (default: all)\n\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"
    • changedInput schema / properties / trigger_type / description
      Previous value: -"Type of trigger: 'incoming_message', 'incoming_call', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', 'calendar_event', 'lead_captured', or 'task_assigned' (a task on the workspace board was assigned to this agent; without one, assigning a task does not start the agent)"New value: +"Type of trigger: 'incoming_message', 'incoming_call', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', 'calendar_event', 'lead_captured', 'task_assigned' (a task on the workspace board was assigned to this agent; without one, assigning a task does not start the agent), or 'job_continuation' (this agent's OWN call from calls.make or video from videos.generate finished; without one the agent is not re-invoked to carry on)"
    • changedInput schema / properties / trigger_type / enum
      Previous value: -[
      -  "incoming_message",
      -  "incoming_call",
      -  "schedule",
      -  "webhook",
      -  "event",
      -  "blockchain_event",
      -  "job_completed",
      -  "calendar_event",
      -  "lead_captured",
      -  "task_assigned"
      -]New value: +[
      +  "incoming_message",
      +  "incoming_call",
      +  "schedule",
      +  "webhook",
      +  "event",
      +  "blockchain_event",
      +  "job_completed",
      +  "calendar_event",
      +  "lead_captured",
      +  "task_assigned",
      +  "job_continuation"
      +]
  2. 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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there\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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query; add tag: <ai_tag id> beside it to put that tag on the CONVERSATION at the tap, so for people, not groups, where any member's tap tags the whole group; add pass: \"<name>\" to open the url with <name>=<the chat's id on its channel, a Telegram user id for a bot chat>, so the site can name the person back in its own events), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there\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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query; add tag: <ai_tag id> beside it to put that tag on the CONVERSATION at the tap, so for people, not groups, where any member's tap tags the whole group; add pass: \"<name>\" to open the url with <name>=<the chat's id on its channel, a Telegram user id for a bot chat>, so the site can name the person back in its own events), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nSupported fields for task_assigned (a task on the workspace board was assigned to this agent):\n- target_session: <session_id> — for a Claude Code agent, the session the task goes to (default: the agent's own session)\n\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"
    • changedInput schema / properties / trigger_type / description
      Previous value: -"Type of trigger: 'incoming_message', 'incoming_call', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', 'calendar_event', or 'lead_captured'"New value: +"Type of trigger: 'incoming_message', 'incoming_call', 'schedule', 'webhook', 'event', 'blockchain_event', 'job_completed', 'calendar_event', 'lead_captured', or 'task_assigned' (a task on the workspace board was assigned to this agent; without one, assigning a task does not start the agent)"
    • changedInput schema / properties / trigger_type / enum
      Previous value: -[
      -  "incoming_message",
      -  "incoming_call",
      -  "schedule",
      -  "webhook",
      -  "event",
      -  "blockchain_event",
      -  "job_completed",
      -  "calendar_event",
      -  "lead_captured"
      -]New value: +[
      +  "incoming_message",
      +  "incoming_call",
      +  "schedule",
      +  "webhook",
      +  "event",
      +  "blockchain_event",
      +  "job_completed",
      +  "calendar_event",
      +  "lead_captured",
      +  "task_assigned"
      +]
  3. 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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there\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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query; add tag: <ai_tag id> beside it to put that tag on the CONVERSATION at the tap, so for people, not groups, where any member's tap tags the whole group), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there\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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query; add tag: <ai_tag id> beside it to put that tag on the CONVERSATION at the tap, so for people, not groups, where any member's tap tags the whole group; add pass: \"<name>\" to open the url with <name>=<the chat's id on its channel, a Telegram user id for a bot chat>, so the site can name the person back in its own events), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"
  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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there\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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there\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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query; add tag: <ai_tag id> beside it to put that tag on the CONVERSATION at the tap, so for people, not groups, where any member's tap tags the whole group), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"
  5. 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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"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. Only accounts THIS workspace answers: a trigger never fires for another workspace's channel, so create it on an agent there\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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"
  6. 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- 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"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- 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 (add track: true to count each tap per conversation, readable as analytics_link_clicks_v in analytics.query), 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.\n- 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.\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- 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.\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- 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.\n- 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.\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\nincoming_message action fields:\n- action: \"reply_text\" (default, normal agent run) or \"join_voice\" (deterministically join the voice channel resolved from the message — requires send_mode=auto)\n- message_source: \"real\" (default) | \"transcript\" | \"both\" — real messages vs turns during a live call. Transcripts are turns that address the agent.\n\ncalendar_event run-mode field (incoming_message uses `action` instead):\n- run_mode: \"text\" (default) or \"voice\" (join the meeting — requires send_mode=auto)\n- 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)"
  7. 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"
      +}
  8. Added
  9. Removed
  10. 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\"}"
  11. 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"
      +]
  12. 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."
  13. 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"
      +]
  14. 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"
      +]
  15. First observed

TDQS

B3.2/5.0
Behavior3/5

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

Annotations already declare this is a non-read-only, non-idempotent, non-destructive, closed-world write, so the safety profile is covered and the description is consistent with it (no contradiction). The description adds only the conceptual role of triggers; it says nothing about what happens on duplicate creation, what the mutation affects downstream, or that downstream runs consume LLM cost — items that matter for a creation 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?

Purpose is front-loaded in one sentence, then a short glossary; no filler or repetition of the schema. The glossary block is the one weak element, since it is an inaccurate subset that would be better removed or completed.

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?

The schema is extremely rich, which carries most of the burden, but for a complex creation tool the description omits two things an agent needs: the full set of trigger types and what it gets back (no output schema, yet the trigger id is required for the sibling update/delete tools). Given schema richness, it is adequate but leaves clear gaps.

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% and the properties (especially the enormous conditions object) already carry all semantics, so baseline 3 applies. The description's only parameter-adjacent content is its trigger-type list, which duplicates the enum but incompletely — it adds no meaning beyond the schema.

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?

The first sentence gives a specific verb+resource ("Create a new trigger for an AI agent") and the second explains what a trigger is conceptually. It does not explicitly differentiate from agents_trigger_update / agents_trigger_delete, and the type list under it names only 4 of the 11 enum values (omitting job_completed, calendar_event, task_assigned, job_continuation, incoming_call, blockchain_event, lead_captured), so an agent skimming only the description would underestimate the tool's scope.

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

Usage Guidelines2/5

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

There is no when-to-use vs. when-not guidance: nothing says to prefer agents_trigger_update for existing triggers, nothing about prerequisites (an existing agent_id, workspace context), and nothing about duplicates. The type glossary gestures at selection but is partial, so it cannot serve as real routing guidance.

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.