Skip to content

Message Routing

Routing lets you shape every incoming message before the LLM sees it. Detect tags, inject context, restrict tools — all configurable in a dedicated TOML file.

Overview

The routing framework runs a three-stage pipeline at BeforeAgentTurn (before safety hooks):

inbound message → detector → (optional) rewriter → action
  • Detector — classifies the message shape (tag prefix/suffix, complexity, scope)
  • Rewriter — transforms the message text (prepend instructions, inject enrichment context)
  • Action — decides what happens (restrict tools, enrich with context, reject, dispatch)

Rules can run in first_match_wins mode (stop at first match) or all_match mode (evaluate every matching rule in order).

Config File

Routing lives in a dedicated routing.toml, discovered in this order (first found wins):

  1. <workspace>/routing.toml
  2. <workspace>/.ugent/routing.toml
  3. ~/.ugent/routing.toml

A missing file means routing is disabled. Editing routing.toml triggers a full agent rebuild on the next reload — the new pipeline takes effect without restarting.

TIP

Any inline [routing] section in ugent.toml is ignored. Routing must live in its own file.

Top-Level Fields

toml
enabled = true            # master switch
mode = "all_match"        # "first_match_wins" (default) | "all_match"

Use all_match when multiple rules should fire on the same message (e.g., one rule enriches context, another prepends it).

Quick Start

toml
# ~/.ugent/routing.toml
enabled = true
mode = "first_match_wins"

# Tag-suffixed messages get a restricted, read-only toolset.
[[rules]]
name = "research-mode"
enabled = true
detector = "suffix"
detector_params = { tag = "#research", case_sensitive = false, strip_suffix = true }
rewriter = "prepend-instruction"
rewriter_params = { template = "You are in research mode. Do not modify files." }
action = "restrict-toolset"
action_params = { allowed = ["web_search", "web_fetch", "workspace_search"], denied = [] }

With this rule, a message like "summarize the auth module #research" is rewritten to "You are in research mode. Do not modify files." and the LLM only sees search and web tools.

Detectors

NameDescriptionKey Params
prefixMatches a literal tag at the start of the messagetag, case_sensitive, strip_prefix
suffixMatches a literal tag at the endtag, case_sensitive, strip_suffix
complexityScores message complexity by keyword density and lengththreshold (0.0–1.0), keywords
out_of_scopeDetects whether a question is outside the configured scoperesearch_keywords, scope_keywords
intent-gateClassifies intent via an external LLM endpointserver_url, timeout_secs
noopAlways matches (useful with all_match for unconditional rules)

Rewriters

NameDescriptionKey Params
prepend / prepend-instructionPrepends a template string to the message using minijinjatemplate (supports , , )
enrichment-prependInjects previously fetched enrichment context into the messageenrichment_keys (list of metadata keys from tool-enrich actions)
noopNo text transformation

Actions

NameDescriptionKey Params
restrict-toolsetLimits which tools the LLM can call this turnallowed (list), denied (list; ["*"] denies all)
tool-enrichCalls a provider to fetch context and stores it in routing metadataprovider, metadata_key, plus passthrough params like max_results, snippet_lines
intent-rejectBlocks the message from reaching the LLMdecision
delegate-researchDispatches the message as a deep-research sub-taskworkflow, report_style, output_format
noopNo action (useful when only the rewriter matters)

Enrichment Providers

Providers wrap one or more tools so a tool-enrich action can inject context before the LLM responds. A legacy codebase-search provider is always available when an agent is attached; explicit [[providers]] entries are preferred for clarity.

Single-Tool Provider

toml
[[providers]]
name = "workspace-search"
type = "tool"
tool_name = "workspace_search"
input_mode = "query_passthrough"    # default
summary_format = "search"           # default
default_params = { limit = 5 }

Multi-Tool Provider

toml
[[providers]]
name = "context-bundle"
type = "tool"
default_params = { limit = 5 }

[[providers.tools]]
tool_name = "workspace_search"
input_mode = "query_passthrough"
summary_format = "search"
default_params = { limit = 8 }

[[providers.tools]]
tool_name = "graph_search"
input_mode = "query_passthrough"
summary_format = "graph"

[[providers.tools]]
tool_name = "workspace_overview"
input_mode = "static"
summary_format = "stats"

Flexible Source Provider

Combines exact MCP/ToolRegistry tool names and optional skill guidance:

toml
[[providers]]
name = "flexible-context"
type = "tool"

[[providers.sources]]
name = "code"
kind = "tool"
tool_name = "mcp_ugent-context_workspace_search"
input_mode = "query_passthrough"
summary_format = "search"
default_params = { max_results = 8, snippet_lines = 20 }

# Optional skill guidance
# [[providers.sources]]
# name = "guide"
# kind = "skill"
# skill_name = "routing-review"
# max_chars = 20000

Recipes

Quick Response Mode

Tag a message with [quick_response] to get a fast, tool-free answer grounded in retrieved context:

toml
[[rules]]
name = "quick-response-enrich"
enabled = true
detector = "prefix"
detector_params = { tag = "[quick_response]", case_sensitive = false, strip_prefix = true }
action = "tool-enrich"
action_params = { provider = "flexible-context", metadata_key = "enrichment.qr", max_results = 15, snippet_lines = 20 }

[[rules]]
name = "quick-response-answer"
enabled = true
detector = "prefix"
detector_params = { tag = "[quick_response]", case_sensitive = false, strip_prefix = true }
rewriter = "enrichment-prepend"
rewriter_params = { enrichment_keys = ["enrichment.qr"] }
action = "restrict-toolset"
action_params = { denied = ["*"] }

Enrich Complex Questions

Automatically inject codebase context when a question mentions refactoring or architecture:

toml
[[rules]]
name = "enrich-complex-fetch"
enabled = true
detector = "complexity"
detector_params = { threshold = 0.6, keywords = ["refactor", "architecture", "design"] }
action = "tool-enrich"
action_params = { provider = "flexible-context", metadata_key = "context" }

[[rules]]
name = "enrich-complex-prepend"
enabled = true
detector = "complexity"
detector_params = { threshold = 0.6, keywords = ["refactor", "architecture", "design"] }
rewriter = "enrichment-prepend"
rewriter_params = { enrichment_keys = ["context"] }
action = "noop"

Rewriter Runs Before Action

Within a single rule, the rewriter runs before the action. So a rule that both enriches (tool-enrich) and prepends (enrichment-prepend) will not work — the rewriter runs before the enrichment metadata exists. Split into two rules sharing the same detector under all_match.

Input and Summary Modes

Provider tools accept these input_mode values:

ModeDescription
query_passthrough (default)The user's message text becomes the tool query
staticUses only default_params — no query input
templateRenders input_template with minijinja
metadata_templateRenders input_template with detection metadata

Summary summary_format values: raw (default), search, graph, stats, template.

Released under the Private Beta License.