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.
pip install pitloom
loom . # SBOM for the Python project in the current dirInstall extras to enable metadata extraction from AI model files or from Hugging Face Hub:
pip install -e ".[ai]" # all supported AI model formatsSee CONTRIBUTING.md for the dev install.
| 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. |
Generate an SBOM for a Python project in the current directory:
loom .
loom /path/to/project -o sbom.spdx3.jsonGenerate 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 --prettyOr 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 worksloom -h shows the full option list.
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 generationThat'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 fragmentsThe 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.
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.
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.
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 SBOMpitloom.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.
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.0See working-docs/implementation/github-action.md for inputs, outputs, and more recipes.
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.
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.
git clone https://github.com/bact/sentimentdemo.git
loom sentimentdemoThe 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.
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.
- SPDX 3.0 Specification
- PEP 770 – SBOM metadata in Python packages
- Design document
- Bennet et al., “Implementing AI Bill of Materials with SPDX 3.0”, The Linux Foundation, 2024.
- 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.
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.
