Loom
The smallest tool that gives any Python script content‑addressed caching, partial re‑execution, and node‑level diffing — without an orchestration platform.
Loom treats every function call in your pipeline the way Bazel treats a build target or Git treats a commit: as a hashed, content‑addressed node in a dependency graph. Change one prompt buried deep in a pipeline, and Loom re‑runs only that step and everything downstream of it — not the whole pipeline.
Table of Contents
- What Loom Solves
- How It Works
- Quick Start
- Key Features
- CLI Reference
- Installation
- Comparison with Existing Tools
- Limitations (Read This)
- Roadmap Status
- Contributing & Tests
- License
What Loom Solves
If you’re hand‑rolling an agent script — not running it through a pipeline platform — you’ve likely faced these problems:
- Tweaking one prompt or one tool halfway through forces you to re‑run the entire pipeline — burning tokens, money, and wall‑clock time.
- When a pipeline’s output changes between two runs, you have no way to see exactly where the two runs diverged — you’re left diffing final text blobs and guessing.
- Debugging means adding print statements and re‑running the whole pipeline again, and again, and again.
Loom solves this by:
- Hashing every step’s source code + arguments (and upstream node hashes) → deterministic cache keys.
- Caching step outputs on disk (or S3/Redis) — identical calls return instantly.
- Recording every run as a list of nodes, so you can
difftwo runs node‑by‑node. - Forking a run with a new input — only the changed step and its downstream steps re‑execute.
All of this works with plain Python functions – no special pipeline declarations, no extra infrastructure.
How It Works
Every @loom.step call is hashed from:
- The source code of the step function itself — so editing a prompt template inside the function body invalidates the cache automatically.
- Its arguments — either their content hash, or, if an argument is itself the traced output of an upstream step, that step’s node hash. This turns a plain chain of Python function calls into a real hashable dependency graph — without any manual wiring.
user code Loom
--------- ----
@loom.step
def plan(q): ─────► 1. hash(source(plan) + q)
... 2. cache lookup
│
hit ◄──┴──► miss
│ │
return cached execute plan(q)
output cache + record
│ │
└─────┬──────┘
▼
tagged output (carries node hash)
│
passed into the next @loom.step call
▼
hash includes the UPSTREAM node hash
(so changing `plan` invalidates every-
thing downstream of it automatically)
---
## Quick Start
```bash
pip install loomtrace
import loom
@loom.step
def plan(query: str) -> str:
return llm.call(f"Plan: {query}")
@loom.step
def execute(plan: str) -> str:
return tool.run(plan)
with loom.Run("research-agent") as run:
p = plan("find competitors of X")
result = execute(p)
run.save()
Run that pipeline again unchanged — every step is served from cache in milliseconds. Change plan’s prompt or the input — only plan and its downstream steps re‑run.
Key Features
Core Caching & Execution
@loom.step– caches any function.loom.Run(name)– context manager; records every step call.run.save()/Run.load(path)– persist/restore runs as JSON.run.fork(pipeline_fn, **kwargs)– re‑run with new inputs; unchanged steps are cached.loom.diff_runs(a, b)/loom.first_divergence(a, b)– node‑by‑node diff.loom.DiskCache(root=...)– default local cache; subclassloom.Cachefor other backends.
Remote Cache Backends (S3 / Redis)
from loom import S3Cache, RedisCache
# S3
cache = S3Cache(bucket="my-bucket", prefix="loom/")
# Redis
cache = RedisCache(url="redis://localhost:6379/0", key_prefix="loom:")
with loom.Run("pipeline", cache=cache) as run:
...
LangChain / LangGraph Adapter
from loom.langchain import wrap_runnable
from langchain.chains import LLMChain
chain = LLMChain(...)
cached_chain = wrap_runnable(chain, name="my_chain")
with loom.Run("lc_run") as run:
result = cached_chain.invoke({"input": "Hello"})
Web UI
loom web --runs-dir .loom_runs --port 5000
Then open http://localhost:5000 to browse runs, inspect nodes, and diff runs visually.
Async Steps & Concurrency
import asyncio
import loom
@loom.async_step
async def fetch_data(query: str) -> str:
await asyncio.sleep(0.1)
return f"Data for {query}"
async def main():
async with loom.AsyncRun("async_demo") as run:
results = await loom.gather(
fetch_data("A"),
fetch_data("B")
)
run.save()
asyncio.run(main())
CLI Reference
| Command | Description |
|---|---|
loom show <run.json> |
List all nodes in a run |
loom diff <a.json> <b.json> |
Node‑by‑node diff |
loom stats <run.json> |
Cache hit rate and timing |
loom web |
Launch the web UI |
Installation
pip install loomtrace
Optional extras:
pip install loomtrace[s3] # S3 support
pip install loomtrace[redis] # Redis support
pip install loomtrace[langchain] # LangChain adapter
pip install loomtrace[web] # Web UI (Flask)
pip install loomtrace[all] # all of the above
Comparison with Existing Tools
Honest take: content‑addressed step caching with automatic invalidation is not a new idea. ZenML and Dagster both already do it, well, in production. Loom is a smaller, single‑purpose version for standalone scripts.
| Tool | Category | What it does | Where it differs from Loom |
|---|---|---|---|
| ZenML | ML pipeline platform | Hashes step code, parameters, and artifacts; caches outputs; invalidates on code changes | Full platform: artifact store, stack config, UI, ML‑lifecycle features. You declare pipelines/steps in its framework. |
| Dagster | Data orchestrator | Op/asset memoization with version‑based cache keys; built‑in lineage and scheduling | Full platform — assets, sensors, a runtime you deploy, not a single importable decorator. |
| LangSmith / Langfuse / Helicone | LLM observability | Log, trace, and visualize LLM calls after the fact | Doesn’t cache or re‑execute — every re‑run still costs full price and time. |
| MLflow | Experiment tracking | Tracks metrics, params, and artifacts | Not content‑addressed caching; no automatic partial re‑execution. |
| DVC | Data/pipeline versioning | Content‑addressed, Git‑like caching for file‑based pipelines | Built around files and CLI pipeline stages, not live in‑process Python call graphs. |
| Bazel / Nix | Build systems | Content‑addressed, incremental builds | Not Python‑ or agent‑aware; infrastructure‑level, not a pip‑installable library. |
| Loom | Single‑purpose library | Same core idea (hash code + args, cache, invalidate on change) + node‑level diff, but as one dependency‑free decorator with no platform | Smaller, narrower — for standalone scripts. |
The takeaway: if you already use ZenML or Dagster, their caching is more mature — use it. Loom exists for the case: “I have a standalone agent script, I don’t want to adopt an orchestration platform, and I want dependency edges inferred automatically from plain Python.”
Limitations (Read This)
- Steps should be pure. Caching assumes output depends only on declared inputs. Hidden side effects (global mutation, reading
time.time()) won’t be tracked correctly. - Value tagging covers most types, not all.
str,tuple,frozenset,list,dict,set, and any object with__dict__are tagged directly.int,float,bool(fixed C layout) fall back to a transparentTracedBoxwrapper – documented, not a silent failure. fork()re‑invokes your pipeline function – it does not resume from a checkpoint. Speed comes from cache hits, just like Bazel/DVC.- No distributed cache – but the
Cacheinterface is pluggable;S3CacheandRedisCacheship today. - Async is supported, but parallel DAG execution is basic (
gather). True multi‑branch concurrency is planned.
Contributing & Tests
Contributions welcome — see CONTRIBUTING.md. Run the test suite with:
pip install -e ".[dev,all]"
pytest tests/
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file loomcache-1.0.0.tar.gz.
File metadata
- Download URL: loomcache-1.0.0.tar.gz
- Upload date:
- Size: 27.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16a80179f4b4beda1cf6d9f34c109e6d8a888b7bf21baac32811ae70226e14fe
|
|
| MD5 |
1ada930a1c10d225d600085b7db012dd
|
|
| BLAKE2b-256 |
f4d4256cd3e9654ef690f1296cc91d6e80735ee0208cf39d38e9b77f2e1f00c3
|
File details
Details for the file loomcache-1.0.0-py3-none-any.whl.
File metadata
- Download URL: loomcache-1.0.0-py3-none-any.whl
- Upload date:
- Size: 24.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60033921642ba9ab791b98a03dd950e045405226479bbd2aa065f369d2633f0b
|
|
| MD5 |
02bdd1a6d1ac065d5510645ea0fe1468
|
|
| BLAKE2b-256 |
1f8b954dd599edef4d4818d011f725ae8fce54faf58532bba460e4a64ebdd9a0
|