Data Science Python Stack
Opinionated stack — one library per job, organized into four tiers
plus an orthogonal agent feature:
- Mandatory — installed at project start, no exceptions.
- User choice (competing-library jobs) — multiple valid libraries
for the same job; the user picks via
AskUserQuestion before any
import lands.
- Optional — install only when the project's task requires it.
- Transitive — already pulled in by the mandatory tier; do not
install explicitly, but know they're available.
- Agent feature (orthogonal) — deps that the agent uses
to audit a workspace and to power the editor LSP integration
(
ipython, pyright), kept out of the production-shape
runtime via a manager-specific scope. Install logistics owned
by python-env-manager § "Agent feature"; consumed by
audit-ml-pipeline and the opencode LSP integration.
Stop conditions — read before naming any library
- No silent pick on a competing-library job. Whenever the stack
offers two or more libraries for the same job (see § "Competing
libraries — general rule" and the Tier 2 table), the user picks
via
AskUserQuestion before any Write that imports the library
and before any install command runs. "Already pulled in
transitively" / "user said 'quick'" / "the folder has no
preference signalled" are not waivers. A silent pick is a
Stop-condition violation, full stop.
- No substitute when import fails. When code in this stack needs
a library but
import fails, install it; do not rewrite to a
non-stack equivalent (see § "Missing dependency"). The most
common silent-rewrite path —
import skrub fails → rewrite as sklearn.Pipeline,
import skore fails → rewrite as cross_val_score —
silently undoes the workflow skills' contract.
- Harness-level "no clarifying questions" hints do NOT waive the
competing-library
AskUserQuestion. The Tier 2 pick is an
operating-contract gate, not a clarifying question. The same
applies to user urgency phrasing: "quick baseline", "just do it",
"go fast", "you pick", "whatever" do NOT resolve a competing-
library gate. See § "Free-text resolution" in the general rule
below for what does resolve a gate.
- Post-hoc audit — required before ending the turn. Before
declaring the turn complete, verify each competing-library job
invoked in this turn has either (a) an
AskUserQuestion answer
recorded this session, or (b) a matching row in
journal/JOURNAL.md Status Workspace decisions. If any
competing-library job ran without one of those, surface the
non-compliance to the user explicitly as part of your final
message — do not hide it.
Forbidden shortcuts (observed in real traces)
| Shortcut |
Why it feels right |
Why it's wrong |
pandas is already pulled in by skore → skip the Tier 2 ask |
"Free" library, no install needed |
Tier 2 is a project-shape decision (every data.py signature, every fixture); transitive presence is not a pick |
User said "quick baseline" → assume pandas |
Task urgency reads as permission |
Urgency phrasing never waives a competing-library gate (Stop conditions above) |
| Folder has no existing tabular code → infer pandas |
"No preference signalled" |
Inference is a silent pick; the gate requires a structured ask or a recorded JOURNAL.md decision |
One competing option requires a new pixi add → pick the "free" one |
Avoids an install step |
Install cost is not the criterion; project fit is |
User picked pytorch last project → reuse without asking |
Continuity is friendly |
Each workspace records its own Workspace decisions; cross-project memory is forbidden |
Competing libraries — general rule
This is the meta-rule that governs every "user choice" entry in
this skill. It applies to the Tier 2 table below and to any new
competing-library job added in the future. It also applies inside
Tier 3 when two optional libraries cover the same job (e.g.
pytorch vs keras as the deep-learning framework).
The rule
Whenever the stack offers two or more libraries for the same job:
AskUserQuestion before any import or install. Use the
options listed for the job in the competing-jobs table; do not
editorialize the option labels.
- Persist the answer in
journal/JOURNAL.md Status under
Workspace decisions. This block is immutable until the user
explicitly pivots. On future sessions, read Status first;
do not re-ask a recorded decision. The persistence contract
lives in iterate-ml-experiment's JOURNAL.md template — the
Workspace decisions block is the source of truth for cross-
session continuity.
- No silent default. Even when one option is "free"
(already pulled in transitively) and the other costs an
install, never pick silently. The free option becoming the
pick is fine; the picking happens via
AskUserQuestion.
Free-text resolution
A user message resolves a competing-library gate only if it
names one of the listed options for the job. Apply in priority
order:
- Exact match (case-insensitive, whitespace-trimmed) to an
option label: resolves the gate. ("use polars", "let's go
with pytorch", "pandas please" → resolved.)
- Library named in a free-text intent ("rewrite the loader
in polars", "I want a keras model"): resolves the gate for
that job.
- No library named ("make it fast", "you pick", "whatever",
"no preference", "quick baseline"): does NOT resolve.
Fall through to the structured
AskUserQuestion.
- "You pick" / "no preference" specifically — surface the
default-on-no-preference for the job (from the Tier 2
table) and ask for confirmation. Never silently pick; never
skip the confirmation step.
Adding a new contested job
When a new job appears in the stack with two viable libraries,
add a row to the Tier 2 competing-jobs table. Every row must
name an explicit Default-on-no-preference — rows without one
are forbidden, because they re-create the silent-pick loophole
this rule exists to close. If a sensible default cannot be
named, the job does not belong in the table; surface the gap to
the user and pick per-project via a free-form AskUserQuestion.
When to invoke this skill
Two events trigger this skill before any other action:
- A library import fails in the stack's domain. The answer is
install (see § "Missing dependency" below), never substitute.
- A library choice has to be made — for tabular data at project
start, or any time code is about to introduce a new dependency
(deep learning, model serving, notebooks, …).
In both cases, read the whole SKILL.md before deciding. The tier
structure below determines whether a library should already be
present, needs a user prompt, or is opt-in — that decision can't be
made from a single index entry.
Missing dependency — install, do not substitute
When code in this stack needs a library but import fails, the answer
is install it, not substitute. Specifically:
- Surface the missing dependency to the user with the exact install
command. Invoke
python-env-manager to detect the project's
environment manager (pixi / uv / poetry / hatch / conda / pip+venv)
and produce the right install command — don't infer the manager
from memory; the project may not use the default. Stop and wait
for confirmation before doing anything else.
- Do not rewrite the code to use a non-stack equivalent
(
sklearn.Pipeline for skrub, cross_val_score + handwritten
metric prints for skore. Substitution silently breaks the contract
that the workflow skills (build-ml-pipeline,
evaluate-ml-pipeline, organize-ml-workspace) rely on.
- This rule overrides "make the code run". If the user prefers a
substitute, they will say so — until they do, install. Reaching
for a substitute because the dependency is missing is the most
common way the stack gets silently undone, so treat the missing
import as a hard stop.
How to use this skill
- Read this whole SKILL.md before picking — the tier structure
determines whether the library should already be installed, needs
a user-choice prompt, or is opt-in.
- Match the task to an entry in the right tier.
- Read the linked
references/<library>.md for the chosen library's
scope and tradeoffs before introducing it.
- Install via
pixi by default. If the project already uses a
different manager (pip+venv, uv, conda), follow that instead.
- Don't substitute libraries silently. If no entry fits the task,
surface the tradeoff to the user.
Tier 1 — Mandatory (install at project start)
These five libraries are always installed in a data-science / ML
project. The first three co-own the modeling workflow:
scikit-learn provides the estimators, skrub provides the
data-cleaning + DataOps layer that sits before them, skore
evaluates the result and persists it as a project on disk. The
fourth, ruff, owns lint + format and is non-negotiable: every
project Claude touches should pass ruff check. The fifth,
pytest, runs the smoke test that every approved experiment
must have per the test-ml-pipeline / smoke-test-ml-pipeline
contract — without pytest the smoke-test gate can't enforce
predict-time correctness, so pytest stays mandatory even when
no other tests have been written yet. Each is named explicitly
even when transitively present, because the workflow skills
(build-ml-pipeline, evaluate-ml-pipeline,
python-code-style, test-ml-pipeline) depend on them
directly and should not silently lose them if upstream packaging
changes.
scikit-learn — tabular ML
algorithms, preprocessing, model-selection helpers. Use
HistGradientBoosting{Classifier,Regressor} instead of pulling in
xgboost or lightgbm. Evaluation, cross-validation reports, and
model comparison are owned by skore — don't inline
cross_val_score / classification_report for analysis output.
skrub — wrap custom dataframe operations
in a sklearn-compatible computation graph that replays
deterministically across train and test splits. Use for the
data-cleaning + feature-engineering layer that sits before the
sklearn pipeline.
skore — predictive-model evaluation built
on top of scikit-learn (evaluate, EstimatorReport,
CrossValidationReport, ComparisonReport) and experiment
tracking via the Project API (skore.Project(...),
project.put(...), project.get(...)). Replaces ad-hoc
cross_val_score + handwritten metric printouts; replaces
mlflow for tracking. Brings numpy, pandas, matplotlib,
seaborn, plotly, joblib, and others transitively (see
Tier 4) — so static and interactive plotting are available
without any extra install.
Install variant per mode. skore.Project(...) supports three
mutually exclusive modes: local (artifacts on disk; no extra
deps), hub (artifacts on Skore Hub; requires skore[hub]
extra + skore.login() before first use), mlflow (artifacts
in an MLflow tracking server; requires the skore[mlflow] extra
plus an explicit mlflow>=3 pin — skore[mlflow] alone can
resolve an unsupported mlflow 2.x).
The choice is a workspace-level decision owned by
organize-ml-workspace § "G-SKORE-MODE" — fired at scaffold
alongside G-PKG-NAME / G-TABULAR / G-ENV-MGR. python-env-manager
§ "Tier 1 install: skore variant per mode" maps the recorded
decision to the right install command per env manager.
Default-on-no-preference: local.
ruff — single-tool lint + format,
replaces black / isort / flake8 / pydocstyle. Install in
the same feature/env as the rest of the Tier 1 stack so
pixi run ruff works without extra activation. The
configuration (rule selection, numpydoc convention, per-file
ignores) and the rule "Claude runs ruff after generating code"
are owned by the python-code-style skill, which also ships the
canonical ruff.toml template.
pytest — test runner for the
smoke-test gate enforced by test-ml-pipeline /
smoke-test-ml-pipeline. Every approved experiment must have a
passing tests/smoke/test_NN_<short_name>.py before its row
in JOURNAL.md can flip to done; pytest is what runs that
test, so the dependency is non-negotiable even on workspaces
that haven't authored any tests yet. Install in the same
feature/env as the rest of the Tier 1 stack.
Tier 2 — Competing-library jobs (user choice)
Jobs in this tier have more than one valid library in the
stack. The user picks via AskUserQuestion before any import or
install (see § "Competing libraries — general rule" above).
Recorded picks live in journal/JOURNAL.md Status Workspace decisions and are read first on every subsequent session.
Competing-jobs table
| Job |
Options |
Default-on-no-preference |
| Tabular dataframe |
pandas (+ pyarrow), polars |
pandas (free via skore) |
| Deep-learning framework |
pytorch, keras (multi-backend) |
pytorch |
| sklearn-compatible DL wrapper |
skorch (pytorch-only), keras (sklearn-compatible API) |
skorch |
| Static vs interactive plotting |
matplotlib / seaborn, plotly |
task-driven — ask which output shape the user wants |
| Model serving / registry |
mlflow.pyfunc + registry, FastAPI + joblib |
mlflow |
Per-option detail:
- Tabular dataframe
pandas (+ pyarrow) — established tabular library;
pyarrow is the recommended Parquet engine + Arrow-backed
dtype backend. pandas is already pulled in by skore
(Tier 4), so the only explicit install for this option is
pyarrow if Parquet IO is in scope. See
pandas /
pyarrow.
polars — Arrow-native tabular library; faster on
large frames, stricter type system. Requires an explicit
install (not pulled in by anything in Tier 1). See
polars.
- Deep-learning framework
pytorch — tensor library with GPU / MPS support and
autograd. Default DL framework in the stack. See
pytorch.
keras — high-level, layer-oriented DL API;
multi-backend (pytorch / TensorFlow / JAX). See
keras.
- sklearn-compatible DL wrapper
skorch — wraps a PyTorch nn.Module so it behaves
like a sklearn estimator (fit / predict, GridSearchCV,
pipelines). See skorch.
keras — exposes a sklearn-compatible API directly
via keras.wrappers.SKLearnClassifier /
SKLearnRegressor. See
keras.
- Static vs interactive plotting — both are already
available transitively via
skore (matplotlib, seaborn,
plotly all land without an explicit install). The ask is
which output shape the user wants for this project, not
which install to run. Pick by output medium: static reports
/ papers / static skore reports → matplotlib + seaborn;
interactive notebooks / dashboards → plotly.
- Model serving / registry — only relevant when the
project's roadmap includes serving a trained model. Skip the
gate entirely if serving is out of scope. When it is in
scope,
mlflow is the default (registry + REST out of the
box); FastAPI + joblib is the lighter custom path.
How a Tier 2 gate fires in practice
- Project start (bootstrap). Every Tier 2 job the project
touches must have a pick recorded in
Workspace decisions
before the matching code is written. The tabular gate fires
on every project; the others fire only when the project's
roadmap brings them in scope.
- Mid-project (new job comes into scope). When a new Tier 2
job becomes relevant (e.g. the project pivots to add a DL
model), the gate fires at that point; the existing
Workspace decisions block is amended with the new row.
- Mid-project (user wants to pivot). Tier 2 decisions are
immutable unless the user explicitly says so. A pivot is a
user-driven event; the skill never auto-pivots even if a
newer library would obviously be a better fit.
Tier 3 — Optional (install on demand)
Add these only when the task calls for them. Do not pre-install.
Deep learning
For NLP, computer vision, or any task where deep learning is the
right tool. None of these are mandatory; reach for them only when
the project's modeling task requires DL.
The which library pick (pytorch vs keras as framework, skorch
vs keras as sklearn-compatible wrapper) is a competing-library
job — owned by Tier 2. This section only covers when to
reach for DL at all; the framework choice has its own row in the
Tier 2 competing-jobs table and fires an AskUserQuestion the
first time DL comes into the project's scope.
Per-library reference material:
pytorch — tensor library with GPU /
MPS support and autograd; also the GPU alternative to numpy
for raw numerical work.
keras — high-level, layer-oriented
deep-learning API; multi-backend (pytorch / TensorFlow / JAX).
skorch — wraps a PyTorch nn.Module
so it behaves like a sklearn estimator (fit / predict,
GridSearchCV, pipelines).
Model serving
The which library pick (mlflow.pyfunc vs FastAPI +
joblib) is a competing-library job — owned by Tier 2. This
section only covers when serving is in scope.
Per-library reference material:
mlflow — model packaging, registry,
and REST serving (mlflow.pyfunc, mlflow models serve).
Use only for serving and registry concerns; tracking
belongs to skore.
Notebooks
For notebook-based work, prefer Python files with # %% cell
markers (jupytext percent format) over .ipynb files. Python
files are diffable and version-control friendly; jupytext handles
the conversion to/from notebook format when needed.
jupyterlab + ipykernel
— ambient in the dev feature (alongside ruff + pytest,
per python-env-manager § "Where does the package belong?").
Always installed; no per-project ask. The reference pages
describe the tools' role, not an opt-in install.
jupytext — Tier 3 opt-in: sync
.ipynb ↔ .py (# %% markers) so the notebook source-of-
truth stays version-control friendly. Install only when the
project wants .ipynb interop with the # %% scripts.
Tier 4 — Transitive (already pulled in; do not install explicitly)
These land in the env as runtime dependencies of the mandatory tier
(or of the chosen tabular library). Documented here so you don't
add a redundant explicit dependency, and so you know what's
available without an extra install.
numpy — N-d arrays, numerical
primitives. Pulled in by scikit-learn and skore.
scipy — scientific computing on top of
numpy (stats, optimize, sparse, signal). Supports the array API.
Pulled in by scikit-learn.
matplotlib — static plotting
foundation. Pulled in by skore (via seaborn).
seaborn — static statistical plots
(distributions, regression, faceting). Pulled in by skore.
plotly — interactive plots (hover,
zoom, pan); browser-based, suited for dashboards and exploratory
notebooks. Pulled in by skore — interactive viz is free, no
extra install needed.
Agent feature — orthogonal to the four tiers
The audit flow owned by audit-ml-pipeline and the editor LSP
integration both need agent-only tooling (ipython + pyright).
These deps don't fit cleanly into the four tiers above:
- They are not Tier 1 mandatory — workspaces that don't run
audits and don't use opencode's LSP never need them.
- They are not Tier 2 user-choice — there is no competing-
library job; this is the one toolchain that powers the
in-process audit runner AND the LSP.
- They are not Tier 3 optional in the same sense as
pytorch / mlflow — the agent feature is agent-orthogonal
tooling, not a modelling library.
- They are not Tier 4 transitive — nothing in Tier 1 pulls
them in.
So they live in their own bucket: the agent feature, a
manager-scoped install that composes alongside (not replaces)
the data-science deps.
| Library |
Role |
ipython |
Powers the shared cell runner audit-ml-pipeline/scripts/run_cells.py via IPython.core.interactiveshell.InteractiveShell.run_cell. Executes # %% cells in-process (audit files AND explore-ml-data's data/eda.py), captures plain-text repr per cell. |
pyright |
Powers the opencode LSP integration for Python files. Surfaces import / type / undefined-symbol diagnostics in the editor. Configured via the bundled pyrightconfig.json template (shipped by python-env-manager). |
Install + config drop: owned by python-env-manager § "Agent
feature". That skill carries the per-manager install table
(pixi features / uv groups / poetry groups / hatch envs / conda
envs / pip+venv extras) and the pyrightconfig.json placement
step.
Consumed by audit-ml-pipeline and the LSP. When either
consumer fires and the agent feature isn't present, the calling
skill routes through python-env-manager's G-AGENT-FEATURE
gate before proceeding.
No kernel registration. The in-process runner doesn't need a
Jupyter kernel.
Distinct from the dev feature's notebook tooling. jupyterlab
ipykernel are ambient in dev for interactive notebook
editing; jupytext stays Tier 3 opt-in. The agent feature
(ipython + pyright) is its own bucket — ipython powers the
in-process audit runner (no kernel), not user-facing notebook work.
A workspace may have any combination of the three concerns.
Conventions
- Environment manager: detection + install commands are owned by
the
python-env-manager skill — invoke it for any add / remove /
upgrade. Default recommendation is pixi; if the project
already uses a different manager (uv / poetry / hatch / conda /
pip+venv), python-env-manager's detection table picks it up
and never substitutes one manager for another.
- Versions: don't pin unless the user asks or there's a known
incompatibility. Exception —
skore and skrub must always be
the latest available release.
- One tool per job: don't introduce a second library for a task
already covered without explicit user request. (One library can
own multiple jobs —
skore covers both evaluation and tracking.
The rule forbids piling a second tool onto a covered job, not a
single tool covering multiple jobs.)
- Line width: wrap text at 88 chars where natural. Don't compress
content to fit; long inline links and code spans are fine to leave
on longer lines.
1---2name: data-science-python-stack3description: Opinionated Python stack for data-science / ML work — one library per job, organized into tiers (mandatory / user choice / optional / transitive). SKILL.md is the index; per-library `references/<library>.md` files carry scope, "pick this when" / "pick something else when", and pairings. TRIGGER when (any of these): (1) **a library import fails** in this stack's domain — the answer is install, not substitute (see § "Missing dependency"); (2) **a library choice has to be made** — explicitly (the user asks "which library for X?") or implicitly (code is about to introduce a new dependency, or the project is being scaffolded and the tabular library hasn't been picked yet); (3) starting a new Python data-science / ML project; (4) the user or current code reaches for a substitute outside the stack (xgboost, lightgbm, black, isort, flake8, poetry, hatch), or reaches for `mlflow` to log params/metrics, or for `cross_val_score` + handwritten reporting — redirect: tracking → `skore` Project API, evaluation / reporting →4---56# Data Science Python Stack78Opinionated stack — one library per job, organized into four tiers9plus an orthogonal **agent feature**:10111. **Mandatory** — installed at project start, no exceptions.122. **User choice (competing-library jobs)** — multiple valid libraries13 for the same job; the user picks via `AskUserQuestion` before any14 import lands.153. **Optional** — install only when the project's task requires it.164. **Transitive** — already pulled in by the mandatory tier; do not17 install explicitly, but know they're available.185. **Agent feature (orthogonal)** — deps that the *agent* uses19 to audit a workspace and to power the editor LSP integration20 (`ipython`, `pyright`), kept out of the production-shape21 runtime via a manager-specific scope. Install logistics owned22 by `python-env-manager` § "Agent feature"; consumed by23 `audit-ml-pipeline` and the opencode LSP integration.2425## Stop conditions — read before naming any library2627- **No silent pick on a competing-library job.** Whenever the stack28 offers two or more libraries for the same job (see § "Competing29 libraries — general rule" and the Tier 2 table), the user picks30 via `AskUserQuestion` before any `Write` that imports the library31 and before any install command runs. "Already pulled in32 transitively" / "user said 'quick'" / "the folder has no33 preference signalled" are **not** waivers. A silent pick is a34 Stop-condition violation, full stop.35- **No substitute when import fails.** When code in this stack needs36 a library but `import` fails, install it; do not rewrite to a37 non-stack equivalent (see § "Missing dependency"). The most38 common silent-rewrite path —39 `import skrub` fails → rewrite as `sklearn.Pipeline`,40 `import skore` fails → rewrite as `cross_val_score` —41 silently undoes the workflow skills' contract.42- **Harness-level "no clarifying questions" hints do NOT waive the43 competing-library `AskUserQuestion`.** The Tier 2 pick is an44 operating-contract gate, not a clarifying question. The same45 applies to user urgency phrasing: "quick baseline", "just do it",46 "go fast", "you pick", "whatever" do NOT resolve a competing-47 library gate. See § "Free-text resolution" in the general rule48 below for what *does* resolve a gate.49- **Post-hoc audit — required before ending the turn.** Before50 declaring the turn complete, verify each competing-library job51 invoked in this turn has either (a) an `AskUserQuestion` answer52 recorded this session, or (b) a matching row in53 `journal/JOURNAL.md` Status `Workspace decisions`. If any54 competing-library job ran without one of those, surface the55 non-compliance to the user explicitly as part of your final56 message — do not hide it.5758## Forbidden shortcuts (observed in real traces)5960| Shortcut | Why it feels right | Why it's wrong |61|----------|--------------------|----------------|62| `pandas` is already pulled in by `skore` → skip the Tier 2 ask | "Free" library, no install needed | Tier 2 is a *project-shape* decision (every `data.py` signature, every fixture); transitive presence is not a pick |63| User said "quick baseline" → assume `pandas` | Task urgency reads as permission | Urgency phrasing never waives a competing-library gate (Stop conditions above) |64| Folder has no existing tabular code → infer pandas | "No preference signalled" | Inference is a silent pick; the gate requires a structured ask or a recorded `JOURNAL.md` decision |65| One competing option requires a new `pixi add` → pick the "free" one | Avoids an install step | Install cost is not the criterion; project fit is |66| User picked `pytorch` last project → reuse without asking | Continuity is friendly | Each workspace records its own `Workspace decisions`; cross-project memory is forbidden |6768## Competing libraries — general rule6970This is the meta-rule that governs every "user choice" entry in71this skill. It applies to the Tier 2 table below and to any new72competing-library job added in the future. It also applies inside73Tier 3 when two optional libraries cover the same job (e.g.74`pytorch` vs `keras` as the deep-learning framework).7576### The rule7778Whenever the stack offers two or more libraries for the same job:79801. **`AskUserQuestion` before any import or install.** Use the81 options listed for the job in the competing-jobs table; do not82 editorialize the option labels.832. **Persist the answer in `journal/JOURNAL.md` Status under84 `Workspace decisions`.** This block is immutable until the user85 explicitly pivots. On future sessions, **read Status first**;86 do not re-ask a recorded decision. The persistence contract87 lives in `iterate-ml-experiment`'s `JOURNAL.md` template — the88 `Workspace decisions` block is the source of truth for cross-89 session continuity.903. **No silent default.** Even when one option is "free"91 (already pulled in transitively) and the other costs an92 install, never pick silently. The free option becoming the93 pick is fine; *the picking happens via `AskUserQuestion`*.9495### Free-text resolution9697A user message resolves a competing-library gate **only** if it98names one of the listed options for the job. Apply in priority99order:100101- **Exact match** (case-insensitive, whitespace-trimmed) to an102 option label: resolves the gate. ("use polars", "let's go103 with pytorch", "pandas please" → resolved.)104- **Library named in a free-text intent** ("rewrite the loader105 in polars", "I want a keras model"): resolves the gate for106 that job.107- **No library named** ("make it fast", "you pick", "whatever",108 "no preference", "quick baseline"): does **NOT** resolve.109 Fall through to the structured `AskUserQuestion`.110- **"You pick" / "no preference" specifically** — surface the111 **default-on-no-preference** for the job (from the Tier 2112 table) and ask for confirmation. Never silently pick; never113 skip the confirmation step.114115### Adding a new contested job116117When a new job appears in the stack with two viable libraries,118add a row to the Tier 2 competing-jobs table. **Every row must119name an explicit `Default-on-no-preference`** — rows without one120are forbidden, because they re-create the silent-pick loophole121this rule exists to close. If a sensible default cannot be122named, the job does not belong in the table; surface the gap to123the user and pick per-project via a free-form `AskUserQuestion`.124125## When to invoke this skill126127Two events trigger this skill before any other action:1281291. **A library import fails** in the stack's domain. The answer is130 install (see § "Missing dependency" below), never substitute.1312. **A library choice has to be made** — for tabular data at project132 start, or any time code is about to introduce a new dependency133 (deep learning, model serving, notebooks, …).134135In both cases, **read the whole SKILL.md before deciding**. The tier136structure below determines whether a library should already be137present, needs a user prompt, or is opt-in — that decision can't be138made from a single index entry.139140## Missing dependency — install, do not substitute141142When code in this stack needs a library but `import` fails, the answer143is **install it**, not substitute. Specifically:144145- Surface the missing dependency to the user with the exact install146 command. **Invoke `python-env-manager` to detect the project's147 environment manager (pixi / uv / poetry / hatch / conda / pip+venv)148 and produce the right install command** — don't infer the manager149 from memory; the project may not use the default. **Stop and wait150 for confirmation before doing anything else.**151- Do **not** rewrite the code to use a non-stack equivalent152 (`sklearn.Pipeline` for `skrub`, `cross_val_score` + handwritten153 metric prints for `skore`. Substitution silently breaks the contract154 that the workflow skills (`build-ml-pipeline`,155 `evaluate-ml-pipeline`, `organize-ml-workspace`) rely on.156- This rule **overrides** "make the code run". If the user prefers a157 substitute, they will say so — until they do, install. Reaching158 for a substitute because the dependency is missing is the most159 common way the stack gets silently undone, so treat the missing160 import as a hard stop.161162## How to use this skill1631641. Read this whole SKILL.md before picking — the tier structure165 determines whether the library should already be installed, needs166 a user-choice prompt, or is opt-in.1672. Match the task to an entry in the right tier.1683. Read the linked `references/<library>.md` for the chosen library's169 scope and tradeoffs before introducing it.1704. Install via `pixi` by default. If the project already uses a171 different manager (pip+venv, uv, conda), follow that instead.1725. Don't substitute libraries silently. If no entry fits the task,173 surface the tradeoff to the user.174175## Tier 1 — Mandatory (install at project start)176177These five libraries are always installed in a data-science / ML178project. The first three co-own the modeling workflow:179scikit-learn provides the estimators, skrub provides the180data-cleaning + DataOps layer that sits before them, skore181evaluates the result and persists it as a project on disk. The182fourth, `ruff`, owns lint + format and is non-negotiable: every183project Claude touches should pass `ruff check`. The fifth,184`pytest`, runs the smoke test that every approved experiment185must have per the `test-ml-pipeline` / `smoke-test-ml-pipeline`186contract — without pytest the smoke-test gate can't enforce187predict-time correctness, so pytest stays mandatory even when188no other tests have been written yet. Each is named explicitly189even when transitively present, because the workflow skills190(`build-ml-pipeline`, `evaluate-ml-pipeline`,191`python-code-style`, `test-ml-pipeline`) depend on them192directly and should not silently lose them if upstream packaging193changes.194195- [`scikit-learn`](references/scikit-learn.md) — tabular ML196 algorithms, preprocessing, model-selection helpers. Use197 `HistGradientBoosting{Classifier,Regressor}` instead of pulling in198 xgboost or lightgbm. **Evaluation, cross-validation reports, and199 model comparison are owned by `skore`** — don't inline200 `cross_val_score` / `classification_report` for analysis output.201- [`skrub`](references/skrub.md) — wrap custom dataframe operations202 in a sklearn-compatible computation graph that replays203 deterministically across train and test splits. Use for the204 data-cleaning + feature-engineering layer that sits before the205 sklearn pipeline.206- [`skore`](references/skore.md) — predictive-model evaluation built207 on top of scikit-learn (`evaluate`, `EstimatorReport`,208 `CrossValidationReport`, `ComparisonReport`) **and** experiment209 tracking via the Project API (`skore.Project(...)`,210 `project.put(...)`, `project.get(...)`). Replaces ad-hoc211 `cross_val_score` + handwritten metric printouts; replaces212 `mlflow` for tracking. Brings `numpy`, `pandas`, `matplotlib`,213 `seaborn`, `plotly`, `joblib`, and others transitively (see214 Tier 4) — so static *and* interactive plotting are available215 without any extra install.216217 **Install variant per mode.** `skore.Project(...)` supports three218 mutually exclusive modes: `local` (artifacts on disk; no extra219 deps), `hub` (artifacts on Skore Hub; requires `skore[hub]`220 extra + `skore.login()` before first use), `mlflow` (artifacts221 in an MLflow tracking server; requires the `skore[mlflow]` extra222 **plus an explicit `mlflow>=3` pin** — `skore[mlflow]` alone can223 resolve an unsupported mlflow 2.x).224 The choice is a workspace-level decision owned by225 `organize-ml-workspace` § "G-SKORE-MODE" — fired at scaffold226 alongside G-PKG-NAME / G-TABULAR / G-ENV-MGR. `python-env-manager`227 § "Tier 1 install: skore variant per mode" maps the recorded228 decision to the right install command per env manager.229 Default-on-no-preference: `local`.230- [`ruff`](references/ruff.md) — single-tool lint + format,231 replaces `black` / `isort` / `flake8` / `pydocstyle`. Install in232 the **same feature/env as the rest of the Tier 1 stack** so233 `pixi run ruff` works without extra activation. The234 configuration (rule selection, numpydoc convention, per-file235 ignores) and the rule "Claude runs ruff after generating code"236 are owned by the `python-code-style` skill, which also ships the237 canonical `ruff.toml` template.238- [`pytest`](references/pytest.md) — test runner for the239 smoke-test gate enforced by `test-ml-pipeline` /240 `smoke-test-ml-pipeline`. Every approved experiment must have a241 passing `tests/smoke/test_NN_<short_name>.py` before its row242 in `JOURNAL.md` can flip to `done`; pytest is what runs that243 test, so the dependency is non-negotiable even on workspaces244 that haven't authored any tests yet. Install in the **same245 feature/env as the rest of the Tier 1 stack**.246247## Tier 2 — Competing-library jobs (user choice)248249Jobs in this tier have **more than one valid library** in the250stack. The user picks via `AskUserQuestion` before any import or251install (see § "Competing libraries — general rule" above).252Recorded picks live in `journal/JOURNAL.md` Status `Workspace253decisions` and are read first on every subsequent session.254255### Competing-jobs table256257| Job | Options | Default-on-no-preference |258|-----|---------|--------------------------|259| Tabular dataframe | `pandas` (+ `pyarrow`), `polars` | `pandas` (free via skore) |260| Deep-learning framework | `pytorch`, `keras` (multi-backend) | `pytorch` |261| sklearn-compatible DL wrapper | `skorch` (pytorch-only), `keras` (sklearn-compatible API) | `skorch` |262| Static vs interactive plotting | `matplotlib` / `seaborn`, `plotly` | task-driven — ask which output shape the user wants |263| Model serving / registry | `mlflow.pyfunc` + registry, FastAPI + `joblib` | `mlflow` |264265Per-option detail:266267- **Tabular dataframe**268 - **`pandas` (+ `pyarrow`)** — established tabular library;269 pyarrow is the recommended Parquet engine + Arrow-backed270 dtype backend. `pandas` is already pulled in by `skore`271 (Tier 4), so the only explicit install for this option is272 `pyarrow` if Parquet IO is in scope. See273 [`pandas`](references/pandas.md) /274 [`pyarrow`](references/pyarrow.md).275 - **`polars`** — Arrow-native tabular library; faster on276 large frames, stricter type system. Requires an explicit277 install (not pulled in by anything in Tier 1). See278 [`polars`](references/polars.md).279- **Deep-learning framework**280 - **`pytorch`** — tensor library with GPU / MPS support and281 autograd. Default DL framework in the stack. See282 [`pytorch`](references/pytorch.md).283 - **`keras`** — high-level, layer-oriented DL API;284 multi-backend (pytorch / TensorFlow / JAX). See285 [`keras`](references/keras.md).286- **sklearn-compatible DL wrapper**287 - **`skorch`** — wraps a PyTorch `nn.Module` so it behaves288 like a sklearn estimator (`fit` / `predict`, GridSearchCV,289 pipelines). See [`skorch`](references/skorch.md).290 - **`keras`** — exposes a sklearn-compatible API directly291 via `keras.wrappers.SKLearnClassifier` /292 `SKLearnRegressor`. See293 [`keras`](references/keras.md).294- **Static vs interactive plotting** — both are already295 available transitively via `skore` (matplotlib, seaborn,296 plotly all land without an explicit install). The ask is297 *which output shape the user wants for this project*, not298 which install to run. Pick by output medium: static reports299 / papers / static skore reports → matplotlib + seaborn;300 interactive notebooks / dashboards → plotly.301- **Model serving / registry** — only relevant when the302 project's roadmap includes serving a trained model. Skip the303 gate entirely if serving is out of scope. When it is in304 scope, `mlflow` is the default (registry + REST out of the305 box); FastAPI + `joblib` is the lighter custom path.306307### How a Tier 2 gate fires in practice308309- **Project start (bootstrap).** Every Tier 2 job the project310 touches must have a pick recorded in `Workspace decisions`311 before the matching code is written. The tabular gate fires312 on every project; the others fire only when the project's313 roadmap brings them in scope.314- **Mid-project (new job comes into scope).** When a new Tier 2315 job becomes relevant (e.g. the project pivots to add a DL316 model), the gate fires *at that point*; the existing317 `Workspace decisions` block is amended with the new row.318- **Mid-project (user wants to pivot).** Tier 2 decisions are319 immutable *unless the user explicitly says so*. A pivot is a320 user-driven event; the skill never auto-pivots even if a321 newer library would obviously be a better fit.322323## Tier 3 — Optional (install on demand)324325Add these only when the task calls for them. Do not pre-install.326327### Deep learning328329For NLP, computer vision, or any task where deep learning is the330right tool. None of these are mandatory; reach for them only when331the project's modeling task requires DL.332333**The *which library* pick (pytorch vs keras as framework, skorch334vs keras as sklearn-compatible wrapper) is a competing-library335job — owned by Tier 2.** This section only covers *when* to336reach for DL at all; the framework choice has its own row in the337Tier 2 competing-jobs table and fires an `AskUserQuestion` the338first time DL comes into the project's scope.339340Per-library reference material:341- [`pytorch`](references/pytorch.md) — tensor library with GPU /342 MPS support and autograd; also the GPU alternative to numpy343 for raw numerical work.344- [`keras`](references/keras.md) — high-level, layer-oriented345 deep-learning API; multi-backend (pytorch / TensorFlow / JAX).346- [`skorch`](references/skorch.md) — wraps a PyTorch `nn.Module`347 so it behaves like a sklearn estimator (`fit` / `predict`,348 GridSearchCV, pipelines).349350### Model serving351352**The *which library* pick (`mlflow.pyfunc` vs FastAPI +353`joblib`) is a competing-library job — owned by Tier 2.** This354section only covers *when* serving is in scope.355356Per-library reference material:357- [`mlflow`](references/mlflow.md) — model packaging, registry,358 and REST serving (`mlflow.pyfunc`, `mlflow models serve`).359 Use **only** for serving and registry concerns; tracking360 belongs to `skore`.361362### Notebooks363364For notebook-based work, prefer Python files with `# %%` cell365markers (jupytext percent format) over `.ipynb` files. Python366files are diffable and version-control friendly; jupytext handles367the conversion to/from notebook format when needed.368369- [`jupyterlab`](references/jupyterlab.md) + [`ipykernel`](references/ipykernel.md)370 — **ambient in the `dev` feature** (alongside `ruff` + `pytest`,371 per `python-env-manager` § "Where does the package belong?").372 Always installed; no per-project ask. The reference pages373 describe the tools' role, not an opt-in install.374- [`jupytext`](references/jupytext.md) — **Tier 3 opt-in**: sync375 `.ipynb` ↔ `.py` (`# %%` markers) so the notebook source-of-376 truth stays version-control friendly. Install only when the377 project wants `.ipynb` interop with the `# %%` scripts.378379## Tier 4 — Transitive (already pulled in; do not install explicitly)380381These land in the env as runtime dependencies of the mandatory tier382(or of the chosen tabular library). Documented here so you don't383add a redundant explicit dependency, and so you know what's384available without an extra install.385386- [`numpy`](references/numpy.md) — N-d arrays, numerical387 primitives. Pulled in by `scikit-learn` and `skore`.388- [`scipy`](references/scipy.md) — scientific computing on top of389 numpy (stats, optimize, sparse, signal). Supports the array API.390 Pulled in by `scikit-learn`.391- [`matplotlib`](references/matplotlib.md) — static plotting392 foundation. Pulled in by `skore` (via `seaborn`).393- [`seaborn`](references/seaborn.md) — static statistical plots394 (distributions, regression, faceting). Pulled in by `skore`.395- [`plotly`](references/plotly.md) — interactive plots (hover,396 zoom, pan); browser-based, suited for dashboards and exploratory397 notebooks. Pulled in by `skore` — **interactive viz is free, no398 extra install needed**.399400## Agent feature — orthogonal to the four tiers401402The audit flow owned by `audit-ml-pipeline` and the editor LSP403integration both need agent-only tooling (`ipython` + `pyright`).404These deps don't fit cleanly into the four tiers above:405406- They are **not Tier 1 mandatory** — workspaces that don't run407 audits and don't use opencode's LSP never need them.408- They are **not Tier 2 user-choice** — there is no competing-409 library job; this is the one toolchain that powers the410 in-process audit runner AND the LSP.411- They are **not Tier 3 optional** in the same sense as412 pytorch / mlflow — the agent feature is *agent-orthogonal*413 tooling, not a modelling library.414- They are **not Tier 4 transitive** — nothing in Tier 1 pulls415 them in.416417So they live in their own bucket: the **agent feature**, a418manager-scoped install that composes alongside (not replaces)419the data-science deps.420421| Library | Role |422|---|---|423| `ipython` | Powers the shared cell runner `audit-ml-pipeline/scripts/run_cells.py` via `IPython.core.interactiveshell.InteractiveShell.run_cell`. Executes `# %%` cells in-process (audit files AND `explore-ml-data`'s `data/eda.py`), captures plain-text repr per cell. |424| `pyright` | Powers the opencode LSP integration for Python files. Surfaces import / type / undefined-symbol diagnostics in the editor. Configured via the bundled `pyrightconfig.json` template (shipped by `python-env-manager`). |425426**Install + config drop: owned by `python-env-manager` § "Agent427feature".** That skill carries the per-manager install table428(pixi features / uv groups / poetry groups / hatch envs / conda429envs / pip+venv extras) and the `pyrightconfig.json` placement430step.431432**Consumed by `audit-ml-pipeline` and the LSP.** When either433consumer fires and the agent feature isn't present, the calling434skill routes through `python-env-manager`'s `G-AGENT-FEATURE`435gate before proceeding.436437**No kernel registration.** The in-process runner doesn't need a438Jupyter kernel.439440**Distinct from the `dev` feature's notebook tooling.** `jupyterlab`441+ `ipykernel` are ambient in `dev` for interactive notebook442editing; `jupytext` stays Tier 3 opt-in. The agent feature443(`ipython` + `pyright`) is its own bucket — `ipython` powers the444in-process audit runner (no kernel), not user-facing notebook work.445A workspace may have any combination of the three concerns.446447## Conventions448449- **Environment manager:** detection + install commands are owned by450 the `python-env-manager` skill — invoke it for any add / remove /451 upgrade. Default *recommendation* is `pixi`; if the project452 already uses a different manager (uv / poetry / hatch / conda /453 pip+venv), `python-env-manager`'s detection table picks it up454 and never substitutes one manager for another.455- **Versions:** don't pin unless the user asks or there's a known456 incompatibility. **Exception — `skore` and `skrub` must always be457 the latest available release.**458- **One tool per job:** don't introduce a second library for a task459 already covered without explicit user request. (One library *can*460 own multiple jobs — `skore` covers both evaluation and tracking.461 The rule forbids piling a second tool onto a covered job, not a462 single tool covering multiple jobs.)463- **Line width:** wrap text at 88 chars where natural. Don't compress464 content to fit; long inline links and code spans are fine to leave465 on longer lines.