## Retraining


## retrain.lexical.retrain_gate()


One gate retrain pass; returns the journaled one-line verdict.


Usage

``` python
retrain.lexical.retrain_gate(
    *,
    force=False,
    fresh_epoch=False,
    dataset_dir=None,
    eval_root=None,
    registry_root=None,
    state_dir=None
)
```


`fresh_epoch` gates the candidate through the no-incumbent promotion path (`~cc_steer.retrain.promotion.gate_promotable()` with no incumbent), ignoring the incumbent's metrics for the one-shot clean-slate cutover. The gate lane keeps no probs store, so there is no reuse guard here (unlike the watcher lane).


## retrain.watcher.retrain_watcher()


One watcher retrain pass; returns the journaled one-line verdict.


Usage

``` python
retrain.watcher.retrain_watcher(
    *,
    force=False,
    fresh_epoch=False,
    recipe,
    dataset_dir=None,
    eval_root=None,
    registry_root=None,
    state_dir=None,
    adapters_dir=None
)
```


Skips when the watcher train view is unchanged and not forced. Otherwise translates the recipe into a `~athome.train.spec.TrainSpec`, hands training, checkpoint selection, and materialization to `athome.train.retrain()`, scores the frozen frame through the served artifact, and gates on those served probs -- on a pass registering, promoting, and seeding the new version's served probs before kicking the live watch agent. Every branch -- skip, spend-cap reject, gate reject, promote -- journals exactly once, and a serving-drift diagnostic persists beside the artifacts for every candidate that materializes, behind a boundary that keeps its own failure off the gate outcome. Every incumbent-relative comparison also persists both arms' per-row probs and the paired-DeLong record (delta, 95% CI, measured rho, and the instrument-card verdict) beside the artifacts, so a reject leaves the comparison auditable.

`fresh_epoch` is the one-shot clean-slate cutover: the incumbent-relative gate (`~cc_steer.retrain.promotion.corrected_gate()`) is skipped entirely -- the candidate promotes on the served AUC floor alone (finite and above chance on the clean frame). It first refuses via `FreshEpochError` if any registered version already carries probs for the current frozen frame, so it can only run once per frame.


## retrain.watcher.WatcherRecipe


Every knob of one watcher LoRA retrain, validated at parse so no degenerate value trains.


Usage

``` python
retrain.watcher.WatcherRecipe(
    tinker_model,
    mlx_id,
    rank,
    learning_rate,
    batch_size,
    epochs,
    checkpoint_fracs,
    max_tokens,
    render_version,
    val_n,
    oversample_corrective,
    budget_fires_per_100,
    spend_cap_usd,
    diagnostic_rows,
    diagnostic_tolerance,
    seed
)
```


The packaged default (`cc_steer/assets/watcher_recipe.json`, via [default()](retraining.md#cc_steer.retrain.watcher.WatcherRecipe.default)) is the E8-winner recipe. An override JSON must carry every field (missing or extra keys crash) and clears the same validation bar.


#### Attributes


`tinker_model: str`  
The Tinker base model id to train the LoRA over.

`mlx_id: str`  
The local 4-bit MLX id the converted adapter serves against.

`rank: int`  
The LoRA rank.

`learning_rate: float`  
The AdamW learning rate.

`batch_size: int`  
The datums per optimizer step.

`epochs: int`  
The passes over the pool; steps derive from it.

`checkpoint_fracs: tuple[float, …]`  
Fractions of the run to checkpoint and score at.

`max_tokens: int`  
Datums longer than this are dropped before batching.

`render_version: int`  
The prompt-rendering contract stamped into the registry metadata.

`val_n: int`  
The target size of the carved val slice that ranks checkpoints.

`oversample_corrective: float`  
The factor corrective positives are oversampled to.

`budget_fires_per_100: float`  
The alert budget the served threshold is fitted at.

`spend_cap_usd: float`  
The hard Tinker spend cap; a projected overspend never launches.

`diagnostic_rows: int`  
The label-stratified rows the serving-drift diagnostic samples Tinker-vs-served.

`diagnostic_tolerance: float`  
The absolute nosteer-prob gap above which a diagnostic row counts as drifted (never blocks).

`seed: int`  
The seed threaded through every deterministic step.


#### Methods

| Name | Description |
|----|----|
| [default()](#cc_steer.retrain.watcher.WatcherRecipe.default) | Parse the packaged E8-winner recipe shipped in the wheel (`cc_steer/assets/watcher_recipe.json`). |
| [from_json()](#cc_steer.retrain.watcher.WatcherRecipe.from_json) | Parse an override recipe; missing or extra keys crash, and every value is validated. |


##### default()


Parse the packaged E8-winner recipe shipped in the wheel (`cc_steer/assets/watcher_recipe.json`).


Usage

``` python
default()
```


##### from_json()


Parse an override recipe; missing or extra keys crash, and every value is validated.


Usage

``` python
from_json(path)
```


## retrain.watcher.register_watcher_adapter()


Register (and by default promote) a built mlx-lm adapter as the `watcher` component.


Usage

``` python
retrain.watcher.register_watcher_adapter(
    adapter_dir, *, metadata, promote=True, root=None
)
```


`adapter_dir` must hold the mlx-lm pair (`adapters.safetensors` + `adapter_config.json`); `metadata` must carry the keys `cc_steer.watcher.drafter_mlx` serves from: `base_model`, `thresholds`, and `render_version`.


## retrain.watcher.seed_incumbent_probs()


Validate an external incumbent probs cache against the frozen frame, then write it through the store.


Usage

``` python
retrain.watcher.seed_incumbent_probs(
    path, *, version, expected_render, eval_root=None
)
```


The cache is the lab's flat `{row_id: P(NO_STEER)}` map. It must cover the current frame exactly -- a missing row is incomplete, a foreign row means it was scored against a drifted eval. Its render is `expected_render`, the incumbent's OWN contract from its registry metadata: a migrated incumbent is scored under the render it serves (the E12 precedent), and the next retrain's `~cc_steer.retrain.evalset.load_probs()` verifies against the same metadata. On success it is written through `~cc_steer.retrain.evalset.write_probs()`, stamped with the frame digest.


## retrain.evalset.freeze_eval()


Copy the exported `<view>/test.parquet` into the eval root and merge its sha256 manifest.


Usage

``` python
retrain.evalset.freeze_eval(view="watcher", *, dataset_dir=None, root=None)
```


Freezes either the `gate` or the `watcher` eval into `<view>_eval.parquet`, keeping the sibling view's manifest entry intact. Idempotent for identical bytes; raises `FrozenViolationError` before writing anything when the frozen file exists with different content, `EmptyEvalContext` -- naming the offending row ids -- when any row's rendered context has no substantive content, and `SplitLeakError` when a sibling `train.parquet` shares any row's exact text with the eval (the E43 train/eval leak), so an invalid eval can never be frozen. Any dedup sidecar the eval builder wrote and the disjointness check's counts merge into the manifest under `<name>.meta`. Returns the frozen file's sha256.


## retrain.evalset.freeze_steer_type()


Build then freeze the steer-type eval; returns the frozen file's sha256.


Usage

``` python
retrain.evalset.freeze_steer_type(
    *,
    dataset_dir=None,
    root=None,
    seed=1729,
    embed=None,
    semantic_threshold=SEMANTIC_THRESHOLD
)
```


## retrain.evalset.freeze_pick()


Build then freeze the pick-prediction eval; returns the frozen file's sha256.


Usage

``` python
retrain.evalset.freeze_pick(
    *,
    decisions_path=None,
    dataset_dir=None,
    root=None,
    seed=1729,
    embed=None,
    semantic_threshold=SEMANTIC_THRESHOLD
)
```


## retrain.evalset.EvalFrame


The frozen watcher eval as the arrays the promotion gate reads.


Usage

``` python
retrain.evalset.EvalFrame(ids, labels, corrective, prose, tails, digest)
```


#### Attributes


`ids: tuple[str, …]`  
The row ids, in file order (probability arrays align to this order).

`labels: np.ndarray`  
`True` for true-steer rows (should fire).

`corrective: np.ndarray`  
`label & category != "direction"` -- a true corrective steer.

`prose: np.ndarray`  
`source_kind != "question_answer"` -- not an option-picking QA event.

`tails: tuple[str, …]`  
The render-v2 flattened context tail per row (the watcher's input).

`digest: DatasetDigest`  
The eval's order-invariant content digest, stamped into the probs store.


#### Methods

| Name | Description |
|----|----|
| [load()](#cc_steer.retrain.evalset.EvalFrame.load) | Build the frame from the frozen `watcher_eval.parquet` under `root`. |


##### load()


Build the frame from the frozen `watcher_eval.parquet` under `root`.


Usage

``` python
load(*, root=None)
```


## retrain.evalset.SteerTypeFrame


The frozen steer-type eval as the arrays a category classifier is scored on.


Usage

``` python
retrain.evalset.SteerTypeFrame(ids, texts, categories, digest)
```


#### Attributes


`ids: tuple[str, …]`  
The row ids, in file order.

`texts: tuple[str, …]`  
The role-blocked classifier input per row.

`categories: tuple[str, …]`  
The judge's eleven-way category label per row (one of `STEER_TYPE_CATEGORIES`).

`digest: DatasetDigest`  
The eval's order-invariant content digest.


#### Methods

| Name | Description |
|----|----|
| [load()](#cc_steer.retrain.evalset.SteerTypeFrame.load) | Build the frame from the frozen `steer_type_eval.parquet` under `root`. |


##### load()


Build the frame from the frozen `steer_type_eval.parquet` under `root`.


Usage

``` python
load(*, root=None)
```


## retrain.evalset.PickFrame


The frozen pick-prediction eval as the arrays an option classifier is scored on.


Usage

``` python
retrain.evalset.PickFrame(ids, texts, chosen, n_options, digest)
```


#### Attributes


`ids: tuple[str, …]`  
The row ids, in file order.

`texts: tuple[str, …]`  
The rendered ask -- question and options -- per row.

`chosen: np.ndarray`  
The user's chosen option index per row.

`n_options: np.ndarray`  
The number of options offered per row (the prediction's valid range).

`digest: DatasetDigest`  
The eval's order-invariant content digest.


#### Methods

| Name | Description |
|----|----|
| [load()](#cc_steer.retrain.evalset.PickFrame.load) | Build the frame from the frozen `pick_eval.parquet` under `root`. |


##### load()


Build the frame from the frozen `pick_eval.parquet` under `root`.


Usage

``` python
load(*, root=None)
```


## retrain.promotion.corrected_gate()


Evaluate the corrected paired gate over common rows at matched budget.


Usage

``` python
retrain.promotion.corrected_gate(
    candidate_fire_scores,
    incumbent_fire_scores,
    *,
    candidate,
    incumbent,
    incumbent_fire_threshold,
    labels,
    warranted,
    harmful_favors_incumbent=None
)
```


Every score input has an explicit higher-is-fire contract. Callers starting from a no-fire probability must orient it before calling this function. The incumbent fires strictly above `incumbent_fire_threshold`; the candidate is then matched conservatively to that fire count. `warranted` selects the caller-defined stratum in which discordant coverage pairs count.


#### Parameters


`candidate_fire_scores: np.ndarray`  
Candidate scores where larger values mean fire.

`incumbent_fire_scores: np.ndarray`  
Incumbent scores where larger values mean fire.

`candidate: str`  
Candidate name recorded in the result.

`incumbent: str`  
Incumbent name recorded in the result.

`incumbent_fire_threshold: float`  
Strict lower bound for incumbent fires.

`labels: np.ndarray`  
Binary labels used for both sentinel AUC calculations.

`warranted: np.ndarray`  
Boolean mask selecting rows where coverage wins and losses count.

`harmful_favors_incumbent: bool | None = None`  
Deferred harmful-fire judgment, or None while pending.


#### Returns


<a href="retraining.html#cc_steer.retrain.promotion.GateResult" class="gdls-link gdls-code"><code>GateResult</code></a>  
Every gate component and the promotion verdict when harmful judging exists.


## retrain.promotion.GateResult


The corrected paired gate over a candidate and incumbent at matched budget.


Usage

``` python
retrain.promotion.GateResult(
    candidate,
    incumbent,
    coverage_wins,
    coverage_losses,
    coverage_sign_p,
    coverage_sig,
    budget_held,
    cell_auc,
    incumbent_auc,
    auc_not_regressed,
    harmful_favors_incumbent,
    promote
)
```


#### Attributes


`candidate: str`  
The candidate's name.

`incumbent: str`  
The incumbent's name.

`coverage_wins: int`  
Warranted rows where only the candidate fires.

`coverage_losses: int`  
Warranted rows where only the incumbent fires.

`coverage_sign_p: float`  
Exact sign-test p-value over discordant coverage pairs.

`coverage_sig: bool`  
Whether coverage significantly favors the candidate.

`budget_held: bool`  
Whether candidate fires do not exceed incumbent fires.

`cell_auc: float`  
The candidate's sentinel AUC.

`incumbent_auc: float`  
The incumbent's sentinel AUC.

`auc_not_regressed: bool`  
Whether candidate AUC is at least incumbent AUC.

`harmful_favors_incumbent: bool | None`  
Whether harmful-fire judging favors the incumbent, or None while judging is pending.

`promote: bool | None`  
The full verdict, or None while harmful-fire judging is pending.


## retrain.promotion.Verdict


One promotion decision: whether to promote and the human-readable reason.


Usage

``` python
retrain.promotion.Verdict(promote, reason)
```


#### Parameter Attributes


`promote: bool`  

`reason: str`
