Skip to content

Versioned Routing Policy

Route agent decisions through versioned, time-travelable policies — so you can prove which rule fired on any past date.

routing For: governance & audit

When an agent’s routing choice is governed by a policy that changes over time, recording the choice is not enough. To reconstruct a past decision you need both the full policy that was in effect on the decision date and the specific rule that fired. A single-version policy store cannot answer that once the policy has changed.

The versioned routing layer stores every policy version in a bitemporal store. Publishing a new version is an append, never a mutation, so reading “the policy as of date X” returns exactly the rule set that was active then.

How it works

  1. Define a PolicyVersion — an ordered list of PolicyRules plus a default_choice.
  2. Publish it to a PolicyRegistry with a valid_from date; publishing is an append, so older versions are never overwritten.
  3. Route a context through an AgentRouter — it selects the first matching rule and records matched_rule_id, policy_version, and rationale.
  4. Reconstruct as-of a past date by passing as_of_transaction_time — the registry returns the rule set that was active then.

Install

Terminal window
pip install briefcase-ai[routing]
from briefcase.routing import (
AgentRouter,
PolicyRegistry,
PolicyRule,
PolicyVersion,
)

Route a Ticket

This example routes a support ticket to a queue based on the ticket context, publishes a second policy version, and reconstructs the earlier decision as-of a past date.

from datetime import datetime, timezone
from briefcase.routing import (
AgentRouter,
PolicyRegistry,
PolicyRule,
PolicyVersion,
)
# 1. Define a policy: route a support ticket to a queue by context.
policy_v1 = PolicyVersion(
policy_id="ticket-routing",
version="1.0.0",
description="Route support tickets to a queue.",
rules=[
PolicyRule(
rule_id="enterprise-priority",
condition={"plan": "enterprise", "priority": "high"},
choice="senior-queue",
rationale="High-priority enterprise tickets go to the senior queue.",
),
PolicyRule(
rule_id="routine-lookup",
condition={"category": {"in": ["faq", "status-check"]}},
choice="self-serve",
rationale="Routine lookups are deflected to self-serve.",
),
],
default_choice="standard-queue",
)
# 2. Publish it to a versioned registry (bitemporal-backed by default).
# valid_from is when the policy takes effect; transaction_time is when
# the registry learned of it (defaults to now if omitted).
registry = PolicyRegistry()
registry.publish(
policy_v1,
valid_from=datetime(2026, 1, 1, tzinfo=timezone.utc),
transaction_time=datetime(2026, 1, 1, tzinfo=timezone.utc),
)
# 3. Route a request through the registry.
router = AgentRouter(
registry,
use_case="ticket-routing",
policy_id="ticket-routing",
)
decision = router.route(
{"plan": "enterprise", "priority": "high", "category": "billing"},
evidence_refs=["tkt-9001"],
)
print(decision.selected) # senior-queue
print(decision.policy_version) # 1.0.0
print(decision.matched_rule_id) # enterprise-priority
print(decision.rationale) # High-priority enterprise tickets ...
print(decision.evidence_refs) # ['tkt-9001']
# 4. Publish a newer version. Older decisions still reconstruct correctly.
policy_v2 = PolicyVersion(
policy_id="ticket-routing",
version="2.0.0",
description="De-escalate enterprise high-priority to the standard queue.",
rules=[
PolicyRule(
rule_id="enterprise-priority",
condition={"plan": "enterprise", "priority": "high"},
choice="standard-queue",
rationale="Updated: enterprise high-priority now goes to the standard queue.",
),
],
default_choice="standard-queue",
)
registry.publish(
policy_v2,
valid_from=datetime(2026, 4, 1, tzinfo=timezone.utc),
transaction_time=datetime(2026, 4, 1, tzinfo=timezone.utc),
)
# Current routing uses v2.
current = router.route({"plan": "enterprise", "priority": "high"})
print(current.selected, current.policy_version) # standard-queue 2.0.0
# Reconstruct the decision as it would have been made before v2 was published.
as_of = datetime(2026, 2, 1, tzinfo=timezone.utc)
historical = router.route(
{"plan": "enterprise", "priority": "high"},
as_of_transaction_time=as_of,
)
print(historical.selected, historical.policy_version) # senior-queue 1.0.0
# Inspect the full version history.
print([v.version for v in registry.history("ticket-routing")]) # ['1.0.0', '2.0.0']
flowchart LR
    A["Context"] --> B["AgentRouter.route"]
    B --> C["PolicyRegistry.get(as_of)"]
    C --> D["PolicyVersion.select"]
    D --> E{"rule matches?"}
    E -- yes --> F["matched rule choice"]
    E -- no --> G["default_choice"]
    F & G --> H["AgentRoutingDecision"]

PolicyRule

A PolicyRule is a single “if the context matches this condition, select this choice” rule. The condition is a small dict predicate evaluated against the routing context.

FieldTypeDescription
rule_idstrStable identifier, recorded as matched_rule_id.
conditiondictPredicate evaluated against the context.
choicestrThe choice selected when the condition matches.
rationalestr (optional)Explanation copied into the decision record.

condition supports three forms:

SyntaxMeaning
{"field": value}equality
{"field": {"in": [a, b]}}membership
{"field": {"ne": value}}inequality

All keys in a condition must match (logical AND). Call matches(context) to test a rule directly:

from briefcase.routing import PolicyRule, PolicyVersion
rule = PolicyRule(
rule_id="routine-lookup",
condition={"category": {"in": ["faq", "status-check"]}},
choice="self-serve",
rationale="Routine lookups are deflected to self-serve.",
)
print(rule.matches({"category": "faq"})) # True
print(rule.matches({"category": "billing"})) # False

PolicyVersion

A PolicyVersion is an ordered list of rules. select(context) evaluates the rules in order and returns the first match. If no rule matches and default_choice is set, the default is returned; otherwise choice is None so the caller can fall back to human review.

policy = PolicyVersion(
policy_id="ticket-routing",
version="1.0.0",
rules=[rule],
default_choice="standard-queue",
)
hit = policy.select({"category": "status-check"})
print(hit.choice, hit.matched_rule_id) # self-serve routine-lookup
miss = policy.select({"category": "billing"})
print(miss.choice, miss.matched_rule_id) # standard-queue None

select returns a PolicyEvaluationResult with choice, matched_rule_id, policy_id, policy_version, and rationale.

PolicyRegistry

PolicyRegistry(store=None) is a versioned registry of policies. It defaults to an in-memory bitemporal store; pass any BitemporalStore for durable storage.

MethodReturnsDescription
publish(policy, *, valid_from, transaction_time=None, source=...)BitemporalRecordAppend a new version. valid_from is a datetime.
get(policy_id, *, as_of_transaction_time=None, as_of_valid_time=None)PolicyVersionThe version visible at the as-of point; latest if no clamp is given.
history(policy_id)list[PolicyVersion]Every published version, oldest first.

valid_from records when a policy takes effect in the real world; transaction_time records when the registry learned of it (defaults to now). An as-of read clamps both, which is how a past decision reconstructs the rule set that was active on its decision date.

AgentRouter

AgentRouter joins a registry to a use case and a policy, and produces an AgentRoutingDecision ready to attach to a Briefcase AI decision snapshot.

router = AgentRouter(
registry,
use_case="ticket-routing",
policy_id="ticket-routing",
candidates_provider=None, # optional: derive candidate choices from context
)

route is synchronous — policy evaluation is a pure, in-memory computation against the bitemporal store. This differs from the I/O-bound, asynchronous BaseRouter; the two are independent abstractions.

route parameterDescription
contextThe dict evaluated against the policy rules.
evidence_refsRecord IDs of the bitemporal rows that informed the decision.
as_of_transaction_timeReconstruct the decision using the policy active on a past date.

The returned AgentRoutingDecision carries the full attribution:

FieldDescription
selectedThe chosen option (may be None if no rule and no default).
policy_versionThe version that produced the choice.
matched_rule_idThe rule that fired, or None for the default.
rationaleHuman-readable explanation from the matched rule.
evidence_refsThe evidence record IDs passed to route.
candidatesThe set of choices the policy could have selected from.

Limits

Conditions do equality, membership, and inequality. Nothing else. There is no numeric comparison, so {"confidence": {"gte": 0.85}} is not expressible: any dict value is read as an operator and an unknown one raises KeyError: unsupported operator in condition. Bucket continuous values upstream ({"confidence_band": "high"}) and keep the policy categorical. The same rule means a condition cannot match a nested dict by value.

A missing context field is a non-match, not an error. rule.matches({}) returns False, so a rule that never fires because of a typo in a field name looks exactly like a rule whose condition was not met. Test rules directly with matches when you publish them.

Version strings are labels, not keys. publish accepts the same policy_id and version twice and appends both, so history grows and an as-of read returns whichever was learned later. The registry versions by time, not by the string you typed.

get returns None for an unknown policy id. No exception, so a misspelled policy_id on an AgentRouter surfaces as an unrouted decision rather than a failure.

evidence_refs are recorded, not checked. route copies the ids you pass straight onto the decision without confirming they exist in any store. A bad reference stays invisible until ExaminerBundle.build rejects it.

No rule and no default gives selected=None. That is the design (fall through to human review), and it means the caller must handle None rather than assuming a choice came back.

API reference

briefcase.routing has PolicyRule, PolicyVersion, PolicyRegistry, AgentRouter, and AgentRoutingDecision.

Where this fits

Versioned routing is the Control act made reproducible: it records which rule fired so the decision can later be reconstructed and proven. It rests on the append-only store from the Store & Query act and feeds the tamper-evident bundles of the Prove act.