Loom
Loom is built and maintained by one person; if it saves your team time, consider sponsoring its development.
Loom is an authoring-time CLI for dbt projects: declare each repeated transformation once, and Loom generates the plain dbt models, tests and properties for it, proves they still match what you declared, and lets you search the whole project before you build something that already exists.
The 30-second example
One small declaration is the whole idea. A pattern is a named, reusable shape; an instance is one concrete model declared against it. This declaration:
pattern: current_hub
instances:
- name: hub_customer
params:
description: Stable customer identity table.
input_models: [stg_customers]
business_key: customer_id
generates this model:
-- Generated by Loom
-- Generator: hub
-- Hash function: sha256
{{ config(materialized='table') }}
SELECT DISTINCT
LOWER(sha256(UPPER(TRIM(CAST(customer_id AS VARCHAR))))) AS customer_hk,
UPPER(TRIM(CAST(customer_id AS VARCHAR))) AS customer_id
FROM {{ ref('stg_customers') }}
WHERE customer_id IS NOT NULL
That is the loop in miniature: the shape is declared once, the model is generated as plain dbt with no Loom macros to call at build time, and the same declaration is what validation, search and coverage read. The first project walks the loop end to end.
Why Loom?
Most dbt projects repeat the same modelling shapes until consistency becomes the problem: copies drift, generators demand a permanent place in your build, and nobody can say with confidence what already exists. Loom's answer, in one line per problem:
| Problem | Loom's answer |
|---|---|
| The same shapes copied model after model, drifting apart | Declare the shape once as a pattern plus one small instance per model; regenerate all of them from the declaration, and let generate --check prove none of them drifted |
| Generators that become permanent runtime dependencies | Loom runs at authoring time only; the generated SQL/YAML is plain dbt with no Loom macros, so dbt runs your project without Loom installed |
| Tooling that covers one paradigm and bends the rest to fit | Generators for Data Vault, Kimball, medallion, 3NF and more under one contract, mixed freely in one project, never converted into each other |
| Migrations that demand everything convert at once | Convert first, then register the rest: drafts where a generator fits (an equivalence proof gates any swap), authoring drafts with one named hole for layered models that fit none, and registration last, because registering a model withdraws it from the drafting pool |
| "Does this already exist?" answered by grepping and guessing | One inventory of every model in the project, generated and hand-written, searchable in the terminal before you build |
| Model knowledge scattered across YAML, docs and heads | One declaration in your git is the authored source; properties, validation, search and coverage all derive from it |
And the guarantee the table rests on: what Loom generates is plain dbt, so you can delete Loom and the project still builds. Adoption is reversible by construction, not by promise.
What Loom is
Loom is three small ideas and four verbs. A generator is the machinery that knows how to build one kind of model (a hub, a staging model, a fact). A pattern is a named, reusable configuration of a generator: the shape your project repeats. An instance set is a collection of instances - concrete models declared against a pattern, supplying only what differs: names, inputs, keys. Hand-written models are not second-class: a registration describes one in the same terms, and it joins the same inventory. The terminology draws the exact boundary between Loom authoring and dbt resources.
Declare, generate, prove, ask. Declare the project once, as patterns, instances and registrations held in your own git. Generate the models, tests and properties from that declaration. Prove the generated files still match the declaration, and that the inventory covers the project. Ask the whole project questions (what exists, what is keyed on this, what would this change touch) before building anything. Generation is episodic; asking is the daily habit.
What generation adds
A generated model is more than its SELECT statement. The generators also produce, from the same declaration and without being asked, the parts of a well-kept model that projects otherwise remember inconsistently or not at all:
- Columns: the hash and surrogate keys a paradigm runs on (hub and link hashes, dimension surrogate keys), the
__loaded_atload timestamp on generated models, and record-source provenance where the generator carries it. - Tests: each generator emits the dbt data tests its own guarantees rest on, default-on and not suppressible:
uniqueandnot_nullon hub and link hashes,relationshipsfrom every link endpoint hash to its hub,not_nullon fact dimension keys and measures, the dedup key'suniqueandnot_nullon silver,not_nullon every gold and mart measure, and their counterparts for the other generators (the full table is in the architecture document). Links additionally carry a guard inside their SQL that fails the build on null, blank or unmatched keys rather than letting a bad join through quietly. - Properties and declarations: a properties document per generated model carrying its description and column documentation from the declaration, so the catalogue dbt shows is written from the same source as the SQL; the dbt source declaration for any source declared inline in metadata; and, for satellites, the generated current-state view alongside the history table.
The boundary is deliberate: Loom originates only the tests its generators' guarantees require. There are no inferred tests on staging models or raw sources, and no business-specific tests: those stay yours to declare, and tests you declare on an instance (generic tests, and dbt unit tests via the unit_tests parameter) are carried into the generated documents verbatim.
What Loom isn't
- Not a dbt replacement, and not in your build. Loom runs at authoring time on your machine; the project it leaves behind is plain dbt that builds without it. There are no Loom macros to install into a dbt project and no runtime component at all.
- Not an orchestrator or a warehouse tool. It schedules nothing, connects to no warehouse to generate (generation needs no warehouse connection), and never runs your models: dbt and your platform do that.
- Not an ingestion tool. It moves no data. Sources, seeds and loading are yours; staging binds the sources you declare.
- Not a general-purpose SQL generator. It generates the shapes its eighteen generators know. Models outside those shapes stay hand-written and are registered, not forced into a fit, and the metadata vocabulary never evaluates arbitrary SQL or Jinja on your behalf.
- Not a migration machine. Conversion is conservative by design: it drafts what provably fits, leaves a named hole where a person must author, and never swaps a model in without an equivalence proof. It will not restructure your project to make it convertible.
- Not a data-quality platform. The tests it emits are paradigm guarantees (above). Business rules, anomaly detection and observability remain your tooling's job, alongside the tests you declare yourself.
- Not a hosted service. It is a local CLI working on files in your git. There is no account, no server and nothing to phone home to.
Status and scope
Loom is Alpha software, and this section is the honest boundary of what that means today, stated here in full rather than by reference. Its interfaces (the CLI, the metadata format, the generated output) may change between releases, and changes are recorded in the changelog. What does not change is the exit guarantee above: generated output is plain dbt, so no Loom change can strand a built project, and if a second metadata format version is ever introduced it ships only with its migration helper in the same release, so declared metadata is never left without a migration path.
Dialect support has exactly two states. Executed and supported: DuckDB. The full lifecycle (generate, seed, build, runtime tests) runs on DuckDB, in CI. Generation only (parse-checked, not executed): PostgreSQL, ClickHouse, Databricks, Snowflake, BigQuery, Redshift, Azure Synapse. Loom generates dialect-flavoured SQL for these dialects and the test suite parse-checks every generator's output under each dialect's grammar; nothing is executed on them. That is a syntax guarantee, not adapter compatibility or a support claim, and no other dialect statement anywhere in this documentation should be read as widening it.
Install
Loom needs Python 3.11 to 3.14 and installs from PyPI:
python -m pip install loom-dbt
Loom does not depend on dbt or on any adapter: install those the way you already do for your project (the examples use dbt-duckdb, the executed dialect). Check the install with loom --version, then take one of the two doors below.
Two doors
There are two ways in, depending on whether the project exists yet. Start from the question you actually have, and take the matching door.
New project: work through the first project, a complete project in fourteen authored files (one source, two staging models, one hub, one hand-written model) whose README walks the loop step by step: what to run, what you should see, and what just happened. When the loop makes sense, the quickstart showcase is the second project: every paradigm composed in one project, with the add-a-model recipe for day-two work. Both examples run locally with no credentials, external data or dbt packages.
Existing project: the adoption playbook converts first and registers the rest, and the order is load-bearing. loom convert drafts a conversion for every model unknown to Loom that fits a generator, and an authoring draft (inputs pre-wired, the transform left as one named hole) for layered models that fit none; nothing is swapped in until an equivalence proof passes. Then loom register inventories the models you are keeping hand-written, making them searchable and coverage-checked, and withdraws them from convert's drafting pool: that withdrawal is why registration comes second, and --include-registered re-opens a registered model when its circumstances change. A project may stay hybrid forever, and many should. The jaffle-shop example shows the whole pathway end to end on dbt's canonical example project: what converted, what was registered instead and why, and a verified remodel of the models that stayed hand-written, for teams weighing that trade.
What you can model
If a shape is in this table, a generator ships for it today. The architecture links are the per-generator contracts and guarantees.
| What you need | Current capability |
|---|---|
| Project and rename raw columns | Staging, using an authored native dbt source; optional project-authored column macros |
| Represent identities and relationships | Hubs, including multi-source hubs (input_models with per-source provenance, the mapping form of record_source), opt-in persistent retention (incremental append on every dialect; merge on Databricks) and first-insert provenance (record_source, __loaded_at, with semantics per generator in Provenance); links join on hub hashes across two or more endpoints, fail the build on null, blank or unmatched keys, and are runtime-verified non-empty on DuckDB, plus dependent-child keys and a non-historised event-link variant carrying payload |
| Store descriptive vault history | Satellites (standard, non-historised, record-tracking, multi-active and effectivity variants over hub or link parents, with generated current-state views), point-in-time tables, bridge tables and reference data constructs |
| Track dimension history | SCD2 snapshot and view, executed on DuckDB, not detached MERGE |
| Build lakehouse pipelines | Medallion bronze (raw load), silver (deduplicated), gold (aggregates) |
| Build dimensional models | Kimball facts with measures and dimension-key columns, dimensions with surrogate keys |
| Build normalized tables and views | 3NF normalized tables with entity/attribute separation, reporting views over normalized inputs |
| Keep discovery current | Validation, generation, manifest-backed coverage and search, including hand-written models |
| Review more advanced changes | Generated dbt tests, source-structure inspection/baselines and migration planning |
The daily workflow
Day to day, Loom is four commands against one declaration. Declare: edit a pattern, instance or registration (small YAML files under metadata/ and local/registrations/), then run loom validate, the gate every other step builds on. Generate: loom generate previews every file it would write, change or leave alone; add --write to apply the preview, and --select <selector> to scope it to a set of models. Prove: loom generate --check fails when any generated file no longer matches the declaration, and loom check runs validation plus manifest-backed coverage in one gate; both belong in CI. Ask: loom search finds what already exists before you build it: with no query it prints a project overview, and a query searches the one inventory (declared metadata and the dbt manifest as a single corpus) by name, key, column, input, generator, paradigm, owner or status, with faceted counts over the result set and a detail view when exactly one object matches. Search and the action commands share one selector grammar, so the string that finds a set in search is the same string you pass to generate or check with --select; search may add clearly labelled close matches by shape overlap (and --similar-to <model> searches by example), while --select on an action command always takes exact matches only.
Authoring with an AI assistant
Loom is designed to be worked by an AI assistant as much as by hand. The authored surface is small, structured YAML that diffs cleanly; every command reports as JSON as well as text; and generation is deterministic, so an assistant can draft and propose while the same gates a human uses (validate, generate --check, the equivalence proofs) arbitrate what lands. To make that practical, six agent skills ship inside the package: task guidance that teaches an assistant Loom's model, its commands and its working rules, version-matched to the CLI by construction.
| Skill | What it covers |
|---|---|
loom-core |
The shared foundation: the mental model, the responsibility boundary (an assistant drafts and proposes; the practitioner decides), write-safety and the selector grammar |
loom-bootstrap |
Starting a brand-new Loom project from zero |
loom-adoption |
Assessing an existing dbt project: convert what fits, register the rest, prove equivalence before any swap |
loom-authoring |
Extending a project already on Loom: new models, new patterns, tests on existing models |
loom-ask |
Read-only questions over the inventory: what exists, what depends on what, whether something like it already exists |
loom-maintenance |
Keeping an adopted project healthy: drift, coverage findings, diagnosing validate and generate failures |
Installing and using them. The skills are ordinary Agent Skills (the open SKILL.md format). They are already on disk wherever Loom is installed, inside the package at loom/.agents/skills/, and are mirrored at skills/ in this repository. Install them from there with your agent harness's skill mechanism (for example npx skills add lincolnp-oss/loom-dbt), or copy the six directories into your project's .agents/skills/ folder; then just ask your assistant for the task ("adopt this project", "add a hub for orders", "what already exists like this") and the matching skill loads its procedure. The working rules the skills enforce are the review-first protocol: the assistant reads the project through Loom's JSON outputs rather than guessing its structure, drafts metadata YAML only and never writes generated files, gates every draft on loom validate, and leaves generate and any overwrite of a practitioner file to you. The full protocol is in the architecture document's Migration and AI-assisted authoring section.
Learn more
| Task | Start here |
|---|---|
| Start a new project | The first project, the guided on-ramp |
| Add a model to a Loom project | The add-a-model recipe |
| Look up a metadata format or option | The metadata reference: a pattern definition and one composite instance as annotated working examples |
| Migrate an existing dbt project | The adoption playbook |
| Prove a project still matches its declaration | loom generate --check and loom check |
| Look up a command or the selector grammar | The command reference |
| Work with Loom through an AI assistant | The bundled agent skills - loom-core, loom-bootstrap, loom-adoption, loom-authoring, loom-ask and loom-maintenance - shipped inside the package at src/loom/.agents/skills/ and mirrored at skills/ in this repository |
| Understand the design | The architecture document and the decision records |
| Diagnose a failure | Diagnostics and recovery |
| Report a problem | SUPPORT.md |
A note on how Loom was built
In the spirit of the section above: Loom itself was developed with substantial AI assistance. Design exploration, implementation, tests and documentation were drafted and iterated with AI agents, under human direction and review, using much the same discipline the bundled skills teach an assistant working in a Loom project: small reviewable changes, explicit decision points, and automated gates that a change must pass before it lands.
The appropriate caveat is that AI involvement is a fact about the process, not a quality claim in either direction. The evidence you can judge is the same evidence any project offers: a test suite of over 1,700 tests runs in CI on Linux and Windows across Python 3.11 to 3.14, together with DuckDB runtime integration tests, an installed-consumer test and an end-to-end quickstart run on every change; the release tree was additionally put through an independent readiness assessment and exercised end to end by a separate local AI agent before publication. And the structural protection does not depend on trusting any of that: what Loom generates is plain dbt, reviewable like any other code, and your project keeps building if you delete Loom entirely. If you find a defect, an issue with a reproduction is the most useful contribution there is.
Contributing and licence
Report problems via GitHub Issues; SUPPORT.md has what to check first and what a useful report contains, and no support response is promised. To contribute, see CONTRIBUTING.md for setup, checks and change expectations. Loom is released under the MIT License; files Loom generates into your project are yours under your own terms, and Loom claims no additional rights in them.
Metadata
Release files for loom-dbt 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| loom_dbt-0.1.0.tar.gz | 392.0 kB | With provenance | Details for loom_dbt-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| loom_dbt-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for loom_dbt-0.1.0-py3-none-any.whl |
Total release size: 821.3 kB
Release files / loom_dbt-0.1.0.tar.gz
| Download URL | loom_dbt-0.1.0.tar.gz |
|---|---|
| Size | 392.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e556cb120fb1fff8d7b21f7d4c6d1151382a9aedd71d4cdc963edfcbf5b31f6e
|
|
BLAKE2b-256 checksum How to use checksums |
b0549a9421aae6ffff00da97800fd64f535a6a4a4c90d99525e872cb87080492
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logRelease files / loom_dbt-0.1.0-py3-none-any.whl
| Download URL | loom_dbt-0.1.0-py3-none-any.whl |
|---|---|
| Size | 429.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5a0d8f6fcc93c30ed3a573b27a4ef67cf5277fcbb05c7e483153a90128d5ac81
|
|
BLAKE2b-256 checksum How to use checksums |
e8048cf9749df009021c0f425546d0efd5bf57e1be5f0c066446a9041eb31ea4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logSecurity for loom-dbt 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for loom_dbt-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.