Rule Engine
The rule engine evaluates if-this-then-that rules at BeforeAgentTurn (priority 100, after routing at priority 50). It can block, dispatch, or pass messages based on keywords, channels, or user identity.
Overview
inbound message
│
▼ (priority 50) RoutingHook — tag detection, tool restriction, context injection
│
▼ (priority 100) RuleEngineHook — rule file evaluation
│
├── no rule matched → Continue (LLM handles it)
├── PassToDefaultAgent → Continue (explicit pass-through)
├── Ignore → Block
├── StartWorkflow(..) → Continue + workflow dispatched
└── RunNativeAction(..) → Continue + native action dispatchedRules and routing are distinct features that both run pre-LLM. Use routing to shape the prompt (detect tags, inject context, restrict tools). Use rules to make decisions about message handling (block spam, dispatch to workflows, route by channel).
Config File
Rules live in a dedicated TOML file, separate from ugent.toml:
# ugent.toml
[rule_engine]
enabled = true
rules_path = "~/.ugent/rules.toml"At BeforeAgentTurn, the engine builds a match context from the inbound message and evaluates rules in descending priority order. The first rule whose condition matches fires its decision; the rest are skipped.
Rule file edits are hot-reloaded — no restart needed.
Quick Start
# ~/.ugent/rules.toml
# Keyword-based workflow dispatch: messages mentioning "report" trigger the report workflow.
[[rules]]
priority = 100
name = "report-trigger"
[rules.condition]
type = "any_of"
conditions = [
{ type = "keyword", value = "report" },
{ type = "keyword", value = "summary" },
]
[services.decision]
type = "StartWorkflow"
workflow = "daily-report"Conditions
| Type | Description | Key Fields |
|---|---|---|
keyword | Matches if the message contains the keyword (case-insensitive) | value |
channel | Matches the message source channel | value (e.g., slack, discord, web) |
source_type | Matches the sender type | value (e.g., user, bot) |
user_id | Matches a specific user | value |
all_of | Boolean AND — all sub-conditions must match | conditions (list) |
any_of | Boolean OR — any sub-condition must match | conditions (list) |
not | Boolean NOT — inverts the sub-condition | condition |
Decisions
| Type | Description |
|---|---|
PassToDefaultAgent | Explicitly pass the message to the LLM (default behavior) |
Ignore | Silently drop the message |
StartWorkflow | Dispatch the message to a named workflow |
RunNativeAction | Run a built-in native action (e.g., memory_compaction, memory_extraction) |
Example: Channel Filtering
Only allow messages from Slack and Discord; ignore everything else:
[[rules]]
priority = 200
name = "channel-whitelist"
[services.condition]
type = "not"
condition = { type = "any_of", conditions = [
{ type = "channel", value = "slack" },
{ type = "channel", value = "discord" },
] }
[services.decision]
type = "Ignore"Example: Per-User VIP Routing
Route messages from a specific user to a premium workflow:
[[rules]]
priority = 300
name = "vip-routing"
[services.condition]
type = "all_of"
conditions = [
{ type = "user_id", value = "U12345678" },
{ type = "channel", value = "slack" },
]
[services.decision]
type = "StartWorkflow"
workflow = "premium-support"Example: Boolean Logic
Combine conditions with nested boolean logic:
[[rules]]
priority = 150
name = "complex-rule"
[services.condition]
type = "all_of"
conditions = [
{ type = "channel", value = "web" },
{ type = "any_of", conditions = [
{ type = "keyword", value = "pricing" },
{ type = "keyword", value = "demo" },
] },
{ type = "not", condition = { type = "keyword", value = "internal" } },
]
[services.decision]
type = "StartWorkflow"
workflow = "sales-handoff"This rule matches web messages that mention "pricing" or "demo" but do NOT contain "internal".
Hot Reload
Editing rules.toml takes effect immediately — the engine watches the file and reloads on save. No restart needed.
Routing vs Rule Engine
| Routing | Rule Engine | |
|---|---|---|
| Priority | 50 (runs first) | 100 (runs after routing) |
| Purpose | Shape the prompt | Make message-handling decisions |
| Can rewrite text | Yes (rewriters) | No |
| Can restrict tools | Yes (per-turn) | No |
| Can block messages | Yes (intent-reject) | Yes (Ignore) |
| Can dispatch workflows | Yes (delegate-research) | Yes (StartWorkflow) |
| Config file | routing.toml | rules.toml |
| Config section | Top-level [routing] | [rule_engine] in ugent.toml |
Use both together: routing enriches and shapes the prompt, then the rule engine decides whether to pass it to the LLM or dispatch to a workflow.