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
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.
- Discover — from your traffic alone, ultrarouter surfaces one workflow worth learning and drafts what "it worked" could mean. Nothing is learned yet. See Discovery.
- 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.
- 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.
- 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.
- 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.
- 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):
| Channel | What it captures | Teaches? |
|---|---|---|
mechanical | Schema validity, tool-call success/failure, parse and test results — things the machine can check. | Yes (model-class tool errors teach; infra errors audit only). |
correction | A human's approved final vs the draft — an edit ratio, changed spans, a hash. | Yes. |
resolution | A claim resolved true / false / partial against the world, often much later. | Yes. |
computable | An oracle or counterfactual result — a deterministic check with a value. | Yes. |
behavioral | Accepted, retried, abandoned, escalated, copied, converted — what the user did next. | No — ranks only. |
proxy | Engagement, 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
toolerror 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:
| Milestone | What you can do | Guide |
|---|---|---|
| M0 | Import usage history and scope a model migration. | Plan a model migration |
| M1 | Declare an objective; capture reviewer corrections joined to the original output; import history. | Capture your first correction · Record outcomes · Import history |
| M2 | Carry a screened candidate through your own application CI and the rollout gate. | Confirm a candidate in your own app |
| M3 | Export 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_knowledgetool — 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 (
pressedis alwaysfalse) 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.
Discovery
How ultrarouter finds a workflow worth learning from your own traffic — the two a-ha moments, the channel ladder, and the law that a proposed objective never teaches.
Hosted prompts
Host your prompt as a versioned object per call type — fetch it from preflight or the SDK, or let the gateway apply it when proxying. A prompt we host is a prompt we can manage.