ultrarouter docs
Concepts

The Flywheel

How ultrarouter turns the outcomes your product already generates into tested, customer-owned improvements — the six channels, the loop, and the laws that keep every claim honest.

Preview — not on the hosted service

The data flywheel and the learning loop run on the local stack only. They are off on the invite-only hosted gateway (the learning.enabled master switch), there is no hosted MCP endpoint, and whether learning improves a given agent is not yet demonstrated. See the evidence status.

Your product already generates the signal that says whether each answer was any good: a reviewer edits the draft, a tool call fails, a user retries, a forecast resolves true or false against a filing months later. The Flywheel is the ledger that captures those outcomes, joins them to the answers that produced them, and — only once you confirm what "it worked" means — turns them into rules and examples that your app can consult, tested behind a standing holdout and exported back to you.

This page is the map. It describes what is built and where the honest boundary sits, and it links to the concept and guide pages that go deep on each stage.

The Flywheel is a preview that runs on the local stack. The mechanisms below — the outcome ledger, the discovery projection, the confirm compiler, the learned-context lever, the live-confirmation estimator, the adoption lease and the training-ready export converter — are all built and covered by the guides. What is not yet a one-click button, a hosted URL or a shipped surface is called out where it belongs, never dressed up as done. GET /v1/flywheel/status reports its learning fields as explicitly unavailable while a workspace is still collecting — a collecting state never implies an improvement occurred.

The loop

The Flywheel is one loop with six stages. Each stage is a page you can read on its own.

  1. Discover — from your traffic alone, ultrarouter surfaces one workflow worth learning and drafts what "it worked" could mean. Nothing is learned yet. See Discovery.
  2. Declare the objective — you confirm the one thing the product refuses to infer: the metric that resolves a workflow, its direction, source and window. See Declare what "it worked" means.
  3. Capture outcomes — the ledger records typed outcome events over six channels, instant or delayed, declared or inferred, and joins each to its artifact. See Record outcomes and Capture your first correction.
  4. Distill, behind a holdout — consented, teaching-eligible outcomes become redacted domain facts and a learned-context module, retrieved for opted-in requests and skipped for the sacred standing holdout. See Holdout and lift.
  5. Confirm live, then adopt — a screened candidate clears your own application CI, then a randomized live contrast against a frozen baseline. Adoption is a revocable lease. See Confirm a candidate in your own app.
  6. Export — the whole thing becomes a training-ready dataset you own. Whether ultrarouter trains a model for your workspace is a separate, governed decision (train_allowed), never a shared or cross-workspace model — and its switches are not built yet. See The training-ready export.

You can also arrive sideways: plan a model migration imports your usage history and scopes a model swap, reusing the same ledger and evidence engines.

The six outcome channels

Every outcome the ledger records lands on exactly one channel — the closed set that answers "what kind of signal is this?" (doc 16 §5.1):

ChannelWhat it capturesTeaches?
mechanicalSchema validity, tool-call success/failure, parse and test results — things the machine can check.Yes (model-class tool errors teach; infra errors audit only).
correctionA human's approved final vs the draft — an edit ratio, changed spans, a hash.Yes.
resolutionA claim resolved true / false / partial against the world, often much later.Yes.
computableAn oracle or counterfactual result — a deterministic check with a value.Yes.
behavioralAccepted, retried, abandoned, escalated, copied, converted — what the user did next.No — ranks only.
proxyEngagement, thumbs, dwell — soft signals.No — ranks only.

The line between the two halves is a law, not a preference: proxies rank; they never teach. A behavioral or proxy row carries a server-assigned learning_use of rank, and calibration, distillation and the export refuse it at the schema boundary. Ranking a candidate is allowed; teaching a lesson from a thumbs-up is not.

Declared ≠ inferred ≠ gateway

The ledger also records where a signal came from, in a separate source column that is rolled up and displayed separately, never blended:

  • declared — your app told us, over the outcome API.
  • inferred — a versioned rule read it out of your agentic traffic (a tool error message, a user quoting-and-editing the prior turn). An inferred event never overrides a declared one for the same target and kind, and an inferred event from an unqualified rule can only be audited.
  • gateway — the gateway already knew (a JSON-schema failure, a streaming error), recorded best-effort and never billed.

source is the server's to assign from your credential and the rule that fired; a caller-supplied source, producer or learning_use is a 400, never a silent strip. That is why a captured signal always means exactly what its provenance says.

The two laws that make it trustworthy

Two guarantees are the reason a Flywheel claim is worth anything. Both are enforced in code, not by review.

A proposed objective never teaches

A discovery proposal is a draft. It is inert. It cannot influence a single request until you confirm it — and that is a compiler guarantee, the strongest true claim in these docs. The trigger compiler (packages/core/src/flywheel/plan/confirm.ts) accepts only a ConfirmedPlan, whose fields are typed origin: Exclude<PlanFieldOrigin, 'drafted'>. An LLM-drafted value is not assignable into a confirmed plan; a proposed objective reaching the compiler is a compile error, not a runtime refusal that has to fire. Downstream, every learning consumer — rollup, distillation, the lever, the export, search — keys on the confirmed objective version and reads the compiled program from the versioned config snapshot, never the proposal rows (a grep gate allows exactly three files to name that table, and none of them is a learning consumer). There is no path from a proposal to a lesson that skips your confirmation.

The standing holdout is sacred

A fixed slice of your traffic is carved into a standing holdout and served the frozen baseline forever, so there is always an untouched comparison. No lever code path ever reads holdout traffic except to skip it. This is not a filter someone can forget: the exemplar index that feeds the learned-context lever is arm-typed — its container keys are treated | pre_lever | imported, so a holdout or lever_holdout candidate is a compile error, and the fail-closed bucketer throws rather than drop one silently. Holdout and lift covers the arms and how lift is measured.

Adoption is a lease, not a switch

When a live confirmation shows a candidate is better, adopting it does not flip a permanent flag. Adoption is a lease: it needs live evidence and a scarce confirmation slot, and it expires. A background sweep runs every 15 minutes; an adopted lever whose lease has lapsed and not been renewed is reverted to the frozen baseline automatically. The failure mode of a forgotten adoption is "it stopped", never "it kept running unwatched." You renew or roll back on the Flywheel screen.

Self-improve — one setting, three modes

Each workflow (call type) has one self-improve setting with three modes, set on the call type's Self-improve card (or through PATCH /v1/call-types/:id):

  • Off — capture outcomes only. Nothing is proposed or adopted. Your ledger is still yours.
  • Assist (the default when the flywheel is on) — evaluate usage, build the knowledge DB, propose what "good" means, test candidate improvements on your eval sets, and push the ones that passed to your Improvements inbox. The ones that failed are kept as internal knowledge ("tried, did not help") — visible on Your knowledge DB. A human adopts.
  • Autopilot — everything Assist does, plus it adopts a confirmed improvement automatically, within guardrails, after the live confirmation passes. You are notified.

The autopilot guardrail contract. Autopilot adopts only when every guard holds: the live confirmation cleared the decision intent's quality bar (the lower confidence bound above the registered bar) and the cost per successful task is within the budget you set (no budget registered means autopilot will not auto-adopt — it fails closed), the A/A instrument is passing, the standing holdout and canary are in place, and a rollback path and an expiring lease exist. It applies per surface (knowledge injection today; model routing and hosted prompts are accepted but not yet wired) and only to gateway-owned modules — code-owned prompts never auto-adopt; they keep the pull-request path. Autopilot uses the exact same adoption path a human press uses (one engine, no second verdict), so canary, rollback, lease expiry and the sacred standing holdout are unchanged. Default is off, one toggle per call type.

The confirmation read (GET /v1/flywheel/candidates/:ref/confirmation) shows the mode, who adopted (system:autopilot when the platform did), and — when autopilot did not fire — the named guard that stopped it, so nothing is hidden.

What is billed, and what is not

Capturing outcomes is not billed per event — artifact and outcome counts are operational counters, not charges, and there is no per-correction fee. What is billable is bounded analysis and approved evaluation compute, reserved and settled through the existing budget engine, with failed attempts counted in actual cost and retries never double-charged. GET /v1/flywheel/status shows the four quantities separated — tracked artifacts, accepted outcomes, analysis units, evaluation compute — so capture is never confused with spend. Test-mode keys (ur_sk_test_*) are captured but excluded from learning, invoicing and your allowance.

The milestones, and where each is documented

The Flywheel ships in stages, and each guide is honest about the boundary of its milestone:

MilestoneWhat you can doGuide
M0Import usage history and scope a model migration.Plan a model migration
M1Declare an objective; capture reviewer corrections joined to the original output; import history.Capture your first correction · Record outcomes · Import history
M2Carry a screened candidate through your own application CI and the rollout gate.Confirm a candidate in your own app
M3Export a training-ready dataset you own.The training-ready export

What you will see next — named, not hidden

Some surfaces a reader might reach for are not built yet, and this page names them rather than implying they exist:

  • The knowledge API and the docs-MCP search_knowledge tool — a way to browse the domain facts the distiller mints — are not shipped. When they land you will be able to read your workspace's derived facts (redacted statistics, never bodies) over the API and search them from an agent; today those facts feed the lever internally.
  • Cross-tenant priors — learning that borrows strength across workspaces — are not part of the Flywheel. A workspace's raw items never cross a workspace boundary; the only consented cross-tenant corpus is Bench, and it is governed separately.
  • The one-click "start rollout" / "adopt" press is a human dashboard action owned by a later packet; the rollout gate today reports readiness (pressed is always false) and never presses.
  • Not on the hosted gateway yet — the learning preview is off on the invite-only hosted gateway (https://api.ultrarouter.io), and there is no hosted MCP endpoint. Run the stack locally (http://localhost:8080) or self-host.

Next steps

  • Read Discovery for how a workflow is found and the "proposed never teaches" law in full.
  • Read Holdout and lift for the standing holdout, the exposure arms and how a real improvement is proven.
  • Walk Record outcomes end to end — objective, instant, delayed, batch, warehouse backfill and flush — in TypeScript, Python and curl.