← Sanctum Router

Implementation and acceptance notes

SANCTUM ROUTER — IMPLEMENTATION AND ACCEPTANCE NOTES
12 October 2026

These notes support the Router product page. They distinguish source implementation,
architectural inference and a historical synthetic acceptance. They do not establish
measured savings, universal provider compatibility, Jev accuracy or current decision-service access.

Router revision: 4a5d40211c9b1c1317575aec5639223694748853
Chat revision: 413ff02d7d2fee8486a984ddeabc6b653f862557

Central example: Chat sends authenticated /v1/route requests. Router checks access,
selects a model and dispatches through provider adapters. The TypeSafe adapter adds a
specialised scoring connection behind that route. This is source-backed architecture;
it is not a production provider-substitution acceptance test. A scoped pilot must test
compatibility with the buyer's application and supported providers.

Version-bound source excerpts

Chat client, Router dispatch and TypeSafe adapter
# E12-chat-client

Revision: 413ff02d7d2fee8486a984ddeabc6b653f862557
File: backend/app/services/router_client.py
Full source SHA-256: b24a7bf3388bb83676c622d92a3f6b9567f9a45efb5ade70da6ead8296f471a7
Extract ranges: [(320, 354), (428, 438), (459, 485)]

```text
320:         "prompt": prompt,
321:         "stream": stream,
322:     }
323:     if context:
324:         payload["context"] = context
325:     if idempotency_key:
326:         payload["idempotency_key"] = idempotency_key
327:     if minimum_quality:
328:         payload["minimum_quality"] = minimum_quality
329:     if cost_ceiling_aud is not None:
330:         payload["cost_ceiling_aud"] = cost_ceiling_aud
331:     if latency_budget_ms is not None:
332:         payload["latency_budget_ms"] = latency_budget_ms
333:     if fallback_policy:
334:         payload["fallback_policy"] = fallback_policy
335:     if allow_paid_fallback:
336:         payload["allow_paid_fallback"] = True
337:     if max_fallback_attempts is not None:
338:         payload["max_fallback_attempts"] = max_fallback_attempts
339:     if messages_raw is not None:
340:         payload["messages"] = messages_raw
341:     else:
342:         # Legacy path: let the Router reconstruct messages from system + prompt
343:         if system_prompt:
344:             payload["system"] = system_prompt
345:     token_limit = max_output_tokens if max_output_tokens is not None else settings.router_max_output_tokens
346:     if token_limit is not None:
347:         payload["max_output_tokens"] = token_limit
348:     if pinned_model:
349:         payload["pinned_model"] = pinned_model
350:         payload["fallback_policy"] = "none"
351:     elif settings.router_default_model:
352:         payload["pinned_model"] = settings.router_default_model
353:     if tools:
354:         payload["tools"] = tools
428:     )
429:     token = await _get_token()
430: 
431:     async def _do_post(token: str) -> tuple[Response, str]:
432:         async with AsyncClient(base_url=settings.router_url, timeout=timeout_seconds) as client:
433:             resp = await client.post(
434:                 "/v1/route",
435:                 json=payload,
436:                 headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
437:             )
438:             return resp, token
459:     router_meta = data.get("metadata")
460:     if not isinstance(router_meta, dict):
461:         router_meta = {}
462: 
463:     result_text = str(data.get("result") or "")
464:     reasoning_text = data.get("reasoning")
465:     model = data.get("model_used") or data.get("model") or router_meta.get("model")
466:     raw_usage = data.get("tokens") or data.get("usage") or router_meta.get("tokens") or router_meta.get("usage")
467:     metadata = {
468:         "result": result_text,
469:         "trace_id": data.get("trace_id"),
470:         "model": model,
471:         "primary_model": data.get("primary_model"),
472:         "pinned": data.get("pinned"),
473:         "fallback_used": data.get("fallback_used"),
474:         "provider": data.get("provider") or router_meta.get("provider"),
475:         "usage": _usage_or_estimate(
476:             raw_usage,
477:             payload=payload,
478:             result=result_text,
479:             reasoning=str(reasoning_text) if reasoning_text else None,
480:             model=model,
481:         ),
482:         "cost": data.get("cost_usd") or data.get("cost") or router_meta.get("cost_usd") or router_meta.get("cost"),
483:         "tool_calls": data.get("tool_calls"),
484:         "reasoning": reasoning_text,
485:         "provider_state": data.get("provider_state"),
```

# E03-request-path

Revision: 4a5d40211c9b1c1317575aec5639223694748853
File: app/api/routes.py
Full source SHA-256: 5cbfa6b3066b88c2f599af455fed1cc471f351aa16ee0860194c6bc0af8ff29b
Extract ranges: [(1491, 1523), (1558, 1584), (1940, 1975), (2100, 2118), (2127, 2179)]

```text
1491: @router.post("/v1/route", tags=["routing"], response_model=RouteResponse)
1492: async def route_request(
1493:     body: RouteRequest,
1494:     request: Request,
1495:     token: TokenPayload = Depends(require_valid_token),  # noqa: B008
1496:     _scope_ok: None = Depends(require_scope(ROUTER_INVOKE_SCOPE)),  # noqa: B008
1497: ) -> RouteResponse | JSONResponse | StreamingResponse:
1498:     # --- Caller identity (needed for error logging) ---
1499:     agent = token.sub
1500:     identity_map = getattr(request.app.state, "identity_map", None)
1501:     display_name = identity_map.lookup(agent) if identity_map else None
1502:     is_admin = ROUTER_ADMIN_SCOPE in token.scopes
1503: 
1504:     # --- Trace ID ---
1505:     trace_id = body.context.trace_id or uuid4()
1506:     session = conversation_session(token.iss, token.sub, body.context.conversation_id)
1507: 
1508:     # --- Resolve app state ---
1509:     catalogue = getattr(request.app.state, "catalogue", None)
1510:     if catalogue is None:
1511:         _log_error_request(
1512:             agent=agent,
1513:             task_type=body.task_type,
1514:             error_type="routing_error",
1515:             error_message="Model catalogue not loaded",
1516:             trace_id=trace_id,
1517:             token_scope=token.scope,
1518:             display_name=display_name,
1519:         )
1520:         return JSONResponse(
1521:             status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
1522:             content=_error_body("routing_error", "Model catalogue not loaded"),
1523:         )
1558:             status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
1559:             content=_error_body(
1560:                 "fx_rate_unavailable",
1561:                 "AUD cost ceiling supplied but FX rate is unavailable",
1562:                 trace_id=trace_id,
1563:             ),
1564:         )
1565: 
1566:     if is_admin:
1567:         effective_ceiling = None
1568: 
1569:     # --- Token estimation ---
1570:     est_input = estimate_input_tokens(body.system, body.prompt)
1571: 
1572:     # --- Candidate filtering ---
1573:     available = frozenset(providers.keys())
1574:     filter_result = filter_candidates(
1575:         catalogue,
1576:         body.task_type,
1577:         caller_scopes=token.scopes,
1578:         minimum_quality=body.minimum_quality,
1579:         effective_cost_ceiling_usd=effective_ceiling,
1580:         latency_budget_ms=resolved.latency_budget_ms,
1581:         estimated_input_tokens=est_input,
1582:         max_output_tokens=resolved.max_output_tokens,
1583:         is_admin=is_admin,
1584:         available_providers=available,
1940:         if isinstance(exc, ProviderRateLimitError):
1941:             return "rate_limit"
1942:         if isinstance(exc, ProviderQuotaExhaustedError):
1943:             return "quota_exhausted"
1944:         return "provider_error"
1945: 
1946:     while True:
1947:         provider_name = current_model.provider
1948:         provider = providers.get(provider_name)
1949: 
1950:         if provider is None:
1951:             fb_reason = "provider_error"
1952:             if not fallback_reason:
1953:                 fallback_reason = fb_reason
1954:             last_error_type = "provider_error"
1955:             last_error_message = f"Provider '{provider_name}' not configured"
1956:             if is_pinned or fallback_attempts >= body.max_fallback_attempts:
1957:                 break
1958:             fb_result = select_fallback(
1959:                 catalogue,
1960:                 current_model,
1961:                 body.task_type,
1962:                 caller_scopes=token.scopes,
1963:                 minimum_quality=body.minimum_quality,
1964:                 effective_cost_ceiling_usd=effective_ceiling,
1965:                 fallback_policy=body.fallback_policy,
1966:                 allow_paid_fallback=body.allow_paid_fallback,
1967:                 max_fallback_attempts=body.max_fallback_attempts - fallback_attempts,
1968:                 estimated_input_tokens=est_input,
1969:                 max_output_tokens=resolved.max_output_tokens,
1970:                 latency_budget_ms=resolved.latency_budget_ms,
1971:                 already_failed=already_failed,
1972:                 available_providers=available,
1973:             )
1974:             if fb_result.exhausted or fb_result.selected is None:
1975:                 break
2100:     response_body = RouteResponse(
2101:         result=provider_response.result,
2102:         provider=provider_response.provider,
2103:         model_used=provider_response.model_used,
2104:         primary_model=primary_model.id,
2105:         pinned=is_pinned,
2106:         fallback_used=fallback_used,
2107:         fallback_reason=fallback_reason,
2108:         cost_usd=actual_cost_usd,
2109:         cost_aud=actual_cost_aud,
2110:         estimated_cost_usd=est_cost_usd,
2111:         estimated_cost_aud=est_cost_aud,
2112:         fx_rate_usd_aud=fx_rate_value,
2113:         fx_rate_date=fx_rate_date,
2114:         latency_ms=provider_response.latency_ms,
2115:         tokens=token_usage,
2116:         trace_id=trace_id,
2117:         status=route_status,
2118:         tool_calls=(
2127:     log_builder = new_log(agent, body.task_type)
2128:     log_builder.trace_id(trace_id)
2129:     log_builder.scopes_held(token.scope)
2130:     if display_name:
2131:         log_builder.agent_display_name(display_name)
2132:     if body.context.agent_asserted:
2133:         log_builder.agent_asserted(body.context.agent_asserted)
2134:     if body.context.workflow:
2135:         log_builder.workflow(body.context.workflow)
2136:     if body.idempotency_key:
2137:         log_builder.idempotency_key(body.idempotency_key)
2138:     log_builder.minimum_quality(body.minimum_quality)
2139:     log_builder.fallback_policy(body.fallback_policy)
2140:     log_builder.provider(provider_response.provider)
2141:     log_builder.primary_provider(primary_model.provider)
2142:     log_builder.model(provider_response.model_used)
2143:     log_builder.primary_model(primary_model.id)
2144:     log_builder.routing_mode("direct" if current_model.routing_mode == "direct" else "zen")
2145:     if provider_response.input_tokens is not None:
2146:         log_builder.input_tokens(provider_response.input_tokens)
2147:     if provider_response.output_tokens is not None:
2148:         log_builder.output_tokens(provider_response.output_tokens)
2149:     if est_cost_usd is not None:
2150:         log_builder.estimated_cost_usd(est_cost_usd)
2151:     if est_cost_aud is not None:
2152:         log_builder.estimated_cost_aud(est_cost_aud)
2153:     if actual_cost_usd is not None:
2154:         log_builder.cost_usd(actual_cost_usd)
2155:     if actual_cost_aud is not None:
2156:         log_builder.cost_aud(actual_cost_aud)
2157:     log_builder.effective_cost_ceiling_usd(effective_ceiling)
2158:     if resolved.cost_ceiling_currency is not None:
2159:         log_builder.cost_ceiling_currency(resolved.cost_ceiling_currency)
2160:     if fx_rate_value is not None:
2161:         log_builder.fx_rate_usd_aud(fx_rate_value)
2162:     if fx_rate_date is not None:
2163:         log_builder.fx_rate_date(fx_rate_date)
2164:     if fx_rate_holder is not None:
2165:         log_builder.fx_rate_source(fx_rate_holder.source)
2166:     log_builder.latency_ms(provider_response.latency_ms)
2167:     log_builder.provider_latency_ms(provider_response.latency_ms)
2168:     log_builder.status(route_status)
2169:     if fallback_used:
2170:         log_builder.fallback_used(True)
2171:     if fallback_reason:
2172:         log_builder.fallback_reason(cast(FallbackReason, fallback_reason))
2173:     if fallback_attempts:
2174:         log_builder.fallback_attempts(fallback_attempts)
2175:     if constraint_violation:
2176:         log_builder.constraint_violation(cast(ConstraintViolation, constraint_violation))
2177:     log_builder.prompt_redacted(body.prompt)
2178:     log_builder.created_at(datetime.now(UTC))
2179:     log_builder.build()
```

# E08-jev-adapter

Revision: 4a5d40211c9b1c1317575aec5639223694748853
File: app/providers/typesafe.py
Full source SHA-256: c8570411929a891e59c593745c0517cfb788b81b6676ecea55921f42936832de
Extract ranges: [(1, 114)]

```text
1: """Chat adapter for TypeSafe Jev.
2: 
3: Jev is an evaluation API, not a chat-completions API. This adapter sends the
4: user text as state, asks one yes/no question, and returns the real score.
5: """
6: 
7: from __future__ import annotations
8: 
9: import time
10: from collections.abc import AsyncIterator
11: from typing import Any
12: 
13: import httpx
14: 
15: from app.providers.base import (
16:     BaseProvider,
17:     ProviderError,
18:     ProviderRateLimitError,
19:     ProviderResponse,
20:     ProviderTimeoutError,
21:     ProviderUnavailableError,
22:     StreamChunk,
23: )
24: 
25: TYPESAFE_ENDPOINT = "https://api.typesafe.ai/v1/systemone"
26: UPSTREAM_MODEL = "jev-latest"
27: 
28: 
29: class TypeSafeChatProvider(BaseProvider):
30:     def __init__(
31:         self,
32:         api_key: str,
33:         *,
34:         client: httpx.AsyncClient | None = None,
35:         endpoint: str = TYPESAFE_ENDPOINT,
36:     ) -> None:
37:         super().__init__(api_key, timeout_ms=30000, max_retries=0)
38:         self._endpoint = endpoint
39:         self._client = client or httpx.AsyncClient(follow_redirects=False)
40:         self._owns_client = client is None
41: 
42:     async def aclose(self) -> None:
43:         if self._owns_client:
44:             await self._client.aclose()
45: 
46:     async def dispatch(
47:         self,
48:         model: str,
49:         system: str | None,
50:         prompt: str,
51:         max_tokens: int,
52:         temperature: float | None,
53:         tools: list[dict[str, Any]] | None = None,
54:         tool_choice: str | dict[str, Any] | None = None,
55:         messages: list[dict[str, Any]] | None = None,
56:         provider_timeout_ms: int | None = None,
57:         provider_state: dict[str, Any] | None = None,
58:     ) -> ProviderResponse:
59:         del model, max_tokens, temperature, tools, tool_choice, provider_state
60:         started = time.monotonic()
61:         state = _state_text(system, prompt, messages)
62:         payload = {
63:             "model": UPSTREAM_MODEL,
64:             "state": state,
65:             "questions": {
66:                 "request": {
67:                     "type": "noul",
68:                     "instructions": "Is the user asking to test or use Jev?",
69:                 }
70:             },
71:         }
72:         try:
73:             response = await self._client.post(
74:                 self._endpoint,
75:                 headers={
76:                     "Authorization": f"Bearer {self.api_key}",
77:                     "Content-Type": "application/json",
78:                 },
79:                 json=payload,
80:                 timeout=self._request_timeout(provider_timeout_ms),
81:             )
82:         except httpx.TimeoutException as exc:
83:             raise ProviderTimeoutError("TypeSafe timed out") from exc
84:         except httpx.TransportError as exc:
85:             raise ProviderUnavailableError("TypeSafe is unavailable") from exc
86:         if response.status_code == 429:
87:             raise ProviderRateLimitError("TypeSafe rate limited the request")
88:         if response.status_code >= 500:
89:             raise ProviderUnavailableError("TypeSafe returned a server error")
90:         if response.status_code >= 400:
91:             raise ProviderError("TypeSafe rejected the request")
92:         try:
93:             body = response.json()
94:             score = float(body["answers"]["request"]["noul"])
95:             usage = body.get("usage") or {}
96:             returned_model = str(body.get("model") or UPSTREAM_MODEL)
97:         except (KeyError, TypeError, ValueError) as exc:
98:             raise ProviderError("TypeSafe returned an unreadable result") from exc
99:         input_tokens = usage.get("input_tokens")
100:         output_tokens = usage.get("output_tokens")
101:         text = (
102:             f"Jev {returned_model} scored this message. "
103:             f"Yes-probability that it asks to test or use Jev: {score:.2f}. "
104:             f"Tokens: {input_tokens} in, {output_tokens} out."
105:         )
106:         return ProviderResponse(
107:             result=text,
108:             provider="typesafe",
109:             model_used=returned_model,
110:             input_tokens=input_tokens if isinstance(input_tokens, int) else None,
111:             output_tokens=output_tokens if isinstance(output_tokens, int) else None,
112:             latency_ms=int((time.monotonic() - started) * 1000),
113:         )
114: 
```

Separate Jev decisions service: historical acceptance

A retained first-party operator record dated 1 October 2026 (SHA-256 827cfe5717a6837d6364cc325dc8a579d423002ad38a4fc117fc00b4be25f7ae) records one successful POST /v1/decisions and GET /v1/decisions/{invocation_id}, both HTTP 200, with full typed result equality. Router revision matches the revision above. That demonstrates the separate deployed request/storage path at that date. It does not establish generic /v1/route provider substitution, routine Chat/Iris adoption, current service enablement, authority to send arbitrary business data or judgment accuracy. The decision service requires dedicated scopes and a subject/use-case/rubric/data-policy grant; consumer configuration and authority must be reviewed separately. Jev's accuracy is unmeasured and its judgments remain advisory. No live model request was made for this page revision.