The context runtime
Deterministic context assembly — lift, compile, render and a pure exit — plus compatibility profiles, memory classes, the lazy SOP catalog and native-delivery adapters. Adopt in one file, leave in one file.
The context runtime is a pure library that decides, deterministically, what one model
call is allowed to see — and records that decision as a hashable manifest. It is
@ultrarouter/context, shipped in the MIT-licensed SDK as @ultrarouter/sdk/context
(TypeScript) and ultrarouter.context (Python). It reads no clock, no environment, no
network and no random source: the same inputs always produce the same manifest hash.
Two promises make it safe to adopt:
- The gateway is a witness, never an author. When a request carries a manifest
(
ur.manifest), the gateway verifies every declared hash against the bytes it received and stores what it saw; it never adds, drops or reorders a module. The single documented exception is the Flywheel's learned data slot, described at the end of this page. - The exit is a pure function.
toMessages(fromMessages(m))returns a message array byte-identical tom. Nothing in the gateway requires a manifest, so you can adopt the runtime in one file and leave in one file.
Availability: the runtime and the guarantees on this page ship today in the SDK context
subpath. The gateway's ur.manifest witness and the dashboard surfaces are part of a
planned build and are documented separately as they ship. This page documents the pure
library you can use now — it does not describe a hosted prompt service (there is none).
Lift → compile → render → exit
The runtime has four pure entry points. You can use any of them alone.
Lift — label an existing prompt
fromMessages runs the same segmenter the gateway uses over a plain OpenAI
messages array and returns a labelled manifest plus the segmenter's own classification.
No model call, no mutation.
import { fromMessages, toMessages } from '@ultrarouter/sdk/context';
const messages = [
{ role: 'system', content: 'You are a careful refund assistant.' },
{ role: 'user', content: 'Draft a refund reply for order 91237.' },
];
const { manifest, labels } = fromMessages(messages);
// `labels` are the gateway segmenter's kinds (system_instructions, user_question, …)
// and eligibility — the SAME code path the hot path runs, never a copy.
const restored = toMessages(manifest);
// `restored` is byte-identical to `messages`: leaving is a pure function (CTX-50).from ultrarouter.context import from_messages, to_messages
messages = [
{"role": "system", "content": "You are a careful refund assistant."},
{"role": "user", "content": "Draft a refund reply for order 91237."},
]
lifted = from_messages(messages)
restored = to_messages(lifted["manifest"])
assert restored == messages # the exit is pure — adopt in one file, leave in one fileCompile — assemble from modules under a contract
compile(envelope, contract, candidates, profile) runs the normative admission algorithm
and returns { ok, manifest, refusals }. Nothing is truncated: a module either fits the
budget whole or is shed with a recorded reason.
import { compile, ULTRAROUTER_PROFILE } from '@ultrarouter/sdk/context';
const envelope = {
id: 'exec-1',
tenant: 'acme',
stage: 'assistant',
mode: 'live',
asOf: '2026-04-01T00:00:00.000Z',
cutoffs: {},
budgets: { tokens: 25 },
seed: 'seed-1',
pins: [],
releases: {},
permissionEpoch: 'epoch-1',
};
const contract = {
stage: 'assistant',
requiredKinds: [],
optionalKinds: ['procedure', 'fact', 'history'],
forbiddenKinds: [],
budgets: { tokens: 25 },
limits: [],
pinPolicy: 'none',
slots: ['instruction', 'reference', 'exemplar'],
};
const module = (id, kind, memoryClass, trustClass) => ({
id,
version: '1',
kind,
memoryClass,
trustClass,
privacyClass: 'workspace_private',
availability: { from: null, to: null },
contentHash: `sha256:${id}`,
tokenEstimate: 10,
facets: {},
});
const candidates = [
{ module: module('runbook', 'procedure', 'procedural', 'approved_derived') },
{ module: module('pricing', 'fact', 'semantic', 'authoritative') },
{ module: module('thread', 'history', 'episodic', 'approved_derived') },
];
const { ok, manifest, refusals } = compile(envelope, contract, candidates, ULTRAROUTER_PROFILE);
// With a 25-token budget the two most stable modules are admitted (procedural, then
// semantic); the episodic `thread` is shed with a BUDGET_EXCEEDED refusal on the manifest.The Python surface is the same call with snake_case helpers and camelCase dict keys:
from ultrarouter.context import compile, ULTRAROUTER_PROFILE
result = compile(envelope, contract, candidates, ULTRAROUTER_PROFILE)
manifest = result.manifest # None when result.ok is False
refusals = result.refusals # every shed / inadmissible module, with its reasonRender — turn a manifest into provider messages
render(manifest, bodies, serializer?) maps each admitted entry's moduleId to its body
text and emits provider messages. The default serializer concatenates instruction-slot
modules into one system message and fences reference/exemplar modules behind a safety
note so untrusted data can never be read as an instruction.
import { render } from '@ultrarouter/sdk/context';
const bodies = new Map([['runbook', 'Verify the order before offering a refund.']]);
const providerMessages = render(manifest, bodies);Compatibility profiles
compile is parameterised by a profile so one compiler serves every adopter. A profile
carries the ordering rule, the memory-class and refusal vocabularies, the profile-level
limits, the render serializer and the manifest encoder (its serialization and hash subject).
ULTRAROUTER_PROFILEis the default for new adopters. It orders admitted modules byvolatility— stable material first (procedural), volatile last (episodic) — which is prompt-cache friendly. Ordering is part ofmanifestHash, so switching an existing workload's order rule is a model-visible change and goes through the ratchet like any other lever; it is never silent.- Parent profiles (the two sibling engines this runtime was extracted from) declare
their own
selectionorder, memory classes, refusal codes and encoder. Under a parent's profile its fixture corpus compiles byte-identically — same manifest bytes, same refusal codes, same rendered messages. That equality is the parity guarantee; a parent's private fields ride on an opaqueextthat the compiler round-trips but never reads for authority.
Memory classes
Every module declares a memoryClass. The core four are a closed vocabulary; a profile can
extend them with defineMemoryClasses(extra). The compiler treats each class as an opaque
label — it never branches on it — but the default volatility order and shed priority read
it, so the class you assign decides what is kept when the budget tightens.
| Class | Meaning | Volatility (default order) |
|---|---|---|
procedural | how to act — rules, runbooks, SOPs | most stable — kept longest |
semantic | durable facts and definitions | stable |
working | this turn's scratch state | volatile |
episodic | conversation and event history | most volatile — shed first |
Budgets and refusals
Admission sheds whole modules, never truncates. Mandatory pins are admitted first and
are never shed; optional modules are admitted by keep-priority until the token (or count)
budget is reached, and each module that does not fit is recorded as a BUDGET_EXCEEDED
refusal on the manifest rather than being silently dropped.
Refusals are data, not exceptions, so one manifest carries both what it admitted and what it
refused — the ledger the witness and the Flywheel read. The base vocabulary is closed and
includes STAGE_CONTRACT_MISMATCH, REQUIRED_CONTEXT_MISSING, FORBIDDEN_KIND,
AVAILABILITY_REFUSED, LIMIT_EXCEEDED, BUDGET_EXCEEDED and
UNTRUSTED_IN_INSTRUCTION_SLOT. The last one is a fail-closed rule: a module whose
trustClass is untrusted may only land in a reference or exemplar slot; an explicit
attempt to place it in an instruction slot is a refusal, and the gateway rejects the same
placement as an invalid_request.
The SOP catalog: compact when planning, full when executing
A Standard Operating Procedure is authored as prose but prose never executes. A reviewed
SOP compiles to an immutable ExecutableSopSpec — ordered phases with allowed actions,
required policies, budgets, stop conditions and artifact contracts — bound to the exact
reviewed bytes (SOP_REVIEW_STALE if the prose changed after review). Branching is data: a
typed transition language (artifact_present(contract), stop_condition(id),
budget_remaining(kind) > n, and the all(…) / any(…) combinators) whose graph is
checked acyclic at compile time, separately from the dependency graph.
The catalog is what makes "dozens of SOPs that load themselves in branches" affordable: each
SOP publishes a descriptor of ≤ 240 characters (≈ 60 tokens under the repo's chars/4
estimator) — its id, its applicability when, its phase names and what it produces, but not
its body. The model plans against descriptors only; a full body is loaded lazily, at the
gated phase boundary, through a SopLoadPort the planning plane never calls.
import { compileSop, catalog, eligible } from '@ultrarouter/sdk/context';
const result = compileSop({ draft, review }); // { ok, spec } | { ok: false, refusals }
if (result.ok) {
const plan = catalog([result.spec]); // ~60-token descriptors, no bodies loaded
const admissible = eligible([result.spec], runtimeState); // ids whose `when` predicate holds
}The descriptor budget is an input contract, not a hope: an over-budget descriptor is a
DESCRIPTOR_OVER_BUDGET compile refusal that names which limit blew, never a truncation. A
30-SOP catalog stays under ~2,000 planning tokens — the two or three bodies a run actually
reaches are loaded on demand, instead of pasting every procedure into every prompt.
Single use is a property of the store, not a claim of a pure function: authorizePhase
builds an execution-bound receipt {spec_hash, phase, args_hash, exec_id} and asks an
AuthorizationReceiptPort to record it; a reused receipt is refused because the store says
duplicate. Two fallbacks are themselves manifest entries so they stay visible in the
ledger: mandatoryOnlyManifest (drop to the phases that must always run when the planner
bound is exceeded) and gapArtifact (a named "what this run could not observe" value a gate
can read).
The Python subset ships lift, compile, render, exit, profiles and memory classes today; the Python SOP compiler is a filed follow-up. Use the TypeScript subpath for SOP compilation until it lands.
Native-delivery adapters
When a provider ships its own context features, the runtime can deliver a compiled manifest
through them instead of pasting everything inline — the runtime decides what deserves
the window; the provider decides how the window is managed. planNativeDelivery takes a
compiled manifest and a target and decides, entry by entry, which channel carries it. The
decision is a table lookup: no branch names a provider.
v1 supports one provider natively — the Anthropic Messages API — with a frozen mapping:
| Manifest entry | Native channel | Provider feature token |
|---|---|---|
| SOP / tool descriptors | deferred tool (tool search) | tool_search_tool_regex_20251119 |
| episodic modules | server compaction window | compact-2026-01-12 |
| episodic modules | context-editing window | context-management-2025-06-27 |
| procedural modules (app opted in) | memory tool | memory_20250818 |
| turn-scoped operator text | mid-conversation system message | model-gated (no beta header) |
Two rules keep it honest. Absence is the fallback: a (provider, model, feature) triple
with no row in the compatibility table — every non-Anthropic provider, and any Anthropic
request that does not carry the required beta header or opt-in — takes the ONE documented
fallback: the catalog is rendered inline and the manifest records
native_delivery: 'unavailable'. Model gating is data: claude-haiku-4-5 has no memory
tool or mid-conversation-system row, so those entries fall back on that model — a missing
row, not a special case. A refusal (NATIVE_UNAVAILABLE) is emitted only when the contract
required native delivery; a contract that merely prefers it degrades silently but recorded.
Native and inline delivery carry the same set of facts — same module ids, hashes and
classifications — only the channel differs.
Adopt in one file, leave in one file
Because the exit is pure and nothing on the hot path needs a manifest, adopting the runtime is reversible by construction:
- Adopt: import from
@ultrarouter/sdk/context(orultrarouter.context), lift or compile your prompt, and sendrender(...)output to any OpenAI-compatible endpoint.ur context lint(an advisory scanner that never modifies a file) flags oversized inline prompts and untrusted values interpolated into instruction surfaces. - Leave: call
toMessages(manifest)and you are back to a plainmessagesarray, byte-for-byte. A stored manifest plus its bodies round-trips as plain JSON. There is no service to turn off.
The learned slot (Flywheel)
The gateway's zero-authority rule has exactly one exception: the Flywheel's learned data
slot. A consented, holdout-tested learned module — entered as memoryClass: semantic,
trustClass: approved_derived, behind a standing holdout — is the one thing the runtime may
inject on your behalf, and only after a live confirmation has proven it. Nothing else the
gateway sees is ever added to your context. See The Flywheel for
the loop and Holdout and lift for how an improvement is
proven before it can teach.
Next steps
- The five products — where Context sits among Router, Evals, Scoring and Flywheel.
- For agents — the machine-first surfaces and the product-select blocks.
- Record outcomes — feed the ledger the learned slot is built on.
The training-ready export
What the Flywheel export is, what `not_trainable` means, and the consent that gates it. Training on your content is a separate, governed decision — it runs only for your own workspace, never a shared or cross-workspace model.
Prompt caching (cache breakpoints)
Send an Anthropic-style cache_control breakpoint on a content part through the OpenAI-compatible surface; the gateway carries it through and the Anthropic adapter serves the marked prefix as a cached block.