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

# ci.wrap

> Opt-in scikit-learn instrumentation by wrapping an estimator.

Wrap a scikit-learn estimator (or `Pipeline`) with a transparent proxy
that opens scopes around `fit`, `predict`, `transform`,
`fit_transform`, `predict_proba`, and `score`. sklearn has no callback
API, so instrumentation is opt-in via explicit wrapping.

## Signature

```python theme={null}
def wrap(estimator: Any) -> Any
```

## Parameters

| Name        | Type | Purpose                                              |
| ----------- | ---- | ---------------------------------------------------- |
| `estimator` | any  | An sklearn estimator or `sklearn.pipeline.Pipeline`. |

Any non-sklearn object passes through unchanged (documented
passthrough).

## Behavior

The returned object is a thin proxy:

* `fit`, `predict`, `transform`, `fit_transform`, `predict_proba`,
  and `score` are wrapped. Each opens a named scope around the call.
* All other attribute access delegates directly to the underlying
  estimator. `hasattr`, `isinstance`, and pickling work normally.
* For `Pipeline`, each step is wrapped recursively so per-step scopes
  appear as children of the pipeline's top-level scope.

## Examples

### Single estimator

```python theme={null}
from sklearn.ensemble import RandomForestClassifier
import cirron as ci

ci.profile()
model = ci.wrap(RandomForestClassifier(n_estimators=100))

model.fit(X_train, y_train)     # opens a `fit` scope
pred = model.predict(X_test)    # opens a `predict` scope
score = model.score(X_test, y_test)
```

### Pipeline

```python theme={null}
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

pipe = ci.wrap(Pipeline([
    ("scale", StandardScaler()),
    ("lr",    LogisticRegression()),
]))

pipe.fit(X, y)     # fit scope at the top; each step fits inside its own scope
```

### Combined with `ci.scope`

```python theme={null}
with ci.scope("grid-search"):
    for params in param_grid:
        model = ci.wrap(RandomForestClassifier(**params))
        model.fit(X, y)
        ci.mark("score", model.score(X_val, y_val))
```

## Version support

Requires `scikit-learn >= 1.3`, the floor declared by the
`cirron-sdk[sklearn]` extra. The proxy works with the classic
estimator API (`fit` / `predict` / `transform` / `fit_transform` /
`predict_proba` / `score`) and with `sklearn.pipeline.Pipeline`.
The newer `set_output` API is orthogonal; wrapping does not
interfere with configured output transforms.

## Why it's opt-in

sklearn has no callback or hook API comparable to Keras callbacks or
HuggingFace's `TrainerCallback`. Auto-wrapping every `BaseEstimator`
subclass would be invasive and prone to breaking user code. Explicit
`ci.wrap(estimator)` is the least-surprise approach.

## Related

<CardGroup cols={2}>
  <Card title="ci.scope" icon="brackets-curly" href="/sdk/reference/scope">
    The primitive wrap builds on; use directly for non-sklearn code.
  </Card>

  <Card title="Profiling guide" icon="chart-line" href="/sdk/profiling">
    Narrative walk-through of training instrumentation.
  </Card>
</CardGroup>
