Skip to main content

Pitloom - SBOM generator for AI models and Python projects

PyPI - Version GitHub License DOI

Automated transparency, woven from the ground up.

Pitloom automates the generation of SPDX 3-compliant SBOMs for AI models and Python projects. It reads metadata directly from Python packages and AI models (GGUF, ONNX, PyTorch, Safetensors), producing standardized SPDX 3 JSON artifacts -- as a CLI, a library, or a native Hatchling build hook.

The Pippin Pitloom

Contents

Quick start

pip install pitloom
loom .            # SBOM for the Python project in the current dir

Optional features

Install extras to enable metadata extraction from AI model files or from Hugging Face Hub:

pip install -e ".[ai]"       # all supported AI model formats

See CONTRIBUTING.md for the dev install.

Usage

Surface Reach for this when...
Command line (loom / pitloom) You want a one-off SBOM from a terminal, a Makefile target, or any shell script.
Hatchling build hook You build wheels with Hatchling and want an SBOM embedded automatically.
Python API You are calling Pitloom from Python code you control.
Python tracking decorator You are training/fine-tuning a model and want to capture provenance as you go, as an SPDX fragment.
GitHub Action Your project isn't Hatchling-based, or you just want CI to produce an SBOM artifact with one uses: line.
Agent Skill You want an AI coding agent to generate (and optionally enrich) an SBOM on request.
Claude Code plugin You use Claude Code and want the Skills installable with one command.

Command line

Generate an SBOM for a Python project in the current directory:

loom .
loom /path/to/project -o sbom.spdx3.json

Generate an SBOM for a single AI model file, without a Python project directory (output written to the current working directory). Supported local formats: GGUF, ONNX, Safetensors, PyTorch (.pt/.pth), Keras, HDF5, NumPy, fastText:

loom -m path/to/model.safetensors -o model.spdx3.json
loom -m path/to/model.gguf --pretty

Or pass a Hugging Face Hub URL or model ID directly -- no local file required. Pitloom fetches metadata from the Hub (model card, config.json, tokenizer_config.json, generation_config.json) and produces an enriched ai_AIPackage SBOM. Requires huggingface_hub (pip install pitloom[huggingface]):

loom -m https://huggingface.co/mistralai/Mistral-7B-v0.1
loom -m Qwen/Qwen3-235B-A22B   # bare model ID also works

loom -h shows the full option list.

Hatchling build hook

Pitloom can embed an SBOM automatically into every wheel you build, at .dist-info/sboms/sbom.spdx3.json, per PEP 770 (wheels only). Add pitloom as a build requirement (Hatchling 1.28.0+ required) and register the hook:

[build-system]
requires = ["hatchling>=1.28.0", "pitloom>=0.11.0"]
build-backend = "hatchling.build"

[tool.hatch.build.hooks.pitloom]
enabled = true    # set to false to skip SBOM generation

That's all -- hatch build/python -m build now embeds the SBOM, always as compact canonical JSON. Basename and fragments are configured under [tool.pitloom]; creator/tool metadata uses the same [[tool.pitloom.creator]] / [[tool.pitloom.creation-tool]] / [tool.pitloom.creation] tables the CLI reads (see Creation metadata below):

[tool.pitloom]
sbom-basename = "sbom"   # -> "sbom.spdx3.json"

[tool.pitloom.fragments]
files = ["fragments/model.json"]   # merge externally tracked fragments

Python API

The SBOM generator can be used programmatically:

from pathlib import Path
from pitloom.core.creation import CreationMetadata, Creator
from pitloom.assemble import generate_sbom

generate_sbom(
    project_dir=Path("/path/to/project"),
    output_path=Path("sbom.spdx3.json"),
    creation_metadata=CreationMetadata(creators=[Creator(name="Your Name")]),
)

pitloom.assemble also exposes generate_ai_model_sbom() (a local model file) and generate_huggingface_sbom() (a Hub model ID or URL), with the same output_path/creation_metadata/pretty keywords.

Python tracking decorator

Developers can annotate scripts or Jupyter notebooks to generate external SBOM fragments that Pitloom will merge during the build process, as a function decorator or a context manager. Use set_model when generating a new model, and use_model when consuming one for inference or evaluation:

from pitloom import loom

@loom.run(output_file="fragments/sentiment_model.json")
def train_model():
    loom.set_model("sentiment-clf")
    loom.add_dataset("imdb-reviews", dataset_type="text")
    # ... training logic ...

@loom.run(output_file="fragments/sentiment_eval.json")
def evaluate_model():
    loom.use_model("sentiment-clf")
    loom.add_dataset("imdb-test-set", dataset_type="text")
    # ... evaluation logic ...

See Python tracking decorator advanced usage below for more details.

Use Pitloom as a GitHub Action

Add SBOM generation to any repository's CI with a single step, for any Python build backend, not just Hatchling:

- uses: bact/pitloom@v0.12.0

See working-docs/implementation/github-action.md for inputs, outputs, and more recipes.

Use Pitloom as an AI-agent skill

skills/sbom/ and skills/enrich/ are ready-to-install Agent Skills for Claude Code and the Claude Agent SDK: sbom generates an SBOM on request; enrich augments an existing one with detail read from a README or model card, via Pitloom's fragment system.

mkdir -p ~/.claude/skills   # or .claude/skills for a project-scoped install
cp -r /path/to/pitloom/skills/sbom /path/to/pitloom/skills/enrich ~/.claude/skills/

See working-docs/implementation/agent-skill.md for full install instructions.

Use Pitloom as a Claude Code plugin

The Skills above are also installable as a plugin, self-hosted from this repository:

/plugin marketplace add bact/pitloom
/plugin install pitloom@pitloom

Once installed: /pitloom:sbom, /pitloom:enrich (or just ask in plain language). See working-docs/implementation/claude-code-plugin.md for what the plugin bundles.

Example

git clone https://github.com/bact/sentimentdemo.git
loom sentimentdemo

The generated SBOM includes project metadata, dependencies with version constraints, SPDX relationships, creator/creation info, and per-field metadata provenance. See a more complete example in the examples/ directory.

Detailed features

Creation metadata

These flags apply to project, AI model, and Hugging Face SBOM generation alike. --creator-name is repeatable -- each occurrence starts a new creator, in order; --creator-type (person default, organization, software-agent, agent) and --creator-email set the type/email of the most recently named creator. --creation-tool records what produced it (default "Pitloom", also repeatable; --no-creation-tool to omit); --creation-comment/--creation-datetime set free-text provenance and an ISO 8601 timestamp:

loom . --creator-name "Alice" --creator-email "alice@example.com"
loom . --creator-name "Acme Corp" --creator-type organization
loom . --creator-name "Acme Corp" --creator-type organization --creator-name Alice
loom . --creation-datetime "2026-01-15T10:00:00Z" --creation-comment "CI run #123"

The same fields can be set in pyproject.toml under [[tool.pitloom.creator]] / [[tool.pitloom.creation-tool]] (CLI flags take precedence, replacing the whole list rather than merging):

[[tool.pitloom.creator]]
name = "Alice"
email = "alice@example.com"
type = "person"       # or "organization", "software-agent", "agent"

[[tool.pitloom.creator]]
name = "Acme Corp"
type = "organization"

[[tool.pitloom.creation-tool]]
name = "MyCompany SBOM Wrapper"

[tool.pitloom.creation]
creation-datetime = "2026-01-15T10:00:00Z"
creation-comment = "Generated in CI pipeline #123"

See Creation metadata for what these fields record and why -- the who/what/when/how model behind every element Pitloom emits.

Loom IDs across fragments (pitloom ids)

Fragments are written by independent runs, so the same dataset or model would normally get a different spdxId in each -- leaving the merged SBOM as disconnected islands. The Loom ID registry (loom-ids.json) fixes that:

pitloom ids generate data src --entity model      # pin ids before running
pitloom ids import existing-sbom.spdx3.json       # or reuse ids from an SBOM

pitloom.loom, loom -m, the build hook, and generate_sbom() all auto-discover the registry (or take it from [tool.pitloom.ids] file), so the same file/entity carries the same id everywhere. Regeneration is stable: an unchanged file keeps its id; changed content gets a fresh one (different bytes are different provenance).

At build time merge_fragments unifies fragment elements -- by shared spdxId, by identical SHA-256 content, or (for the per-fragment "Pitloom" Agent/Tool copies) by structural equality; never by name alone. Fragment envelopes are dropped, duplicate relationships removed, the document's profileConformance gains ai/dataset as appropriate, and a second software_Sbom rooted at the merged ai_AIPackage is added, so the wheel ships one connected AI-pipeline graph: the packaged training script generates the model, which was trainedOn datasets that trace back via hasInput to the raw data.

Python tracking decorator advanced usage

loom.run accepts the same creation metadata as the CLI and build hook, via creation_metadata=CreationMetadata(...). With none given, the fragment records the unattended-run default (Pitloom itself as both creator and tool).

The run also records which script produced what: the calling script becomes a software_File (with a SHA-256 hash) with generates relationships to the model it trained and/or the output datasets it wrote. Datasets that exist on disk get verifiedUsing SHA-256 hashes. These generates edges are scoped build (LifecycleScopedRelationship) -- they describe a build-time step, not something that runs in the shipped artifact. Contrast with the hasDataFile relationship Pitloom emits when it detects a script using a model file at runtime (e.g. a predict.py that loads it) -- that one is scoped runtime.

A single run can cover more than one independent preprocessing stage -- e.g. producing train/valid/test splits from separate raw sources in one loom.run block -- without their hasInput lineage bleeding into each other. Pass input_datasets= on add_output_dataset() to name exactly which add_input_dataset() calls a given output derives from:

with loom.run("fragments/preprocess.json") as run:
    for split in ("train", "valid", "test"):
        sources = [f"rawdata/{split}/{label}.txt" for label in labels]
        for source in sources:
            run.add_input_dataset(source, dataset_type="text")
        run.add_output_dataset(
            f"data/{split}.txt", dataset_type="text", input_datasets=sources
        )

Omit input_datasets (the default) when a run has exactly one output batch -- it then derives from every input the run declared, as before.

Metadata provenance

Pitloom tracks the source of each metadata field in the SBOM using the SPDX 3 comment attribute, so questions like "why does the SBOM say the concluded license is MIT?" have a traceable answer. See Metadata provenance for the full explainer and a worked example.

References

License

  • Source code: Apache License 2.0.
  • Documentation: Creative Commons Attribution 4.0 International.
  • Test fixture AI models: individually licensed (Apache-2.0, CC0-1.0, or MIT); see tests/fixtures/README.md. Source repository only -- not included in distribution packages.

Name

A pit loom is a traditional handloom built into a ground-level pit to house its internal mechanisms and the weaver's legs. This "grounded" design provides stability and precision during the weaving process.

We use the loom as a metaphor for the tool's function: it weaves disparate threads of metadata into a cohesive SBOM, creating a transparent, structured "fabric" for the software build.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pitloom-0.12.0.tar.gz (427.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pitloom-0.12.0-py3-none-any.whl (162.9 kB view details)

Uploaded Python 3

File details

Details for the file pitloom-0.12.0.tar.gz.

File metadata

  • Download URL: pitloom-0.12.0.tar.gz
  • Upload date:
  • Size: 427.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pitloom-0.12.0.tar.gz
Algorithm Hash digest
SHA256 127e460489461c35164a016b975a28a000eb9e587abd1baccd5aeedb7d8ffc9e
MD5 afba74024f2d6e6b81f464cde12efeef
BLAKE2b-256 a1c6f03affe521943be5992eee7f375e74d1908d0bd0d2765e1b238eadd97f69

See more details on using hashes here.

Provenance

The following attestation bundles were made for pitloom-0.12.0.tar.gz:

Publisher: pypi-publish.yml on bact/pitloom

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pitloom-0.12.0-py3-none-any.whl.

File metadata

  • Download URL: pitloom-0.12.0-py3-none-any.whl
  • Upload date:
  • Size: 162.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pitloom-0.12.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8b041845952beaa2b99efbbc9833dec94e06ed55dc9678a2743f9fd1cc857c40
MD5 0b618ff2eb3052afd214a83f616983dd
BLAKE2b-256 56635623d8dd5d37386be9d95ef34e2dc7bb78725add1884b21aef1924a66339

See more details on using hashes here.

Provenance

The following attestation bundles were made for pitloom-0.12.0-py3-none-any.whl:

Publisher: pypi-publish.yml on bact/pitloom

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page