> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nimbusbci.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Overview

> Wrap any frozen encoder, personalize online with a Bayesian head, and ship apps on BrainState — the nimbus-bci product wedge.

Nimbus middleware is the **product wedge**: a Bayesian personalization head for **frozen neural / foundation embeddings**. Encoders stay external. Apps consume thin `BrainState` outputs.

**One-liner:** anything that maps trials to embeddings plugs in; Nimbus owns personalization, uncertainty gating, and cheap online updates — not the trunk.

<Tip>
  **Product takeaway (Paper 1):** ship one Personalizer; treat fine-tune as a **priced escape hatch**, not the default integration path. Use the head where embedding capacity exists; stay cal-only when clean already wins.
</Tip>

<img src="https://mintcdn.com/nimbus-e9e10bf8/ORAHLwKs0-fyBfKL/images/personalizer-api.png?fit=max&auto=format&n=ORAHLwKs0-fyBfKL&q=85&s=ff6e5dd2fbd3e08967c8e542f62cb83f" alt="One API across many trunks: frozen trunk epochs to embeddings, Personalizer Bayesian head, app state intent + uncertainty" width="2144" height="672" data-path="images/personalizer-api.png" />

*One Personalizer API across trunks — from the Universal Personalizer manuscript (Paper 1).*

<img src="https://mintcdn.com/nimbus-e9e10bf8/uBHvLDYqiHy1Z__n/images/personalizer-ft-trunks.png?fit=max&auto=format&n=uBHvLDYqiHy1Z__n&q=85&s=c632ae0021b4aab82b00271c0899712c" alt="Core: L0 / Bayesian head / linear probe / full FT (severe · n=40)" width="2591" height="1825" data-path="images/personalizer-ft-trunks.png" />

*Why the hatch stays priced: across classical trunks, stream Personalizer (L1) recovers most of the L0→L3 gain and usually beats a matched linear probe — details on [Evidence](/personalizer/evidence).*

## Ladder legend (plain language)

| Label | What it is | Shipped? |
| - | - | - |
| **L0 — calibration-only** | Fit the Bayesian head on calibration data once; no stream updates | Yes — fit once |
| **L1 — stream head** | L0 + online head updates on new labeled trials | Yes — default product path |
| **LP — linear probe** | Logistic regression on the same frozen embeddings | Bench control (not a Personalizer head) |
| **L2 — affine map + head** | Supervised affine map on embeddings, then the head | Opt-in via `transform="affine"` / `fit_affine()` (research / offline; not deployment default) |
| **L3 — full fine-tune** | Warm-start fine-tune of the encoder | Escape hatch (outside Personalizer; priced); `[train]` PEFT / BaLoRA opt-in |

Blue `rec %` on the figure = how much of the **L0→L3 accuracy gain** L1 recovers: `(acc_L1 − acc_L0) / (acc_L3 − acc_L0)`. `≥FT` means L1 matches or beats L3.

How the Paper 1 / 1b numbers were produced (datasets, bandpass, session split, synthetic stress): [Evidence → How the experiments work](/personalizer/evidence#how-the-experiments-work-data-flow).

<Info>
  Classical CSP / bandpower / Riemann features still work. Use `Personalizer.for_features(...)` or the standalone heads (`NimbusLDA`, …) documented under [Models](/model-specification). Upgrading from 0.5? See [Migration to 0.6](/python-sdk/migration-0.6).
</Info>

## Start here

<Columns cols={2}>
  <Card title="Encoder contract" icon="plug" href="/personalizer/encoder-contract">
    `wrap` → Personalizer → save/load rules.
  </Card>

  <Card title="BrainState" icon="brain" href="/personalizer/brain-state">
    App-facing intents, presets, and bridges.
  </Card>

  <Card title="Evidence" icon="chart-line" href="/personalizer/evidence">
    Head vs probe vs FT, LDA/QDA/Softmax, REVE, transfer map.
  </Card>

  <Card title="Paradigms & profiles" icon="route" href="/personalizer/paradigms">
    MI / P300 / Riemann recipes and day-2 profiles.
  </Card>

  <Card title="Transforms" icon="sliders-horizontal" href="/personalizer/transforms">
    Embedding-space alignment before the head.
  </Card>

  <Card title="Python Quickstart" icon="rocket" href="/python-sdk/quickstart">
    Fit a Personalizer in a few minutes.
  </Card>
</Columns>

## Minimal example

```python theme={null}
from nimbus_bci import Personalizer, wrap

enc = wrap(model.encode, model_id="partner-encoder", embedding_dim=64)
# for_deployment = strict gating + LDA (common production case).
# Pin accept rate with fit_calibrated(...) when default gates don't match your trunk.
adapter = Personalizer.for_deployment(
    enc, ["left", "right"], paradigm="motor_imagery"
)
adapter.fit(X_cal, y_cal)
states = adapter.predict(X_test)  # list[BrainState] or BrainStateBatch
```

Other factories: `for_research` (permissive + knobs), `for_features` (no trunk), `for_riemann` / `for_p300` (`nimbus-bci[riemann]`), `for_balora` (`nimbus-bci[train]`). Paradigm recipes and day-2 profiles: [Paradigms & profiles](/personalizer/paradigms).

## Integrator checklist

1. **When to adapt** — prefer the adaptation gateway, or the thin mean-shift helper with a **user-supplied** threshold:

```python theme={null}
# Label-agnostic gateway (head / transform / trunk PEFT)
adapter.adapt(X_new, y_new)  # or y=None for unsupervised paths

# Optional: decide whether to spend labels on a stream update
dec = adapter.recommend_adapt(X_stream, tau=your_tau)
if dec.should_adapt:
    adapter.partial_fit(X_lab, y_lab)
```

Policy: stay on the current head when clean; stream-update when shift clears `tau`; full fine-tune stays a manual escape hatch. Portable day-2 warm-start: `export_profile()` → `from_profile(...)`. See [Evidence](/personalizer/evidence).

2. **Trial gate (different signal)** — `preset="strict"` / `"permissive"` (0.6 rename; was `research` / `consumer`); inspect `BrainState.rejected`, `need_more_data`, `uncertainty`. See [BrainState](/personalizer/brain-state).

3. **Head stability** — after online updates, use `calibration_sufficient` / `calibration_status` on an unlabeled pool.

4. **Encoder swap** — freeze trunk; match `embedding_dim`; never pass logits as embeddings; keep `model_id` consistent across `save` / `load` / profiles. See [Encoder contract](/personalizer/encoder-contract).

## Next read

<Columns cols={2}>
  <Card title="Encoder contract" icon="plug" href="/personalizer/encoder-contract">
    Wrap rules and save/load.
  </Card>

  <Card title="BrainState" icon="brain" href="/personalizer/brain-state">
    App-facing intents and presets.
  </Card>

  <Card title="API Reference" icon="book" href="/python-sdk/api-reference#middleware">
    Personalizer and middleware symbols.
  </Card>

  <Card title="Heads / Models" icon="box" href="/model-specification">
    LDA, QDA, Softmax, STS.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.