Skip to content

bact/pitloom

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Pitloom - An 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.

Ways to use Pitloom

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.

Usage

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 my-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 above):

[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:

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 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.

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 my-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.

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.11.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.

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.

About

SBOM generation for Python & AI projects. Extract metadata from GGUF, ONNX, and PyTorch models. Build SBOM directly from Hugging Face URL. Native Hatchling build-hook.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages