SKILL.md
python-api
Discover the public API of any installed Python package, cache what matters, never trust training-data memory.
Three durable rules:
- Lookup against the installed version, never memory.
Recognition is not a lookup. The version may have renamed / re-signatured / deprecated the symbol you remember.
- Cache to
scratch/api/<lib>/<version>/<topic>.mdso the
next agent doesn't repeat the probe.
- Bundled
references/≠ workspace cache. Bundled refs are
durable workflow patterns; cache files are per-version extracts.
Next-step pointers
| Came here for… | After lookup, next is… |
|---|---|
| Symbol signature for code about to be written | → continue caller's flow (e.g. build-ml-pipeline § next step) |
| "Which library / which entry?" | → continue with the picked symbol; cache lands |
| Bootstrap turn (first workspace) | → required minimum cache lands; see references/bootstrap_cache.md |
| Failure debugging (KeyError / AttributeError) | → see Stop condition "Lookup failure ≠ artifact missing" |
What kind of question? → Shape
Pick the shape by question type before picking a tool. Wrong shape produces a wrong-looking cache file and burns a turn.
| The user's question is shaped like… | Shape |
|---|---|
"What's in scratch/api/<lib>/<version>/?" (always check first) |
0 cache hit |
| "Which entry point for <task>?" | Stack orientation below, then Shape 1 / 1b to confirm |
| "What's the signature of X?" / "What args does X take?" / "What's the return type?" | 1b LSP hover (fast) → fall through to 1 if hover is sparse |
| "What does X do?" / "Full docstring of X?" (need Parameters / Examples / See Also) | 1 symbol card (pydoc) |
| "What's in module Y?" (open-ended discovery) | 2 module surface |
"Search for symbols matching foo* across the env" |
2b LSP workspace symbol |
| "How does X work?" / "Which of A or B should I use?" | 3 narrative |
"What does X return when <arg> is <value>?" |
3 narrative |
| "What's the recommended pattern for …?" | 3 narrative |
**Shape 3 is the right answer when the question depends on a condition over an argument.** help() carries a Returns section but typically does not enumerate dispatch behaviour under each argument value — that lives in narrative docs.
Shape 1b vs Shape 1. Pyright hover gives the type signature (richer inferred return types than inspect.signature) + the first paragraph of the docstring — fast, no Python execution. Pydoc gives the full docstring with all sections — slower but authoritative. Default escalation: 1b first for "what's the signature"; fall through to 1 if hover is empty / one-liner.
Prerequisites for Shape 1b / 2b (LSP shapes). Pyright available via opencode LSP AND pyrightconfig.json pointing at the lsp env. If either is missing, LSP shapes are unavailable — use Shape 1 / 2 directly. The agent feature: installed row in JOURNAL.md Status Workspace decisions is the precondition; see python-env-manager § Agent feature.
Stop conditions — read before any lookup
- No symbols from memory. Every function / class / method / arg
must come from a lookup this turn — inspect.signature, a scratch/api/<lib>/<version>/ file, or a fresh WebFetch. Recognition does not count. Sticky named cases — full list in references/namedtraps.md: - skrub: tabularlearner → tabularpipeline in 0.7+. - skrub: markasy(targetcolumn) → signature dropped the positional arg in 0.9+; use .skb.select("...") before mark. - skore: Project.get(...) is by id, not user-facing key; enumerate via project.summarize() first.
- Never fabricate a probe result. If the probe hasn't executed,
the Signature / help() sections must remain blank or marked <pending probe execution>. Same rule for Shape 3: do not paraphrase docs from memory; cache file holds verbatim extracts.
- Version-correct first. Resolve
<pkg>.versionbefore any
lookup. The version subfolder is the cache freshness key.
- Cache hit before fresh fetch. List
scratch/api/<lib>/<version>/ before Shape 1 / 2 / 3.
- Lookup failure ≠ artifact missing. A
KeyError/
AttributeError on a registry-style API (project.get(key), getattr(obj, name), dict[key]) is almost always the lookup shape (id vs key, wrong accessor) — not a missing artifact. Named instance: skore.Project.get(...) resolves by id, not by user-facing key; project.summarize() enumerates (key, id) pairs. Never substitute by re-creating the artifact — that lands a duplicate row.
- **All Python execution goes to
scratch/<YYYY-MM-DD><HHMMSS><short>.py. No exceptions.** Every Python command — pixi run python -c, python -c, heredoc-style python << 'EOF', or any inline Python — is forbidden, regardless of length. Write to scratch first, then execute via pixi run python scratch/<ts>_<short>.py. Applies to version checks, import smokes, signature lookups, module surface dumps, docstring extraction, anything. If you catch yourself typing python -c — STOP and write the file.
- **
inspect.signature/dir(...)/pydoc.render_doc/
help(...) executed inline is NOT a python-api consultation.** These are the exact APIs this skill wraps. Running them via python -c does NOT satisfy the "python-api consulted" pre-flight row in sibling skills. The deliverable is a scratch/api/<lib>/<version>/<topic>.md file written this turn.
pydoc.render_doc, notdoc.docis empty /
misleading on properties, descriptors, decorated callables, and accessors — exactly the cases the cache disambiguates.
- Narrative findings get cached. A WebFetch result read and
discarded is forbidden. Land it in scratch/api/<lib>/<version>/<topic>.md with the source URL on the first line.
- A probe without a cache write is not a completed lookup.
Probe records the investigation; cache file records the conclusion. Turn end without scratch/api/<lib>/<version>/<topic>.md on disk = incomplete.
Forbidden shortcuts
| Shortcut | Why it's wrong |
|---|---|
| Recognise the symbol name from training data → write the call | Memory keyed to arbitrary version; install may have renamed / re-signatured |
| Probe ran, answer on screen → stop without writing the cache | Probe is investigation; cache is conclusion. Next session repeats the probe |
Bundled references/X.md exists → treat as the cache |
References are workflow patterns; cache is per-version extracts. Both must exist |
| Version subfolder missing → write into the latest existing one | Subfolder is the freshness key. Create the right one |
Multi-symbol → string several inspect.signature into one inline python -c |
All Python execution goes to scratch — no inline -c allowance. Multi-symbol → one scratch file → one consolidated cache file |
Used python -c "import <pkg>; print(<pkg>.version)" for a quick version check |
Rule is unconditional. Length is not the criterion — traceability is. Version checks go to scratch/<ts>version<pkg>.py |
Cache exists for the topic; ran inline inspect.signature(X) to re-confirm one arg name |
No inline single-signature carve-out exists. Every Shape 1 lookup uses the probe template |
Used python -c "...inspect.signature..." instead of writing the Shape 1 probe |
Probe records the investigation; cache records the conclusion. Next session needs the file, not your transcript |
| User pasted a docs URL → treat as the answer | Lookup still requires inspect or WebFetch + cache write. URLs are leads |
Use doc instead of pydoc.render_doc |
doc is empty on many accessors; cache file must be readable standalone |
First action — every turn that triggers this skill
- Resolve the version. Write
scratch/<YYYY-MM-DD><HHMMSS>version<pkg>.py with import <pkg>; print(<pkg>.version), run via pixi run python scratch/<ts>version_<pkg>.py. No inline python -c.
- List the cache:
ls scratch/api/<lib>/<version>/. - Cache hit? Read the matching file. Done.
- Cache miss? Classify the question (table above) → run Shape
1 / 1b / 2 / 2b / 3.
- Emit the pre-flight checklist with each box marked.
Pre-flight — emit before any lookup
Pre-flight (python-api):
- [ ] Package version resolved this turn: <lib> <version>
Evidence: Write scratch/<ts>_version_<lib>.py (this turn) +
`pixi run python scratch/<ts>_version_<lib>.py` output.
**Inline `python -c "..."` is NOT evidence.**
- [ ] Cache listed this turn (Shape 0): `ls scratch/api/<lib>/<version>/`
Evidence: tool output (paste the listing, even if empty)
- [ ] Question shape classified: signature | module surface | narrative
Evidence: name the shape + one phrase from the question
- [ ] Lookup decision: cache hit (Read <file>) | Shape 1 | 1b | 2 | 2b | 3
Evidence: name the file Read, probe script written, LSP
operation requested, or URL fetched
- [ ] (Shape 1b / 2b only) LSP preconditions confirmed:
`agent feature: installed` in JOURNAL.md AND
`pyrightconfig.json` at project root pointing at the `lsp` env
Evidence: Read journal/JOURNAL.md + Read pyrightconfig.json
(this turn) | "n/a — not an LSP shape"
- [ ] Cache file lands on disk before turn end
Evidence: Write scratch/api/<lib>/<version>/<topic>.md (this turn)
| "n/a — cache hit, already on disk + Read this turn"
| "n/a — Shape 2b ad-hoc discovery"
**Inline `inspect.signature(...)` / `dir(...)` / `pydoc.render_doc(...)`
WITHOUT a corresponding cache file is NOT evidence. Re-do as Shape 1.**
- [ ] If Shape 1 / 1b: Usage section filled in (Call / Don't call / Trap / Returns)
Evidence: Edit scratch/api/<lib>/<version>/<topic>.md (this turn)
| "n/a — cache hit / Shape 2 / 2b / 3"
- [ ] Pre-flight re-emitted with evidence before final message.
Evidence: this checklist appears in the end-of-turn summary.
The four shapes
Shape 0 — cache hit
ls scratch/api/<lib>/<version>/ 2>/dev/null
Topic-matching file present → Read it. First line carries the source URL or inspect: ref; re-verify against live docs if a file looks suspicious. Cache miss → Shape 1 / 2 / 3.
Shape 1 — symbol card (pydoc → cache file)
One probe script does both: introspects the symbol AND writes the cache file in a single execution. Small follow-up Edit fills the Usage bullets (Call / Don't call / Trap / Returns).
Probe template: → references/probe_templates.md § Shape 1.
Multi-symbol consolidation. Several symbols sharing a topic (e.g. Project.put / Project.get / Project.summarize under project_local) → iterate over a tuple of dotted paths inside the probe and concatenate sections into one <topic>.md. **One topic file per topic, not per symbol.**
No inline carve-out for single-signature checks. Even when a cache exists and you want to re-confirm one arg, run a fresh probe (or Read the existing cache).
Shape 1b — LSP hover (no Python execution)
Pyright via the opencode LSP returns hover info (type signature + first paragraph of docstring) for a symbol at a file:line:character position. First stop for "what's the signature" questions when Shape 1b is known available this session (see Step 0 below); fall through to Shape 1 otherwise, or when the hover output is sparse.
Step 0 — probe LSP availability (mandatory, once per session)
The opencode LSP server reads pyrightconfig.json once at session startup. Mid-session edits do NOT reload. If the LSP returns (import) <name>: Unknown for every external library, the config was written after the LSP initialized — Shape 1b is unavailable this session. Sticky decision: use Shape 1 for ALL signature questions until next opencode session.
Probe procedure + interpretation table + every footgun (session-startup ordering, conda-style envs, character offset, scratch-excluded paths, C-extension types): → references/shape1blspsetup.md.
After availability is confirmed
- Write a probe file (under
tests/orsrc/<pkg>/—scratch/
is excluded from pyright's analysis). Template: → references/probe_templates.md § Shape 1b.
- Call
lsp(operation="hover", filePath=..., line=..., character=...). - Cache the result at
scratch/api/<lib>/<version>/<topic>.md with Source: lsp-hover: <lib>.<dotted> @ <version> and a ## LSP hover section.
- Escalate to Shape 1 if hover is empty / one-liner.
Shape 2 — module surface
Scratch probe that dumps both top-level surface and submodule list, then writes surface.md directly. No inline python -c.
Probe template: → references/probe_templates.md § Shape 2.
One topic per file. Replace only on a version bump.
Shape 2b — LSP workspace symbol (cross-package search)
Given a query string, return all matching symbols across the lsp env's site-packages. Use for discovery before committing to a dotted path; faster than dir() on every candidate module.
lsp(
operation="workspaceSymbol",
filePath="<any-existing-Python-file>",
line=1, character=1,
query="<symbol substring>",
)
(filePath/line/character are ignored by pyright for workspaceSymbol; only used to select the LSP server.)
Return: list of (name, kind, location) tuples. NOT a Shape-2 replacement (doesn't enumerate a full module surface). Use as a discovery aid before running Shape 1 / Shape 2 on the right dotted path. Cache only if the search becomes recurring.
Shape 3 — narrative
Conceptual questions where the signature alone doesn't answer it — "how does X work?", "which of A/B should I use?", "what does X return when <arg> is <value>?". Procedure:
- WebSearch for the versioned docs URL. Query:
<library> <MAJOR.MINOR> docs <topic>. Don't construct URLs from memory.
- WebFetch the most relevant result whose URL contains the
installed version (/0.18/, /0.18.0/). Reject any URL with /latest/ or /stable/ — those drift on republish.
- Cache verbatim to
scratch/api/<lib>/<version>/<topic>.md:
```markdown # <topic>
Source: <full URL> Fetched: <YYYY-MM-DD>
<paste salient sections verbatim — do NOT paraphrase from memory. Skip nav chrome; keep prose that answers the question.> ```
Cache filename: snake_case mirror of the docs URL slug. One topic per file. Replace only on version bump.
Cache file contract
Every scratch/api/<lib>/<version>/<topic>.md follows a four-section shape:
# <topic>
Source: inspect: <lib>.<dotted> @ <version> | <docs URL>
Probed: <YYYY-MM-DD>
## Signature
<fenced block — exactly as inspect.signature returned it, Shape 1 only>
## help() (or "## Docs extract" for Shape 3, "## LSP hover" for 1b)
<verbatim pydoc.render_doc / WebFetch extract / hover output.
Never paraphrase.>
## Usage (agent synthesis)
- **Call:** import path + arg shape (2-3 line snippet)
- **Don't call:** named substitutes that look right from memory
- **Trap:** version-specific rename / deprecation / footgun (empty if none)
- **Returns:** return type + the one accessor the next caller needs
Bootstrap turns have a required minimum cache to leave behind — file list + audit procedure: → references/bootstrap_cache.md.
Cache layout & lifecycle
scratch/api/
├── skrub/0.9.0/
│ ├── data_ops.md # Shape 3 narrative
│ ├── tabular_pipeline.md
│ └── surface.md # Shape 2 dir() dump
├── skore/0.18.0/
│ ├── evaluate.md
│ └── project_local.md
└── sklearn/1.8.0/
└── cv_splitters.md
- Version subfolder ==
<pkg>.versionexactly. No
abbreviation (0.18.0 ≠ 0.18).
- First line is the source URL or
inspect:ref. - Gitignored by the existing
scratch/*rule. Regenerable. - Append-on-success. Once written, frozen. Replace only on
version bump (the version-subfolder convention handles this).
- Cache miss in a stale subfolder is impossible by construction.
Shape 0 reads by version; old subfolder content is invisible.
scratch/ conventions — probes vs. cache
Two structured uses of scratch/, both gitignored:
| Ad-hoc probes | API doc cache | |
|---|---|---|
| Path | scratch/<YYYY-MM-DD><HHMMSS><short>.py |
scratch/api/<lib>/<version>/<topic>.md |
| What | Multi-line inspect walks, sanity checks, metric extraction |
Signature / help() / docs extract per topic |
| Naming | Timestamped (chronological) | Topic-organised |
| Lifecycle | Append-only after success; overwrite on error in same loop | Append-on-success; replace on version bump |
| Inline cap | No inline python -c. All execution goes to scratch |
n/a (probes write the cache) |
Never edit an experiment script to add agent-only print(...) calls for inspection. Inspection goes in scratch/.
Never re-run an experiment / project.put from a scratch probe. Scratch is read-only against the skore Project (use project.summarize() then project.get(id)).
Read-only rule extends to audit/. The audit flow places one # %% file per experiment under audit/<NN><shortname>.py and executes it via the bundled runner. Same summarize() → get(id) → report.* discipline. Executed digests land in scratch/audit/<stem>/ (gitignored). Two artifacts, two lifecycles:
| API doc cache | Audit executed digest | |
|---|---|---|
| Owner | python-api |
audit-ml-pipeline |
| Source | inspect.signature / pydoc.render_doc / WebFetch |
Executing audit/<stem>.py |
| Lifecycle | Append-on-success; replace on version bump | Overwritten on every audit re-execution |
| Read by | Any skill needing a symbol's signature/contract | The agent during iterate-ml-experiment § 4 narrative work |
The workspace's scratch/ folder does not carry a README.md — rules of this importance live in the skill that's loaded at use-time.
Stack orientation — where things live
Tier-1 named entry points. Consult before a Shape 2 surface dump for "where does X live" — the named entry is often the right answer.
scikit-learn
sklearn.metrics— scoring (accuracyscore,rocauc_score,
meanabsoluteerror, make_scorer).
sklearn.preprocessing— stateful scalers / encoders / imputers
(StandardScaler, OneHotEncoder, KBinsDiscretizer).
sklearn.pipeline/sklearn.compose—Pipeline,
make_pipeline, ColumnTransformer.
sklearn.model_selection— splitters (KFold,GroupKFold,
TimeSeriesSplit, traintestsplit) + search (GridSearchCV).
- Estimators:
sklearn.linear_model/sklearn.ensemble/ etc.
skrub
- One-call featuriser / learner:
skrub.tabular_pipeline
(top-level function — not tabular_learner, not TabularLearner; renamed in 0.7+).
- Per-column encoders (top-level):
TableVectorizer,
DatetimeEncoder, TextEncoder, StringEncoder.
- DataOps DSL (lazy pipeline graph): the
.skbnamespace on
every node — X.skb.apply, X.skb.applyfunc, X.skb.markasX, X.skb.markasy, X.skb.makelearner, .skb.preview, .skb.full_report.
- Sources / variables:
skrub.var(name, value=...),
skrub.asdataop({...}).
- Selectors: `skrub.selectors.{numeric, categorical, string,
...}`.
skore
- Evaluation: `skore.evaluate(estimator, X=None, y=None,
data=None, *, splitter=..., ...) — dispatches by splitter to EstimatorReport (holdout) / CrossValidationReport (CV splitter) / ComparisonReport (multi-key). When splitter is omitted it reuses a DataOp markasX(cv=...) if present, else an 80/20 holdout; an explicit splitter= overrides the DataOp cv`.
- Project:
skore.Project(name, *, mode="local", **kwargs)
(per-mode kwargs: workspace= for local dir / hub workspace name, tracking_uri= for mlflow) with put(key, report) / summarize() (DataFrame indexed by id with key column, rows ascending by date) / get(id) — get is by id, not by key — and static Project.delete(name, *, mode, **kwargs).
Full per-library surface map (rare submodules, accessor cheat sheet): → references/stack_orientation.md.
When the installed package is wrong
Two options, neither involves "writing from memory of a different version":
- Route to
python-env-manager§ "Upgrade / pin" to bump or pin.
Re-do the lookup against the new install.
- Adapt to what's installed — change the approach.
Companion skills
| Skill | Relationship |
|---|---|
python-env-manager |
Owns versions (the version it resolves keys scratch/api/<lib>/<version>/) |
data-science-python-stack |
Owns which library; this skill takes the library as given |
build-ml-pipeline / evaluate-ml-pipeline / iterate-from-skore / smoke-test-ml-pipeline |
Workflow skills that dispatch here when they need a symbol |
What this skill does NOT do
- Maintain pre-baked signature listings in the skill folder.
Per-version extracts live in the workspace cache; the skill folder carries durable workflow patterns.
- Generate cache files on install. Caching is on-demand.
- Explain a library's API at depth — that's the library's own
docs. This skill points at them and caches what's worth keeping.
- Auto-author references. New
references/<topic>.mdfiles are
user-gated via AskUserQuestion (see references/authoring_protocol.md).
References (load on demand)
Durable workflow patterns (bundled with the skill):
references/probe_templates.md— full code for Shape 1 (pydoc
symbol card), Shape 1b (LSP hover probe file), Shape 2 (module surface dump). Copy + edit LIB / DOTTED / TOPIC.
references/shape1blspsetup.md— Step 0 availability probe,
interpretation table, footguns (session-startup ordering, conda-style envs, character offset, scratch-excluded paths, C-extension types).
references/stack_orientation.md— full per-library surface
map (sklearn / skrub / skore).
references/premarkalignment.md— 3-layer skrub DataOps
pattern for history-dependent pipelines. Read before authoring history-dep code.
references/skrub_interop.md— howSkrubLearnerintegrates
with skore.evaluate. Read before writing experiments/NN_*.py.
references/bootstrap_cache.md— required minimum cache files
for a bootstrap turn + audit procedure.
references/named_traps.md— version-specific renames /
deprecations the agent has hit in real traces.
references/authoring_protocol.md— when and how to land a new
reference doc (user-gated via AskUserQuestion).