opencode-skills-collection 4.1.7 → 4.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +7 -1
  2. package/bundled-skills/court/SKILL.md +141 -0
  3. package/bundled-skills/court/references/jurors.json +14 -0
  4. package/bundled-skills/court/scripts/court.py +348 -0
  5. package/bundled-skills/darkmoon-pentest/SKILL.md +123 -0
  6. package/bundled-skills/lognorm/SKILL.md +86 -0
  7. package/bundled-skills/lognorm/references/playbooks.md +76 -0
  8. package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +6 -6
  9. package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package.json +1 -1
  10. package/bundled-skills/parallel-search-mcp/SKILL.md +12 -0
  11. package/bundled-skills/scarf-single-cell/SKILL.md +326 -0
  12. package/bundled-skills/scarf-single-cell/references/LICENSE.md +29 -0
  13. package/bundled-skills/scarf-single-cell/references/clustering-and-embedding.md +270 -0
  14. package/bundled-skills/scarf-single-cell/references/data-access.md +269 -0
  15. package/bundled-skills/scarf-single-cell/references/features-and-graphs.md +279 -0
  16. package/bundled-skills/scarf-single-cell/references/gene-blacklists.md +182 -0
  17. package/bundled-skills/scarf-single-cell/references/integration-and-comparisons.md +295 -0
  18. package/bundled-skills/scarf-single-cell/references/markers-and-annotation.md +280 -0
  19. package/bundled-skills/scarf-single-cell/references/performance-and-export.md +291 -0
  20. package/bundled-skills/scarf-single-cell/references/pipeline-runs-and-artifacts.md +273 -0
  21. package/bundled-skills/scarf-single-cell/references/plotting.md +260 -0
  22. package/bundled-skills/scarf-single-cell/references/quality-control.md +290 -0
  23. package/bundled-skills/session-handoff/SKILL.md +112 -0
  24. package/bundled-skills/shark/SKILL.md +134 -0
  25. package/bundled-skills/shark/references/investors.json +12 -0
  26. package/bundled-skills/shark/scripts/shark.py +302 -0
  27. package/bundled-skills/telegram/assets/boilerplate/nodejs/package-lock.json +6 -6
  28. package/bundled-skills/telegram/assets/boilerplate/nodejs/package.json +1 -1
  29. package/bundled-skills/whatsapp-cloud-api/assets/boilerplate/nodejs/package-lock.json +6 -6
  30. package/bundled-skills/whatsapp-cloud-api/assets/boilerplate/nodejs/package.json +1 -1
  31. package/package.json +1 -1
  32. package/skills_index.json +201 -0
@@ -0,0 +1,291 @@
1
+ # Performance and export
2
+
3
+ Resource budgets, out-of-core rules, remote I/O on mounts, and exporting counts, metadata,
4
+ markers, and pipeline-run results. Docs: <https://scarf.readthedocs.io/en/latest/concepts/memory_and_execution.html>,
5
+ <https://scarf.readthedocs.io/en/latest/tutorials/remote_stores.html>, <https://scarf.readthedocs.io/en/latest/tutorials/import_and_export.html>,
6
+ <https://scarf.readthedocs.io/en/latest/tutorials/custom_analyses.html>, <https://scarf.readthedocs.io/en/latest/concepts/benchmarks.html>.
7
+
8
+ ## When to use
9
+
10
+ - Before a large run, on a shared host, or in a batch job.
11
+ - When analysis on a mount is slow or will make many passes over counts.
12
+ - When handing results to Scanpy, Seurat, R, a count model, or a colleague.
13
+ - When a step may outlast the agent shell's foreground limit.
14
+
15
+ ## Key concepts
16
+
17
+ - `mem_budget` is a planning budget (block size, write concurrency, feature batch size), not an
18
+ RSS cap. Graphs, marker batches, and allocator overhead come on top: in the benchmarks a 5M-cell
19
+ run peaked at 51.6 GiB on a 48 GiB budget, and marker search alone added about 32 GiB.
20
+ - Resolution order: explicit `mem_budget`/`nthreads` argument, then env `SCARF_MEM_BUDGET` /
21
+ `SCARF_WORKERS`, then detection (cgroup and CPU-affinity aware). Specs: bytes (int), `"8G"`,
22
+ `"512M"` (binary units, G = 1024^3 bytes), or a fraction of detected memory such as `"0.6"`.
23
+ A bare `"8"` is rejected as ambiguous.
24
+ - More workers mean more concurrent buffers and remote requests; pair them with a `mem_budget`.
25
+ - Converters need a `mem_budget` that fits the default count layout and refuse before writing
26
+ when it does not (`references/data-access.md`). Prefer more memory over the smaller layout the
27
+ refusal names: smaller layouts slow every later gene-major read.
28
+ - A `DataStore` is not thread-safe: call it from one thread at a time. Use separate stores or
29
+ separate mount targets for independent parallel analyses.
30
+ - Opt-in parallel UMAP, tSNE, and ANN builds record the resolved worker count in provenance.
31
+ Pass `nthreads` explicitly when such results must stay reusable across machines.
32
+ - Counts stream in blocks. `ds.RNA.rawData` is a lazy `ChunkedArray`; `.compute()` on a large
33
+ slice materializes it. `ds.to_anndata(...)` always builds `X` in memory; ordinary
34
+ `scarf.to_h5ad(assay, path)` and `scarf.to_mtx` stream.
35
+ - On a mount, every pass over counts is a network read; artifacts (normalized, PCA, graph) live
36
+ in the local target. `local_cache` (PCA) stages normalized data only when the datastore
37
+ location itself is remote, so it is skipped for a local mount target.
38
+
39
+ ## Recipes
40
+
41
+ ### Set budgets and batch-friendly output
42
+
43
+ ```python
44
+ import scarf
45
+
46
+ scarf.configure_output(progress=False, timestamps=True)
47
+ scarf.set_verbosity(level="INFO", filepath="scarf-run.log") # logs go to this file, not stdout
48
+ ds = scarf.DataStore("analysis.zarr", mem_budget="16G", nthreads=8, min_features_per_cell=-1)
49
+ print(ds.summary().to_dict()["resources"]) # {'memory_bytes': ..., 'workers': 8, ...}
50
+ ```
51
+
52
+ Environment equivalent: `SCARF_MEM_BUDGET=16G SCARF_WORKERS=8 python job.py`.
53
+ `configure_output(level=...)` does not hide progress bars; pass `progress=False` explicitly.
54
+
55
+ ### Stream counts instead of materializing
56
+
57
+ Slice lazily, then iterate ordered row blocks; never `.compute()` the whole matrix.
58
+
59
+ ```python
60
+ import numpy as np
61
+
62
+ run = ds.pipeline.open(label="baseline")
63
+ cell_index = np.flatnonzero(run.cells.fetch_all("I"))
64
+ hvg = np.flatnonzero(np.asarray(run.features.fetch_all("highly_variable_features"), dtype=bool))
65
+ selected = ds.RNA.rawData[:, hvg][cell_index, :]
66
+ detected = np.concatenate(
67
+ [np.count_nonzero(block, axis=1) for block in selected.stream_blocks(nthreads=4, msg="HVGs")]
68
+ )
69
+ print(ds.RNA.rawData[:3, :5].compute()) # tiny slices only
70
+ ```
71
+
72
+ ### Make repeated passes over a mount cheap
73
+
74
+ Repack copies the remote counts once into a self-contained local store, keeping artifacts and
75
+ pipeline runs. It needs disk for the full counts plus `countsT`.
76
+
77
+ ```bash
78
+ python -m scarf.tools.repack_zarr work/pbmc.zarr work/pbmc_local.zarr --mem-budget 4G
79
+ ```
80
+
81
+ For a mount of a few thousand cells, repack takes tens of seconds and reopening drops from 10 to
82
+ 20 s to under 1 s. The output has no `matrixSource`, and `ds.pipeline.open(label=...)` still
83
+ works. `--data-only` drops analyses; `--nthreads`, `--profile {fast_local,cloud}` and
84
+ `--storage-options` (JSON, e.g. `'{"skip_signature": true}'` for public S3) also exist.
85
+
86
+ ### Run long steps from an agent shell
87
+
88
+ Some agent shells stop a foreground command after about 10 minutes, and an unbounded wait loop can
89
+ hang for as long. Write the step as a script that logs to a file and ends with `DONE` or `FAILED`:
90
+
91
+ ```python
92
+ # step_baseline.py
93
+ import sys
94
+ import traceback
95
+
96
+ import scarf
97
+
98
+ scarf.configure_output(level="WARNING", progress=False)
99
+ try:
100
+ ds = scarf.DataStore("analysis.zarr", min_features_per_cell=-1)
101
+ run = ds.pipeline.run(label="baseline")
102
+ print("run", run.run_id, run.status, flush=True)
103
+ except Exception:
104
+ traceback.print_exc()
105
+ print("FAILED", flush=True)
106
+ sys.exit(1)
107
+ print("DONE", flush=True)
108
+ ```
109
+
110
+ Start it detached with a timeout and keep the PID in a file (shell variables do not survive between
111
+ agent tool calls). Make sure `timeout`'s SIGTERM reaches Python. Behind a wrapper such as `uv run`, put `timeout` inside it: Scarf then recorded
112
+ the run as `interrupted` in 6 of 7 tests; `timeout 1800 uv run ...` left it `running` both times.
113
+
114
+ ```bash
115
+ mkdir -p logs
116
+ nohup timeout 1800 python step_baseline.py > logs/baseline.log 2>&1 &
117
+ echo $! > logs/baseline.pid
118
+ ```
119
+
120
+ Wait in bounded slices that stay under the shell limit and fail fast:
121
+
122
+ ```bash
123
+ LOG=logs/baseline.log; PID=$(cat logs/baseline.pid); state=running
124
+ for i in $(seq 1 50); do # 50 x 10 s, under a 10-minute limit
125
+ alive=1; kill -0 "$PID" 2>/dev/null || alive=0 # check first: a dead process wrote its last line
126
+ if grep -qx DONE "$LOG"; then state=done; break; fi
127
+ if grep -qx FAILED "$LOG" || grep -q Traceback "$LOG"; then state=failed; break; fi
128
+ if [ "$alive" = 0 ]; then state=died; break; fi
129
+ sleep 10
130
+ done
131
+ echo "state=$state"; tail -n 5 "$LOG"
132
+ ```
133
+
134
+ `running`: call the loop again. `failed`: read the traceback. `died`: the process ended without a
135
+ final line (timeout, out of memory, kill); `ds.pipeline.list_runs()` shows what was recorded. The
136
+ variable is `state` because zsh reserves `status`.
137
+ Check the saved PID with `kill -0`: `pgrep -f step_baseline.py` also matches the launching shell,
138
+ so it never reports the job as gone. On macOS, `timeout` is `gtimeout` from GNU coreutils.
139
+
140
+ ### Export a completed run to AnnData or H5AD
141
+
142
+ Run export uses the frozen cells and feature universe and holds `X` in memory. `cell_type` below
143
+ and in later recipes is a placeholder for your own label column, not held-out author labels.
144
+
145
+ ```python
146
+ adata = ds.to_anndata(run=run) # obs: frozen fields; obsm: X_umap; X: raw counts
147
+ live = ds.cells.to_pandas_dataframe(["ids", "cell_type", "RNA_nCounts"]).set_index("ids")
148
+ adata.obs = adata.obs.join(live) # live labels are not in the run: join on cell ids
149
+ adata.obsm["X_pca"] = np.asarray(ds.load_artifact(run["pca"])["data"][:]) # run analysis cells
150
+ graph = ds.load_graph(graph=run["connectivity_map"], symmetric=True, upper_only=False)
151
+ assert graph.shape[0] == adata.n_obs
152
+ adata.obsp["connectivities"] = graph
153
+ adata.write_h5ad("baseline_run.h5ad")
154
+
155
+ scarf.to_h5ad(ds.RNA, "baseline_run_plain.h5ad", run=run) # one call, no extras
156
+ ```
157
+
158
+ `adata.var_names` are feature IDs; symbols are in `adata.var["names"]`. Pass
159
+ `snapshot_columns=["cell_type"]` to `ds.pipeline.run` to freeze live labels into future run
160
+ exports (see `pipeline-runs-and-artifacts.md`).
161
+
162
+ ### Export a complete assay (streaming)
163
+
164
+ ```python
165
+ scarf.to_h5ad(ds.RNA, "full_assay.h5ad") # every cell, including I False; live metadata
166
+ scarf.to_mtx(ds.RNA, "full_assay_mtx") # matrix.mtx, genes.tsv, barcodes.tsv
167
+ scarf.to_mtx(ds.RNA, "full_assay_mtx_gz", compress=True) # Cell Ranger 3 names, .gz
168
+ ```
169
+
170
+ ### Export a gene panel or normalized values
171
+
172
+ ```python
173
+ panel = ds.to_anndata(from_assay="RNA", feature_names=["CD3E", "MS4A1", "LYZ", "NKG7"])
174
+ normed = ds.to_anndata(run=run, matrix="normed") # library size scaled to 1000 per cell, no log
175
+ ```
176
+
177
+ `feature_names` (exact, unique, ordered) and `feature_indexes` are mutually exclusive and are
178
+ rejected together with `run=`. Without `run`, `cell_key` (default `"I"`) selects cells, and
179
+ `obs_names` are the cell `ids`: a panel export is the cheapest per-cell marker table.
180
+
181
+ ### Export markers and metadata tables
182
+
183
+ ```python
184
+ ds.export_markers_to_csv(marker=run["markers"], csv_filename="markers_wide.csv") # names only
185
+ ds.get_markers(marker=run["markers"]).to_csv("markers_long.csv", index=False) # with stats
186
+ run.cells.to_pandas_dataframe(["ids", "clusters", "doublet_score"]).to_csv("run_cells.csv", index=False)
187
+ ds.cells.to_pandas_dataframe(["ids", "cell_type"], key="I").to_csv("cells.csv", index=False)
188
+ ```
189
+
190
+ ### Export pseudobulk counts
191
+
192
+ Raw counts per population x sample plus a design table, for DESeq2, edgeR or another count model.
193
+
194
+ ```python
195
+ bulk = ds.make_bulk(
196
+ "cell_type", secondary_groups="sample_id", # one column per population x sample
197
+ cell_selection=run["analysis_cell_selection"],
198
+ aggr_type="sum", feature_label="id", # raw counts; Ensembl ids are unique
199
+ )
200
+ cells = ds.cells.to_pandas_dataframe(["cell_type", "sample_id", "donor_id", "condition"])
201
+ cells = cells[np.asarray(run.cells.fetch_all("I"), dtype=bool)]
202
+ assert cells.groupby("sample_id")[["donor_id", "condition"]].nunique().eq(1).all().all()
203
+ cells["column"] = cells["cell_type"].astype(str) + "_" + cells["sample_id"].astype(str)
204
+ by_column = cells.groupby("column")
205
+ design = by_column.first().assign(n_cells=by_column.size()).reindex(bulk.columns) # empty -> NaN
206
+ design["n_cells"] = design["n_cells"].fillna(0).astype(int)
207
+ keep = design["n_cells"] >= 10 # choose a floor and report what you drop
208
+ assert np.allclose(bulk.to_numpy(), np.round(bulk.to_numpy()))
209
+ bulk.loc[:, keep].round().astype("int64").to_csv("pseudobulk_counts.csv")
210
+ design.loc[keep].to_csv("pseudobulk_design.csv")
211
+ print(design[keep].groupby(["cell_type", "condition"])["donor_id"].nunique().unstack(fill_value=0))
212
+ ```
213
+
214
+ `make_bulk` emits every group x subgroup pair (all-zero when empty), named `f"{group}_{subgroup}"`;
215
+ a name collision raises. Sums keep the count dtype (often float32). Model one population at a time
216
+ with donor-level terms; with repeated samples per donor, include the donor (paired) or sum its
217
+ samples first, and avoid rank-deficient designs (integration-and-comparisons.md).
218
+
219
+ ### Write a self-contained subset store
220
+
221
+ ```python
222
+ keep = np.flatnonzero(run.cells.fetch_all("I")) # or cell_key="my_bool_column"
223
+ scarf.SubsetZarr("run_cells.zarr", assays=[ds.RNA], cell_idx=keep).dump()
224
+ sub = scarf.DataStore("run_cells.zarr") # first open must be writable
225
+ ```
226
+
227
+ The subset copies counts locally (no `matrixSource`), keeps every feature and cell column, and
228
+ contains no artifacts or pipeline runs. Use it to hand off, or before `to_mtx` of run cells.
229
+
230
+ ## What a run export contains
231
+
232
+ | Item | `to_anndata(run=run)` / `to_h5ad(assay, path, run=run)` | Otherwise |
233
+ |---|---|---|
234
+ | Cells | the run's analysis selection | `to_h5ad(ds.RNA, path)` exports all rows |
235
+ | Features | the full feature universe; HVG flag in `var["highly_variable_features"]` | `feature_names=` without `run` |
236
+ | `X` | raw counts; `matrix="normed"` (to_anndata only) for scaled values | |
237
+ | `obs` | frozen fields: cell-cycle, `leiden_*`, `paris`, `clusters`, `doublet_score`, `snapshot_columns` | join `ds.cells` by `ids` |
238
+ | `obsm` | `X_umap` | PCA: `ds.load_artifact(run["pca"])["data"]` |
239
+ | `obsp`, markers, run report | not exported | `ds.load_graph`, `get_markers`, `run.report(format="markdown")` |
240
+ | MTX | `to_mtx` has no `run=` | `SubsetZarr` with run cells, then `to_mtx` |
241
+
242
+ ## Parameters that matter
243
+
244
+ | Parameter | Default | Change when |
245
+ |---|---|---|
246
+ | `mem_budget` / `SCARF_MEM_BUDGET` | detected system or cgroup memory | shared host or batch job; leave headroom for graphs and markers |
247
+ | `nthreads` / `SCARF_WORKERS` | detected CPUs | shared host, remote stores, reproducible parallel UMAP/ANN |
248
+ | `configure_output(progress=)` | `True` | `False` in logs and batch jobs |
249
+ | `run_pca(local_cache=)` | `"auto"` | path string to keep scratch for a remote store; plan `cells x features x 4` bytes |
250
+ | `to_anndata(matrix=)` | `"raw"` | `"normed"` for scaled values |
251
+ | `to_h5ad(embeddings_cols=)` | `["UMAP", "tSNE"]` live column prefixes | legacy stores only; rejected with `run=` |
252
+ | `to_mtx(compress=)` | `False` | `True` for Cell Ranger 3 file names |
253
+ | `get_markers` / `export_markers_to_csv` `min_score`, `min_frac_exp` | `0.25`, `0.2` | widen or narrow the exported marker list |
254
+ | `make_bulk(aggr_type=, feature_label=, pseudo_reps=)` | `"mean"`, `"index"`, `1` | `"sum"` and `"id"` for count models; never `pseudo_reps` above 1 for inference |
255
+ | `SubsetZarr(reset_cell_filter=)` | `True` (new `I` all true) | `False` to carry `I` over |
256
+
257
+ ## Check before moving on
258
+
259
+ - `ds.summary().to_dict()["resources"]` shows the budget actually in effect.
260
+ - At `INFO`, lines such as `execution countsTCellBand: ... fetch=...s` show remote fetch time per
261
+ stage; the run report records stage timing and sampled RSS.
262
+ - After an export: reload, compare shape to `run.cells.fetch("ids").shape` and feature count,
263
+ confirm `obsm["X_umap"]`, and check joined labels have no unexpected missing values.
264
+ - Pseudobulk: integer counts, `design` aligned to `bulk.columns`, empty and tiny columns dropped,
265
+ enough donors per condition in each population you model.
266
+ - A background step ended with `DONE`, and its run report says `completed`.
267
+
268
+ ## Pitfalls
269
+
270
+ - Every `to_anndata` call (panel, run, or full) materializes `X`; size it before exporting a
271
+ large atlas. Ordinary `to_h5ad` streams but writes every cell, inactive ones included.
272
+ - Run export rejects `from_assay`, `cell_key`, `feature_names`, and `feature_indexes`.
273
+ `to_h5ad(run=)` also rejects `embeddings_cols`, `skip_recalc_nfeats=False`, and `nthreads`.
274
+ - Run `obs` lacks live columns (author labels, QC) unless captured with `snapshot_columns`.
275
+ - `export_markers_to_csv` writes names only, filtered by `min_score`/`min_frac_exp`; use
276
+ `get_markers(marker=ref)` for statistics. Pass the exact marker ref, never a guess.
277
+ - Remote costs for a few thousand cells on a Hugging Face mount: open 10 to 20 s, a new mount a
278
+ few minutes, the pipeline about a minute (cell cycle, HVG, normalization and markers each
279
+ re-read `countsT`), exports seconds to tens of seconds. A 4-gene panel export still reads every
280
+ cell's count rows.
281
+ - `SubsetZarr` keeps all features, drops artifacts and runs, refuses an existing destination
282
+ unless `overwrite_existing_file=True`, and refuses a destination overlapping the source.
283
+ - `to_mtx(compress=True)` on CELLxGENE stores writes gene biotypes as feature types; see
284
+ `data-access.md` before re-importing.
285
+
286
+ ## See also
287
+
288
+ - `data-access.md`: opening stores, mounts, Cytebase, imports.
289
+ - `pipeline-runs-and-artifacts.md`: run reports, `snapshot_columns`, artifact loading.
290
+ - `markers-and-annotation.md`: marker statistics and thresholds.
291
+ - `integration-and-comparisons.md`: donors, two-arm donors and tests behind a pseudobulk design.
@@ -0,0 +1,273 @@
1
+ # Pipeline runs and artifacts
2
+
3
+ Run the standard RNA recipe, reopen and audit runs, inspect and trace immutable artifacts, branch
4
+ from a run with exact refs, and record the analysis for handoff. Docs:
5
+ <https://scarf.readthedocs.io/en/latest/reference/api/pipeline.html>, <https://scarf.readthedocs.io/en/latest/reference/api/artifacts.html>,
6
+ <https://scarf.readthedocs.io/en/latest/concepts/provenance.html>, <https://scarf.readthedocs.io/en/latest/tutorials/reuse_and_tracing.html>,
7
+ <https://scarf.readthedocs.io/en/latest/analysis_with_agents.html>.
8
+
9
+ ## When to use
10
+
11
+ - First pass on any RNA store: one `ds.pipeline.run(label=...)` gives QC filtering, HVGs, PCA,
12
+ graph, UMAP, Leiden candidates, Paris, doublet scores and markers as one durable record.
13
+ - Resuming work: open a run by label or id; inspect any result before consuming it.
14
+ - Alternatives outside the fixed recipe: take refs from a completed run and pass them on.
15
+
16
+ ## Key concepts
17
+
18
+ - Every result is an immutable artifact addressed by an `ArtifactRef` (frozen dataclass):
19
+ `scope` (`"assay"` or `"datastore"`), `kind`, `artifact_id` (64 hex chars; there is no `.id`),
20
+ `assay` (`None` for datastore scope). Cell selections and metadata snapshots are datastore-scoped.
21
+ - Reuse is content-addressed: same operation, parameters and exact input refs returns the existing
22
+ complete artifact. Changing anything creates a new artifact; upstream ones are reused.
23
+ `invalidate_cache=True` on a granular method forces a fresh one.
24
+ - `ds.pipeline.run` writes artifacts plus a run record only. It never edits live `I` or adds
25
+ metadata columns, and needs `zarr_mode="r+"` (the default); a read-only store raises
26
+ `PermissionError`, though `open`, reports and plots still work.
27
+ - `PipelineRun` is a read-only `Mapping[str, ArtifactRef]` (`keys`, `items`, `values`, `get`,
28
+ `run["pca"]`) plus `run_id`, `label`, `status`, `recipe` (`"basic_rna_analysis"`), `assay`,
29
+ `started_at_ns`, `finished_at_ns`, `report()`, `cells`, `features`. Outputs and views work only
30
+ on `status == "completed"`; otherwise they raise `RuntimeError`.
31
+ - Labels are immutable. Only a completed run acquires its label. A completed label cannot be
32
+ reused (`ValueError` before any work). Failed and interrupted runs reserve nothing.
33
+ - There is no resume. A new run reuses every complete artifact from earlier attempts.
34
+ - `run.cells` and `run.features` are frozen views of captured fields only. Live columns such as
35
+ author labels are absent unless listed in `snapshot_columns` on the run.
36
+
37
+ ## Recipes
38
+
39
+ ### Run the baseline
40
+
41
+ Default outputs and their kinds: `input_cell_selection`, `analysis_cell_selection`
42
+ (`cell_selection`), `feature_universe`, `highly_variable_features` (`feature_selection`),
43
+ `cell_cycle`, `normalized`, `pca` (`reduction`), `ann_index`, `neighbors`, `connectivity_map`,
44
+ `embedding_initialization`, `umap` (`embedding`), `leiden_0.5` ... `leiden_1.25`
45
+ (`cluster_labels`), `paris` (`cluster_cut`), `cluster_selection`, `clusters` (the selected Leiden
46
+ ref itself, not a copy), `doublets` (`doublet_score`), `markers` (`marker_table`). Harmony adds
47
+ `harmony` after `pca`; disabled stages omit their keys. A fresh baseline took 22 s on local
48
+ counts, 10 s of it one-time compilation in the graph stage; a second run in the same process took
49
+ 11 s (10x 5K PBMC docs dataset). On remote counts expect about a minute for a few thousand cells.
50
+
51
+ ```python
52
+ import scarf
53
+
54
+ scarf.configure_output(level="WARNING", progress=False)
55
+ ds = scarf.DataStore("analysis.zarr", nthreads=4, min_features_per_cell=-1)
56
+ run = ds.pipeline.run(label="baseline")
57
+ list(run.keys())
58
+ run["clusters"] == run["leiden_0.5"] # True on the 10x 5K PBMC docs dataset
59
+ ```
60
+
61
+ ### Reopen, list and report runs
62
+
63
+ `open` needs exactly one of `label` or `run_id`; an unknown label raises `KeyError`. `list_runs`
64
+ is newest first, all statuses unless filtered, `limit=20` by default.
65
+
66
+ ```python
67
+ run = ds.pipeline.open(label="baseline")
68
+ same = ds.pipeline.open(run_id=run.run_id)
69
+ ds.pipeline.list_runs(status="completed", limit=5)
70
+ report = run.report(format="dict") # keys: run, stages, summary
71
+ [(s["stage"], s["status"], s["metrics"]["wallSeconds"]) for s in report["stages"]]
72
+ report["run"]["config"] # resolved settings, including filtering method and params
73
+ len(report["summary"]["createdArtifacts"]), len(report["summary"]["reusedArtifacts"])
74
+ print(run.report(format="markdown")) # config, stage table, outputs, failure or interruption
75
+ ```
76
+
77
+ ### Read frozen fields
78
+
79
+ `run.cells.columns` here: `I, ids, names, s_score, g2m_score, cell_cycle_phase, umap_1, umap_2,
80
+ leiden_*, paris, clusters, doublet_score`, then any `snapshot_columns`; filter and Harmony columns
81
+ are not exposed. `run.features.columns`: `I, ids, names, highly_variable_features`. `fetch` returns
82
+ analysed rows; `fetch_all` aligns to every stored cell (3,847 and 5,025 on the 10x 5K PBMC docs
83
+ dataset, whose live `I` holds 3,948).
84
+
85
+ ```python
86
+ df = run.cells.to_pandas_dataframe(["umap_1", "umap_2", "clusters"])
87
+ clusters = run.cells.fetch("clusters") # int labels starting at 1
88
+ analysed = run.cells.fetch_all("I") # boolean mask over every stored cell
89
+ n_hvgs = int(run.features.fetch_all("highly_variable_features").sum())
90
+ mito = ds.cells.fetch_all("RNA_percentMito")[analysed] # a filter column, aligned to run cells
91
+ ```
92
+
93
+ ### Configure a run
94
+
95
+ Shortcuts and `params` sections are mutually exclusive for the same setting. Validation errors
96
+ (unknown section, column collision, reused label, `leiden={}`) raise before any run record exists.
97
+ `leiden.selected` replaces the silhouette choice and skips `cluster_selection`.
98
+
99
+ ```python
100
+ custom = ds.pipeline.run(label="leiden_custom", leiden={"partitions": [0.4, 0.8]})
101
+ pinned = ds.pipeline.run(
102
+ label="leiden_pinned",
103
+ params={"leiden": {"partitions": [0.5, 1.0], "selected": 1.0}},
104
+ umap=False, doublets=False, markers=False, cell_cycle=False, paris=False,
105
+ )
106
+ pinned["clusters"] == pinned["leiden_1.0"] # True; no cluster_selection key
107
+ labelled = ds.pipeline.run(label="with_labels", snapshot_columns=["cell_type"]) # full recompute
108
+ "cell_type" in labelled.cells.columns # True
109
+ ```
110
+
111
+ `cell_type` stands for a label column you own; never snapshot held-out author labels.
112
+
113
+ ### Handle failures and interruptions
114
+
115
+ A stage error raises `PipelineExecutionError` (`run_id`, `stage`, cause in `__cause__`). SIGTERM,
116
+ SIGINT, SIGHUP or `KeyboardInterrupt` stop at the next safe checkpoint (in testing, the running
117
+ stage finished and kept its artifact) and record `status="interrupted"`. Rerun with the same
118
+ label; completed artifacts are reused (interrupted during UMAP: rerun 10 s, 16 reused; 10x 5K
119
+ PBMC docs dataset). Run it as `timeout 1800 python job.py`; behind a wrapper such as `uv run`, put
120
+ `timeout` inside it (`uv run timeout 1800 python job.py`), because `timeout 1800 uv run ...` killed the run without the handler (performance-and-export.md).
121
+
122
+ ```python
123
+ from scarf import PipelineExecutionError
124
+
125
+ try:
126
+ ds.pipeline.run(label="bad", params={"paris": {"min_cluster_size": -5}})
127
+ except PipelineExecutionError as error:
128
+ failed = ds.pipeline.open(run_id=error.run_id)
129
+ error.stage, failed.status, failed.report(format="dict")["run"]["error"]
130
+ interrupted = ds.pipeline.list_runs(status="interrupted")
131
+ ```
132
+
133
+ A process killed mid-stage (SIGKILL, OOM, or a `timeout` outside a `uv run` wrapper) leaves a record
134
+ stuck at `status="running"` with `summary["uncleanIncomplete"]` true; it claimed no label, so a
135
+ rerun with the label works. Check the process, not the status. Not executed (needs a hard-killed
136
+ finalizer): if a process died after claiming its label, the label stays blocked. After confirming
137
+ the process is gone, call `ds.pipeline.abandon_label_claim(label=..., run_id=..., reason=...)`,
138
+ then rerun. It refuses completed owners and has no timeout heuristic.
139
+
140
+ ### Inspect, load and find artifacts
141
+
142
+ ```python
143
+ status = ds.inspect_artifact(run["pca"])
144
+ status.exists, status.complete, status.path, status.operation # ..., "run_pca"
145
+ status.parameters # {'dims': 21, 'feat_scaling': True}
146
+ status.inputs # serialized refs: normalized, feature_scaling, pca_cell_selection
147
+ status.input_ref("normalized") == run["normalized"] # True
148
+ status.execution_options, status.created_at_ns, status.scarf_version
149
+ group = ds.load_artifact(run["pca"]) # zarr group: data, loadings, center
150
+ group["data"].shape # (analysed cells, 21)
151
+
152
+ ds.list_artifacts(complete_only=True) # assay scope, default assay
153
+ ds.list_artifacts(kind="cell_selection", scope="datastore")
154
+ on_graph = ds.list_artifacts(
155
+ kind="cluster_labels",
156
+ operation="run_leiden_clustering",
157
+ inputs={"graph": run["connectivity_map"]},
158
+ )
159
+ [ds.inspect_artifact(ref).parameters["resolution"] for ref in on_graph]
160
+
161
+ decision = ds.load_artifact(run["cluster_selection"])
162
+ decision.attrs["selectedKey"], decision.attrs["candidateKeys"], decision["scores"][:]
163
+ ```
164
+
165
+ ### Branch from a completed run and trace lineage
166
+
167
+ Pass exact run refs to granular methods. Same inputs return the run's own artifacts; new
168
+ parameters create a branch. Explicit normalization/PCA/graph chains: features-and-graphs.md.
169
+ Clustering choices: clustering-and-embedding.md.
170
+
171
+ ```python
172
+ graph = run["connectivity_map"]
173
+ ds.run_leiden_clustering(graph, resolution=0.5) == run["leiden_0.5"] # reused, no recompute
174
+ fine_clusters = ds.run_leiden_clustering(graph, resolution=2.0) # new cluster_labels ref
175
+ fine_markers = ds.run_marker_search(fine_clusters, features=run["feature_universe"])
176
+
177
+ cells = run["analysis_cell_selection"]
178
+ hvgs = run["highly_variable_features"]
179
+ ds.run_normalization(cells, hvgs) == run["normalized"] # True: reused
180
+ ds.plots.embedding(layout=run["umap"], color_by=fine_clusters, show=False).close()
181
+
182
+ lineage = ds.lineage({"selected": run["clusters"], "fine": fine_clusters})
183
+ markdown = lineage.to_markdown() # Mermaid graph (shared upstream once) plus details
184
+ mermaid = lineage.to_mermaid()
185
+ snapshot = ds.summary().to_dict() # columns, artifact inventory, run counts, labels
186
+ ```
187
+
188
+ ### Record the analysis for handoff
189
+
190
+ Write the prospective record (question, unit of inference, input refs, planned writes,
191
+ alternatives, decision criteria) before the first mutating call, then extend it into the handoff:
192
+ add metadata roles and confounding, evidence per alternative, unsupported claims, required
193
+ validation and exported files. Refs round-trip with `to_dict()` and `ArtifactRef.from_dict()`.
194
+
195
+ ```python
196
+ import json
197
+ from pathlib import Path
198
+
199
+ out = Path("handoff")
200
+ out.mkdir(exist_ok=True)
201
+ refs = {"cellSelection": "analysis_cell_selection", "featureSelection": "highly_variable_features",
202
+ "graph": "connectivity_map", "selectedClusters": "clusters"}
203
+ record = {
204
+ "question": "Which broad populations are present?",
205
+ "unitOfInference": "cells from one sample; no replicate-level claims",
206
+ "pipelineRun": {"runId": run.run_id, "label": run.label, "status": run.status},
207
+ "refs": {name: run[key].to_dict() for name, key in refs.items()}
208
+ | {"fineClusters": fine_clusters.to_dict()},
209
+ "alternatives": "Leiden 0.5 (pipeline choice) vs 2.0",
210
+ "decision": "provisional", "openQuestions": ["Does MAD filtering remove a real population?"],
211
+ }
212
+ (out / "handoff.json").write_text(json.dumps(record, indent=2))
213
+ (out / "run_report.md").write_text(run.report(format="markdown"))
214
+ (out / "lineage.md").write_text(lineage.to_markdown())
215
+ saved = json.loads((out / "handoff.json").read_text())
216
+ scarf.ArtifactRef.from_dict(saved["refs"]["fineClusters"]) == fine_clusters # True
217
+ ```
218
+
219
+ ## Parameters that matter
220
+
221
+ | Parameter | Default | Change when |
222
+ | --- | --- | --- |
223
+ | `label` | `None` | Always name runs you will reopen; pick a new name per variant. |
224
+ | `cell_key` | `"I"` | A boolean column already defines the cohort. |
225
+ | `filtering` | `True` (pooled MAD, 3 MADs, over `RNA_nCounts/nFeatures/percentMito/percentRibo`) | Data already filtered (`False`) or per-sample or manual bounds needed (mapping; see quality-control.md). |
226
+ | `harmony_batch_columns` | `None` | Real batch columns exist; see integration-and-comparisons.md. |
227
+ | `hvg_count` / `pca_dims` / `neighbors_k` | `1000` / `21` / `11` | A representation choice is under test. `pca_dims=0` skips PCA (output key `reduction`). |
228
+ | `umap` | `True` | `False` skips `embedding_initialization` and `umap`. |
229
+ | `leiden` | `True` = `[0.5, 0.75, 1.0, 1.25]` | Other candidates: `{"partitions": [...]}`; pin with `params["leiden"]["selected"]`. |
230
+ | `cell_cycle`, `paris` | `True` | Skip to save time (cell cycle is the slowest stage on remote counts). |
231
+ | `doublets`, `markers` | `True` | `False` to skip; both are required off when `leiden=False`. |
232
+ | `snapshot_columns` | `()` | Run-mode plots or `run.cells` need live columns. Choose on the first run (see Pitfalls). |
233
+ | `params` | `None` | Stage settings (`hvg`, `pca`, `umap`, `leiden`, `tsne`, `membership_strength`, ...). |
234
+ | `callback` | `None` | Progress events: `event.kind`, `event.stage`, `event.error`. |
235
+
236
+ ## Check before moving on
237
+
238
+ - `run.status == "completed"` and the expected keys are present.
239
+ - `run.report(format="markdown")`: stage statuses, wall seconds, created versus reused counts,
240
+ resolved `filtering` config, and any failure or interruption section.
241
+ - Kept cells: `run.cells.fetch_all("I").sum()` versus the store total. Audit removed cells (by
242
+ your own clusters or a marker panel, not held-out labels) before trusting clusters.
243
+ - `decision["scores"]` from `cluster_selection`: silhouette is a provisional baseline, not ground
244
+ truth.
245
+ - For any ref you consume: `ds.inspect_artifact(ref).complete` is `True`, and `operation`,
246
+ `parameters` and `inputs` match what you intend.
247
+
248
+ ## Pitfalls
249
+
250
+ - `snapshot_columns` (and Harmony columns) enter the cell snapshot that feeds filtering, so adding
251
+ one changes `analysis_cell_selection` and recomputes everything downstream. Same parameters under
252
+ a new label reused all 26 artifacts in 2 s; adding `["cell_type"]` created 24 of 27 again
253
+ (10x 5K PBMC docs dataset).
254
+ - `ds.list_artifacts(kind="cell_selection")` silently returns `[]`; pass `scope="datastore"`.
255
+ - Compare cell selections from different routes by mask, not by ref: equal values can have
256
+ different ids.
257
+ - A `PipelineRun` is bound to the `DataStore` object that opened it. After reopening the store,
258
+ reopen the run, or plotting raises `ValueError`.
259
+ - `run.get(key)` returns `None` for a disabled stage, but raises `RuntimeError` on a run that did
260
+ not complete.
261
+ - `run.cells.fetch("clusters")` is integer, while `get_markers(...)["group_id"]` is a string.
262
+ - In a backgrounded non-interactive shell, SIGINT is ignored; use SIGTERM to stop a run cleanly.
263
+ `SIGKILL` or OOM leaves an incomplete run with no cleanup.
264
+ - Never read private Zarr paths or mutate `ds.z`, `ds.zw` or run records; use the methods above.
265
+
266
+ ## See also
267
+
268
+ - quality-control.md: filtering policies behind `analysis_cell_selection`.
269
+ - features-and-graphs.md: explicit normalization, PCA, ANN, neighbours and graph chains.
270
+ - clustering-and-embedding.md: re-clustering, UMAP/t-SNE branches, partition comparison.
271
+ - markers-and-annotation.md: `get_markers`, marker tables and labels.
272
+ - plotting.md: `ds.plots.*(run=run, ...)` versus explicit refs.
273
+ - performance-and-export.md: `ds.to_anndata(run=run)` and resource settings.