Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Open-source FAIR biology data framework. Version artifacts (AnnData, DataFrame, Zarr), track lineage, validate via ontologies (Bionty), query datasets. Integrates with Nextflow, Snakemake, W&B, scVI. For scRNA-seq use scanpy; for ontology lookups use bionty.
.claude/skills/jaechang-hits-lamindb-data-management/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 122% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 156% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 174% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 180% | 0% |
LaminDB is an open-source data framework for biology that makes data queryable, traceable, and FAIR (Findable, Accessible, Interoperable, Reusable). It combines data lakehouse architecture, lineage tracking, biological ontology validation, and a unified Python API for managing biological datasets from raw files to annotated, curated artifacts.
bashpip install lamindb # With extras for specific data types pip install 'lamindb[bionty,zarr,fcs]'
Setup: Requires instance initialization before use:
bashlamin login lamin init --storage ./my-data --name my-project # Or with cloud storage: # lamin init --storage s3://my-bucket --name my-project --db postgresql://...
Instance types: Local SQLite (development), Cloud + SQLite (small teams), Cloud + PostgreSQL (production).
pythonimport lamindb as ln ln.track() # Start lineage tracking # Save an artifact import pandas as pd df = pd.DataFrame({"gene": ["TP53", "BRCA1"], "score": [0.95, 0.87]}) artifact = ln.Artifact.from_df(df, key="results/gene_scores.parquet", description="Gene importance scores") artifact.save() print(f"Saved: {artifact.uid}, size: {artifact.size}") # Query artifacts results = ln.Artifact.filter(key__startswith="results/").df() print(f"Found {len(results)} artifacts") ln.finish()
Artifacts are versioned data objects (files, DataFrames, AnnData, arrays).
pythonimport lamindb as ln import pandas as pd import anndata as ad ln.track() # From DataFrame df = pd.DataFrame({"sample": ["A", "B"], "value": [1.5, 2.3]}) artifact = ln.Artifact.from_df(df, key="experiments/batch1.parquet").save() print(f"ID: {artifact.uid}, Version: {artifact.version}") # From AnnData adata = ad.read_h5ad("counts.h5ad") artifact = ln.Artifact.from_anndata(adata, key="scrna/batch1.h5ad", description="scRNA-seq batch 1").save() # From file path artifact = ln.Artifact("results/figure.png", key="figures/fig1.png").save() # Load back df_loaded = artifact.load() # Returns DataFrame/AnnData/etc. path = artifact.cache() # Returns local file path
python# Versioning artifact_v2 = ln.Artifact.from_df(df_updated, key="experiments/batch1.parquet", revises=artifact).save() print(f"v1: {artifact.uid}, v2: {artifact_v2.uid}") print(f"Latest version: {artifact_v2.is_latest}") # Delete (archive first, then permanent) artifact.delete(permanent=False) # Archive # artifact.delete(permanent=True) # Permanent deletion
Automatic provenance capture for reproducibility.
pythonimport lamindb as ln # Start tracking — captures notebook/script, environment, user ln.track(params={"method": "PCA", "n_components": 50}) # All artifacts created within this block are linked to this run input_data = ln.Artifact.get(key="raw/counts.h5ad") adata = input_data.load() # ... analysis code ... output = ln.Artifact.from_anndata(adata, key="processed/pca.h5ad").save() # View lineage graph output.view_lineage() ln.finish() # Finalize tracking
Search and filter artifacts by metadata, features, and annotations.
pythonimport lamindb as ln # Basic filtering artifacts = ln.Artifact.filter(key__startswith="scrna/").df() print(f"Found {len(artifacts)} scRNA-seq artifacts") # Filter by metadata recent = ln.Artifact.filter( created_at__gte="2026-01-01", size__gt=1000000 ).df() # Filter by annotated features immune = ln.Artifact.filter( cell_types__name="T cell", tissues__name="PBMC" ).df() # Single record retrieval artifact = ln.Artifact.get(key="results/final.parquet") # Exact match, raises if not found artifact = ln.Artifact.filter(key="results/final.parquet").one_or_none() # Returns None if missing # Full-text search results = ln.Artifact.search("gene expression PBMC") # Streaming large files (without full load into memory) artifact = ln.Artifact.get(key="large_dataset.h5ad") backed = artifact.open() # AnnData-backed mode subset = backed[backed.obs["cell_type"] == "B cell"]
Curate datasets against schemas and ontology terms.
pythonimport lamindb as ln import bionty as bt # Annotate artifacts with features artifact = ln.Artifact.get(key="scrna/batch1.h5ad") artifact.features.add_values({ "tissue": "PBMC", "condition": "treated", "organism": "human", "batch": 1 }) # Validate with schema curator = ln.curators.AnnDataCurator(adata, schema) try: curator.validate() artifact = curator.save_artifact(key="validated/batch1.h5ad") print("Validation passed") except ln.errors.ValidationError as e: print(f"Validation failed: {e}") # Standardize cell type names using ontology adata.obs["cell_type"] = bt.CellType.standardize(adata.obs["cell_type"])
Access standardized biological vocabularies for annotation.
pythonimport bionty as bt # Available ontologies # bt.Gene (Ensembl), bt.Protein (UniProt), bt.CellType (CL), # bt.Tissue (Uberon), bt.Disease (Mondo), bt.Pathway (GO), # bt.CellLine (CLO), bt.Phenotype (HPO), bt.Organism (NCBItaxon) # Import and search ontology bt.CellType.import_source() results = bt.CellType.search("T helper") print(results.head()) # Get specific term t_cell = bt.CellType.get(name="T cell") print(f"Ontology ID: {t_cell.ontology_id}") # Explore hierarchy children = t_cell.children.all() parents = t_cell.parents.all() print(f"Children: {[c.name for c in children]}") # Validate a list of terms validated = bt.CellType.validate(["T cell", "B cell", "Unknown_type"]) # Returns boolean array: [True, True, False]
Group related artifacts for batch operations.
pythonimport lamindb as ln # Create a collection artifacts = ln.Artifact.filter(key__startswith="scrna/batch_").all() collection = ln.Collection(artifacts, name="scRNA-seq batches Q1 2026").save() print(f"Collection: {collection.name}, {collection.n_objects} artifacts") # Query collection for artifact in collection.artifacts.all(): print(f" {artifact.key}: {artifact.size} bytes") # Organize with hierarchical keys # Convention: project/experiment/datatype/file # e.g., "immunology/exp42/scrna/counts.h5ad"
| Entity | Purpose | Example | |--------|---------|---------| | Artifact | Versioned data object | counts.h5ad, results.parquet | | Run | Single code execution | Notebook run, script execution | | Transform | Code definition (notebook, script, pipeline) | analysis.ipynb | | Feature | Typed metadata field | tissue, condition, batch | | Collection | Group of related artifacts | "Experiment batches" | | ULabel | Universal label for custom categorization | "high_quality", "pilot" |
| Format | Method | Use Case | |--------|--------|----------| | DataFrame | Artifact.from_df() | Tabular data, metadata tables | | AnnData | Artifact.from_anndata() | Single-cell data | | MuData | Artifact.from_mudata() | Multi-modal data | | Any file | Artifact("path") | Images, FASTQ, custom formats | | Zarr | Via zarr extra | Large array data | | TileDB-SOMA | Via tiledbsoma extra | Scalable cell-level queries |
Every analysis session should be wrapped:
pythonln.track(params={"key": "value"}) # Start: captures code, environment, user # ... analysis ... ln.finish() # End: finalizes lineage links
pythonimport lamindb as ln import anndata as ad ln.track() # Register multiple experiments data_files = ["batch1.h5ad", "batch2.h5ad", "batch3.h5ad"] tissues = ["PBMC", "bone_marrow", "PBMC"] conditions = ["control", "treated", "treated"] for i, (file, tissue, condition) in enumerate(zip(data_files, tissues, conditions)): adata = ad.read_h5ad(file) artifact = ln.Artifact.from_anndata( adata, key=f"scrna/batch_{i}.h5ad", description=f"scRNA-seq batch {i}" ).save() artifact.features.add_values({ "tissue": tissue, "condition": condition, "batch": i }) print(f"Registered batch {i}: {artifact.uid}") # Query across all experiments treated_pbmc = ln.Artifact.filter( key__startswith="scrna/", features__tissue="PBMC", features__condition="treated" ).all() print(f"Found {len(treated_pbmc)} matching datasets") # Load and concatenate import anndata as ad adatas = [a.load() for a in treated_pbmc] combined = ad.concat(adatas) print(f"Combined: {combined.shape}") ln.finish()
pythonimport lamindb as ln import bionty as bt import anndata as ad ln.track() # 1. Import ontologies bt.CellType.import_source() bt.Gene.import_source(organism="human") # 2. Load raw data adata = ad.read_h5ad("raw_counts.h5ad") print(f"Raw: {adata.shape}") # 3. Validate and standardize cell types validated = bt.CellType.validate(adata.obs["cell_type"].unique()) if not all(validated): adata.obs["cell_type"] = bt.CellType.standardize(adata.obs["cell_type"]) # 4. Validate gene names gene_validated = bt.Gene.validate(adata.var_names) print(f"Valid genes: {sum(gene_validated)}/{len(gene_validated)}") # 5. Curate and save curator = ln.curators.AnnDataCurator(adata, schema) curator.validate() artifact = curator.save_artifact(key="curated/validated_counts.h5ad") print(f"Saved curated artifact: {artifact.uid}") ln.finish()
ln.track()ln.Artifact.get(key=...); cache to local pathln.Artifact(...).save()ln.finish() — lineage automatically links inputs to outputs| Parameter | Function | Default | Options | Effect | |-----------|----------|---------|---------|--------| | key | Artifact() | None | String path | Hierarchical storage key (e.g., "project/data.h5ad") | | description | Artifact() | None | String | Human-readable description | | revises | Artifact() | None | Artifact | Previous version to revise | | params | ln.track() | None | Dict | Parameters for the current run | | organism | bt.Gene.import_source() | None | "human", "mouse" | Organism for ontology | | permanent | .delete() | False | True/False | Permanent vs archive deletion | | __startswith | .filter() | — | String | Key prefix filter | | __gte, __lte | .filter() | — | Value | Greater/less than or equal | | __contains | .filter() | — | String | Substring match |
ln.track() / ln.finish(): This captures lineage automatically. Without it, artifacts have no provenance.project/experiment/datatype/file.ext (e.g., immunology/exp42/scrna/counts.h5ad). This enables prefix-based queries.revises= parameter to create new versions, not new keys for the same dataset..filter().df() to inspect metadata first, then .load() or .open() (backed mode) for large files..filter() to find relevant artifacts, then load only what you need.pythonimport lamindb as ln from pathlib import Path ln.track() data_dir = Path("raw_data/") for fcs_file in data_dir.glob("*.fcs"): artifact = ln.Artifact(str(fcs_file), key=f"flow_cytometry/{fcs_file.name}").save() artifact.features.add_values({"assay": "flow_cytometry", "source": "batch_import"}) print(f"Registered: {fcs_file.name} -> {artifact.uid}") ln.finish()
pythonimport lamindb as ln artifact = ln.Artifact.get(key="results/final_analysis.h5ad") # View lineage graph (opens in browser or notebook) artifact.view_lineage() # Programmatic lineage access run = artifact.run print(f"Created by: {run.transform.name}") print(f"User: {run.created_by.name}") print(f"Date: {run.created_at}") print(f"Input artifacts: {[a.key for a in run.input_artifacts.all()]}")
pythonimport bionty as bt bt.CellType.import_source() t_cell = bt.CellType.get(name="T cell") # Explore hierarchy print(f"Parents: {[p.name for p in t_cell.parents.all()]}") print(f"Children: {[c.name for c in t_cell.children.all()]}") # Find all descendants descendants = t_cell.children.all() for child in descendants: grandchildren = child.children.all() print(f" {child.name}: {[gc.name for gc in grandchildren]}")
| Problem | Cause | Solution | |---------|-------|----------| | InstanceNotSetupError | Instance not initialized | Run lamin init --storage ./data --name my-project | | ln.track() fails | No transform context | Run inside a notebook/script, not REPL; or pass transform explicitly | | Artifact key conflict | Key already exists (not a version) | Use revises= for versioning, or choose a different key | | ValidationError | Data doesn't match schema | Run curator.validate() to see specific failures; standardize terms | | Slow queries on large instances | No index on filtered field | Use .df() for overview first; add database indexes for frequently filtered fields | | Ontology import fails | Network issue or wrong organism | Check internet connection; specify organism="human" explicitly | | FileNotFoundError on .cache() | Cloud artifact not synced | Check storage connectivity; use artifact.load() instead for in-memory access |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | pass→fail | 17,121 | 14,082 | -18% | 1 | 1 | 0% | 3,643 | 7,609 | +109% | 0 | 0 | — |
case-01 | fail→pass | 14,060 | 14,952 | +6% | 1 | 1 | 0% | 2,965 | 6,590 | +122% | 0 | 0 | — |
case-02 | fail→pass | 12,627 | 11,388 | -10% | 1 | 1 | 0% | 2,693 | 6,885 | +156% | 0 | 0 | — |
case-09 | pass→pass | 8,570 | 5,585 | -35% | 1 | 1 | 0% | 1,700 | 5,654 | +233% | 0 | 0 | — |
case-04 | fail→pass | 16,063 | 4,064 | -75% | 1 | 1 | 0% | 3,599 | 5,319 | +48% | 0 | 0 | — |
case-05 | pass→pass | 17,116 | 10,213 | -40% | 1 | 1 | 0% | 3,375 | 6,410 | +90% | 0 | 0 | — |
case-06 | pass→pass | 6,263 | 4,155 | -34% | 1 | 1 | 0% | 1,299 | 5,419 | +317% | 0 | 0 | — |
case-07 | fail→pass | 11,036 | 35,714 | +224% | 1 | 1 | 0% | 2,042 | 5,600 | +174% | 0 | 0 | — |
case-08 | fail→pass | 8,571 | 4,662 | -46% | 1 | 1 | 0% | 1,946 | 5,444 | +180% | 0 | 0 | — |
case-10 | fail→pass | 12,881 | 4,193 | -67% | 1 | 1 | 0% | 2,524 | 5,348 | +112% | 0 | 0 | — |
case-11 | fail→pass | 9,309 | 3,392 | -64% | 1 | 1 | 0% | 1,818 | 5,160 | +184% | 0 | 0 | — |
case-12 | fail→pass | 7,236 | 4,696 | -35% | 1 | 1 | 0% | 1,540 | 5,283 | +243% | 0 | 0 | — |
case-13 | fail→pass | 14,501 | 5,199 | -64% | 1 | 1 | 0% | 2,781 | 5,588 | +101% | 0 | 0 | — |
case-14 | pass→pass | 8,686 | 2,787 | -68% | 1 | 1 | 0% | 1,828 | 5,062 | +177% | 0 | 0 | — |
case-15 | fail→pass | 16,416 | 7,335 | -55% | 1 | 1 | 0% | 3,300 | 6,025 | +83% | 0 | 0 | — |
case-16 | fail→pass | 7,952 | 3,777 | -53% | 1 | 1 | 0% | 1,837 | 5,367 | +192% | 0 | 0 | — |
case-17 | fail→pass | 14,084 | 3,147 | -78% | 1 | 1 | 0% | 2,956 | 5,252 | +78% | 0 | 0 | — |
case-18 | pass→pass | 5,728 | 4,484 | -22% | 1 | 1 | 0% | 1,248 | 5,538 | +344% | 0 | 0 | — |
case-19 | pass→pass | 9,009 | 5,565 | -38% | 1 | 1 | 0% | 1,717 | 5,757 | +235% | 0 | 0 | — |
case-20 | fail→pass | 14,106 | 4,378 | -69% | 1 | 1 | 0% | 2,961 | 5,518 | +86% | 0 | 0 | — |
case-21 | fail→pass | 8,999 | 4,057 | -55% | 1 | 1 | 0% | 1,805 | 5,286 | +193% | 0 | 0 | — |
case-22 | pass→pass | 3,348 | 1,719 | -49% | 1 | 1 | 0% | 570 | 4,818 | +745% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +59 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.