Reduce token cost with Metrum AI Router – routing intelligence you can train Metrum AI released an open-source AI Router that routes agent workloads to the cheapest eligible model meeting a defined quality-and-cost contract, citing a committed Harbor run in which Codex on `big-coder` consumed 48,470 Harbor input tokens in a single deterministic job. The router admits token budgets and quotas before any cache-miss upstream call, expires routing evidence via the group `contract` field `max_eval_age_days`, and has its Learned Routing Policy abstain when uncertainty is high. It targets platform teams managing caller keys, model groups, and monthly token budgets across many engineers, such as an illustrative 5,000 engineers sharing routed groups, and requires Go as declared in `go.mod` plus Python 3 for local bootstrap. Agent workloads often make dozens of model calls per task. Paying frontier price for each call is the default path, and token spend shows up after the monthly budget is gone. In one committed Harbor run, Codex on big-coder used 48,470 Harbor input tokens in a single deterministic job docs/harbor-case-study.md /metrum-ai/router/blob/main/docs/harbor-case-study.md . A model group is a quality and cost contract you define. Routing picks the cheapest eligible candidate with evidence of meeting that contract. Evidence expires through the group contract field max eval age days , so stale validation drops targets from eligibility. Learned Routing Policy abstains when uncertainty is high. Token budgets and quotas are admitted before any cache-miss upstream call. See Model Group Contracts model-group-contracts and docs/MODEL GROUP CONTRACTS.md /metrum-ai/router/blob/main/docs/MODEL GROUP CONTRACTS.md . php flowchart LR Caller Caller -- Admit Auth and budget admission Admit -- Eligible Request-shape eligibility Eligible -- LRP Learned Routing Policy LRP -- Upstream Selected upstream Upstream -- Feedback Status usage cost latency Collect Offline collect label train eval -- Bundle Validated policy bundle Bundle -- LRP Who this is for - Platform teams that own caller keys, model groups, and monthly token budgets across many engineers illustrative: 5,000 engineers sharing routed groups . - Teams that mix private OpenAI-compatible targets with hosted providers under one group contract. - Operators willing to spend an afternoon on local bootstrap, then a longer shadow window before enforce. Who this is not for - Teams that want a hosted service operated by someone else. - Teams with a single provider and no private models. - Teams that need the project to guarantee provider uptime, model quality, or compliance outcomes architecture-limitations /metrum-ai/router/blob/main/docs-site/docs/reference/architecture-limitations.md . Community participation is governed by CONTRIBUTING.md /metrum-ai/router/blob/main/CONTRIBUTING.md , the Code of Conduct /metrum-ai/router/blob/main/CODE OF CONDUCT.md , and GOVERNANCE.md /metrum-ai/router/blob/main/GOVERNANCE.md . Questions and bugs follow SUPPORT.md /metrum-ai/router/blob/main/SUPPORT.md ; suspected vulnerabilities must use the private reporting path in SECURITY.md /metrum-ai/router/blob/main/SECURITY.md . Trademark use is governed by TRADEMARKS.md /metrum-ai/router/blob/main/TRADEMARKS.md . Hosted docs: overview https://llm-api.apps.metrum.ai/docs/overview and /docs/ on a running router. Technical brief: docs/solution-brief.md /metrum-ai/router/blob/main/docs/solution-brief.md . Doc ownership map: docs/DOCS MAINTENANCE.md /metrum-ai/router/blob/main/docs/DOCS MAINTENANCE.md . Hosted product docs: overview https://llm-api.apps.metrum.ai/docs/overview also served from a running router at /docs/ . Prerequisites are Go as declared in go.mod and Python 3 for the local bootstrap. An OpenAI API key is enough for one Chat completion. Building and starting the router require no private repository access. git clone https://github.com/metrum-ai/router.git cd router python3 scripts/local dev bootstrap.py --out-dir tmp/local-dev Set OPENAI API KEY in tmp/local-dev/env.json. go run ./cmd/metrum-ai-router --config tmp/local-dev/config.yaml The bootstrap issues a local runtime license.json SKU oss-self-managed and a caller token file tmp/local-dev/router.token . It does not print secrets. Confirm /readyz , then GET /v1/models and one Chat request as in Local Quickstart /metrum-ai/router/blob/main/docs-site/docs/getting-started/local-quickstart.md . config.example.yaml remains the full catalog reference. Do not copy it for a first local trial. For packaged installs, use the public installation guide /metrum-ai/router/blob/main/docs-site/docs/installation/index.md . The documented deployment modes are Linux binary, Docker Compose, and Kubernetes. See Architecture, Platforms, And Limitations /metrum-ai/router/blob/main/docs-site/docs/reference/architecture-limitations.md for the operator-owned boundaries and explicit non-goals. Static weights go stale when models and prices change. Generic learned routers often lack a quality floor, abstention, and operator-owned labels. Without those controls, cheap targets under-shoot acceptance tests and spend stays locked to the expensive anchor for weeks. Learned Routing Policy LRP trains offline on your outcome data. It selects only among router-eligible targets, abstains under uncertainty, and ships behind strategy: external with an explicit external policy.mode promotion path you own. The operator runbook is docs/LEARNED ROUTING POLICY.md /metrum-ai/router/blob/main/docs/LEARNED ROUTING POLICY.md . The service README is services/learned-routing-policy/README.md /metrum-ai/router/blob/main/services/learned-routing-policy/README.md . Synthetic holdout comparison anchor vs routed from docs/evidence/learned-routing-policy/public-training.json /metrum-ai/router/blob/main/docs/evidence/learned-routing-policy/public-training.json seed 42, 800 requests, n=113 holdout . Abstention rate is not in that JSON. Synthetic holdout: anchor vs routed n=113 | Policy | Cost USD | Quality mean | Floor violations | Abstention rate | n | |---|---|---|---|---|---| | always anchor | 0.151829 | 1.0 | 0.0 | not measured | 113 | | lrp | 0.1507895 | 1.0 | 0.0 | not measured | 113 | | always cheapest | 0.0151829 | 0.48672566371681414 | 0.5132743362831859 | not measured | 113 | | bt only | 0.151829 | 1.0 | 0.0 | not measured | 113 | | oracle | 0.1507895 | 1.0 | 0.0 | not measured | 113 | promotable is false. Failed gates cost vs anchor and real data and embedding show promotion gates working as designed on synthetic data. A real run needs real embeddings and real outcomes before any live enforce decision. See learned-routing-case-study /metrum-ai/router/blob/main/docs-site/docs/evaluation/learned-routing-case-study.md and public-training.json /metrum-ai/router/blob/main/docs/evidence/learned-routing-policy/public-training.json . 1. Define acceptance tests and a quality floor for the model group. 2. Collect approved traffic with lrp collect . 3. Label outcomes with lrp fanout and lrp judge . 4. Train and gate with lrp featurize , lrp train , lrp eval , and lrp validate . 5. Run lrp serve with external policy.mode: shadow for at least the documented 24-hour staging shadow window; treat 7-day staging enforce as a separate later gate docs/LEARNED ROUTING POLICY.md /metrum-ai/router/blob/main/docs/LEARNED ROUTING POLICY.md , lrp-train-and-serve /metrum-ai/router/blob/main/docs-site/docs/routing/lrp-train-and-serve.md . 6. Read shadow vs served comparison from decision telemetry and usage reports. 7. Flip staging to external policy.mode: enforce ; rollback is one config change to mode: baseline . Without per-target quality and length models, selection guesses from prices or static weights and under-shoots the floor. LRP trains per-target quality and output-token models offline with LightGBM. Quality scores are isotonic-calibrated. Train and serve share the same feature definitions and embedding artifacts. See docs/LEARNED ROUTING POLICY.md /metrum-ai/router/blob/main/docs/LEARNED ROUTING POLICY.md . Cheapest-first without a floor sends 0.513 of the synthetic holdout under the quality floor always cheapest at n=113 . Among targets the router already marked eligible, LRP picks the cheapest predicted to meet the operator quality floor. If none meet the floor, it picks the highest predicted quality. Unknown prices are not treated as free. Optional per-project floors, upstream latency gates, and cache-aware cost estimates apply at selection. Cache savings are used only when trustworthy cache metadata and a catalog cached-input price are present. See docs/LRP SELECTION CONSTRAINTS.md /metrum-ai/router/blob/main/docs/LRP SELECTION CONSTRAINTS.md . A confident wrong prediction sends a hard request to a weak model. Abstention and ensemble uncertainty cut that path. lrp train --ensemble-size 5 fits a bootstrap ensemble. With uncertainty abstention: true , high calibrated quality standard deviation abstains to abstention anchor or first fallback and labels lrp:uncertain . Thompson exploration exploration strategy: thompson is restricted to exploration projects . PSI and embedding-centroid drift can recommend shadow; the router external policy.mode remains the activation authority. Bradley-Terry cold start injects baseline predictions only for targets already eligible. See docs/LRP UNCERTAINTY.md /metrum-ai/router/blob/main/docs/LRP UNCERTAINTY.md . Mixing host-side execution with LLM scores collapses pass/fail meaning and pollutes training. Deterministic verifiers run only inside the isolated judge worker. LLM judging and human audit are separate outcome classes with different meanings. See docs/LRP VERIFIERS.md /metrum-ai/router/blob/main/docs/LRP VERIFIERS.md and docs/LRP HUMAN JUDGE.md /metrum-ai/router/blob/main/docs/LRP HUMAN JUDGE.md . Flipping learned influence without a reversible mode burns a weekend of incident rollback. Router external policy.mode values internal/router/external strategy.go /metrum-ai/router/blob/main/internal/router/external strategy.go : - baseline : no policy call; first eligible configured target. - shadow : policy is called and recorded; first eligible configured target is served. - enforce : the policy recommendation is served default when mode is omitted . Rollback of learned influence is mode: baseline or restoring prior group config. Operators may require Ed25519-signed bundles before load. See docs/LRP SIGNED BUNDLES.md /metrum-ai/router/blob/main/docs/LRP SIGNED BUNDLES.md . Post-completion external policy.feedback posts request ID, status, usage, cost, latency, TTFB, and selected target. It does not retrain quality models. See internal/router/external policy feedback.go /metrum-ai/router/blob/main/internal/router/external policy feedback.go and docs/EXTERNAL POLICY CONTEXT.md /metrum-ai/router/blob/main/docs/EXTERNAL POLICY CONTEXT.md . Field names match config.example.yaml /metrum-ai/router/blob/main/config.example.yaml and internal/router/config.go /metrum-ai/router/blob/main/internal/router/config.go . Catalog-only LRP groups stay commented until exact-shape validation and protected evaluation pass. models: workload-staging: strategy: external external policy: url: http://127.0.0.1:18093/route allow hosts: 127.0.0.1 mode: shadow timeout ms: 500 max response bytes: 65536 include request: true on error: fail closed headers: X-LRP-Auth: ${LRP POLICY AUTH HEADER} feedback: enabled: true targets: - { provider: example chat, model ref: example-model, weight: 1 } include request: true belongs only in trusted infrastructure. Enable model-group pii filter before sending request content. Loopback URL matches current router egress rules for the LRP sidecar. uv sync --project services/learned-routing-policy --locked make lrp-test make lrp-synthetic-demo Synthetic results do not authorize live promotion. Evidence means checked-in artifacts with dates, gates, and reproducible commands. Synthetic results are published so gates and wiring can be inspected before anyone spends on real embeddings. Figures below do not authorize live promotion. | Claim | Evidence | Source | Date | |---|---|---|---| | Synthetic holdout LRP quality mean 1.0, floor violation 0, cost USD 0.1507895 n=113 | Single synthetic run, seed 42, 800 requests | docs/evidence/learned-routing-policy/public-training.json /metrum-ai/router/blob/main/docs/evidence/learned-routing-policy/public-training.json | 2026-09-09 | | promotable is false; gates cost vs anchor and real data and embedding failed | Same snapshot | public-training.json /metrum-ai/router/blob/main/docs/evidence/learned-routing-policy/public-training.json | 2026-09-09 | | Synthetic embeddings, mock outcomes; real LightGBM and router wiring | Operator and public case study | docs/LEARNED ROUTING POLICY.md /metrum-ai/router/blob/main/docs/LEARNED ROUTING POLICY.md , learned-routing-case-study /metrum-ai/router/blob/main/docs-site/docs/evaluation/learned-routing-case-study.md | 2026-09-09 | | Native shadow: LRP recommended strong ; router served configured-first cheap | Single synthetic inference snapshot | docs-site/docs/routing/lrp-train-and-serve.md /metrum-ai/router/blob/main/docs-site/docs/routing/lrp-train-and-serve.md | 2026-09-09 | | Harbor Codex reward 1, Claude Code reward 0 on big-coder | One deterministic weighted-group run; not LRP | docs/harbor-case-study.md /metrum-ai/router/blob/main/docs/harbor-case-study.md | 2026-06-29 | Counting spend after completion lets concurrent large-cap requests overshoot a monthly cap before anyone sees the invoice. Token-budget admission reserves estimated input tokens, tool/schema payload size, and the requested output cap before a cache-miss upstream call. TPM, daily token, monthly token, and lifetime key budgets include in-flight reservations. Completed requests reconcile to reported usage; failed or canceled requests release the reservation; cache hits do not consume persisted token quota. API Key Flow api-key-flow , internal/router/service.go , internal/router/quota.go Illustrative: 5,000 engineers share one caller project with a $1,500/month token-budget cap. When remaining monthly tokens map to $12 of headroom and the next request reserves an estimated $18 of input-plus-output cap, admission fails with 429 / quota-exhausted or 403 / key-exhausted when the lifetime key is done before any provider call. Usage reports show the rejection without an upstream attempt. Arithmetic: $1,500/month ÷ 5,000 engineers ≈ $0.30/engineer/month of shared headroom if the cap is fully used. callers: - id: example-standard-dev rate: { rpm: 120, tpm: 200000, concurrent: 8 } quota: day: { requests: 5000, tokens: 20000000 } month: { tokens: 400000000 } key: { lifetime tokens: 2000000000, soft pct: 90, on exhaust: disable } Forwarding first and waiting for a provider 400 spends a billed call on an ineligible shape. Illustrative: one 8,000-token tool request rejected locally at $2.50 per million input tokens avoids about $0.02 of upstream spend 8000 / 1e6 × 2.50 . The router filters dialect, per-skin tool support, modalities, structured outputs, reasoning controls, max-token honoring, and payload size before strategy selection. If none remain, it returns 502 no-eligible-target with no upstream attempt. internal/router/service.go , internal/router/request shape eligibility.go Static routing weights do not expire when an eval ages out. After max eval age days: 30 , a target whose validated at is older than 30 days drops from eligibility even if its weight is still positive. An optional model-group contract applies require tags , min eval quality score , min eval pass rate , max eval age days , and allowed validation status . Model Group Contracts model-group-contracts , internal/router/contract.go models: support-chat: strategy: weighted contract: quality floor: require tags: validated min eval quality score: 0.90 min eval pass rate: 0.95 max eval age days: 30 allowed validation status: passed Provider list prices change; historical reports that reprice old rows rewrite past months. Illustrative: a 20% mid-month list-price cut would rescale 15 prior days of USD if rows were not frozen at request time. Each usage row stores input/output price per million, pricing source and date, computed USD, plus routing/policy/pricing fingerprints. Savings baselines are source-dated operator comparisons. Usage Reports usage-reports , internal/router/usage db.go Separate gateways for private GPUs and hosted APIs force callers to pick a model name per request. vLLM, SGLang, and any OpenAI-compatible service register as catalog targets with the same activation rules. One group can weight a private target with a hosted fallback after exact-shape validation, for example 80/20 in the snippet below. docs/SELF HOSTED UPSTREAMS.md /metrum-ai/router/blob/main/docs/SELF HOSTED UPSTREAMS.md models: mixed-hardware: strategy: weighted targets: - { provider: private vllm, model ref: small-local, weight: 80 } - { provider: hosted chat, model ref: fallback, weight: 20 } A single account rate limit turns one 429 into a fleet outage for that model. Illustrative: three provider accounts pooled 1:1:1 absorb three times the per-account RPM before the group is empty. A group can pool the same model across provider accounts or endpoints. Optional provider/model/target shaping can start bounded adaptive cooldowns after classified 429 or quota exhaustion when those knobs are enabled. Fallback runs on retryable classes. Ordinary non-retryable 4xx is not replayed to another provider. API Key Flow api-key-flow , internal/router/upstream shape.go Shipping provider keys or raw prompts into a routing script creates a second secret surface. Illustrative: one leaked key forces rotation across every caller that shared it, often a multi-day outage window. Scripts and external policies receive safe identifiers only. Provider keys are injected server-side. Optional pii filter runs before cache key, routing input, and upstream call redact only , redact and restore , fail on match . Content capture is opt-in, redacted, and AES-256-GCM encrypted. TypeScript Routing typescript-routing , PII Filtering pii-filtering , internal/router/content capture.go A routing dispute without a joinable request id becomes a week of log spelunking. When usage persistence, diagnostics, and optional decision telemetry are enabled, attempts, traces, traffic-shape events, request shapes, translation shapes, sanitized upstream errors, and terminal errors join by request id . /admin/reports/api/request-evidence returns a completeness-scored bundle. Decision telemetry stores scalar buckets only. Pre-selection failures may have no routing-decision row. Usage Reports usage-reports , internal/router/decision telemetry.go Hosted gateways keep prompts and keys on someone else's control plane. This core is Apache-2.0. No license key required by default. Optional signed-license verification is local. Prompts and responses are not retained by default zero-day retention unless you enable capture . The documented policy is not to train on traffic. Linux binary, Compose, and Kubernetes are the documented runtimes. Operators can run on-premises or air-gapped infrastructure. Editions editions , deployment-paths /metrum-ai/router/blob/main/docs-site/docs/licensing/deployment-paths.md , architecture-limitations /metrum-ai/router/blob/main/docs-site/docs/reference/architecture-limitations.md Pointing Claude Code or Codex at a generic OpenAI proxy still fails tool and catalog startup checks. Keep the two smoke commands in CLI Smoke Tests cli-smoke-tests ; Harbor already showed Codex and Claude Code against the same big-coder group in one deterministic pair of jobs docs/harbor-case-study.md /metrum-ai/router/blob/main/docs/harbor-case-study.md . Claude Code uses ANTHROPIC BASE URL and ANTHROPIC AUTH TOKEN . Codex uses /v1/codex/models.json as a caller-filtered Responses catalog. Tool-bearing requests bypass the response cache. Containerized tool variants: coding-agent-clients /metrum-ai/router/blob/main/docs-site/docs/getting-started/coding-agent-clients.md containerized-tool-smokes . From Explicit Limitations And Non-Goals /metrum-ai/router/blob/main/docs-site/docs/reference/architecture-limitations.md explicit-limitations-and-non-goals : - Model-group names and provider availability are deployment-defined; the project does not guarantee access to any provider or model. - Catalog metadata is not capability proof. Tools, images, API bridges, and large request shapes require direct upstream and router-level validation. - The in-process response cache is per process and is cleared by restart. - After the first native SSE event, the HTTP response is committed. A later failure cannot change the caller's 200 , append a reliable error envelope, or fall back to another target; clients must detect a missing terminal event. - SQLite is not a shared multi-writer database and must not back horizontally scaled router replicas. - The project does not provide provider uptime, model-quality, legal, compliance, or support-service guarantees. Remaining bullets live on that page. Does LRP see my prompts? By default the external-policy payload uses derived scalars such as token and tool counts, not prompt text. external policy.include request: true sends request content only to a trusted sidecar; enable pii filter first. External Routing Policy Service external-routing-policy-service What happens when the LRP sidecar is down? Default on error: fail closed returns 502 routing-policy-error and no upstream call. Optional on error: fallback serves the first eligible configured target instead. docs/LEARNED ROUTING POLICY.md /metrum-ai/router/blob/main/docs/LEARNED ROUTING POLICY.md Can I run without LRP? Yes. Use static , weighted , failover , dynamic score , or TypeScript script strategies. LRP is optional behind strategy: external . How is this different from LiteLLM? LiteLLM Server manages a unified interface to 100+ LLMs in OpenAI ChatCompletions/Completions format, plus cost tracking, auth, spend, budgets, and load balancing https://docs.litellm.ai/docs/proxy/quick start https://docs.litellm.ai/docs/proxy/quick start . Does it learn online? No. external policy.feedback posts status, usage, cost, and latency for offline pipelines. It does not retrain quality models internal/router/external policy feedback.go /metrum-ai/router/blob/main/internal/router/external policy feedback.go . What do I need to run on GPU? Measured CPU BGE embedding stage latency is published for the LRP case study. There is no universal GPU requirement for the router or LRP sidecar. Private GPUs remain optional as routing targets learned-routing-case-study /metrum-ai/router/blob/main/docs-site/docs/evaluation/learned-routing-case-study.md , docs/SELF HOSTED UPSTREAMS.md /metrum-ai/router/blob/main/docs/SELF HOSTED UPSTREAMS.md . What does Enterprise edition add? Enterprise is a separate distribution with production validation, named support, signed releases, and related commercial entitlements Editions editions . How do I roll back learned routing? Set external policy.mode: baseline or restore prior group config . That stops policy influence without redeploying binaries internal/router/external strategy.go /metrum-ai/router/blob/main/internal/router/external strategy.go . Where does data live? State, usage, and optional content capture stay on operator-controlled storage. Upstream provider calls and approved judging can still transfer prompts and responses outside your network boundary when you configure those paths. One caller-facing model group can select different upstream models for different request shapes. The committed offline proof seeds observations, disables affinity/cache, posts testdata/proof/trivial.json and testdata/proof/complex.json through dynamic score , then projects /admin/reports/api/request-evidence fields: make proof-routing Expected output generated by the mock harness, not hand-written : { "modelGroup": "proof-routing", "requestedModel": "proof-routing", "selectedCandidateIndex": 0, "selectedModel": "cheap-summarizer", "selectedProvider": "mock", "strategy": "dynamic score", "termNames": "summarize cheap" }, { "modelGroup": "proof-routing", "requestedModel": "proof-routing", "selectedCandidateIndex": 1, "selectedModel": "validated-coder", "selectedProvider": "mock", "strategy": "dynamic score", "termNames": "code validated" } Illustrative curl against a pre-warmed router with decision telemetry and admin drilldown enabled cold-start weights and default affinity can pin both requests to one target; use make proof-routing as the reproducible gate : for f in testdata/proof/trivial.json testdata/proof/complex.json; do RID=$ curl -sS "$ROUTER/v1/chat/completions" \ -H "Authorization: Bearer $ROUTER TOKEN" \ -H 'Content-Type: application/json' -d @"$f" \ -D - -o /dev/null | awk -F': ' '/ Xx -Request-Id/{print $2}' | tr -d '\r' curl -sS "$ROUTER/admin/reports/api/request-evidence?request id=$RID" \ -u "$ADMIN USER:$ADMIN PASS" | jq '{ requestedModel: .request.requestedModel, modelGroup: .request.modelGroup, selectedProvider: .request.provider, selectedModel: .request.model, strategy: .decisionTelemetry.routingDecisions 0 .strategy, selectedCandidateIndex: .decisionTelemetry.routingDecisions 0 .selectedCandidateIndex, termNames: .decisionTelemetry.dynamicScoreTerms | select .selected==true | .termName }' done Former README headings remain reachable below or from this index. The response cache is in-process and not persistent. Configure it with: server: cache: enabled: true max bytes: 134217728 default ttl: 15m default ttl is the maximum duration for an entry. max bytes is the total LRU byte budget. Entries are evicted when expired or when the cache exceeds max bytes . The cache key is based on normalized request semantics, caller/project scope, and selected target: caller id, project, model group, system/input/messages, max tokens, temperature, stop sequences, selected sampling fields top p , seed , frequency penalty , presence penalty , logit bias , reasoning/thinking, previous response id , provider, and target model. It does not use the raw request body, caller request IDs, router request IDs, caller tokens, or provider response IDs. Unknown behavior-changing request fields bypass the cache. Cached payloads are sanitized before storage. The router caches text, model, stop reason, usage, and warnings, but not upstream id , raw provider payloads, or provider-specific metadata. Every caller-facing response gets a fresh router-owned resp ... ID, including cache hits. Cache hits are logged with cache=hit and cached usage for telemetry. They do not call providers and do not increment persisted quota/lifetime token counters. Each request also records a cache snapshot with enabled state, item count, occupied bytes, max bytes, and occupancy percentage so usage reports can show cache hit rate and occupancy over time. The deployment artifact is a binary package. Operators should not need this source tree on the deployment host. make build build Docusaurus docs, then local router/tool binaries with embedded docs make build-go-only local router/tool binaries without rebuilding docs make package linux amd64 and linux arm64 tarballs make package-all same as package make package-docker linux amd64 and linux arm64 Docker packages make package-docker-all same as package-docker Release package targets require a clean git tree and reject -dirty versions. Use ALLOW DIRTY PACKAGE=1 only for local development artifacts that will not be shipped. Package tar creation runs with COPYFILE DISABLE=1 , and package validation rejects macOS AppleDouble metadata, unexpected package files, missing package-safe docs, internal/private markers, raw secrets, local state, and wrong binary architecture. Each tarball contains: bin/metrum-ai-router bin/metrum-ai-router-token-gen bin/metrum-ai-router-usage-report bin/metrum-ai-router-migrate bin/metrum-ai-routerctl bin/metrum-ai-router-fleetctl bin/metrum-ai-router-fleet-sign bin/metrum-ai-router-license bin/metrum-ai-router-customer-lifecycle config/config.example.yaml config/env.example.json config/enterprise-license-skus.json config/scripts/router.ts docs/PACKAGE README.md docs/BINARY INSTALL.md docs/DOCKER COMPOSE INSTALL.md docs/KUBERNETES INSTALL.md docs/PACKAGE VALIDATION.md docs/solution-brief.md docs/LICENSE.md LICENSE NOTICE THIRD PARTY NOTICES.md MODEL LICENSES.md caddy/Caddyfile metrum-ai-router-fleetctl plan|deploy|status|delete|customer is the binary-package-only Fleet lifecycle contract. metrum-ai-router-fleet-sign issues protected intent/admission/delete documents and ships only in binary packages never in customer Docker images . metrum-ai-router-license issues signed runtime-policy license.json files and ships only in binary packages never in runtime Docker images . plan , deploy , and delete consume one mode- 0600 , profile-key-signed, reference-only deployment intent; it contains the protected profile, runtime bundle, and license references without their resolved values. customer create|status|smoke|grant-caller|get-config|list-callers|revoke-caller|update-quota|quota-status|update-config|delete orchestrates disposable SQLite Fleet instances from the packaged binary alone no Python/repo . metrum-ai-routerctl provides customer-local safe config, caller-token-file, license, model, and aggregate-usage operations and is included in Docker images; Fleet binaries are not. The default deployment is SQLite state with one Router container and one replica; it neither provisions nor binds RDS. Dedicated RDS requires an explicit approved database profile manifest branch and a separately signed, scoped external admission that Fleet never creates. After disposable-E2E evidence exists, one qualified maintainer may self-review before a production-like non-production rehearsal. Packages ship canonical metrum-ai-router binaries only; older CLI names are source-only exit-2 notices under cmd/ and are not packaged. Fleet and multi-environment customer CLI guidance lives in docs/MULTI ENVIRONMENT DEPLOYMENT CLI.md /metrum-ai/router/blob/main/docs/MULTI ENVIRONMENT DEPLOYMENT CLI.md and docs/CUSTOMER INSTANCE OPERATIONS RUNBOOK.md /metrum-ai/router/blob/main/docs/CUSTOMER INSTANCE OPERATIONS RUNBOOK.md . Packaged Markdown is copied only from scripts/package docs allowlist.txt . That allowlist is limited to package-safe offline bootstrap docs such as docs/PACKAGE README.md , quick install notes, validation notes, and the package-safe solution brief. Full customer/admin docs are built from docs-site/ and served by the router under /docs/ . Internal source-checkout runbooks such as docs/DOCS MAINTENANCE.md , production runbooks, private host procedures, source-maintenance notes, and security-review working notes are intentionally not packaged. Private production runbooks, private host details, SSH paths, live compose config paths, raw token/provider-key patterns, AppleDouble . metadata, and unexpected source/local-state files are blocked by package validation. The router binary embeds the Docusaurus build output. At runtime, browser access to / redirects to /docs/ ; API and operations routes such as /v1/ , /metrics , /admin/ , /healthz , and /readyz keep precedence. Authenticated admin report assets, when enabled, are embedded separately under /admin/reports/ and are not part of public Docusaurus docs. Public product docs live under docs-site/docs/ and are organized as an operator journey: overview, getting started, installation, self-managed licensing, configuration, routing, providers and models, API compatibility, agents/tools/vision, usage and reports, security and governance, operations, troubleshooting, evaluation, reference, and release/upgrade guidance. Internal operator and maintainer docs live under docs/ . Use docs/DOCS MAINTENANCE.md /metrum-ai/router/blob/main/docs/DOCS MAINTENANCE.md to decide which internal runbook owns each public section and which verification commands to run. Behavior changes affecting routing, auth, models, CLI/API usage, telemetry, deployment, licensing, security, or production operations normally require both public Docusaurus updates and matching internal/operator doc updates. Docker packages contain prebuilt image tarballs plus compose deployment assets: images/metrum-ai-router-