Chat triggers¶
This page lists what a message in a bound channel does, the fields of a botMessage trigger, the alertmanager
preset, and the decisions, notes and limits of a trigger's threads. The fields' types are in the
Binding reference.
What a message does¶
These are the defaults, which a Binding with no chat.triggers gets.
| Message | What happens |
|---|---|
| A mention in a bound channel, not in a thread | A new thread and a new session. |
| A reply in a thread Agent Kourier owns | Continues that session. With chat.threadReplies: mention, only a reply that mentions Agent Kourier does; the rest are ignored, except a typed answer to a pending question that takes text replies. |
| A mention inside a thread Agent Kourier does not own, in a bound channel | A new session anchored to that thread, which Agent Kourier owns from then on. |
| A reply without a mention in any other thread | Ignored. |
| Any message Agent Kourier posted, including the edit events of its own posts | Ignored, always. |
| A message from any other bot or app | Ignored, unless a botMessage trigger names its bot ID. |
| An edit or deletion of any message, or another non-message subtype | Ignored. Plain messages, thread_broadcast and file_share (its text only) count as messages. |
| A direct message | Not served yet. |
The trigger list¶
- An empty
chat.triggersis[{type: mention}], the table above. - A list that names triggers is the whole list. A Binding that lists only
botMessagetriggers starts no session from a mention, in or out of a thread. - Triggers are tried in order, and the first that matches handles the message. A trigger's index in the list names it in logs and metrics.
- v1 has two types:
mentionandbotMessage. Amentiontrigger takes no other field. - Whether a reply in an owned thread needs a mention is
chat.threadReplies, not a trigger.
botMessage fields¶
A botMessage trigger works for any bot, named by its bot ID. The alertmanager preset is the only preset that ships:
for another bot, write match, extract, thread, limits and promptTemplate yourself.
| Field | Meaning |
|---|---|
from.botId |
The one bot whose messages the trigger accepts, by Slack bot ID (B...), never by display name. Agent Kourier's own messages are never handed on, even if a trigger names its bot ID; that mistake is logged once, at warn. |
match |
Message field to regex. Every entry must match. |
exclude |
Message field to regex. No entry may match. |
extract |
Named values taken from a field by a regex whose named group has the entry's name. A message in which an extract does not match does not match the trigger. |
thread.by |
key: messages with the same extracted key share one thread, anchored to the first. |
thread.cooldown |
1m to 168h. No new turn for a key inside the window, which runs from the message that started the turn. Required with thread.by. |
thread.closeWhen |
Extracted field to value, for example {state: RESOLVED}. A matching message posts a note and starts no turn. |
thread.maxIdle |
At least twice the cooldown, at most 720h; unset, the larger of 24h and twice the cooldown. An open thread with no message of its key for this long is abandoned, and the next fire starts a new investigation. It must be longer than the sender's repeat interval. |
promptTemplate |
A Go template over the trigger's view of the message (below). Parsed and run once at load against a placeholder view, so a mistyped field rejects the Binding. |
limits.rateLimit |
maxRuns starts in any per (10s to 24h), and maxConcurrent investigations at once. |
limits.maxRunsPerDay |
Starts in any 24 hours. An explicit 0 is no cap. |
preset |
alertmanager fills in match, extract, thread, limits and promptTemplate (below). |
A trigger with neither a preset nor limits is unlimited.
Message fields a regex can read: text, title, titleLink, fallback, footer and attachmentText. The Slack
adapter flattens the message's first legacy attachment into the last five; text is the message's own text. All are
raw, as delivered. Later attachments are not read.
Regexes are RE2 (linear time), at most 512 bytes, and compile at load, as do the named groups and the template. A Binding that fails any of them is rejected.
The alertmanager preset¶
For Alertmanager's Slack receiver. A Binding's own entries override the preset's: a match, exclude or extract
entry replaces the preset's entry of that name; thread.by, thread.cooldown, thread.closeWhen and
promptTemplate replace the preset's; each limits field the Binding sets replaces the preset's, and the others stay.
An empty value means unset, so a Binding cannot switch a preset entry off; to drop a match entry, override it with
'.*'.
| Part | Preset value |
|---|---|
match.title |
FIRING (<n>) - or RESOLVED - at the start, optionally behind a bracketed tag of up to 32 bytes such as [kind] |
match.fallback |
[FIRING], [FIRING:<n>] or [RESOLVED] at the start: Alertmanager's default fallback |
extract.state |
FIRING or RESOLVED, from the title |
extract.alertname |
The rest of the title after - |
extract.key |
The group label values from the fallback, without the count |
thread |
by: key, cooldown: 4h, closeWhen: {state: RESOLVED} |
limits |
5 starts in any 10 minutes, 2 at once, 40 a day |
promptTemplate |
Asks the agent to investigate the alert with its read-only tools and report the likely cause, the evidence it checked, and the next steps a person should take |
What a matched message does to its thread¶
For a trigger with thread.by: key. A close is a message whose extracted fields all equal closeWhen; any other
match is a fire.
| Message | The key has | Decision | What happens |
|---|---|---|---|
| fire | no thread, a closed one past its cooldown, one whose turn never started and is over a minute old, or an open one idle past maxIdle |
started |
A new investigation in a thread anchored to this message. The cooldown begins once its turn is taken. |
| fire | an open thread that is not idle past maxIdle, or a closed one inside its cooldown |
repeat |
"Fired again at <time>." in that thread, which reopens. No turn. |
| fire | this very message as its thread (a redelivery) | dedup |
Nothing, if its turn started. |
| fire, key empty | empty_key |
An investigation of its own, with no cooldown and no note. | |
| close | an open thread whose turn started | closed |
"Resolved at <time>." in that thread, which closes. No turn. |
| close | no thread, a closed one, or one whose turn never started | close_unknown |
Ignored and counted. |
| fire | would be started, and a limit is reached |
limited |
No turn and nothing in the alert's thread. Counted, and in the window's digest line. |
| fire | the first prompt failed its check or could not be rendered | not_started |
"Not investigated: ..." in the thread, an audit entry, and a metric. No cooldown. |
A trigger with no thread has none of this: every match starts a turn. The notes are fixed text and a time, never any
of the message.
Limits and the digest line¶
Only a start is limited: a repeat, a close and a note never are. Rate and daily counts are sliding windows that survive a restart. Concurrency counts the trigger's investigations that have an active task (running, or paused for a person) or a queued turn. When several limits are reached, the daily cap is reported first, then the rate, then the concurrency.
Once for each window and trigger, one line at the top level of the channel counts the starts a limit stopped, for
example "7 alert messages not investigated in the last 10m (rate limit)." A window opens with the first stopped start
and lasts the trigger's rateLimit.per (an hour for a trigger with only a daily cap). The line holds a count, a length
and a limit, never any text of an alert.
The first turn's prompt¶
The template sees:
| Field | Value |
|---|---|
.Channel |
The channel the message arrived in. |
.State |
The extracted state, empty if none. |
.Key |
The extracted key. |
.Fields |
Every extracted value, by name. |
.Link |
The Slack permalink of the triggering message, built by Agent Kourier, never a field of the message. |
.Source |
The preset's source name. |
.Fields, .State and .Key come out of the sender's message, so each value the template reads is checked before it
reaches the instruction part: the alert-name shape (ASCII letters, digits and _.-:, at most 128 bytes) for fields,
state, channel and source; the same plus / for the key; and a plain http or https URL of at most 256 bytes for
the link. A value that fails refuses the turn (not_started). The preset's key holds spaces, so a template should
read .Fields entries, not .Key.
After the rendered template, the whole message follows, normalized and with its secrets redacted, inside a block labelled as untrusted data.