ADR 013 - SystemOne (typed decision) capability and the TypeSafe provider
- Status: superseded in part by ADR 017 (public format, capability name)
- Date: 2026-09-26 (amended 2026-09-26: Jev as a reranker)
Context
TypeSafe’s Jev is the first “System One” model: instead of generating text it
answers typed questions about a state with calibrated probabilities. Its
whole API is one endpoint:
POST https://api.typesafe.ai/v1/systemone
Authorization: Bearer <key>
{ "model": "jev-latest",
"state": "<string | object | array>",
"questions": { "<id>": { "type": "noul" | "choice" | "score",
"instructions": ..., "criteria": ... } } }
The response carries one typed answer per question id (a noul probability,
a choice with its distribution and confidence, or a score with its
legend, distribution and confidence), the versioned model that answered,
and usage.{input_tokens, output_tokens}. Pricing is per input token only.
None of LUMEN’s three capabilities fits. Chat is free text; embeddings are vectors; rerank is a single relevance score per document with no notion of named questions, options or levels. Squeezing Jev into one of them would lose the thing that makes it useful (the typed, multi-question answer) while the operator still wants the gateway’s keys, budgets, quotas, token accounting, fallbacks and metrics in front of it.
Decision
-
A fourth first-class capability,
systemone.Capability::SystemOneserializes as"systemone"everywhere a capability string appears (capabilities = [...]in config,GET /v1/models, the Prometheuscapabilitylabel,usage_log.capability, the admin usage filter). The name follows the model class rather than a vendor, so a second System One vendor slots in as another provider kind. -
POST /v1/systemone, wire-compatible with TypeSafe. The request body is TypeSafe’s, unchanged; the response is TypeSafe’s plus, only when the gateway had to estimate, ausage.estimated: trueflag (ADR 003). The TypeSafe SDKs readTYPESAFE_BASE_URL, so pointing it at LUMEN (andTYPESAFE_API_KEYat a LUMEN virtual key) makes an existing Jev client go through the gateway with no code change.GET /v1/modelskeeps LUMEN’s OpenAI shape, so the SDKs’models.list()is the one call that does not work through the gateway; documented, not papered over. -
Validation at the edge, answers passed through. LUMEN forwards a non-2xx upstream as
LM-3003without its body (secrets and upstream detail never reach the client, CLAUDE.md rule 8), which would turn every TypeSafe422into an opaque 502. So the gateway validates the documented request contract itself and answersLM-1001with a precise message: non-emptymodel, astate, a non-emptyquestionsmap (LM-2011when empty, the SystemOne twin ofLM-2010), each question’stypeone ofnoul/choice/scorewith non-nullinstructions,choicecriteria a non-empty map of at most 255 options,scorecriteria an array of at most 10 levels, and no duplicate question ids. Duplicate top-level keys are rejected while parsing, so what is validated is exactly what is forwarded. An unknown questiontypeis rejected: a new TypeSafe question type is a LUMEN upgrade, not a silent passthrough.instructions,criteriaentries andstatestay opaque JSON (strings, objects or arrays are all legal upstream). Unknown top-level request fields and every response field are carried verbatim and in order, so LUMEN never has to understand an answer to serve it. The request body is shared behind anArc, so a retry or fallback attempt costs a refcount, not a copy of the state. -
SystemOneProvidertrait,typesafeprovider kind. One method,evaluate(req, cancel), with the usualCancellationTokencontract. Thetypesafekind defaults its base URL tohttps://api.typesafe.ai(overridable), authenticates with a bearer key, and maps429/529through the shared status classifier (529is a retryable 5xx, so the executor retries and falls back as for any overloaded upstream; aRetry-Afteron a 429 is honoured). Fallback chains, per-model timeouts, retries and circuit breakers apply unchanged, e.g. a pinnedjev-1.13.0falling back tojev-latest. -
Token accounting (ADR 003). Upstream
usage.input_tokensandoutput_tokenswin when reported.usageis parsed leniently: a missing, null or malformed block (or a zero input count) never fails a billed answer, it just falls through to the estimate. In that case (the SDK schema marksusageoptional) the gateway estimates input as the byte heuristic over the raw JSON text ofstateplus every question, which is what TypeSafe ingests, and flags itestimated; output is estimated as zero, since answers are tiny and Jev does not bill them. Admission reserves the same input estimate before the call. Cost uses the existingcost_per_1m_input/cost_per_1m_outputmodel prices (Jev:0.042/0), so hard budgets bite with no new pricing field. -
Privacy.
state, questions and answers are request content: never logged, never inusage_log, never in errors, exactly like prompts.
Consequences
- One more route, one more trait, one more registry map; the request path is
the same shape as
/v1/rerank(resolve chain, admit, execute, settle), so no new latency or locking. - The edge validation duplicates part of TypeSafe’s contract and can drift
from it. Limits are pinned to the documented values (2026-09) and covered by
tests; an upstream loosening shows up as a LUMEN rejection, an upstream
tightening as an
LM-3003. - A per-key or per-tenant choice of rerank converter is NOT part of this
decision; see
docs/design/systemone-rerank-mapping.md.
Amendment (2026-09-26): Jev as a reranker
Superseded in part by ADR 014: the converter is no longer a
[providers.models.rerank]block on atypesafemodel. It is theremapof a target of a rerank virtual model, which also adds thescore,compositeandchoicestrategies. The mapping, packing and accounting below still hold.
A typesafe model that declares rerank is served by a converter,
TypesafeRerankProvider, which implements RerankProvider over the model’s
SystemOneProvider, so the whole /v1/rerank path (validation, ordering,
top_n, document echo, admission, fallbacks) is unchanged.
- Mapping.
state = {"query": <query>}; onenoulquestion per document, id = the document index, with structured instructions{"document": <text>, "question": <converter instructions>}and the converter’scriteria.true/criteria.false.relevance_scoreis the noul. One call carries many documents, so the query is billed once per call, and Jev evaluates questions independently, so documents never see each other. - Converter config. An optional
[providers.models.rerank]block (instructions,criteria.true,criteria.false), each defaulting to a generic relevance question. It is operator-authored, per model id, and rejected at boot on any other kind or capability. Clients cannot supply it. - Packing. At most 100 documents and about 48k estimated tokens per upstream call (under Jev’s 64k), up to 4 calls in flight; a document over about 4,096 estimated tokens is truncated (Cohere’s default), so one document always fits the 32k state-plus-question budget.
- Accounting. The summed upstream
input_tokensis rerankusage.total_tokens(if any call omits usage, the whole count falls back to the ADR 003 estimate). Rerank cost becomes search-unit price pluscost_per_1m_inputon those tokens; models priced only per search are unaffected.