Projections
Overview
routing.projections is the coordination layer between raw signal detection and final decision matching.
Signals answer "what matched?" Decisions answer "which route should win?" Projections fill the gap in between: "how should several signal results be coordinated into reusable routing facts?"
Use it when you need one of these behaviors:
- resolve one winner inside a competing domain or embedding lane
- combine several weak signals into one continuous routing score
- centralize threshold policy once and reuse the named result across many decisions
What Problem Does It Solve?
Signals are intentionally narrow. A keyword rule, embedding rule, domain classifier, or context detector tells the router one local fact about the request. Decisions are intentionally boolean. They combine those facts into route selection rules.
That leaves a practical gap:
- several domain or embedding signals may match at the same time, but the route only wants one winner
- many weak signals may collectively mean "this request is hard" or "this answer needs verification"
- the same threshold story may need to be reused by many decisions without copying numeric logic everywhere
Without projections, that coordination logic gets pushed into decisions, duplicated across routes, and mixed back into the detector layer. routing.projections keeps that coordination as its own explicit layer.
Relationship to Signals and Decisions
Think of the routing pipeline in three layers:
routing.signalsextracts reusable facts from the request.routing.projectionscoordinates or aggregates those facts.routing.decisionsmatches boolean policy rules to choose a route.
More concretely:
partitionscoordinate existingdomainorembeddingmatches and keep one winnerscoresaggregate matched signals into one numeric valuemappingsturn that numeric value into named projection outputs- decisions continue to reference raw signals with their native types such as
domain,embedding, orkeyword - decisions reference projection outputs only through
type: projection
Two important boundaries from the current implementation:
- decisions do not reference partition names directly
- decisions do not reference score names directly
Only mapping.outputs[*].name becomes a decision-visible projection(...) target.
Runtime Flow
In the current runtime, projections happen after signal extraction and before decision evaluation:
- base signals run under
routing.signals routing.projections.partitionsreduce competingdomainorembeddingmatchesrouting.projections.scorescompute numeric values from matched signals and confidencesrouting.projections.mappingsemit named outputs such asbalance_reasoningorverification_required- decisions combine raw signals plus those named outputs
That is why partitions feel "closer to signals", while mappings feel "closer to decisions".
Current Contract
The repo uses one projection-first naming story across authoring and runtime:
- DSL authoring uses
PROJECTION partition,PROJECTION score, andPROJECTION mapping - canonical runtime config stores the same contract under
routing.projections.partitions,routing.projections.scores, androuting.projections.mappings
The current implementation supports:
- partitions with
exclusiveorsoftmax_exclusive - scores with
method: weighted_sum - mappings with
method: threshold_bands - optional mapping calibration with
method: sigmoid_distance
Workflow
- Define reusable detectors under
routing.signals. - Use Partitions when one domain or embedding family should resolve to one winner before decisions read it.
- Use Scores to combine matched signals into a weighted score.
- Use Mappings to turn that score into named routing bands.
- Reference those bands from
routing.decisions[*].rules.conditions[*]withtype: projection.
Balance Recipe Example
The maintained deploy/recipes/balance.yaml recipe shows the intended pattern:
balance_domain_partitionresolves one winning domain across the maintained routing domainsbalance_intent_partitionresolves one winning embedding intent lanedifficulty_scoreblends context, structure, keyword, embedding, and complexity evidencedifficulty_bandconverts that score intobalance_simple,balance_medium,balance_complex, andbalance_reasoningverification_pressureplusverification_bandproduce reusable verification outputs such asverification_required- decisions such as
premium_legalorreasoning_mathcombine rawdomainmatches with projection outputs
That recipe is important because it shows projections being reused across many routes, not just one toy example.
Canonical Shape
routing:
signals:
embeddings:
- name: technical_support
threshold: 0.75
candidates: ["installation guide", "troubleshooting"]
- name: account_management
threshold: 0.72
candidates: ["billing issue", "subscription change"]
context:
- name: long_context
min_tokens: "4000"
max_tokens: "200000"
projections:
partitions:
- name: support_intents
semantics: exclusive
members: [technical_support, account_management]
default: technical_support
scores:
- name: request_difficulty
method: weighted_sum
inputs:
- type: embedding
name: technical_support
weight: 0.18
value_source: confidence
- type: context
name: long_context
weight: 0.18
mappings:
- name: request_band
source: request_difficulty
method: threshold_bands
outputs:
- name: support_fast
lt: 0.25
- name: support_escalated
gte: 0.25
PROJECTION partition support_intents {
semantics: "exclusive"
members: ["technical_support", "account_management"]
default: "technical_support"
}
PROJECTION score request_difficulty {
method: "weighted_sum"
inputs: [
{ type: "embedding", name: "technical_support", weight: 0.18, value_source: "confidence" },
{ type: "context", name: "long_context", weight: 0.18 }
]
}
PROJECTION mapping request_band {
source: "request_difficulty"
method: "threshold_bands"
outputs: [
{ name: "support_fast", lt: 0.25 },
{ name: "support_escalated", gte: 0.25 }
]
}
When to Use
Use projections when:
- competing domain or embedding lanes should collapse to one winner
- route difficulty or verification pressure is spread across several weak signals
- multiple decisions should share the same tiering logic such as
simple / medium / complex - you want threshold policy in one place instead of copy-pasting it across route rules
When Not to Use
Skip projections when:
- one raw signal already expresses the route condition clearly
- multiple matches should remain independently visible to decisions
- a decision can stay readable with ordinary boolean composition and does not need shared weighted logic
Dashboard
The dashboard exposes the same projection contract directly:
Config -> Projectionsmanages partitions, scores, and mappings in canonical config formConfig -> Decisionscan reference mapping outputs with condition typeprojectionDSL -> VisualshowsProjection Partitions,Projection Scores, andProjection Mappingsas editable entities alongside signals, routes, models, and plugins
For raw import/export, the DSL page still decompiles the current router YAML into routing-only DSL and recompiles the edited DSL back into canonical YAML.
Next Steps
- Read Partitions for exclusive domain or embedding winner selection.
- Read Scores for weighted aggregation over matched signals.
- Read Mappings for named routing bands and
type: projectiondecision references.