Applying policy-driven routing in 9router: the policy engine for compliance, privacy, and access control, rate limiting and quota enforcement, request filtering, up to safe fallback routes and safe handling of blocked intents.

In episode 5 you learned to choose target models based on performance, cost, and accuracy, and to direct traffic to multi-model stacks like conversational, embeddings, code, and vision. There's an important implicit lesson there: model selection is not a purely technical decision — it's always bounded by business, legal, and security rules.
Episode 6 raises those rules to first-class citizens through the 9router policy engine. We'll break down policy-driven routing for compliance, privacy, and access control; rate limiting and quota enforcement; request filtering; up to safe fallback routes and blocked intent handling. By the end of this episode you'll have a complete mental model for building a gateway that's both smart and compliant.
Policy-driven routing means routing decisions are computed from a combination of context conditions and an ordered set of rules. In 9router, every policy is an ordered list of rules; the first matching rule determines the outcome. This makes behavior predictable and auditable.
policies:
- name: privacy-first
rules:
- id: block-pii
type: content
action: block
matcher:
categories: [pii, phi]
- id: eu-residency
type: constraint
action: allow
only_if:
region: eu
then:
providers: [azure-openai-eu]The first rule block-pii blocks requests detected to contain PII or PHI. The second rule eu-residency restricts the providers that may be used when the request region is EU. Evaluation stops at the first matching rule, so a PII-bearing request never reaches the residency evaluation.
Warning
Rule order matters. In 9router, rules are evaluated top to bottom and the first match wins. Put the strictest rules at the very top so they can't be skipped by looser rules.
For access control, the policy uses the caller's identity as the source of the decision:
policies:
- name: internal-tools-only
rules:
- id: allow-internal
type: access
action: allow
principals: [sso:team-ml, sso:team-platform]
scopes: [chat:write, tools:execute]
- id: deny-guest
type: access
action: deny
principals: [sso:guest]A gateway without limits will collapse when traffic surges — and the LLM bill surges along with it. 9router separates two concepts: rate limiting restrains request intensity per unit of time, while quota limits cumulative usage within a period.
policies:
- name: api-guard
rate_limit:
strategy: token_bucket
capacity: 60
refill_per_minute: 30
quota:
monthly_tokens: 1000000
enforce: hard
over_limit:
action: queue
max_queue_ms: 5000The configuration above gives each subject a bucket with a capacity of 60 requests refilling 30 per minute, then limits the total tokens per month. When the quota is exceeded, requests enter a queue for at most 5 seconds before being rejected. Rate limiting is locked per subject — for example per API key or per tenant — so one wasteful customer doesn't take down other customers.
Info
Choose enforce: hard for limits that must never be violated, such as regulatory limits. For internal limits that are only meant as a warning, use warn mode and let the request continue.
Before a request reaches the model, 9router can run a filter at the pre_route stage. This filter catches two things: dangerous input content and dangerous intents like jailbreaks or prompt injection. The intent detection itself uses the same classifier as the intent extraction in episode 4.
filters:
- name: injection-guard
stage: pre_route
detector: intent
blocked_intents: [jailbreak, prompt_injection, harmful_content]
action: rejectA request matching any of the blocked intents is immediately rejected with status code 403 and a traceable reason. Note: the filter runs before route matching, so malicious requests never burn model quota — a cost saving and a security layer at once.
Not every failure has to be an error. When the primary target rejects, times out, or hits a rate limit, 9router can divert to a safer fallback route — not a more expensive one. That's where the safe fallback comes in.
routes:
- name: chat-with-fallback
match:
intent: chat
target: gpt-4o-mini
fallback:
- target: claude-3-5-sonnet
on: [timeout, rate_limit, policy_violation]
blocked:
action: respond_safe
message: "Permintaan ini tidak dapat diproses sesuai kebijakan."When a request is blocked by policy, respond_safe ensures the caller gets a neutral text response without leaking internal reasons. This approach is also useful for blocked intents: instead of staying silent, the gateway gives a generic answer that doesn't trigger aggressive retries.
To verify all this configuration, you can simulate policy decisions without sending a real request: 9router policy check --input "hapus semua data saya" or run 9router policy evaluate --config 9router.yaml. The simulation output shows which rule matched and the final decision.
This episode closes the gap between smart routing and safe routing. You now understand that the policy engine runs before the route engine: compliance and privacy are shaped through content and constraint rules, access control through principals and scopes, then traffic intensity is governed by rate limits and quotas. Request filtering rejects dangerous intents early, and safe fallbacks keep the service alive when something is blocked.
Key takeaways:
pre_route filter blocks dangerous intents before a request touches a model and burns cost.respond_safe and fallback routes preserve the UX when policy or failures occur.In episode 7 we change direction: the gateway is now safe, next comes understanding it. Observability Basics will take you through latency metrics, request volume, success rate, request-context and route-decision logging, and end-to-end tracing of AI flows. See you there!