Changelog
Release notes for every version.
All notable changes are documented here. Format follows Keep a Changelog;
versions follow SemVer. Releases are cut by the Prepare release workflow
(scripts/release.py), which turns the Unreleased section into a dated section.
Unreleased#
Added#
SLO-aware routing (the platform plan's open "rolling p50/p90/p99 per endpoint" row)
realtime.LatencyWindow: each target inHealthRegistrykeeps its last 256 observed latencies with nearest-rank p50 / p90 / p99 (health_snapshot()gainslatency_p50_ms,latency_p90_ms,latency_p99_ms; the platform's/api/v1/statsand the Health page show them next to the breaker state).RequestConstraints.preferred_max_latency_ms: a soft latency target.HealthStrategynow scores the observed p90 (once five calls are in the window; EWMA, then the declared latency before that) against the hardmax_latency_ms, else this soft target, elsewith_health(latency_slo_ms=...), so a target with a good mean and a bad tail is penalised without being excluded. Accepted byosr route --constraint preferred_max_latency_ms=..., the MCProutetool,POST /api/v1/routeconstraints and workspace / tenant policy (preferred_max_latency_msinPOLICY_KEYS; the stricter of request and policy wins).
Dashboard chart kit
components/charts: the platform's own chart primitives on top of the time-series ones -RankedBars(horizontal ranking with values),Donut(share with a centre label),Bars,ScatterPlot,Sparkline,Meter(bounded value against a threshold),ShareBar(stacked composition, HTML only) andHeatmap(day x hour density, HTML only);ChartTooltip,ChartLegend,ChartEmpty, aSERIESpalette withseriesColor()and the theme tokens--chart-axis,--chart-grid,--chart-cursor,--chart-other(light and dark).components/dashboard/analytics.tsxrenders the API's breakdowns as charts instead of tables:TargetBreakdown,EndpointShare,TrafficHeatmap,SavingsChart(routed against the baseline per day),SavingsByTarget,PlanMix,DecisionsBreakdown,LatencyHistogram,RouteTraffic,BreakerGrid,StrategyWeights,OutcomesByTarget.StatCardtakes atrend(delta pill with direction) and aninfotooltip. Tests:platform/web/tests/charts.test.tsx.
Notifications and alert delivery
- Alerts are now delivered, not only shown.
platform/api/osr_platform/notifications.py: the API re-evaluates the workspace and deployment alert rules everyOSR_PLATFORM_ALERTS_INTERVAL_S(60 s; one replica through a Redis lease) and turns each condition into a notification episode (opened, escalated, resolved) kept in an inbox with unread counts, published to thealertsKafka topic in cluster mode and delivered to channels: e-mail (platform mailer),httpswebhooks (JSON withX-OSR-Event,X-OSR-Deliveryand an HMACX-OSR-Signature), Slack and Teams incoming webhooks. Channels filter by minimum severity and rule names, keep last delivery / last error, have Send test; failed webhooks are retried on the next rounds (three attempts). EndpointsGET /api/v1/notifications[/unread],POST /api/v1/notifications/read,GET|POST /api/v1/notifications/channels,PATCH|DELETE /api/v1/notifications/channels/{id},POST .../channels/{id}/test,GET /api/v1/notifications/deliveries; operator equivalents under/api/v1/admin/notificationsplusPOST /api/v1/admin/notifications/evaluate. Dashboard page/dashboard/notificationswith a bell and unread badge in the header; console page/admin/notifications.
The platform's own observability
- The platform observes itself; no external metrics, tracing or alerting system is involved (the OTLP bootstrap
and the Prometheus / Grafana / Jaeger compose profile added earlier were removed).
osr_platform.telemetry: aDbEventSinkon the tracer persists every span and event to the platform database from a bounded queue and a daemon writer (never on the request path;OSR_PLATFORM_TELEMETRY_STORE, on by default;OSR_PLATFORM_TELEMETRY_RETENTION_DAYS, 14, purged by the retention sweep), soGET /api/v1/events(since/until,source,persisted,retention_start) andGET /api/v1/trace/{id}(source) survive restarts andGET /api/v1/activitymarks persisted requeststraced.HttpMetrics.serieskeeps per-minute HTTP counters for 24 hours.GET /api/v1/telemetry/series?window=1h|6h|24h|7d|30dbuckets the workspace's metered traffic (requests, failures, p50 / p95, cost, tokens; per target and endpoint) and, withstats, the deployment's HTTP requests, 4xx / 5xx and p50 / p95 / p99.osr_platform.alerts+GET /api/v1/alerts: rules evaluated in-process - budgets at 80 % / exhausted (workspace and tenants), failing requests, and onstatsplans readiness, 5xx rate, latency SLO, open breakers, drift alarms, autopilot errors, a stale SLM, tracing off - each naming the dashboard page that fixes it. Web:AlertsPaneland 24-hour traffic / latency series on the overview, deployment HTTP series with a window selector and the alerts on Health, a 1 h - 7 d window on Events served from the store, trace view labels the source. Tests:platform/api/tests/test_telemetry.py.
Platform services and model providers
- The platform API runs as one process or as several:
docs,rankings,marketplace,providers,onboarding,accounts(sign-in, sessions, workspaces, keys, tenants, policy, governance, usage),billing,admin,mcp(the/mcpModel Context Protocol endpoint),openai(the/v1proxy; streamed completions are relayed through the gateway chunk by chunk) androuting(/api/v1/route, feedback, targets, stats, audit, the trace / event / learning reads of its decisions and the autopilot) each have their own entry point (osr-platform-<name>,python -m osr_platform.services <name>, ports 8091-8101) on the API image (platform/api/osr_platform/services.py). The API is the gateway: withOSR_PLATFORM_<NAME>_URLset it forwards that domain's paths (headers, body,X-Forwarded-For) and marks the answer withX-OSR-Service; unset, it serves the domain in-process./api/v1/info,/status,/estimateand the health / metrics endpoints always stay with the API; every service also serves/healthz,/readyzand/metrics. Each process owns its background work (PlatformContext: the API bootstraps operators and evaluates alerts, the owner ofroutingruns the autopilot and the telemetry store,rankingsrefreshes the reference catalogue,marketplaceseeds the registry).GET /api/v1/info->services,GET /api/v1/admin/services(topology + live health) and the/admin/servicespage; the devcompose.yamlandplatform/docker-compose.ymlstart every service next to the gateway. - Model providers managed at run time:
ProviderStore(tablesproviders,provider_models) withGET|POST /api/v1/admin/providers,GET|PATCH|DELETE /api/v1/admin/providers/{id},POST /api/v1/admin/providers/{id}/check(probes/models, records latency and the upstream model ids) andPUT|DELETE /api/v1/admin/providers/models/{target_id}; presets for OpenAI, Azure, OpenRouter, Anthropic, Mistral, Groq, Together, Fireworks, DeepSeek, Ollama, vLLM, LiteLLM. The mountedOSR_PLATFORM_PROVIDERSfile is imported once and stays merged underneath; every write re-binds the handlers at once and other API replicas re-read the store everyOSR_PLATFORM_PROVIDERS_RELOAD_S(30 s). PublicGET /api/v1/providers: the OpenRouter-style catalogue (provider, upstream model, reference prices, context, health, 7-day traffic per executable target; never credentials) and its page on the site,/providers. Console page/admin/providers. GET /api/v1/onboarding: the getting-started checklist of a workspace (e-mail confirmed, key, first route, first outcome, provider connected, teammate invited) with links and the completing API call; the dashboard overview shows it until every step is done. Signup, verification, password reset and invitation links moved into theonboardingrouter (same paths).
Changed#
Website and platform as two services, one hostname
- The signed-in product now lives under
/platform:/platform/login,/platform/signup, password recovery, invitations and/platform/cli/authorize; the dashboard at/platform/dashboard/...; the operator console at/platform/admin/...; the playground at/platform/playgroundand the publish flow at/platform/marketplace/publish. The website (landing,/docs,/models,/vendors,/rankings,/pricing,/marketplace,/compare,/roi,/estimate) keeps the root. The old paths redirect (308, query string preserved), the OAuth redirect URI registered at providers stays<web>/auth/callback, and links the API generates (emails, invitations, device sign-in, Stripe return, alerthrefs) use the new paths. - Deployment: a third container app,
osr-platform(azd serviceplatform, the web image, internal ingress), serves/platform;osr-webforwards those requests to it (OSR_WEB_PLATFORM_URL,platform/web/src/proxy.ts). A single web process without that variable serves both halves (next dev, Compose, the CI smoke container).
Fixed#
- Azure cluster mode connected to PostgreSQL, Redis and Kafka through
<app>.internal.<domain>names, which resolve to the HTTP ingress and never answer on a TCP port; the API hung insidepsycopg.connectuntil the probes killed it and the platform silently kept serving from the previous SQLite revision.infra/now uses the bare container-app names (osr-postgres:5432,osr-redis:6379,osr-kafka:9092), the database URL carriesconnect_timeout=10(also the default inDatabasefor PostgreSQL URLs without one) and the API and domain services have a Startup probe so a slow first boot is not restarted mid-migration.
1.0.0 - 2026-09-07#
1.0 freezes the public API. Every opensmartroute.* name in tests/public_api.json is stable from here;
removals go through errors.deprecated() for at least one minor first. The release rows of the v1.0
readiness table are met from the repository (python scripts/release.py readiness); the adoption rows -
independent security report, accepted leaderboard listings, two production users - stay open and are
tracked in docs/ROADMAP.md, docs/SECURITY_REVIEW.md, examples/leaderboard/results/README.md and
ADOPTERS.md.
Added#
Routing SLM and datasets
RouterSLM.info(): metadata, shape (encoder, dims, embedder), calibration, size and the fit history of a model without its weights;osr slm infoprints it and the platform'sGET /api/v1/learninguses it.osr collect --file NAME=PATH: load a benchmark you downloaded by hand (.jsonl/.json/.csvin the--presetshape;--tierfolds model columns onto your targets) into the dataset cache - the path for gated RouterBench / RouterEval copies.
Release readiness
scripts/release.py readinessdistinguishes release rows (repository evidence; block a1.xversion incheck) from adoption rows (third-party evidence; tracked and printed, never blocking), and counts the freeze step0.Y.0 -> 1.0.0as a non-breaking release step when the changelog has no removals and the taggedtests/public_api.jsononly grew.
Platform administration and the operator console
- Operators sign in to
/adminwith a username and password (POST /api/v1/admin/auth/loginmints anosr_op_session valid for twelve hours; scrypt password hashes; sign-in attempts rate limited per address).OSR_PLATFORM_ADMIN_USERNAME/OSR_PLATFORM_ADMIN_PASSWORDcreate - or reset - the firstsuperadminat API start-up; the staticOSR_PLATFORM_ADMIN_TOKENkeeps working and counts as a superadmin. Superadmins manage operators (/api/v1/admin/operators); every operator changes their own password and sessions. - Full administration API and web console: deployment overview (
/api/v1/admin/overview), users (search, create with personal workspace / plan / password / first key, detail with workspaces, identities and sessions, rename, disable, reset password, sign out everywhere, delete), workspaces (search, create organizations for an owner email, plan / name / slug / disable / delete, members with roles, API keys, tenants with validated configuration, policy, SSO and usage), every tenant across workspaces, plans. Pages/admin,/admin/users[/{id}],/admin/workspaces[/{id}],/admin/tenants,/admin/operators,/admin/login. Every mutation is written to the access log asoperator:<username>and published as anadminevent. - Email + password for workspace users:
POST /api/v1/signupaccepts apassword(and then also returns a browser session),POST /api/v1/auth/password/loginsigns in by email,POST|DELETE /api/v1/auth/passwordset, change or remove it (has_passwordon the user);/login,/signupand/dashboard/accounthave the forms.OSR_PLATFORM_PASSWORD_LOGIN=falseturns it off.
Self-hosted cluster: PostgreSQL, Redis, Kafka as containers
-
OSR_PLATFORM_DATABASE_URL=postgresql://...runs the platform store on PostgreSQL throughpsycopg(the same SQL is written once; the backend rewrites placeholders,INSERT OR IGNORE,REAL/identity columns); the whole test suite passes against PostgreSQL (OSR_TEST_DATABASE_URL). SQLite stays the single-replica default. -
OSR_PLATFORM_REDIS_URLshares rate-limit windows between replicas (atomic sliding window in Redis, falls back to per-replica limiting when Redis is unreachable) and persists the enterprise learners' state in Redis instead of the state directory. -
OSR_PLATFORM_KAFKA_BOOTSTRAPpublishesusage,feedbackandadminevents as JSON to Kafka topicsosr.<name>(any Kafka-protocol broker; never prompt text; broker outages drop events instead of failing requests).GET /readyzreportsdatabase_dialect,redisandevents;GET /api/v1/infoand/adminreportstorage({database, clustered, redis, events}). -
platform/docker-compose.yml: the complete platform on any Docker host with PostgreSQL, Redis, a single-node KRaft Kafka broker, the API and the web app - no cloud service involved.infra/:OSR_PLATFORM_CLUSTER_MODE(default true) addsosr-postgres,osr-redisandosr-kafkacontainer apps with internal TCP ingress and their own Azure Files shares, and scalesosr-apitoOSR_PLATFORM_API_MAX_REPLICASreplicas; the API image ships thepsycopg,redisandkafka-pythonclients (osr-platform-api[cluster]). -
The routing SLM as its own service:
osr-platform-slm(osr_platform.slm_service, the API image with another entry point;slmin the Compose stack,osr-slmon Container Apps) consumes thetrainingtopic (request id + prompt, published by the API whenOSR_PLATFORM_TRAINING_EVENTS=true) andfeedback, runs theSelfImproverchampion/challenger cycle on a schedule or on demand and writes promotions to/data/autopilot/slm.json; API replicas reload that file everyOSR_PLATFORM_SLM_RELOAD_Sseconds (and serve it even without a mounted bundle). Operator endpointsGET /api/v1/admin/slm,/slm/reports,POST /slm/cycle,/slm/predictproxy to the service (OSR_PLATFORM_SLM_URL); the console page/admin/slmshows model, evidence, cycles and a prompt probe. -
Microservice deployment of the platform:
platform/docker-compose.ymlrunsdocs,rankings,marketplace,providers,onboardingandslmas their own containers (python -m osr_platform.services <name>) behind the API gateway (OSR_PLATFORM_<NAME>_URL, answers carryX-OSR-Service); Container Apps cluster mode adds the internal appsosr-docs,osr-rankings,osr-marketplace,osr-providers,osr-onboarding(HTTP ingress, 1-3 replicas, shared configuration and/datashare) and pointsosr-apiat them. The operator console page/admin/servicesshows the topology with a live health probe per remote service; CI's cluster smoke asserts each domain is served by its own container. -
compose.yaml: the whole hosted platform on a workstation withdocker compose up --watch- development imagesplatform/api/Dockerfile.dev(editable installs, uvicorn--reload) andplatform/web/Dockerfile.dev(next dev), Compose Watch syncingsrc/,platform/,docs/,examples/and the skills into the running containers so edits show immediately, a persistent data volume, and an optionalllmprofile that starts Ollama withdeploy/compose/providers.ollama.yamlso chat completions execute end to end without cloud keys. -
python scripts/release.py readiness: the v1.0 readiness table ofdocs/ROADMAP.mdcomputed from the repository - frozen API snapshot and test, performance envelope, reference deployments, review pack and CI safety suite, published leaderboard run, two consecutive non-breaking minor steps (changelog sections without removals, verified against the taggedtests/public_api.jsonwhen both tags exist), independent report rows indocs/SECURITY_REVIEW.md,acceptedrows under Listings inexamples/leaderboard/results/README.mdand Production users rows in the newADOPTERS.md.--requireexits 1 while a row is open andrelease.py checkapplies the same rule to any1.xversion, so the Release workflow cannot tag 1.0.0 before the evidence exists; the workflow prints the table in its job summary. -
Tests for the OpenTelemetry bridge (
OpenTelemetrySinkdriven through a fakeopentelemetryAPI: spans, nesting through the context,traceparentparent, events, counters, duration histogram, error status, missing-extra error). -
Documentation trust gates:
tests/test_docs_claims.pyfails when any Markdown source names anosrcommand or flag the CLI parser does not have, or anOSR_*variable nothing in the code or deployment configuration reads;platform/api/tests/test_platform_docs_claims.pyfails when a documented HTTP endpoint is not a route of the platform app (method included), a documented/dashboard/<page>has no page, a dashboard page or nav entry is undocumented, a user-facingdocs/*.mdis missing from the site catalogue, or a CHANGELOG section lacks a date, body or compare link. Historical tagsv0.2.0,v0.3.0,v0.4.0mark the commits that carried those versions so the compare links and the readiness snapshot check resolve.
Changed#
- Marketing and docs honesty: the editions matrix credits the community edition with the guard, PII
redaction, metrics and tracing it actually runs; the SLM showcase caveats the 0.94 figure as a
single-source number; the pricing page labels the
ssofeature; the hero shows the version only when the API reports it; the Learning page points operators atPOST /api/v1/admin/autopilot/cycleinstead of a button that could only fail;platform/README.mddocumentsMODELS_REFRESH,REGISTRY_AUTO_PUBLISHandREGISTRY_SEED; the Helm chart and deploy guide state that autopilot promotion is per pod; RESEARCH.md reflects the gated RouterBench / RouterEval sources.
0.5.0 - 2026-09-06#
Added#
Roadmap: everything 1.0 needs from the repository
examples/leaderboard/results/: the published 0.5.0 benchmark run made with the leaderboard recipe (SLM trained on Arena-55k + RouteLLM battles, seed 0; MT-Bench human, PPE human and WebDev Arena held out at the suite level,--limit 1000): one JSON per suite for the learned router (--baselines --calibration --robustness) and for the declarative router, plus a README with the data table, the SLM SHA-256 and the result table (learned 0.52-0.57 vs declarative 0.19-0.42 and static task table 0.54-0.67; paraphrase 0.88-1.00; held-out isotonic ECE 0.03-0.05). Published on the documentation site as/docs/leaderboard-results.docs/SECURITY_REVIEW.md: the external security review pack - engagement terms, system and trust boundaries, the threat-model reference, an evidence table with a reproduction command per row, nine ordered reviewer questions, known gaps that need not be re-reported and the review log where a third-party report is linked.SECURITY.mdsupported versions updated to 0.5.x / 0.4.x.docs/ROADMAP.md: v0.9 is Complete (its exit criterion now names the published review pack; the independent report moved to the v1.0 readiness table as the adoption item it is) and v1.0 has an explicit readiness table: five repository items met, one of two stable minors done, three adoption rows open (independent report, accepted leaderboard listings, two production users).
Added#
Hosted platform: billing and SSO as deployment inputs
infra/main.bicepacceptsstripeSecret,stripeWebhookSecret,stripePrices,ssoProvidersandmetricsPublic(azdOSR_PLATFORM_STRIPE_SECRET,_STRIPE_WEBHOOK_SECRET,_STRIPE_PRICES,_SSO_PROVIDERS,_METRICS_PUBLIC; GitHub secrets / variables of the same names in the deploy job). Container secrets are added only when set, so a provision without them changes nothing; with the Stripe values the hosted pricing page goes to Checkout instead of the operator instructions.GET /metricson the Azure deployment now requiresX-Admin-Tokenby default.platform/api/tests/test_docs_links.pychecks that every/docs/<slug>#anchorlink in the web app and everyfile.md#anchorlink between the Markdown sources points at an existing heading (the site'srehype-slugids). The container smoke in.github/workflows/platform.ymlnow also requests/models,/rankings,/marketplace,/playground,/estimate,/roi,/mcp,/login,/signup,/cli/authorize,/docs/REFERENCE,/sitemap.xml,/openapi.jsonand/api/v1/status.- The web app has a unit test suite (
platform/web/tests/*.test.ts, vitest,npm test; part ofnpm run checkand therefore of the CI web job): the documentation catalogue against the content snapshot, the Markdown renderer on the real documents (heading ids, link rewriting, Shiki, skill frontmatter), changelog version parsing, the/api//v1proxy (forwarded headers, 304/204, 502),pageMetadataand JSON-LD builders and the formatting helpers. - Browser end-to-end journey for the hosted web app (
platform/web/e2e/customer-journey.spec.ts, Playwright,npm run e2e; the CI web job runs it against the standalone build and a throw-away platform API): sign up in the UI and read the one-time key, dashboard overview, every sidebar page, a playground decision and the free-plan upgrade hint, key create and revoke, the activity row, sign-out and the login redirect, a rejected key, and the public pages with live data. The dashboard sidebar is anavigationlandmark ("Dashboard").
Fixed#
osr login --url <server> --token osr_local_...printed "Signed in to URL as URL": a self-hostedosr servetoken has no account behind it, so the message now names the URL once ("Signed in to URL (self-hosted edition)").- Web: the dashboard sidebar never listed Governance, Events, Learning and Health (the pages existed and were in
dashboardNav, but not in the sidebar's section map); they are now an "Operate" section, and any future nav entry without a section renders under "More" instead of disappearing. The section label reads "Organization" like the rest of the site. - Web: on 1024-1279 px viewports the header's "Compare" link rendered over the search box; the wide search box now appears only where it fits (md-lg without the desktop nav, xl and up with it) and the icon button elsewhere.
- Web: documentation sidebar entries were hard-clipped mid-word ("Cost estimates and the MCP se") because Radix ScrollArea lays its content out as a table; the viewport content is now block-level and long titles wrap.
- Web: the support and privacy pages linked
/docs/PLATFORM#13-errorsand#12-privacy-and-data-handling, two sections behind the current numbering (#15-errors,#14-privacy-and-data-handling)./dashboard/billingpoints at support when self-serve billing is disabled instead of only showing the operator command. platform/README.md:OSR_PLATFORM_SSO_PROVIDERSis a JSON map keyed by provider id, not a list.
Added#
Marketplace: the public skills ecosystem
osr_platform.harvestimports the Agent Skills ecosystem and the official MCP registry into the marketplace: everySKILL.mdin each repository on the skills.sh leaderboard, GitHub code search for the long tail (token required), and the latest active version of every MCP server as atoollisting with its remote endpoints. Rows become OCM manifests withmetadata.source(provider, repository, path, ref, url), the upstream owner as publisher, the author's licence and an install command in the readme; the listing page shows a Source card that links back.platform/api/scripts/harvest_skills.py collect|stats|publishruns it (resumable JSONL cache, batches through the new admin endpointsGET|POST /api/v1/admin/registry/import, or straight into SQLite). Marketplace facets are cached for 60 s and the index paginates with a window (the hosted catalogue now has 140 000+ listings). Every listing payload carries a compactsource(provider, url, repository) and cards show a provenance badge. The catalogue keeps itself current from inside the platform process:OSR_PLATFORM_MARKETPLACE_REFRESH_S(weekly on Azure) harvests what is new upstream and publishes the slugs the store lacks in small batches (harvest.MarketplaceRefresher; status underGET /api/v1/admin/registry/import,POST /api/v1/admin/registry/import/refreshruns it now). From a machine,publish --only-newasksPOST /api/v1/admin/registry/import/checkwhich slugs exist and sends only the rest. Browsing 100k+ listings stays fast: covering indexes for facets, search counts and the popular sort, cached stats and a start-up prewarm.docs/MARKETPLACE.md"Imported catalogue".
Roadmap exit criteria: v0.5 and v0.6 closed
eval.criteria.effort_token_savings()andsynthetic_effort_rows()measure the effort-routing half of the v0.5 exit criterion: a reasoning model exposed throughexpand_elastic()aseffort="low"/effort="high"siblings, routed by the realRouterwithEffortStrategy+TokenBudgetStrategy, spends 61.6 % of the always-think tokens at equal quality (2 000 rows).EvalRow.tokens(per-target tokens spent) is the new dataset field andosr eval DATASET --effortruns the same comparison on rows collected from live thinking / non-thinking pairs.eval.agentic(AgentTask,load_agentic_tasks(),synthetic_agentic_tasks(),task_routing_frontier()) measures the tau-bench half of the v0.6 exit criterion: multi-step tasks with a per-agent per-step success / latency table are replayed throughProgressRouter+TaskCreditwithCallableHarnessagents and compared with every always-use-agent-X policy on common random numbers; routed accuracy 0.987 vs 0.977 for the best single agent at 45.4 % of its latency (1 000 tasks).osr eval TASKS.jsonl --agentic [--retries N]runs it on a real table.scripts/exit_criteria.pyandcriteria.run_all()include both; 8/8 criteria met.docs/ROADMAP.mdmarks v0.5-v0.8 Complete; v0.9 closes with the review pack and v1.0 gets its readiness table (see above).- v0.4 public-suite measurement published in
docs/ROADMAP.md: on the held-out MT-Bench / PPE / WebDev Arena human-preference suites relabelled to three tiers, the learned router beats the declarative router by 10-14 pp and every naive baseline, but not the always-best-single-tier task table (weak pairwise labels, noise floor not measurable on single-sample rows); ECE <= 0.10 holds only after fitting a calibrator. calibration_report()returnsheld_out: aTemperatureScalerand anIsotonicCalibratorfitted on the even rows and scored on the odd rows (raw vs temperature vs isotonic ECE / Brier), soosr eval --calibrationshows what a fitted calibrator would achieve out of sample instead of only the raw ECE.examples/leaderboard/: the three-tier catalogue (targets.yaml) and the collect / train / evaluate recipe behind the v0.4 table, i.e. the reproducible config the v1.0 leaderboard submissions link to; published on the documentation site as "Benchmarks and leaderboard recipe" (/docs/leaderboard, Python SDK section).
Changed#
Web site redesign
- The hosted web app has a light, editorial design: Inter for text, Instrument Serif for display titles (marketing,
docs and auth pages), Geist Mono for code and numerals, Quicksand kept for the wordmark; a warm neutral palette
(white pages, hairline rules, ink buttons and links) with the brand hues reserved for the mark, charts and target
kinds. The landing page has a new section structure and copy (hero with the live ledger of most-routed targets,
vendor strip, six routing principles, how it works, featured models, model landscape, code, live catalogue,
editions, research, get started) while keeping every live data feed. Marketing pages, the light footer, the auth
pages, the dashboard shell and the documentation shell (sidebar, topbar, titles) follow the same system; the
documentation prose itself is unchanged.
docs/BRAND.mdrecords the website palette and typography. - The site header dropdown menus render in the shared centered viewport (they previously overflowed the right
viewport edge and flickered on hover). The landing page gains a live usage band (tokens routed all time and in
the last 24 hours, requests, success rate, models, accounts), a value section with concrete savings / audit /
cost numbers, per-industry routing examples and a real healthcare
rules.yaml, and restrained motion (scroll reveals, animated counters, a vendor-logo marquee, all disabled underprefers-reduced-motion). A later typography pass removed the italic mid-headline pivot from every display heading, the decorative hero glow and the card tilt. - Vendor icon coverage matches the live catalogue (Kwaipilot, Meituan LongCat, IBM Granite added; the Moonshot AI mark switched to the visible monochrome variant) and curated vendor display names win over harvested ones. The hero prompt examples are chip pills, a floating back-to-top button appears on every page, the rankings page shows the daily per-target traffic and vendor/domain share charts, and alert notices flow inline text correctly.
- A dark landing-page band presents the routing SLM: the collect / train / eval / serve pipeline, the measured holdout numbers, and the autopilot, champion-challenger and distillation loops.
Added#
Account lifecycle end to end
- Anyone can sign up with any email address and a password, and recover the account:
POST /api/v1/auth/password/forgotemails a single-use, one-hour reset link (always202, never reveals whether an address exists) andPOST /api/v1/auth/password/resetsets the new password, revokes every session, confirms the address and signs the person in. Web pages/forgot-password,/reset-passwordand a forgot password? link on/login. - Email confirmation: signup sends a link to
/verify-email; SSO profiles with a verified address, accepted invitations and password resets confirm it too.email_verifiedandlast_login_aton the user object; the account page shows a banner with Send again (POST /api/v1/auth/verify,POST /api/v1/auth/verify/send). - Self-service deletion (
POST /api/v1/me/deletewith the email typed and the password): sessions, identities and memberships go, solo workspaces are disabled with their keys, an organization with other members needs another owner first; a notice is emailed. Delete my account on/dashboard/account. - Invitations are emailed to the invitee (the link is still returned once to the inviter); a removed member's sessions on that workspace are revoked.
- Account audit log (
audit_logtable): signups, sign-ins and failed attempts, password and email changes, invitations, joins, role changes, removals, deletions and operator actions, mirrored on theadminevent topic.GET /api/v1/me/audit(own history, shown on the account page),GET /api/v1/workspace/audit(admins),GET /api/v1/admin/audit-logand the/admin/auditconsole page. - Outbound email (
osr_platform/mail.py): standard-library SMTP fromOSR_PLATFORM_SMTP_URL(smtp://STARTTLS orsmtps://) withOSR_PLATFORM_MAIL_FROM; every message is also kept in an outbox, so without a mail server operators hand the links over from/admin/mail(GET /api/v1/admin/mail[/{id}]).mailinGET /api/v1/info; Bicep/azd parameters and the deploy workflow carry the two variables. - Operator console per user: Send reset link (
POST /api/v1/admin/users/{id}/reset-link, link returned for support), Resend confirmation / Mark verified (POST .../verify-link,PATCHwithemail_verified), lifecycle history (GET .../audit), last sign-in; users created without a password get a reset link to choose one.
Single sign-on configuration from the CLI
scripts/sso.ps1configures the hosted platform's sign-in providers end to end:microsoftcreates the Microsoft Entra app registration withaz(work/school and personal accounts,<WEB_URL>/auth/callbackredirect, Graphopenid email profile User.Read, service principal, two-year client secret),google/github/gitlabwalk through the provider consoles and prompt for the client pair (no API exists for those OAuth clients),show,remove,push(GitHub Actions secret) andapply(azd provisionkeeping the running images, then verifiesGET /api/v1/auth/providers). Secrets are never printed.- JSON-valued deployment inputs are now base64-encoded parameters (
OSR_PLATFORM_SSO_PROVIDERS_B64,OSR_PLATFORM_STRIPE_PRICES_B64, decoded ininfra/main.bicep):azdsubstitutes them textually intomain.parameters.json, so a raw JSON value made every provision fail.
Web site analytics and search optimisation
- Google Analytics 4 on the public pages of the hosted web app with Consent Mode v2: a consent banner (Accept /
Decline, stored in
localStorageasosr-consent, changeable on the privacy page), denied defaults until the visitor accepts, truncated IPs, onepage_viewper client-side navigation andsign_up/loginevents as the conversions for Google Ads. The tag never loads under/dashboardand is absent when the build has noNEXT_PUBLIC_GA_MEASUREMENT_ID(azd:OSR_GA_MEASUREMENT_ID; optionalOSR_GOOGLE_ADS_ID,OSR_GOOGLE_SITE_VERIFICATION,OSR_BING_SITE_VERIFICATION). - Every public page now ships a canonical URL, keywords, Open Graph and Twitter card (
pageMetadatainplatform/web/src/lib/seo.ts); the landing page has a descriptive title, and the model, marketplace, comparison and documentation pages have per-entity titles and descriptions. schema.org JSON-LD:OrganizationandWebSitesite-wide,SoftwareApplicationfor the product and for each marketplace listing (with ratings),Productoffers andFAQPageon pricing,BreadcrumbListandTechArticleon documentation, model and comparison pages.robots.txtblocks the dashboard, proxies, one-time links and query-string variants; the sitemap lists canonical paths only. The privacy page and the platform guide describe the analytics tag. - Search and social distribution end to end: generated Open Graph cards for every page (
/og?title=..., brand typeface, used automatically bypageMetadata), programmatic vendor hubs (/vendorsand/vendors/<vendor>with per-vendor prices, context windows, benchmarks and FAQ), a/compareindex for the alternatives pages,/llms.txtfor AI assistants, an Atom release feed at/feed.xml(advertised on every page), aSearchActionon theWebSiteschema andItemListschema on hub pages. IndexNow: the web app servesINDEXNOW_KEYat/indexnow.txt(azd:OSR_INDEXNOW_KEY) and the platform workflow submits the sitemap to Bing/Yandex after each deploy (platform/web/scripts/indexnow.mjs). Analytics: Core Web Vitals reported to GA4, outbound-link anddata-trackcall-to-action events,copy_codeon install snippets and Google Ads conversion labels per event (OSR_GOOGLE_ADS_CONVERSIONS, e.g.sign_up=AW-123/label). Vendors and Compare join the header, footer and command palette.
Added#
CLI installers and sign-in
- Native installers for the
osrcommand:curl -LsSf https://opensmartroute.ai/install.sh | sh(Linux, macOS) andirm https://opensmartroute.ai/install.ps1 | iex(Windows). Both pickuv tool install,pipxor a private venv (never the system Python), bootstrap uv when nothing else is available, honourOSR_VERSION,OSR_EXTRAS,OSR_INSTALLERandOSR_NO_MODIFY_PATH, putosron PATH and verify withosr --version. The scripts live at the repository root, ship in the sdist and with every GitHub release, and the web app serves them at/install.shand/install.ps1;osr --versionprints the SDK version. osr loginsigns in to the hosted platform (community or enterprise edition) with the OAuth 2.0 device authorization grant (RFC 8628): the CLI prints a short code, opens/cli/authorize, the user approves in the browser and the platform mints a workspace API key for that machine.osr login --token/--with-tokenstore a pasted key or a self-hosted server token instead;--urland--profilekeep several deployments side by side.osr whoami,osr logout [--all],osr token generate|create|list|revokeandosr mcp --remote(bridge to the signed-in workspace without copying the key) complete the set. Credentials are stored per profile in~/.config/opensmartroute/credentials.json(%APPDATA%\opensmartrouteon Windows,OSR_CONFIG_DIR); flags, thenOSR_API_URL/OSR_API_KEY, then the saved profile decide which one applies.opensmartroute.credentials:config_dir,CredentialStore,Credential,PlatformClient,device_login,whoami,generate_token,token_kind,redact- stdlib only, injectable transport.brandinggainsWEBSITE,REPOSITORY,INSTALL_SCRIPT_SH|PS1,API_URL_ENV,API_KEY_ENV,CONFIG_DIR_ENV,TOKEN_PREFIX,LOCAL_TOKEN_PREFIXandplatform_url();errors.AuthenticationError(OSR_AUTH).- Self-hosted
osr servecan now require access tokens:serve --token T(repeatable),--generate-token(prints anosr_local_...token once with the matchingosr logincommand),--require-auth, orServerSettings(OSR_SERVER_AUTH_TOKENS,OSR_SERVER_AUTH_TOKENS_FILE,OSR_SERVER_REQUIRE_AUTH);create_app(router, auth_tokens=[...]). Every path except/healthz,/readyz,/metrics,/whoamiand the OpenAPI documents then needsAuthorization: BearerorX-API-Key(constant-time compare, 401 withWWW-Authenticate). NewGET /whoamidescribes the server and whether the caller is authenticated. Helm chart:auth.tokens,auth.existingSecret,auth.required. - Platform API:
POST /api/v1/auth/device/code(anonymous, rate limited),GET /api/v1/auth/device/{user_code},POST /api/v1/auth/device/approve|deny(admin role, plan key quota) andPOST /api/v1/auth/device/token(authorization_pending,slow_down,access_denied,expired_token); device codes are stored hashed and expire after 15 minutes. Web:/cli/authorizeapproval page (sign-in redirect, code entry, key name, approve/deny), install one-liners on the docs landing page, footer and integrations page.
Tracing and observability
opensmartroute.observability: aTracerthat every stage of a request reports to - spansrequest,http.request,route,plan,execute,autopilot.cycleand events for signals, policy, pinning, narrowing, shortlist, ranking (per-strategy timings), escalation, exploration, abstention, fallback, plan slots, execution steps, outcomes, task credit, calibration, breaker transitions, cache hits and misses, tenant rejections, slow decisions, guard verdicts, shadow comparisons and A/B verdicts, fair-share throttling and autopilot drift (SPAN_NAMES,EVENT_NAMES). Sinks:MemorySink(ring buffer withevents()/trace(request_id)),MetricsSink(counters, p50/p95/p99,prometheus()),LoggingSink,FileSink(JSONL) andadapters.optional.OpenTelemetrySink(otelextra: OTel spans nested through the OTel context and joined to an inboundtraceparent, span events, counters and a duration histogram).configure_tracing(*sinks)/get_tracer()for the process-wide tracer,Router(tracer=),RouterBuilder.with_tracing(...),current_tracer()/use_tracer()for components; trace ids come fromtraceparent, a UUID request id or a hash of the request id. Request text never enters a span (text_digest()); attributes are bounded and JSON-clean; a failing sink is counted, never raised.ObservabilitySettings(OSR_OBSERVABILITY_METRICS|MEMORY_EVENTS|LOG_EVENTS|EVENTS_FILE|OTEL|SAMPLE_RATE|ATTRIBUTE_MAX_LEN).osr serve:http.requestspan per request (probes and observability endpoints excluded),X-OSR-Trace-Idresponse header,GET /events(filter by request id, trace id, name glob, kind, level),GET /trace/{request_id}, tracer counters appended toGET /metrics,observabilityblock in/stats.osr route --eventsprints the trace of the decision; every CLI router traces into the sinks named byOSR_OBSERVABILITY_*. The hosted platform wires the same tracer into both editions (/api/v1/stats). Newdocs/OBSERVABILITY.md.
Metrics, caching and AI governance end to end
enterprise.MetricsTelemetryis a real metrics store:snapshot()returns decision counts per target, policy rejections, errors by type, a routing-latency histogram with p50/p95/p99, mean confidence, outcomes per target (success rate, cost, quality, latency) and uptime;prometheus(namespace=, extra=)renders the Prometheus exposition format (osr_route_decisions_total,osr_route_latency_ms_bucket,osr_outcome_cost_usd_total,osr_decision_cache_hits_total, ...).CacheMiddleware.stats(),invalidate()andlen();RouterBuilder.with_cache(ttl_s, max_size)andwith_audit(sink, outcomes=True)so the hash chain also records how each decision turned out.osr serve:/healthzand/readyzprobes,GET /metricsin Prometheus format (router metrics plus tracer counters and atargetsgauge), request-latency and error observation on every routed call. The Helm chart and the Azure Container Apps template use/readyzfor readiness; the chart's defaultpodAnnotationscarryprometheus.io/scrape|path|port.- Platform API observability (
osr_platform.observability): every response carriesX-Request-Id(echoed when the client sends one) andServer-Timing; JSON access log onosr.platform.accesswith route template, status, latency and account; per-route HTTP counters, latency histogram and in-flight gauge.GET /healthz(targets, uptime),GET /readyz(503 with the failing check: database, catalogue, router, audit file) andGET /metrics(osr_platform_http_*plus the router's metrics; public by default,OSR_PLATFORM_METRICS_PUBLIC=falserestricts it toX-Admin-Token).GET /api/v1/statsaddshttp,controls,cacheandobservabilityblocks; breaker snapshots serialise cleanly (budget_remaining: nullinstead of infinity). - Platform caching: public catalogue reads (
/api/v1/info,/models*,/rankings,/llms*,/catalogue,/stats/public) returnETagandCache-Control: public, max-age, stale-while-revalidateand answer304toIf-None-Match(OSR_PLATFORM_HTTP_CACHE_MAX_AGE_S,0disables). An optional decision cache in front of either edition (OSR_PLATFORM_ROUTE_CACHE_TTL_S) reuses recent decisions for identical requests; hit rates appear in/api/v1/statsand/metrics. - Platform governance: a workspace policy (
GET|PUT|DELETE /api/v1/policy, admin role) applies the tenant constraint keys plusdaily_budget_usd/monthly_budget_usdto every request of the workspace; tenants accept the same budget keys. Policy and tenant constraints merge deterministically (boundaries fill in, caps take the stricter value, deny lists union, allow lists intersect); unknown keys, unknown targets and allow/deny overlaps are rejected with400. Executed spend is attributed per tenant (X-OSR-Tenant); once a budget is exhausted routed calls return429withRetry-AfterandX-Budget-Limit|Used|Period.GET /api/v1/governancereturns the whole posture in one payload: controls in force (input guard, PII redaction, steering strip, metrics, tracing, audit chain, circuit breakers, decision cache, learning persistence), audit-chain verification, retention, policy, budgets and spend, quota, tenants with monthly spend, and the catalogue's data boundaries and PII-capable targets. Usage rows older thanOSR_PLATFORM_RETENTION_DAYS(default 365,0keeps everything) are purged by a background sweep. - Web:
/dashboard/governance(policy editor with budgets, controls, budget bars, audit and retention, catalogue boundaries, tenant spend) and/dashboard/health(enterprise: HTTP traffic, error rate, routing decisions and latency histogram, circuit breakers, decision cache hit rate, per-route table, auto-refresh). The tenant editor gains daily and monthly budgets. The proxy forwardsIf-None-Match,ETag,Server-Timingand theX-Budget-*headers;ApiErrorexposesrequestIdandbudget.
Observability portal: traces, events and readiness in the dashboard
- Platform API:
GET /api/v1/trace/{request_id}returns the activity row of one of the workspace's requests plus every span and event the tracer buffered for it (trace_id,spans, rootduration_ms;404for another workspace's id,events: []once evicted).GET /api/v1/eventslists recent tracer events scoped to the workspace through its request ids (namewith*wildcard,kind,level,request_id,limitup to 2000; deployment-wide events without a request on plans withstats;404whenOSR_OBSERVABILITY_MEMORY_EVENTS=0).GET /api/v1/statusis a public readiness endpoint for status pages (checks, edition, versions, uptime, controls, tracing buffer;503while a check fails,no-store).POST /api/v1/routeresponses carrytrace_idandX-OSR-Trace-Id;GET /api/v1/activitymarks rows still in the buffer withtracedand returns the buffer state.usage(request_id)is indexed. - Closed loop:
POST /api/v1/feedbackis now stored per request (feedbacktable, purged with usage retention);GET /api/v1/trace/{request_id}returns the request'soutcomesand its hash-chainedauditrecords (decision and outcome, plans withaudit), and activity rows carry anoutcomesummary (reports,success, meanquality). - Web:
/dashboard/events(live event stream with auto-refresh, stage/kind/level filters, most-frequent names, click-through to the trace); a trace drawer on every traced activity row and a Trace tab in the playground rendering a span waterfall (offsets and durations relative to the root span, nested events, level badges, expandable attributes, problems filter) with a How it was routed story (signals, policy rejections and guard verdicts, ranking bars, decision with fallbacks and execution steps), the reported outcome (or the feedback call to make) and the audit records; an Outcome column in the activity log; a Systems card on/dashboard/health(also shown in the community edition) and a status pill on the overview polling/api/v1/status. Newcomponents/observability/trace-view.tsxandsystem-status.tsx; the proxy forwardsX-OSR-Trace-Id.
Routing SLM and autopilot on the hosted platform
- SDK:
EnterpriseRouternow runsRouter.observerson the auto-learning path too, so an autopilot's drift hook sees every outcome in the enterprise edition (it was silently dead there).SelfImproverreports each cycle as alearn.improveevent (rows, champion vs challenger accuracy, verdict, reason) and each promotion aslearn.promote;Autopilot(tracer=)names the tracer cycles report to when the host's tracer is not the process-wide one. - Platform:
OSR_PLATFORM_SLMserves a routing SLM as theslmstrategy;OSR_PLATFORM_AUTOPILOT(with_OFFLINE,_SOURCES,_INTERVAL_S,_MIN_ROWS) runs the self-improvement loop in-process on the platform's own feedback, promoting a challenger only when it beats the champion and saving it toDATA_DIR/autopilot/slm.json(preferred over the mounted file after a restart).GET /api/v1/learning(any plan) returns the strategy ensemble and weights, learner state (persistence, drift resets, quarantine), per-target outcome statistics (JSON-safe), the SLM in service (rows, sources, encoder, calibration, fit history) and the autopilot status with recent improvement reports;POST /api/v1/admin/autopilot/cycleschedules a cycle;controls/infogainslmandautopilot. - Web:
/dashboard/learning(strategy weights, outcomes per target, SLM card with accuracy sparkline and fit history, autopilot card with drift, cycles, promotions, champion-vs-challenger and a Run cycle action, improvement-cycle table); Governance lists the SLM and autopilot controls.
Azure DNS zone and custom domain
infra/dns.bicep(parameterdnsZoneName, azdOSR_DNS_ZONE): public Azure DNS zone for the platform with apex A to the Container Apps environment static IP,www/apiCNAMEs to the app FQDNs,asuid.*TXT records for hostname validation and SPF/DMARC reject records; outputsOSR_DNS_NAME_SERVERS.scripts/domains.ps1is now Azure-only (phasesdns,bind,verify,clean; no Cloudflare token): it creates the zone, waits for the registrar delegation, binds the hostnames with managed certificates, rebuilds web with the domain baked in and checks site, API, MCP and redirects end to end;cleanretires the supersededosr-platformcontainer app.- Web:
platform/web/src/proxy.ts301s alias hostnames listed inOSR_WEB_REDIRECT_HOSTS(set by Bicep to the web alias domains, e.g.www.) to the canonicalNEXT_PUBLIC_SITE_URL. - Apex managed certificates validate over HTTP (
TXTvalidation never completed for the apex); subdomains keep CNAME validation. - Continuous deployment: the
azd upjob in.github/workflows/platform.ymlsigns in with an OIDC federated credential bound to theproductionGitHub environment, reads the custom-domain parameters from repository variables and seedsSERVICE_*_IMAGE_NAMEfrom the running apps so a provision never swaps them to the placeholder image. Setup commands inplatform/README.md("Continuous deployment").
Quotes, recommended models and the MCP server
opensmartroute.estimate: quote a request before sending it.estimate(router, request, output_tokens=, kinds=, prices=, quality_tolerance=, decision=)routes as usual and prices every ranked candidate (TargetEstimate: input / output / per-call cost, latency, quality estimate, context fit, energy and CO2, rationale) into aRequestEstimatewith therecommended,cheapest(within a quality tolerance of the best),best_qualityandfastestpicks andsavings_usd;estimate_tokens/estimate_messages_tokensheuristics,target_prices(splitusd_per_1k_input/usd_per_1k_outputwhen declared),PriceHookfor live price feeds. CLIosr estimate TEXT [--output-tokens N] [--cost-weight W] [--monthly R] [--json].opensmartroute.mcp_server: a standard-library Model Context Protocol server (2025-06-18, JSON-RPC 2.0) over any router. Toolsroute,estimate,recommend(priority balanced / cost / quality / speed),explain,list_targets,feedback, plusask(route and execute) andmarketplace_search/marketplace_getwhen theexecute/marketplacehooks are given; resourcesosr://targetsandosr://stats;serve_stdiotransport,RemoteMCP/bridge_stdioHTTP forwarder. CLIosr mcp [--list-tools]serves a catalogue over stdio,osr mcp --url <platform>/mcp --api-keybridges to a hosted platform.osr servemountsPOST /mcpandGET /mcp.- Platform:
POST /api/v1/estimate(anonymous, rate limited per client, or keyed and metered asestimate; vendor / model names, live catalogue prices, optionalmonthly_requestsprojection),GET /api/v1/models/recommended(recommended, cheapest, best-quality and fastest pick per use case - chat, code, reasoning, summarise, extraction, pii - from live quotes, plus a leaderboard, cached one minute) and the MCP endpointPOST /mcp/GET /mcp(tagmcp; scoped to the workspace, metered asmcp,askon the plan tier, marketplace tools backed by the registry). Web:/estimatepage (quote form, four picks, signals, candidate table, monthly projection), "Recommended per use case" on/models, Dashboard -> Integrations with copy-paste MCP configuration for VS Code, Cursor, Windsurf, Claude Code and Claude Desktop, and/mcpproxied on the web origin.docs/MCP.md.
Web app chrome and platform pages
- Header: the main navigation collapses into the menu below the
lgbreakpoint (it overflowed on tablets), the menu closes on navigation and lists the signed-in shortcuts, active items carryaria-current, and a "Skip to content" link targets<main id="main">. - Footer: plain headings, four groups (Product, Developers, Resources, Company) with the REST and Python references labelled apart, licence / SDK version / edition as a definition list, and a legal bar (Terms, Privacy, Support, Security).
- New pages
/support(documentation entry points, issues and discussions, account and billing, security reports, enterprise, service status),/privacy(what the platform stores per table, request content handling, browser storage, processors, retention, access and deletion) and/terms(accounts and keys, plans and payment, content, acceptable use, marketplace, availability, suspension, open source, liability). Linked from the footer, the signup form, the dashboard footer, the 404 and error pages, the command palette and the sitemap; the CI container smoke test requests them. - Command palette lists every public page (marketplace, estimate, pricing, REST API, support).
Marketplace and declarative stacks
opensmartroute.stack: declarative stacks - one YAML/JSON document (osr: "1",kind: stack) holding targets (plain or OCM), rules, settings, objective andimportsof other stacks, catalogues or marketplace templates (registry://slug@version).load_stack,validate_stack,plan_stack(diff against a deployed stack),dump_stack,starter_stackandStack.router()mirror the infrastructure-as-code check / diff / apply workflow; the CLI gainsosr stack init | validate | plan | apply(--registry,--against,--out,--route).examples/stack.yamlis a complete support-desk example.- Hosted marketplace (
platform/api/osr_platform/registry.py, tagmarketplace): listings of kind template, agent, skill, persona, tool, llm and prompt with slug, semver versions and changelog, publisher, licence, tags, Markdown readme, price, installs and star ratings. Public browse and detail (GET /api/v1/registry,/registry/kinds,/registry/{slug},/manifest?version=,/reviews), publisher lifecycle draft -> review -> published -> archived (POST /registry,PATCH,/submit,/versions,/archive,/unarchive,GET /me/listings), installs and purchases (POST /registry/{slug}/install,GET /me/installs, one-time Stripe checkout confirmed by webhook), reviews (PUT/DELETE /registry/{slug}/reviews) and moderation under/api/v1/admin/registry(approve,reject,flags,unpublish). Manifests are validated against the OCM / stack schemas; template manifests may be sent as{"yaml": "..."}. SettingsOSR_PLATFORM_REGISTRY_AUTO_PUBLISH(free listings go live on submit) andOSR_PLATFORM_REGISTRY_SEED(seed the repository's skills, the starter and example templates, a persona and prompts, all verified). - Web:
/marketplace(kind rail, search, filters, featured, pagination),/marketplace/{slug}(about, use-it snippets, manifest, reviews with histogram, versions, install / buy / rate),/marketplace/publish(four-step wizard, no code needed for prompts and personas) andDashboard -> Marketplace(my listings with status and reviewer notes, edit page with new-version form, installed items). Marketplace links in the main navigation, footer, dashboard and sitemap. User docs:docs/MARKETPLACE.md(site page/docs/MARKETPLACE). - OCM cost block accepts
context_tokens;capability_from_targetemits it. PlatformAPI.optional_principaldependency (anonymous or authenticated) andBilling.listing_checkout_url/Billing.on_purchasefor one-time purchases.
Documentation site
- The hosted web app (
platform/web) now renders the documentation itself as a static Next.js site at/docs:scripts/sync-content.mjssnapshots the repository Markdown (README,CHANGELOG,CONTRIBUTING,SECURITY,docs/**,deploy/README.md,spec/ocm/README.md,.claude/skills/*/SKILL.md) intocontent/;src/lib/docs/catalogue.tsis the single source of truth for sections, slugs and summaries;src/lib/docs/render.tsrenders with remark/rehype (GFM, GitHub-compatible heading ids, Shiki highlighting, Mermaid, relative-link rewriting). Pages get a collapsible sidebar, scroll-spy table of contents, breadcrumbs, reading time, code copy buttons, previous/next links and an "Edit on GitHub" link. New pages: every Agent-Skills package (/docs/skills/<name>, with its frontmatter),/docs/deploy,/docs/ocm,/docs/contributing,/docs/security-policy./docs/search.jsonfeeds documentation pages and headings into the Ctrl/Cmd+K palette; the sitemap lists every page. The docs no longer depend on the platform API being up. - The site is organised for end users of the hosted platform, the Python SDK and the pip package:
sections Start, Hosted platform, Python SDK, Self-hosting, Concepts, Security, Agent skills and
Releases, with a section tab bar and audience paths on the index. Internal material
(
docs/PLATFORM_PLAN.md,docs/GO_TO_MARKET.md,docs/BRAND.md,docs/sales/,platform/README.md) is no longer published on the site and stays in the repository. docs/PLATFORM.md: platform guide for end users (authentication,/api/v1/routefields and constraints, the/api/v1/estimatequote, execution, the OpenAI-compatible endpoint, feedback, plans and quotas, organizations and roles, organization SSO, tenants, the marketplace, the MCP server, dashboard pages, privacy, error handling).docs/MARKETPLACE.mdis published under Hosted platform.docs/GUIDE.mdlists everyosrsubcommand grouped by task.- REST API reference generated from the platform's OpenAPI document:
/docs/api(authentication schemes, base URL, conventions, endpoint groups) and one page per tag (/docs/api/routing,/docs/api/openai,/docs/api/public,/docs/api/account,/docs/api/workspaces,/docs/api/auth,/docs/api/billing,/docs/api/mcp,/docs/api/marketplace) with parameters, request and response schemas, curl and JSON examples; operations are searchable from the palette. Operator-only/api/v1/admin/*paths are left out whatever tag they carry. - Version menu in the docs top bar built from
CHANGELOG.md(current release, unreleased changes, links to each release section and PyPI). .claude/skills/osr-platform: Agent-Skills package for the hosted platform (API, web app, the documentation site andazddeployment); rootAGENTS.mdand.github/copilot-instructions.mdpoint coding agents at the gates, the easy-to-break rules and the skill for each task.
Platform API
- The OpenAPI document declares
bearerandapiKeysecurity schemes (replacing the per-operationauthorization/x-api-keyheader parameters), tag descriptions for every route group and a richer info block.platform/api/openapi.jsonis a committed snapshot regenerated withpython platform/api/scripts/export_openapi.py;--check(run in CI and bytest_openapi_snapshot_is_current) fails on drift.
Routing SLM and self-improvement
learning.RouterSLM: a self-contained small routing model (hashed dual encoder, temperature calibration, target snapshots) that trains onEvalRows, predicts a calibrated distribution and a quality/cost/latency utility per candidate, learns online from outcomes, and saves to a compact JSON file (save/from_file,SLM_FORMAT).learning.distill_router()compresses the whole ensemble (rules, capability fit, learners, judge) into one SLM from its traces or propensities.learning.SLMStrategyputs it into the ensemble (weightOSR_WEIGHTS_SLM) with online updates;Routeraccepts it like any other strategy andosr --slm model.json routeloads one from the CLI.adapters.ModelCatalogue/ModelCard: live model catalogue from OpenRouter (prices, context, modalities, tool support) and Hugging Face model cards (downloads, likes,model-indexbenchmarks ->quality_from_benchmarks,quality_from_popularity), merged per model id and persisted;targets()filters by quality/price intoRouteTargets (card_to_targetreplaces injection-risky third-party descriptions),cost_benchmark(),frontier()(Pareto),to_markdown(); fail-softrefresh().adapters.WebKnowledge: web-search discovery with keyless providers (huggingface_search,duckduckgo_search) andbrave_searchwhenBRAVE_API_KEYis set;fetch_bytes/fetch_json/fetch_page_text/html_to_text(https-only, byte-capped, risk-scored pages); a cached hit list thatdiscover_models()feeds into the catalogue.eval.DatasetCollector/DatasetSource/KNOWN_SOURCES: collect routing datasets from the Hugging Face datasets-server (fetch_hf_rows,iter_hf_rows,collect_dataset) into a JSONL cache, plusrows_from_feedback()(outcomes -> labelled rows) andsynthetic_rows()(ontology seed prompts scored by capability fit);corpus()dedupes and caps,split()is a deterministic holdout.DEFAULT_SOURCESare the publicly served pairwise battle setsroutellm-battles(RouteLLM GPT-4-judged battles) andarena-55k(LMArena human preference); the RouterBench and RouterEval presets stay inKNOWN_SOURCESflaggedgated(the Hub does not serve their rows without authentication).from_pairwise_row()turns a battle into anEvalRow(ties make both sides acceptable);model_quality()fits a Bradley-Terry model over the battles and returns each model's win probability against the field, which the catalogue applies as anarenaquality prior.ARENA_TIERS/tier_of()/tier_model_map()fold the retired arena model names onto the operator's tiers (osr collect --tier frontier=llm-frontier --tier mid=llm-mid --tier small=llm-small, also onosr improve);DatasetCollector.derive()remaps an existing cache offline so tiered corpora never need a second download.collect()keeps partial pages when the Hub rate-limits mid-way and never replaces a fuller cache with a shorter one;fetch_bytesretries 429/5xx with exponential backoff honouringRetry-After(OSR_SLM_FETCH_RETRIES,OSR_SLM_FETCH_BACKOFF_S,OSR_SLM_FETCH_BACKOFF_MAX_S) and pages pauseOSR_SLM_PAGE_PAUSE_S.adapters.fetch_leaderboard_quality(): Open LLM Leaderboard results (IFEval, BBH, MATH, GPQA, MuSR, MMLU-Pro) per model via the datasets-server;ModelCatalogue.apply_quality()maps them (and the arena win rates) onto cards by exact id ormodel_key()(case/variant-insensitive tail), ranking sources popularity < catalogue < arena < leaderboard so a measured prior is never downgraded;refresh(leaderboard=True),targets(measured=True)andosr catalogue --leaderboard --quality-from DATA_DIR --measured;osr slm train --catalogue catalogue.json --source NAMEtrains on measured, priced catalogue models and on selected caches only.adapters.attach_chat_handlers(): give every LLM target achat_handleron one OpenAI-compatible client (metadata.modelnames the upstream model, or a default). The CLI does this at startup whenOSR_LLM_BASE_URLis set (OSR_LLM_API_KEY,OSR_LLM_API_KEY_ENV,OSR_LLM_MODEL), so the container's/v1/chat/completionsexecutes against OpenAI, Azure OpenAI, vLLM, Ollama, OpenRouter or LiteLLM instead of answering 502.huggingface_search()limits model hits totext-generationand falls back to per-keyword queries (the Hub matches repo names, not natural language);fetch_json()returnsNonefor an empty 200 body (DuckDuckGo does that under load).- Deployment:
OSR_SLM(Docker entrypoint, Helmconfig.slm->/config/slm.json) loads a routing SLM into the served ensemble;OSR_LLM_BASE_URL/OSR_LLM_MODELare declared in the image. learning.SelfImprover/ImprovementReport: the closed loop - refresh catalogue, discover models and datasets on the web, gather evidence (feedback, collected datasets, synthetic), train a challenger on the train split, calibrate it on the holdout, and promote it over the champion only when holdout accuracy improves byOSR_SLM_MIN_GAIN; JSONL history,run(interval_s)loop.- CLI:
osr catalogue,osr collect,osr slm train|eval|predict|info,osr improveand the global--slmoption;settings.SLMSettings(OSR_SLM_*) andWeightSettings.slm. - Transformer encoders for the SLM.
learning.AttentionEncoderis a pure-Python transformer block (hashed token embeddings, sinusoidal positions, multi-head self-attention, residual, attention pooling, hand-derived gradients) thatContrastiveRouter(attention=...)adds to the hashed query encoder so tokens can condition on each other; opt in withOSR_SLM_ENCODER=attention(OSR_SLM_ATTENTION_HEADS,_HEAD_DIM,_MAX_TOKENS,_VOCAB); the block is persisted in the model file (core.encoder,core.attention) and restored byfrom_file.learning.EmbeddingFeaturizer/load_embedder()append a frozen pretrained sentence-transformer embedding (extraembeddings, or anyCallable[[list[str]], list[list[float]]]) as dense features, soWlearns a linear head on top of it;RouterSLM(embedder=...),OSR_SLM_EMBEDDER=<model>/OSR_SLM_EMBEDDER_SCALE, the file records the embedder name andfrom_file(embedder=...)takes a custom one back. - Self-operation:
learning.AutopilotrunsSelfImprovercycles inside a live process on a schedule and on drift (learning.DriftMonitor, Page-Hinkley over the success indicator and quality of every outcome the router learns from), rate-limited by a minimum gap, hot-swapping an accepted challenger into the servedSLMStrategyand rewriting the model file; failures are recorded, never fatal.Router.observersis the outcome hook it (and any drift monitor or metric) plugs into.osr serve --autopilot [--autopilot-interval S --cache-dir DIR --catalogue FILE --source NAME --search QUERY --tier TIER=TARGET --offline --min-rows N],create_app(router, autopilot=...)exposesautopilotinGET /statsandPOST /autopilot/cycle; settingsOSR_SLM_AUTOPILOT_INTERVAL_S,_MIN_GAP_S,_DRIFT_THRESHOLD,_DRIFT_MIN_N,_REMEMBER; container / Helm:OSR_AUTOPILOT=1,OSR_AUTOPILOT_ARGS,OSR_CACHE_DIR,OSR_CATALOGUE.status()["busy"]says a (possibly multi-minute) cycle is in flight.DatasetCollector.corpus()/cached()skip the improver's ownimprove-history.jsonland any non-row line in the cache dir - the second cycle used to fail withEvalRow.__init__() missing 'text'when the log shared the dir. - Routing datasets:
osr collectknows ten public sources verified against the datasets-server - the 2024-25 LMArena releases (arena-100k,arena-140k,ppe-human,webdev-arena),mt-bench-human,reward-bench,ultrafeedback(per-model judge scores) androutellm-gpt4(RouteLLM's own "is the cheap model good enough" labels), next toroutellm-battlesandarena-55k; gated ones stay listed but off by default. Parsers understand the newer layouts (typed conversation turns,winnerlabels, per-model score lists) and carry the code / language / math / hard-prompt tags intocontext;tier_of()has name rules and a parameter-count fallback for the 2024-26 model generations (gpt-4o-mini-> mid,gemini-2.5-pro-> frontier,llama-3.1-8b-> small ...) andDatasetSource.tiersfolds every side onto your catalogue at parse time (--tierno longer needs an exhaustive model table). Collection is resumable: a<name>.meta.jsonrecords the raw offset, a run cut short by a 429 keeps its rows and the nextosr collectcontinues from there (DatasetCollector.progress()); retries back off up to five times / 60 s.corpus()fillsOSR_SLM_MAX_ROWSround-robin across sources so one large dataset cannot crowd the others out. - SLM training is faster and generalises better. With numpy installed (extra
fast, now in the serve image)ContrastiveRouter.fit()runs the same SGD on dense arrays - about 10x faster (18 000 rows in under a minute) - and inference projects through a dense mirror ofW;OSR_SLM_BACKEND=auto|numpy|pythonpicks the arithmetic, both paths seed identically and produce the same model file (tested to 1e-9), so a pure-Python deployment reads a numpy-trained model unchanged. The hashed featurizer caches token hashes. The model gains a learned per-target logit bias (B, the base rate a unit-norm dot product cannot express; older files load with none), inverse-time learning-rate decay (OSR_SLM_LR_DECAY, default 2) and regularised defaults (epochs4,l21e-3): on the 18 000-row mixed corpus the previous defaults overfit below the constant "always the mid tier" baseline (0.62 vs 0.64 on a random holdout); the new ones sit at 0.64-0.66 with holdout loss 0.98-1.00 vs 1.11 and are stable across seeds. - The SLM no longer harms mixed catalogues. Scored rows train a softmax over the scored population (the
targets any row of the corpus scores, plus
OSR_SLM_NULL_TARGETSlearned "none of these" logits) instead of the whole catalogue, so a corpus of LLM battles leaves the embeddings of tools, skills and agents it never compared untouched (they used to be driven to p=0.0005 and any SLM tookexamples/eval_dataset.jsonlfrom 0.83 to 0.43; it is 0.80 now).SLMStrategy/ContrastiveStrategyabstain on targets the model has never compared (ContrastiveRouter.Brecords them;RouterSLM.predict_proba()with no explicit candidates ranks only those), score the rest centred (best 1.0, mean 0.5, so a flat softmax is "no preference" rather than a boost) and scale confidence by the share of the request's candidates they know. Chosen on three leave-one-source-out suites (PPE 0.561, MT-Bench 0.508, WebDev Arena 0.506 - seedocs/ROADMAP.mdv0.4). eval.load_dataset()raisesConfigurationErrornaming the file and line for a line that is not JSON, not an object or has no text (it used to surface as aTypeErrortraceback), andosr catalogue | collect | slm | improveprint every SDK error as oneerror:line with its path / line / preset / backend detail and exitContrastiveRouter(backend=...)with an unknown backend is aConfigurationErrortoo.
Agentic core (PLATFORM_PLAN Phase 0)
signals.EventSignal,WorkflowSignal,parse_event(),EventInfoand theEVENT_*lexicons: textless requests (context["event"],context["workflow"]) get domain / action signals so events route to workflows and agents; rules matchevent:globs andworkflow: true.strategies.SessionAffinityStrategy/SessionState: keep multi-turn conversations on the target that owns the session unless quality drops;Router.route(..., exclude=[...])and fallback candidates;PLAN_ROLESis kind-aware (agent / workflow plans need no persona); per-call pricing on targets (cost.usd_per_call,RouteTarget.cost_per_call,estimated_cost());observehook.- Proxy:
osr/auto:<variant>presets (cheap,fast,quality,private,agentic,llm),osrrequest block (exclude, objective, plan, fallbacks),X-OSR-Appattribution andX-OSR-Target/X-OSR-Request-Idresponse headers; metadata carries alternatives and cost. - Executors:
adapters.http_handler(),mcp_tool_handler()and queue-backed targets (Queueprotocol,InMemoryQueue,queue_handler(),PendingResult) so agents, tools and human queues execute through the same plan runner. - Open Capability Manifest:
spec/ocm/(JSON Schema + README),opensmartroute.ocm(validate_capability,target_from_capability,capability_from_target,load_capabilities,bind_endpoint),load_targets()accepts manifests and mixed catalogues,osr validateandosr export-ocm. - Routing Audit and savings:
eval.routing_audit()/AuditReport/load_audit_log()replay logged traffic against a catalogue (savings vs baseline, policy violations, route mix);osr audit logs.jsonl --baseline ... --markdown|--json [--min-savings];enterprise.SavingsLedgertelemetry sink (RouterBuilder.with_savings(),EnterpriseRouter.savings) with per-request baseline-vs-routed entries andSavingsReport.to_markdown(). - CLI:
osr route --constraint KEY=VALUE(hard constraints) and--kinds a,b;--event,--session,--exclude. - Platform:
GET /api/v1/savings,GET /api/v1/signals,GET /api/v1/admin/pql(product-qualified leads); web/roicalculator,/compare/{openrouter,litellm,portkey,diy}pages and the dashboard savings view. - Docs: sales enablement kit under
docs/sales/(discovery guide, demo script, Routing Audit, pilot charter, ROI calculator, security pack, battlecards, pricing and proposal, case study and QBR).
Research track (every previously Research roadmap row now has code, tests and a module reference)
learning.ContrastiveRouterandContrastiveStrategy: RouterDC-style contrastive training on (query, target) pairs. Softmax cross-entropy overq . e_t / tauagainst a row's acceptable set (acceptable_set(row, slack), every target withinslackof the best score), so several right answers are not penalised;fit(objective="distilled")trains against Zooter-style reward-distilledsoft_labels(scores, temperature)instead of one-hot labels; hashed encoders, pure-Python SGD, online updates from outcomes,state()/load().learning.PolicyGradientStrategy,decision_reward(),decision_regret()andRegretReport: end-to-end REINFORCE routing (Router-R1 / RLCascadeRouter) on hashed request features with an EWMA baseline and entropy bonus, trained on the decision utility rather than a quality prediction;decision_regret(rows, targets, choose, predict)reports decision regret next to prediction MAE so the two losses can be compared on the same dataset.eval.headroom:routing_headroom()splits oracle minus best-single into the label-noise floor and measurable headroom;target_diversity()(pairwise disagreement, winner entropy / share);min_catalogue(rows, targets, fraction)(smallest subset keeping a fraction of the oracle);scaling_curve()(oracle / best-single / headroom against catalogue size);learnability_by_difficulty()(per-difficulty-bucket headroom and noise floor, the "least learnable where most valuable" check for agentic pools).strategies.MemoryRouter,MemoryTier,MemoryItem,ImportanceGate,RecallResult: BudgetMem-style memory-tier routing. Tiers carry capacity, write / read cost per token and latency; a hashed logistic gate decides what is worth writing and learns fromfeedback(used_ids, recalled); value decays pertick(), lowest-value residents are demoted when a tier is full;recall(query, budget_tokens)fills a token budget by relevance x value.strategies.ModalityStrategy,ModalityEscalation,EscalationResult,request_modalities(): penalise text-only targets when a request carries images / screens / audio / video / files that a text surrogate (OCR, caption, transcript) only describes; text-first escalation keeps a Beta posterior on "the surrogate sufficed" per modality set and calls the multimodal target first when that probability is low.strategies.SemanticCache,SemanticCacheStrategy,CacheEntry,CacheHit: a semantic cache as aDESTINATIONtarget.SemanticCache.target()serves near-duplicate answers (cosine over the router's embedder, TTL, LRU bound, source-based invalidation); the strategy scores only cache targets from the hit similarity and learns per-similarity-band hit quality from outcomes.strategies.AnnotatorPool,AnnotatorSkill,QuorumPlan,quorum_accuracy(),HumanRoutingStrategy: QUORUM-style routing amongHUMANtargets. Per-domain annotator accuracy is estimated by Dawid-Skene EM over labels (with gold labels and direct quality reports as evidence, prior seeded fromquality_prior);select_quorum(domain, target_accuracy, budget)picks the cheapest majority-vote quorum that reaches the target accuracy.strategies.SpeculativeCascade,SpeculativeResult,expected_mode_costs(): speculative draft / strong cascades. A Beta acceptance posterior per complexity bucket decides betweendraft_only,speculate(both in parallel, strong cancelled when the draft is accepted) andstrong_onlyby expected cost + latency under the request'sObjective.learning.HistoryTargetStrategy,HistoryTargetModelandhistory_vector(): multi-turn routing on history-target joint embeddings (MTRouter). A logistic model overh * e_t(recency-weighted hashed history and current text, catalogue target embedding) with shared weights and a per-target bias learns that the same follow-up routes differently depending on the conversation so far; inductive for unseen targets;context["last_target"]earns an incumbent bonus that becomes a penalty whencontext["last_failed"]is set;state()/load()/merge(), persisted byAutoLearner.errors.deprecated(name, since=, removal=, replacement=)anderrors.OpenSmartRouteDeprecationWarningimplement the public API deprecation policy documented in CONTRIBUTING.md.signals.VerbalisedDifficultySignalandparse_difficulty(): a small model's verbalised difficulty (0.7,7/10, "hard") fromcontext["difficulty"]or a callable blends intocomplexityand raisesreasoning_need.signals.DraftResponseSignalanddraft_features()derive hedging, self-correction, query overlap and length ratio from a cheap draft answer.signals.semantic_entropy(),semantic_clusters(),response_uncertainty()(meaning clusters over sampled answers, hedging, optional P(True) judge),signals.UncertaintyGate(aCascadequality gate /MixtureOfAgentstrigger built on them) andsignals.EventTrigger/TriggerRule(threshold rules that fire named actions).math.DirichletProbe(evidential Dirichlet head with digamma loss and KL regulariser, pure Python SGD,predict()returns probabilities and epistemic uncertaintyK/S) andstrategies.HiddenStateStrategy(state_fn, targets, dim)that routes on host hidden states and lowers its confidence on unfamiliar states.math.EnergyModel(per-target ridge fit ofWh = e0 + e_in * prompt + e_out * output, gCO2 via a grid factor) andmath.HardwareProfile/hardware_profile()priors for A100, H100, L4, RTX 4090, 16-core CPU and an edge NPU.strategies.expand_elastic(parent, [BudgetVariant(...)])creates<id>@<budget>siblings of an elastic model;strategies.TokenBudgetStrategyscores budget fit (truncation risk vs unused capacity).strategies.EdgeCloudStrategy: Thompson posterior of edge success per complexity bucket, decode time against the latency SLO, upload penalty for the cloud tier and a hard exclusion when the request's data boundary forbids a public target.strategies.AuctionStrategy,AuctionResult,default_bid(): error-aware reverse auction where each bidder's claimed success is corrected by its observed bias; second-price payment.strategies.ProtocolPolicy,ProtocolRule,DEFAULT_RULES,failure_risk(): risk / budget / task-type rules choosesingle,cascade,aggregate,debateorhandoff; a per-protocol ledger reports which protocol paid.strategies.SelfEscalationandwrap_stream(): Bayesian competence posterior updated per streamed chunk with an optimal-stopping rule;SelfEscalation.for_target()seeds the prior fromquality_prior.strategies.MixtureOfAgents,majority_vote(),AggregateResult: call the top-k alternatives when the decision is unsure and the budget allows, aggregate by semantic majority (or a supplied synthesiser) and emit oneOutcomeper participant.learning.UserAdaptiveStrategy(per-user Beta posteriors shrunk toward neighbour users and the global posterior;observe_user()warm start),learning.SkillAffinity(profile-bucket skill relevance forselect_skill_set(relevance=)),profile_vector(),profile_bucket().learning.MixtureCureModel(Weibull mixture-cure on cumulative trajectory risk with censoring, grid MLE) andlearning.HandoffPolicy(permanent hand-off on eventual-failure probability or horizon hazard; releases the task'sTaskPinspin).opensmartroute.discovery:schema_match()/extract_entities()/SchemaAwareStrategy(coverage of requiredinput_schemaproperties by typed entities in the request),CachePreservingSelector(per-session prefix-stable tool ordering with lazy eviction andprefix_hit_ratio()),SkillGraph(requires/conflicts/composesedges plus learned co-usage;compose()returns a dependency order).adapters.recommend_servers(),ServerCard,ServerRecommendation: rank MCP servers for a task with BM25 + hashing-cosine fusion over server cards and hard constraint filters.adapters.load_semantic_router_config()/SemanticRouterImport: import a vLLM semantic-routerconfig.yaml(categories, model scores, reasoning flags, pricing, system prompts) as targets and rules.load_skillsexposesosr-requires,osr-conflictsandosr-composesfrontmatter asmetadata["requires" | "conflicts" | "composes"]forSkillGraph.tests/test_roadmap_research.pycovers every item above;tests/public_api.jsonlists the new names.
Decorator SDK
opensmartroute.sdk:ComponentRegistryplus the decorators@strategy,@signal,@policy_rule,@middleware,@telemetryand@target/@tool/@skill/@agent. Plain functions are adapted throughFunctionStrategy,FunctionSignalandFunctionMiddleware; classes are registered as factories.registry.router()/registry.builder()assemble aRouter/RouterBuilderfrom everything declared;include("pkg.mod[:hook]")anddiscover()(entry-point groupopensmartroute.plugins) load plugins. The process-wide registry is exported asopensmartroute.componentsand the top-level decorators bind to it.RouterBuilder.with_components(registry, targets=, strategies=, signals=, policy=, middleware=, telemetry=)wires a registry into an existing builder.strategies.default_strategies(seed=, settings=, state_dir=)builds the default ensemble.
Settings
opensmartroute.settings: immutableSettingsgrouped intoRoutingSettings,PolicySettings,RulesSettings,CapabilitySettings,BanditSettingsandWeightSettings;configure(),get_settings(),Settings.from_env()(OSR_<GROUP>_<FIELD>overlay with typed parsing andConfigurationErroron bad values) andSettings.env_keys().Router,RouterBuilder,Policy,RulesStrategy,CapabilityStrategyandBanditStrategyacceptsettings=; every previously hardcoded threshold, weight and scale now resolves from it.Router.weight_of(strategy)reports the effective ensemble weight.osr settings [--json]prints the effective tunables, theirOSR_*keys and which are overridden.
Agent skills
.claude/skills/: eight Agent-Skills packages (osr-routing-catalogue,osr-decorator-sdk,osr-enterprise-builder,osr-evaluation,osr-security-hardening,osr-integrations,osr-deploy-serve,osr-contributing) that teach coding agents such as Claude Code how to use and extend the library. They are validTargetKind.SKILLtargets:tests/test_skills.pyloads them withload_skills, checks the routing hints against the signal ontology and asserts the router fills the plan's skill slot with the right package for a representative request.osr skills [ROOT] [--json]validates and lists SKILL.md packages (default rootbranding.SKILLS_DIR=.claude/skills);osr --skills ROOT ...adds them to any routing command.
Documentation coverage
docs/REFERENCE.md: generated API reference listing every module undersrc/opensmartrouteand every exported name with its kind, signature and one-line summary; re-exports link to their definition. Produced byscripts/api_reference.py(write|check|report), stdlib only.- Every exported class, function, constant and type alias now carries a one-line summary (docstring,
or a same-line comment for constants).
tests/test_docs.pyfails on an undocumented name, a stale reference, a module missing from the reference, or adocs/*.mdfile not linked from the README.
Policy composition
Policyis an ordered chain ofPolicyRulecallables (enabled,allow_list,deny_list,allowed_kinds,region,DataBoundaryRule,pii,tenant,cost_budget,latency_slo,input_tokens,context_window,tools,modalities,LanguageRule,JailbreakRule).Policy.with_rules(*rules)appends rules (subclasses that overridecheckare folded in as a single rule);Policy.rule_namesanddefault_rules()expose the chain.
Branding
opensmartroute.branding: single source of truth for brand-derived names (BRAND,PACKAGE,CLI,ENV_PREFIX,ERROR_CODE_PREFIX,METADATA_PREFIX,STATE_DIR,ENTRY_POINT_GROUP) withenv_key(),error_code(),metadata_key(),logger(),user_agent()andversion(). Error codes,osr-*frontmatter keys, the HTTPUser-Agent, MCPclientInfo, the OpenAI proxy alias and the FastAPI title/version derive from it.
Learning
- Judge-score calibration:
LLMJudgeStrategy(calibrate=True, min_fit=20, calibrator=)keeps anIsotonicCalibratorper judge, pairs each raw judge score with the served target's observed outcome and re-fits on everyupdate();Router.learn()forwards outcomes to the judge even when it is only used for escalation. Raw scores stay visible in the rationale (raw=); state round-trips throughstate()/load(). learning.warm_start_from_matrix(rows, strategies, weight=): offline full-information reward matrix(prompt, {target: reward})replayed into every learner (LinUCB ridge per arm, IRT, Thompson posteriors, task table, Markov rewards) before going online.MarkovStrategy(state_from="auto" | "task_type" | "domain", decay=): conversation state is the learned task type joined with the domain (not only the domain); per-request state memory so an outcome credits the state that was scored.MarkovChain(decay=)forgets transition counts,RoutingMDP(decay=)keeps exponentially weighted rewards.- Federated merge on every learner:
MarkovChain.merge,RoutingMDP.merge,MarkovStrategy.merge,BanditStrategy.merge,TaskTableStrategy.merge;learning.merge_learners(local, remote)pairs same-named strategies across replicas.AutoLearner.refresh()implements the writer / reader pattern for replicas sharing oneStateStore. - Cascade steps feed every learner:
CascadeStep.cost_usd,CascadeResult.total_cost_usd,CascadeResult.outcomes(request)(one per-stepOutcome, rejected steps are failures) andRouter.learn_cascade(request, result). Learners keep their per-request memory whileOutcome.stepis set so multi-step trajectories credit every step. - Strategies condition on the plan role:
Router._build_plantags sub-route signals withsignals.extra["role"];BanditStrategycontexts,TaskTableStrategyrows (skill:<task>plusfamily:skill:<fam>),MarkovStrategystates and every learner's request memory are keyed by role (strategies.base.role_of/memory_key), so a target's record as a skill or persona never leaks into its record as the primary answerer.TaskTableStrategynow learns from role outcomes.
Economics
- Energy and carbon as cost dimensions:
RouteTarget.unit_energy(cost["wh_per_1k_tokens"]),RouteTarget.unit_carbon(cost["gco2_per_1k_tokens"], or energy xcost["gco2_per_wh"]) andObjective(energy=, carbon=)weights applied to the log-normalised values in the utility.
Resilience
AutoLearner.load()quarantines corrupt or incompatible learner state instead of failing: the file is renamed*.corrupt-<ts>(or the store key copied underquarantine/), the learner starts blank andAutoLearner.quarantinedlists(strategy, reason). A failingStateStore.getis recorded, not raised.FileAuditSinkresumes its hash chain from the last line on disk across restarts and gainsFileAuditSink.verify(path) -> (ok, n, first_problem)detecting edited, removed or unparseable lines.LLMJudgeStrategyfilters judge output item by item: non-dict items, unparseable / NaN scores and hallucinated or injected target ids are skipped without discarding the rest of the ranking; non-string judge returns are a failure mode with zero confidence.scripts/bench.py --scalesweeps 16 / 64 / 256 / 1024 targets with and without retrieval narrowing and reports p50 / p95 / p99;docs/ARCHITECTURE.mddocuments the measured envelope.tests/test_resilience.py: audit-chain verification, judge JSON failure modes, state-corruption recovery (files and stores), multi-replica hand-off.
Deployment
deploy/Dockerfile(non-root, multi-stage,OSR_*environment toosr serve),deploy/entrypoint.shand the Helm chartdeploy/helm/opensmartroute(ConfigMap-driven catalogue, hardened pod security context, HPA, PDB, NetworkPolicy, optional PVC with a multi-replica warning).
Roadmap exit criteria
eval.criteria: every offline-measurable exit criterion indocs/ROADMAP.mdas a simulation that drives the realRouter/ learners and returns aCriterionResult-cold_start_ratio()(v0.5),multi_round_vs_best_single()(v0.6),match_at_1_at_scale()withsynthetic_tool_catalogue()(v0.7),conformal_coverage(),knapsack_never_exceeds_cap()andope_within_live_ci()(v0.8), plusbootstrap_ci()andrun_all().scripts/exit_criteria.py [--full] [--json]runs them and exits non-zero when one fails.osr eval --multi-round [--threshold] [--max-rounds]comparesMultiRoundExecutoragainst the best single target on a dataset whose rows carry per-targetscores.- Cold-start exploration:
Router(explore_rate=, explore_min_samples=, explore_seed=)andRoutingSettings.explore_rate/explore_min_samplesoccasionally serve the least-seen viable candidate until it has enough outcomes; propensities include the exploration mass andsignals.extra["explored"]marks the request. MultiKnapsackBandit(on_capped="release" | "abstain")andcapped(scores): when every arm would breach a hard cap the bandit can abstain instead of releasing the cheapest arm.MultiRoundExecutor(failures_before_switch=)forwards the switch policy toProgressRouter.- Thread safety:
Router.lock(re-entrant) guards strategy scoring, every learner update, task pins, request memory, retrieval narrowing and the exploration RNG; the LLM judge is scored outside it.AutoLearner(lock=)shares that lock (EnterpriseRouter/RouterBuilderpass it), snapshots are deep-copied and sequence-numbered so a slowsave()never overwrites a newer one, and a separate I/O lock orders writers againstload()/refresh().SemanticCache,MemoryRouter,AnnotatorPoolandModalityEscalationlock their own state. SpeculativeCascade(concurrency=, draft_timeout_s=, history_size=): bounded worker pool sized for concurrent speculative runs, draft timeout, draft exceptions escalate to the strong target (details["draft_error"]/["draft_timeout"]), late strong failures are swallowed after an accepted draft,draft_failures/strong_failurescounters andstats().ModalityEscalation.runescalates when the text target raises (details["reason"]) instead of failing.MultiRoundExecutor: rounds whose handler raised becomeRound(error=...)and the loop continues on another target; one judged learn per round (no provisional + correction double count);deadline_ms,return_best,credit_task,RoundResult.stop_reason/best/errors/total_cost_usd;arun()for coroutine handlers and anasyncjudge.ProgressRouter.run_step(learn=) routes viarouter.route, records a failed step before re-raising, estimates step cost from the target's unit cost when the handler reports none;arun_steptwin.tests/test_e2e_scenarios.py: real-scenario suite with no stubs - the shippedexamples/catalogue and rules driven end to end through an in-process OpenAI-compatible provider fleet over HTTP (PII boundary, tenant deny lists and data boundaries, persona plans, metrics, hash-chained audit, learner state surviving a restart), a provider outage opening and recovering a circuit breaker, the FastAPI service over anEnterpriseRouter(OpenAI proxy, feedback, stats, error mapping), theosrCLI (route,eval --frontier,--min-accuracygate,targets,stats),ProgressRouter/MultiRoundExecutoragentic loops, a real MCP server subprocess over stdio, and 24 concurrent requests through the full middleware stack.EnterpriseRouter.credit_task(): delivers the delayed task reward through the router and persists the re-credited learner state;MultiRoundExecutor(EnterpriseRouter)now closes the task-credit loop.- Execution: a primary target without a handler (an instructions-only skill or persona) runs on the
plan's
llmslot - its instructions are disclosed in the system prompt, the model answers, and the model is credited with arole="llm"outcome - instead of failing with "has no handler". - Execution enforces the PII boundary set up by
GuardMiddleware(redact=True): targets whose constraints allow PII receive the original text (context["pii_restored"]), every other target keeps the placeholders (context["pii_redacted"]), and the mapping never reaches a handler. create_app()accepts anEnterpriseRouter(middleware, health, telemetry and audit apply to every HTTP request;/statsincludes the health snapshot) and mapsSecurityError/ValidationErrorto 400,NoRouteErrorto 422,TargetUnavailableErrorto 503 andExecutionErrorto 502.DomainActionSignaldetects arithmetic and unit conversions (17% of 2,450,12 * 4,5 miles to km, percent / calculate / square root ...) as themathdomain, so calculator-style tools andmax_complexitymath rules fire; lexicon keywords made of symbols (%) now match.examples/targets.yaml: the public cloud models and the research agent carryconstraints.pii_allowed: false, making PII a hard policy stop instead of only a rule preference.
Hosted platform (platform/, separate from the zero-dependency SDK): platform/api (package
osr-platform-api, FastAPI) and platform/web (osr-platform-web, Next.js 16)
osr_platform.app.create_platform_app()/osr-platform-api: the routing API as a service:/api/v1/route(decision, signals, ranked breakdown, policy rejections;plan/execute),/feedback,/targets,/catalogue,/usage,/keys,/tenants,/stats,/audit, the OpenAI-compatible/v1/models+/v1/chat/completions(model: "auto"or a pinned target), and/api/v1/docsserving the repository Markdown (README, CHANGELOG,docs/) rendered to HTML with a table of contents and Mermaid blocks as JSON.OSR_PLATFORM_WEB_URL/OSR_PLATFORM_CORS_ORIGINSpoint/and Stripe redirects at the web app and allow it as a browser origin.- Web app: landing page with a live catalogue, documentation (sidebar, table of contents, Mermaid),
pricing, a playground (decision view with ranked utility bars, signals, policy rejections, plans,
execution; OpenAI-compatible chat with
autoor pinned model), signup / sign-in (key shown once, stored in the browser only) and a dashboard (overview with usage chart, API keys, usage by target and endpoint, tenants editor, audit trail with chain verification, plan and billing). Next.js route handlers proxy/api/*,/v1/*and/openapi.jsontoOSR_API_URL, so the browser only talks to the web origin; server components render from the API at request time. - API keys (
osr_live_..., SHA-256 at rest,Authorization: BearerorX-API-Key), plans (free / pro / enterprise with per-minute and per-day quotas, key and tenant caps, feature gates;OSR_PLATFORM_PLAN_OVERRIDES), usage metering per key / endpoint / target with tokens and cost, admin API (X-Admin-Token) and an optional Stripe checkout + signed-webhook billing hook that is inert untilOSR_PLATFORM_STRIPE_*is configured. State is SQLite (WAL, with a rollback-journal fallback for SMB mounts). - Editions:
OSR_PLATFORM_EDITION=communityruns the coreRouter;enterpriserunsRouterBuilderwith auto-learning persistence, health circuit breakers,GuardMiddleware, metrics and a hash-chainedFileAuditSink, and unlocks tenants (per-tenant data boundary / region / deny lists / cost caps applied as hard constraints),/statsand/audit(filtered per account). OSR_PLATFORM_PROVIDERSmaps targets to OpenAI-compatible providers (Azure OpenAI, OpenAI, vLLM, Ollama ...) throughadapters.openai_compat; targets without a provider stay routable as decisions.platform/api/Dockerfileandplatform/web/Dockerfile(non-root, health checks; the API keeps a/datavolume),infra/Bicep (Container Appsosr-api+osr-web, Azure Files, ACR, Log Analytics, Azure OpenAI with gpt-4.1 family deployments),azure.yamlforazd up, and thePlatformworkflow (API tests, web lint/typecheck/build, two-container smoke, OIDCazddeploy). Reference deployment:rg-osr-prodin Sweden Central.- Public catalogue and rankings, measured on the deployment rather than curated:
GET /api/v1/models(model cards with capabilities, constraints, pricing per 1k / 1M tokens, executable flag, requests, tokens, cost, success rate, latency, domains and week-over-week trend),GET /api/v1/models/{id}(daily series, health, effort siblings),GET /api/v1/rankings?days=&domain=(share of tokens and requests, trend, per-domain leaders) andGET /api/v1/stats/public(headline figures). Usage rows now storerequest_id,model,domainandcomplexity(additive SQLite migration) so every figure is reproducible from the same table that meters billing. GET /api/v1/activity: newest-first per-account request log withendpoint/target/keyfilters andbefore=cursor paging.POST /v1/chat/completionsextensions:models(candidate list; the router scores only those and falls back down the ranking when an upstream call fails, up to three times),stream: true(text/event-streamchunks,[DONE]terminator) and anosrobject (objective,constraints,tenant,plan,fallbacks). Theopensmartroutemetadata gainedplan,fallback_from,cost_usdandlatency_ms; responses carryX-Request-Id,X-OSR-Target,X-OSR-Confidence.- Web:
/modelsexplorer (search, domain, executable, kind and sort),/models/{id}pages with traffic chart, routed-domain bars, capabilities, constraints, examples and copyable call snippets for the live base URL,/rankingsleaderboard (window, metric and domain filters), dashboard/activitywith expandable request rows and feedback snippet, playground compare mode (up to four targets in parallel with cheapest / fastest badges and fallback markers;?model=deep link from model pages), and a landing page fed by the public stats (hero figures, most-routed targets, domain leaders, candidates + streaming sample, three-step getting started). Sitemap lists models and rankings. - Platform single sign-on:
GET /api/v1/auth/providers,GET /auth/{provider}/start?next=andPOST /auth/{provider}/callback(PKCE, HMAC-signed state, GitHub / Google / Microsoft / GitLab presets and custom OIDC issuers viaOSR_PLATFORM_SSO_*),osr_sess_browser sessions with 30-day TTL (GET /auth/sessions,POST /auth/logout?everywhere=,DELETE /auth/sessions/{id}), linked identities (GET /auth/identities,DELETE /auth/identities/{provider}; refuses to remove the only way in),/api/v1/mereportsviaandidentities,/api/v1/infolistsssoproviders. New SQLite tablesidentitiesandsessions(additive migration). - Web: "Continue with GitHub / Google / ..." on login and signup,
/auth/callbackexchange page (shows the first API key once when the account is created),/dashboard/account(profile, linked identities, browser sessions with revoke and sign out everywhere). Shared UI toolkit undercomponents/ui(Radix-based button variants, dialog, dropdown menu, tooltip, select, switch, progress, avatar, table, sonner toasts) and a Recharts chart kit undercomponents/charts(TimeBars,TimeArea,RankedBars,Donut,Sparkline) with one palette and shared tooltip/legend/empty states. Redesigned header (compact nav, Ctrl/Cmd+K catalogue search, account menu), light hero with a prompt box that opens the playground pre-filled (/playground?q=), rankings charts (share of requests, traffic by domain), dashboard usage stacked bars and quota progress, and a table view on/models. - Platform users, workspaces and roles (B2C and B2B): new SQLite tables
users,memberships,invitesandsso_connections,accounts.kind(personal/organization) andaccounts.slug; existing databases are migrated on start (every account becomes a personal workspace owned by its user).POST /signupand first SSO sign-in create the user plus a personal workspace;GET/POST /workspaces,POST /workspaces/{id}/switch,PATCH/DELETE /workspace,GET /workspace/members,PATCH/DELETE /workspace/members/{user_id}(owner > admin > member, last owner protected),POST/DELETE /workspace/invites(link shown once, 14-day TTL, seats =plan.max_members: free 3, pro 10, enterprise 500),GET /auth/invites/{token}andPOST /auth/invites/{token}/accept. Key and tenant mutations need admin; API keys act as owner of their workspace./meaddsuser,role,workspaces; usage and activity rows carryuser_id. Organization SSO (enterprise plan featuresso):GET/PUT/DELETE /workspace/ssostores the company IdP (Google, Microsoft, GitLab, GitHub or any OIDC issuer, allowed email domains, default role), served as providerorg-<slug>with just-in-time membership;GET /auth/discover?email=returns the sign-in options for an email domain. - Web: workspace switcher in the dashboard sidebar,
/dashboard/members(invite, roles, remove, pending invitations),/dashboard/workspace(create organization with its first key, rename/slug, organization SSO connection, leave),/invite/[token]acceptance page, and the sign-in page discovers company SSO from the typed email ("Continue with"). - Custom domains: Bicep parameters
webAliasDomains,apiCustomDomain,customDomainCertificates(managed certificates, TXT validation for the apex), outputs for the static IP and domain verification id;scripts/domains.ps1configures Cloudflare DNS, redirects (.comto.ai,wwwto apex) and SSL settings and binds the domains in two provision phases (seeplatform/README.md).
Changed#
- Web: the UI toolkit under
platform/web/src/components/uiis now the shadcn/uinew-yorkkit (components.json,radix-uiumbrella package,class-variance-authority,cmdk,vaul,tw-animate-css) with the brand tokens mapped onto the kit's CSS variables inglobals.css. Header (navigation menu with grouped product/resources panels, mobile sheet, command search), footer, dashboard shell (sidebar), docs sidebar (collapsible sections, mobile sheet) and version menu (dropdown) are rebuilt on it. Hand-rolled tables, filter chips, selects, switches, search inputs, stat cards, accordions and sliders across the marketing pages, models explorer/catalogue, marketplace, rankings, playground, estimator, ROI calculator and dashboard pages use the kit primitives (DataTable,StatCard,FilterChip,SearchInput,Selectwrapper,Switch,Progress,Accordion,Slider). The per-scope@radix-ui/react-*packages,cnandnext-themesdependencies are removed. Router(slot_quality_floor=, narrow_above=, narrow_to=)andRouterBuilder.with_retrieval(narrow_above=, narrow_to=)default toNone(= settings) instead of literals;Router.calibratordefaults toTemperatureScaler(settings.routing.softmax_temperature)and plan-slot margins use it instead of a private softmax.RouterBuilder.with_auto_learning(),with_health()andwith_queue_awareness()no longer inject hardcoded weights; the defaults come fromWeightSettings.MultiKnapsackBandit: the sliding-window spend meter is O(1) per step (running sums over a bounded deque); shadow prices are updated in budget units (avg / B - 1) so a budget expressed in tokens no longer blows the penalty up; the "release the cheapest arm" fallback only triggers when every arm is blocked, not when utilities happen to be negative.- Documentation: the README is a short overview (pitch, install, quick start, what it routes, how it
decides, production, security, documentation index, roadmap); the detailed walkthroughs (targets,
execution, real providers, learning, decorator SDK, enterprise builder, research-track modules,
CLI, routing latency, repository layout) moved to
docs/GUIDE.md.docs/ROADMAP.mdmarks every research-track row as implemented with its module;docs/RESEARCH.mdanddocs/MATH.mddescribe the corresponding techniques and formulas (sections 10 to 16).
Fixed#
PageHinkleyaccumulatedx - mean - delta, so a perfectly stationary quality stream alarmed afterthreshold / deltasamples andAutoLearnerreset healthy targets; the tolerance now has the textbook sign (+ delta) and a constant stream never drifts.Bandit.meanis abstract andLinUCB.mean()returns the intercept estimate instead of a stub.OpenAICompatClientderives itsUser-Agentfrom the package version instead of a literal.enterprise.ops.ABTest.observeno longer divides by zero when the control arm has no samples yet.server.py: request bodies were parsed as query parameters (HTTP 422) becausefrom __future__ import annotationsstringified the locally defined pydantic models; removed.execute()re-raised anExecutionErrorfrom a provider handler (upstream 4xx, malformed body) without recording a failed outcome, so learners and health never saw those failures.EnterpriseRouter.learn()with auto-learning bypassed the router's feedback store, task-credit buffer and pins (only the strategies were updated);RouterBuilder.with_feedback()no longer double-records through a learner sink.CacheMiddlewarekeyed only on text / constraints / objective, so a pinned (candidates=) orplan=Truecall could be served another call's cached decision; per-call routing options are now part of the key.
0.4.0 - 2026-09-04#
Added#
Execution and plans
Router.run(),AsyncRouter.run()andEnterpriseRouter.run()route and execute the resulting plan through the newopensmartroute.executionmodule: theskillslot's handler runs as a pre-processor (may return a rewrittenRouteRequest, astrattached ascontext["skill_output"], orNone), persona / skill / primaryinstructionsare composed intocontext["system"], the primary handler (LLM, skill or agent harness) is called, and oneOutcomeper participant is recorded and learned from. ReturnsExecutionResult(response, text, target_id, steps, system_prompt, effective_request, outcomes).RouteTarget.instructions(persona prompt, skill body or agent standing instructions, disclosed only when the target is part of the plan),RouteTarget.effort(reasoning-effort variant) andRouteTarget.family(siblings share breaker and budget state).Outcome.task_id,Outcome.roleandOutcome.stepso delayed, task-level rewards can be joined to every call in a trajectory and plan slots learn separately from the primary.RouteRequest.profile(user / tenant attributes) andRouteRequest.history(session turns) feedProfileSignalandHistorySignal.Router(slot_quality_floor=0.5): a persona / skill slot is only filled when its best candidate's quality estimate clears the floor.
Strategies and learning
TaskTableStrategy(static task-type table with Wilson intervals;fit_task_table()from outcomes),DeferStrategy(learning-to-defer forTargetKind.HUMAN),EffortStrategyanddecide_effort()(think vs. no-think),ProgressRouterandMultiRoundExecutor(progress-guided step routing and a Router-R1 style route / execute / judge loop),CascadePlannerwithBeliefTracker(cascade as a finite-horizon MDP or POMDP with a stop action).learning.TaskCredit(uniform / discounted / last / blended credit assignment from a terminal task reward),learning.TaskPins(admission-time pinning),ExampleMiner,RequestMemory,SimilarityFallback,target_embedding(),nearest_targets()andwarm_start()for cold-start targets.math.calibration:TemperatureScaler,IsotonicCalibrator,ConformalCalibrator(split conformal candidate sets with a coverage guarantee).Router(calibrator=, conformal=)exposes calibrated confidence andRouteDecision.candidate_set.- Forgetting (
decay < 1) inIRTModelandBradleyTerry;BradleyTerry.merge()for federated sufficient statistics; Sherman-Morrison rank-1 updates and surfaced posterior variance inLinUCB.
Signals
signals.ontology.TaskOntology(families, types, subtypes with seed templates) andsignals.models(HashedFeaturizer,HashedClassifier,HashedRegressor,synthesize_dataset()) behindTaskTypeSignal,LearnedDifficultySignal,ReasoningNeedSignal,OutputLengthSignal,SensitivitySignal; weights ship as JSON (SignalModelBundle) and load withRouter(extractors=learned_extractors(SignalModelBundle.from_file(path)))orosr --models path.osr train --out models.json [--from rows.jsonl] [--synth] [--gadget detector.json].
Catalogue interop and scale
opensmartroute.retrieval:BM25Index,DenseIndex,Retriever,rrf()(reciprocal-rank fusion),select_skill_set()(submodular skill-set selection under a token budget),tool_search_target()andexecute_tool_target()meta-tools.Router(retriever=, narrow_above=500, narrow_to=50)switches to retrieve-then-rank for large catalogues.adapters.mcp:tools_from_mcp(),connect_mcp()(stdio JSON-RPC),enrich_description(),sign_manifest()/verify_manifest()(HMAC-SHA256 or Ed25519),description_risk();osr mcp-manifest sign|verify.adapters.a2a:fetch_agent_card(),agent_from_card(),skills_from_card(),a2a_handler().adapters.frameworks:langgraph_node(),langgraph_condition(),maf_router_executor(),openai_tool_spec(),route_and_execute().adapters.personas:load_personas()/persona_target()from Markdown, JSON, CSV and Copilot*.agent.md/*.chatmode.mdfiles.- Agent-harness adapters:
AgentHarness,HarnessResult,CallableHarness,HTTPHarness(408 / 429 / 5xx map toTargetUnavailableError),SubprocessHarness,harness_handler(). - SKILL.md loader (
load_skill,load_skills,skill_from_markdown) for agentskills.io folders; works with or without PyYAML. - OpenAI-compatible proxy in the HTTP server:
POST /v1/chat/completions(model: "auto"routes, executes and learns) andGET /v1/models;GET /healthz.
Evaluation
eval.baselines: random, cheapest, best-prior, most-expensive, static task table, oracle,multi_sample_oracle()andnoise_floor();osr eval --baselines.eval.robustness:repeat_flip_rate(),paraphrase_robustness(),profile_swap_fairness(),coreset(),diversity();osr eval --robustness.eval.frontier:frontier3(),hypervolume(),ablation_report();osr eval --frontier3 --ablation.eval.ope: IPS, SNIPS and doubly-robust off-policy estimates with weight clipping and effective sample size;osr ope logs.jsonl.eval.datasets: presets for RouterBench, LLMRouterBench, RouterEval, xRouteBench and RouterXBench;osr eval --preset.calibration_report()(ECE, Brier, reliability bins);osr eval --calibration.osr eval --min-accuracy Xexits with status 2 below the threshold; CI uses it as the routing accuracy gate.
Enterprise
enterprise.stores:RedisStateStore,SQLStateStore(DB-API 2.0; PostgreSQL, SQLite, MySQL),VersionedStateStore(schema envelope with forward migrations),EncryptedStateStore(AES-256-GCM, key rotation;cryptoextra),BatchedStateStore(write-behind),NamespacedStateStore.RouterBuilder.with_state_store()persists learner state through any of them.enterprise.ops:ShadowMiddlewareandABTest(shadow or A/B routing judged by Wald's SPRT),FairShareMiddleware(dominant-resource fairness across tenants),InflightTrackerandQueueAwareStrategy(Erlang-C / Kingman wait estimates from live in-flight counts).RouterBuilder.with_shadow(),.with_fair_share(),.with_queue_awareness(),.with_calibration(),.with_retrieval(),.with_router_options().opensmartroute.settings: frozen, environment-overridableSettings(OSR_<GROUP>_<FIELD>) for every tunable constant;opensmartroute.brandingcentralises package identifiers.deploy/: non-root referenceDockerfile,entrypoint.shand a Helm chart (Deployment, Service, Ingress, HPA, ConfigMap). Releases publishghcr.io/isathish/opensmartroute:<version>.
Security
security.gadget.GadgetDetector(learned confounder-gadget classifier;InputGuard(learned=True)),security.injection.injection_risk()andinspect_injection(),security.limits.ResourceLimiterandResourceLimitMiddleware(per-task caps on steps, tool calls, depth, tokens, cost, wall-clock),security.provenance.OriginPolicy(sensitive parameters of state-changing tools must originate from the user turn),security.safety.run_safety_suite()(red-team routing cases);osr safety [--learned-guard]exits with status 2 on any failure.
Project
- Public API snapshot test (
tests/public_api.json,tests/test_public_api.py): adding or removing an exported name fails CI until the snapshot is updated deliberately. - Release automation:
scripts/release.py(version consistency, changelog sections, release preparation),Prepare releaseandReleaseworkflows (release PR, tag, GitHub release with changelog notes, PyPI trusted publishing with PEP 740 attestations, SLSA build provenance, GHCR image), CodeQL for Python and workflows, Dependabot for actions and Python dependencies. - CI matrix covers CPython 3.10 to 3.13 on Linux plus 3.12 on Windows, checks formatting, builds and smoke-tests the container image.
examples/end_to_end.py,examples/skills/sql-reporting/SKILL.md;instructionson the example personas and skills.
Changed#
Router.execute()/AsyncRouter.execute()keep returning the raw handler response but now go through the plan-aware executor.EnterpriseRoutergainsexecute()(returnsExecutionResult).load_targetsandload_rulesare exported from the package root.- Project metadata: repository URLs point at
isathish/OpenSmartRoute; classifiers declare CPython 3.13 andTyping :: Typed; the sdist no longer ships brand assets and fonts.
Fixed#
config.load_documentand the eval dataset loader read files as UTF-8 explicitly; on Windows the locale default (cp1252) failed on the shippedexamples/targets.yaml.
0.3.0 - 2026-09-04#
Added#
opensmartroute.adapters: stdlibOpenAICompatClient(timeouts, exponential backoff withRetry-After, typed error mapping, key never logged) withjudge_fn,embedder,chat_handlerglue; lazysentence_transformers_embedderandOpenTelemetryTelemetry.opensmartroute.config:load_targets/load_rules/load_documentwithConfigurationErrorreporting file, entry and reason.TargetRegistry.from_filenow delegates to it.scripts/bench.py— reproducible routing-latency benchmark;scripts/brand_build.py— logo system generator with WCAG 2.2 audit and platform-standard exports.- Brand identity: rounded-geometric wordmark (Quicksand) and Poppins text, vendored under OFL; light and dark palettes verified ≥ 4.5:1 text / ≥ 3:1 graphics.
docs/ROADMAP.mdwith per-release exit criteria;docs/RESEARCH.mdrewritten as a survey with a formal problem statement, taxonomy, systems comparison and idea→module map.
Fixed#
HealthPolicyconsumed a rate-limit token for every admissible candidate on every route; it now only checks availability, andEnterpriseRouterreserves one token for the chosen target./feedbackendpoint droppedcomplexity, so IRT never learned from HTTP feedback.
Changed#
cli._load_rules(private, but used by examples) replaced by publicconfig.load_rules.- Removed the earlier gradient ring / wordmark SVGs in favour of the generated system.
0.2.0 - 2026-09-04#
Added#
opensmartroute.math: Thompson/UCB1/LinUCB/ε-greedy bandits, cost-aware Lagrangian wrapper, IRT (2PL) model, Bradley–Terry + Elo, Markov chain + routing MDP (value iteration), EWMA/Welford, Page–Hinkley and ADWIN-lite drift, Wilson interval, Pareto/TOPSIS, Erlang-C / Little's law / Kingman queueing.opensmartroute.learning:IRTStrategy,PreferenceStrategy,LinUCBStrategy,MarkovStrategy,AutoLearner(fan-out + drift detection + atomic persistence).opensmartroute.realtime: circuit breaker, token-bucket rate limit, rolling budget,HealthRegistry,HealthPolicy,HealthStrategy.opensmartroute.enterprise:RouterBuilder,EnterpriseRouter, middleware chain (CacheMiddleware,TenantMiddleware,TimeoutMiddleware), telemetry ports (LoggingTelemetry,MetricsTelemetry),StateStoreports, hash-chainedFileAuditSink.opensmartroute.security:InputGuard(confounder-gadget detection),Redactor,GuardMiddleware,sanitize_for_prompt,load_secret.opensmartroute.aio.AsyncRouter.- Typed exception hierarchy in
opensmartroute.errors;py.typedmarker. - Pinned rules (
pin: true) andprimary: falseplan-only targets. - CI (ruff, mypy, bandit, pytest, eval gate), SECURITY.md, CONTRIBUTING.md.
Changed#
- Lexicon matching is now whole-word (regex
\b) — fixes false positives likesqlinaustralia. - Cost/latency normalisation is log-scaled; default
Objectiveweights lowered. NoRouteErrornow derives fromOpenSmartRouteErrorand carriesdetails["rejections"].
0.1.0 - 2026-09-04#
- Initial release: core types, registry, signals, policy, rules/capability/similarity/bandit/
llm-judge/cascade strategies, router with plans, feedback store, eval harness, CLI, FastAPI server.
Predates the repository history: the code is the initial commit tagged
v0.2.0.