Automation rules¶
If/then pipelines that react to ticket-lifecycle events. Built on the
same ActionExecutor as bot-UI and API writes — one audit trail,
one event surface.
Model¶
A rule is:
name— human labeltrigger— one event class name (TicketCreated,TicketTagged,SlaBreached, …)conditions— list of predicates AND-joinedactions— list ofActionRefrun sequentiallycooldown_s— don't re-fire on the same ticket within N secondsenabled— off by default until you flip it
Supported triggers¶
| Trigger | When |
|---|---|
TicketCreated |
New ticket |
TicketAssigned |
Assignment changed (incl. to null) |
TicketTagged |
Tag added / removed |
TicketPriorityChanged |
Priority changed |
TicketClosed |
Closed |
TicketReopened |
Reopened |
SlaWarned |
SLA warn-threshold crossed |
SlaBreached |
SLA breach-threshold crossed |
Condition operators¶
Conditions are pure predicates over the ticket document. Available ops:
| Op | Meaning |
|---|---|
eq / ne |
Equals / not equals |
in / not_in |
Value is / isn't in a given list |
contains |
List field contains value (e.g. tags contains billing) |
gt / lt |
Numeric greater-than / less-than |
field supports dotted paths (meta.lang).
Action catalogue¶
Every built-in action from the ActionExecutor registry is available:
| Action | params shape |
|---|---|
assign |
{"assignee_id": 123} — null to unassign |
tag |
{"tag": "billing"} |
untag |
{"tag": "billing"} |
set_priority |
{"priority": "high"} |
close |
{"reason": "auto_resolved"} |
reopen |
{} |
add_internal_note |
{"text": "Billing VIP — auto-escalated"} |
Plugins can register more; /rule_new accepts any name from the
registry.
Worked example — billing auto-escalate¶
"When a ticket is created, tagged billing, priority high, assign the billing team and drop an internal note."
/rule_new "Billing auto-escalate" TicketCreated \
{"conditions":[
{"field":"priority","op":"eq","value":"high"},
{"field":"tags","op":"contains","value":"billing"}
],
"actions":[
{"name":"assign","params":{"assignee_id":123456}},
{"name":"add_internal_note","params":{"text":"Auto-routed to billing team"}}
],
"cooldown_s": 0}
The bot replies with the new rule ID (disabled). Enable it:
/rule_enable <rule_id>
Dry-run¶
/rule_test <rule_id> <ticket_id>
Evaluates conditions without executing, returns per-condition pass/fail:
Dry-run: ✅ WOULD FIRE
✅ priority eq 'high'
✅ tags contains 'billing'
Cooldowns + caps¶
cooldown_s is per-rule, per-ticket. A rule that fires on
SlaWarned with a 30-minute cooldown won't re-fire for the same
ticket if it crosses the warn threshold again within the window (it
publishes RuleSkipped(reason="cooldown") instead).
max_fires_per_day is in the model but not enforced yet — the
implementation lands in a follow-up once we have enough in-the-wild
signal on what the right default is.
Events¶
Every rule execution emits exactly one RuleExecuted or
RuleSkipped event. Consume via the in-process event bus or via
outgoing webhooks (rule.executed):
{
"event": "rule.executed",
"rule_id": "…",
"ticket_id": "…",
"trigger": "TicketCreated",
"actions_succeeded": 2,
"actions_failed": 0,
"latency_ms": 34
}
Versioning + audit¶
Every rule save bumps version; the previous payload is pushed to
history on the document so ops can audit "who changed this, when,
why" after the fact.
Web admin (primary UI)¶
The /admin/rules page in the SPA has a full builder modal: name, trigger dropdown, add/remove conditions (field + op + value), add/remove actions (name + JSON params), cooldown seconds, enabled toggle. Each rule card has Enable / Disable / Delete buttons.
Admin commands reference (Telegram bot)¶
| Command | Purpose |
|---|---|
/rules |
List rules |
/rule_new "name" <trigger> <JSON> |
Create (disabled) |
/rule_enable <id> |
Enable |
/rule_disable <id> |
Disable |
/rule_delete <id> |
Delete |
/rule_test <rule_id> <ticket_id> |
Dry-run |
API¶
| Method | Path | Scope |
|---|---|---|
GET |
/api/v1/rules |
rules:read |
GET |
/api/v1/rules/{id} |
rules:read |
POST |
/api/v1/rules |
rules:write |
PATCH |
/api/v1/rules/{id}/enabled |
rules:write |
DELETE |
/api/v1/rules/{id} |
rules:write |
See API write reference for request/response schemas.
Design notes¶
- Conditions are pure — evaluation is side-effect free, so dry-run and production use share code.
- Actions are sequential — order in the
actionsarray is the order they run. This matters for things likeset_priority → tag → close. - Failures are isolated — one failed action doesn't abort the
batch;
actions_failed > 0inRuleExecutedsurfaces partial success. - The evaluator attaches at boot, not on first event — so a restart doesn't drop any rules that should fire on the next event.