The training-ready export
What the Flywheel export is, what `not_trainable` means, and the consent that gates it. Training on your content is a separate, governed decision — it runs only for your own workspace, never a shared or cross-workspace model.
The Flywheel's last stage turns your consented outcome ledger into a training-ready dataset: same-prompt supervised (SFT) and preference examples with pinned versions, an executable grader, and immutable content-addressed splits. The export is yours, and whether ultrarouter trains a model from your content is a separate, governed decision described below.
This page explains what the export contains, why some examples are labelled
not_trainable, the consent that has to be on before a single example is produced, and
how any training that happens is governed.
The export converter and its guarantees are built — the conversion, the trainability classifier, the immutable splits, the readiness report and the consent-revocation tombstones are all in code and covered by tests. What is a later surface is the packaged download from the dashboard; where this guide reaches that step it says what you will see, never a fabricated button. The consent below is live today.
How training is governed
The no-training promise was reversed on 2026-09-20. The contract now is exact:
We process authorized workspace content to train and evaluate models exclusively for your workspace. We do not use it to train shared models or another customer's models. Approved subprocessors, retention, disabling and deletion terms are disclosed.
Training is allowed only when every one of six conjuncts holds, each failing closed on missing evidence:
train_allowed = platform_enabled AND workspace_enabled AND contract_authorized
AND source_eligible AND retention_valid AND provider_approved- platform_enabled / workspace_enabled — a
superadmin-only platform switch (default on) and a customer-visible per-workspace switch (default on for new workspaces under a training agreement accepted at signup). A superadmin may turn training off globally but can never override a customer's OFF. - contract_authorized — a versioned training agreement accepted for the workspace, plus the applicable processing authority for the people in the data (recorded per jurisdiction, never assumed). Content captured under the previous no-training promise needs source-linked renewed authorization before it may train.
- source_eligible — workspace ownership, current source consents, validated train-eligible labels, and both holdouts excluded.
- retention_valid — checked for every contributing source.
- provider_approved — the operation, environment, region, subprocessors, no-reuse terms and regulated scope. A BAA alone is not sufficient; regulated / PHI workspaces stay off until an approved scope and contract chain exists.
No lever and no trainer ever reads holdout traffic. Training authorization is separate from
training_export_consent: enabling the export never authorizes training, and neither permission
silently grants the other. Raw items never cross a workspace.
What is built, and what is not. The two switches and the train_allowed enforcement are built
(row TRN0b): the superadmin-only platform switch (default on) lives in the admin console, the
per-workspace switch (default on) is on your Flywheel screen, and train_allowed is one pure
function enforced at every gate. Turning the workspace switch off — or withdrawing consent —
invalidates the authorization epoch: serving of that workspace's adapters stops within one 5 s
window, active jobs are cancelled, and a stale provider callback can never restore an artifact
(not even after training is re-enabled). What is not built is a trainer: no training run of
any kind executes yet. Until a governed trainer lands (row TRN1), no customer-data training run
happens, and the grep-level guarantee (TST-FLW-TRAINING-GOVERNED) holds that no production code
path launches an ungated training job. The export is a dataset a customer can also train on
themselves; your data stays tenant-isolated.
What the export contains
The converter (@ultrarouter/core#buildTrainingExport) produces:
- SFT examples — a same-prompt completion, model-attributed, with the exact prompt/ config version and the recipe version the reference was derived under pinned to each row.
- Preference examples — chosen/rejected pairs from your corrections.
- An executable grader spec — reference values plus the closed FLW-24 predicate
(
equals,within_tolerance,ge,le,resolved_by_source) a consumer runs against a candidate model's output. The grader is reused for the test split. - Immutable, content-addressed splits — every prompt's split is a deterministic bucket
of
(dataset salt, prompt hash):train [0,80),validation [80,90),test [90,100). Re-running over the same inputs reproduces a byte-identical bundle; a changed input yields a new hash and a new version, never a mutated one. - A readiness report — counts, coverage and limitations. It states what the dataset can and cannot support; it never claims that learning or improvement occurred.
What not_trainable means
not_trainable is a first-class label, never a silent drop. Every candidate becomes an
exported example; one that cannot honestly be trained on is exported carrying its reason,
so a consumer can count what it cannot use rather than wonder why rows went missing. A
not_trainable example carries no training target — the SFT completion or preference
pair is simply absent.
The reason vocabulary is closed, and it fails closed (an undeterminable state maps to the most conservative reason, never to "trainable"):
| Reason | Why it cannot teach |
|---|---|
proxy_only | A proxy channel row — proxies rank, they never teach. |
rank_only | A behavioral (or otherwise rank-carrying) row — a ranking signal, not a label. |
audit_only | learning_use is audit (infra/unclassified tool error, or an unqualified inferred rule). |
holdout_arm | The artifact's exposure arm is the sacred standing / lever holdout — the holdout never teaches. |
replay_arm | The FLW-75 replay hindcast arm — it ranks, never teaches. |
mixed_arm | A mixed artifact — its requests disagree, so it is ineligible for a target. |
unqualified_extractor | The teaching label came from an unqualified extractor — never a training label. |
missing_consent | Consent for the source was withdrawn. |
no_reference | No accepted/corrected reference output exists to learn from. |
The rule underneath the table is the same law that runs through the whole Flywheel:
proxies rank; they never teach. A training target can only be minted through the
by-construction teaching gate — a proxy or behavioral row is structurally incapable of
producing one, so the label and the target can never disagree. The export teaches only from
the arms the rest of the system teaches from: treated, pre_lever and imported; the
holdout, lever_holdout, replay and mixed arms are absent by construction.
The consent that gates it
The export is owner-gated and off by default. Two consents matter:
training_export_consent— allows the training-ready export to be produced at all.corrections_consent— required for the stored final bodies the SFT targets are drawn from.
Both are owner-only, audited, and bump the config version. Set them on the Flywheel screen, or over the API (a signed-in owner session, not a raw key):
# Session-authenticated, owner-only. Enables the export and pins corrections retention.
curl -sS -X PATCH "$UR_BASE_URL/v1/workspaces/ws_…/flywheel-consents" \
-H "Cookie: better-auth.session_token=$UR_SESSION" \
-H "Content-Type: application/json" \
-d '{ "training_export_consent": true, "corrections_consent": true }'Revocation reaches the export. The bundle carries its consentLineage — the consents
its sources derive from — so revoking a consent tombstones every export whose lineage
crosses it. Revocation stops new derivations and marks existing exports; it never rewrites
what was already produced. Neither consent defaults on, and both are revocable.
What you will see next
The converter, the trainability classifier, the split rule, the readiness report and the
revocation tombstones are built and tested. The packaged retrieval — a downloadable
dataset from the Flywheel screen, with its readiness report and not_trainable breakdown —
is published as that surface ships. Until then, the substance is real: the consent gate is
live, the conversion is deterministic and content-addressed, and the export/training
separation holds today — enabling the export never authorizes training.
What this proves — and what it does not
Proves. Your consented outcome ledger can become a portable, versioned, content-addressed
training dataset you own — with every untrainable example labelled and counted, teaching
confined to the non-holdout arms by construction, and revocation reaching existing exports.
No production code path launches an ungated training job (TST-FLW-TRAINING-GOVERNED).
Does not prove. An export is a dataset, not a result. It does not demonstrate that training on it improves anything — proving that is a separate, governed step (yours to run, or ours per-workspace once the switches ship). The readiness report states counts and limitations, never a lift.
Next steps
- The Flywheel — where the outcomes the export draws from are captured.
- Holdout and lift — why the holdout and
replayarms are excluded from every training target. - Record outcomes — the capture that fills the ledger the export reads.
Plug in your decision record
Turn the approve/reject record you already keep into correction outcomes with ZERO integration — a decision connector pulls your owner decisions, matches each delivered item to the draft the gateway captured, and records the result. Plus the experimental echo measurement and what it does and does not see.
The context runtime
Deterministic context assembly — lift, compile, render and a pure exit — plus compatibility profiles, memory classes, the lazy SOP catalog and native-delivery adapters. Adopt in one file, leave in one file.