This release is a pre-release and may not be stable for production use.
tether
Version control (fingerprints, pins, & forks) for heterogeneous datasets.
Status: alpha. See the changelog.
State: vibe coded with Claude Fable 5.1. USE AT YOUR OWN RISK.
Think of tether as DVC for branchable systems: like DVC it commits small
manifests into your git/jj repo, but where DVC only fingerprints files, tether
also pins and forks live systems. Every backend is fingerprinted; what
else tether can do with each depends on what the system offers:
| Backend | Recover | Pin | Fork | Promote | Merge | Diff | History |
|---|---|---|---|---|---|---|---|
| local files and directories; object-store prefixes (S3, GCS, Azure) | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
| single object-store objects with versioning enabled | 🟡 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| git / jj repositories | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Icechunk repositories | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| Neon Postgres databases | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Apache Iceberg tables | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| Delta Lake tables | 🟡 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| Lance datasets | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
| lakeFS repositories (experimental) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| DuckLake catalogs (experimental) | 🟡 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| Dolt databases (experimental) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Recover: can a committed state be opened again later? ✅ held by a pin;
🟡 reopenable by version id for as long as the system keeps that version
(object versioning, Delta/DuckLake snapshots) -- tether records it but has no
ref of its own to hold it; ❌ tether can only tell you it changed. Pin: a
durable native ref (tag, protected branch) tether creates at commit time.
Fork: a writable branch off a pin for tether new. Promote:
fast-forward the base branch to the fork; Merge: a native three-way merge
when it is not a fast-forward. Diff: tether diff --content describes
what changed inside the object. History: tether log lists the object's
own snapshots/versions/commits. The full matrix -- state fields, flags,
per-backend caveats -- is in the
backends guide.
Documentation: https://evanlyall.com/tether/ -- getting started, concepts, pinning, branching and writing, reclaiming storage, use cases, CLI, configuration, backends, registries and SQL, writing a backend, caveats and performance, plus the generated API and CLI reference.
The model
A dataset is a git/jj repository with one small manifest per object, and its bookmarks and the stores' branches are one shape:
- Commits hold references. Each dataset commit records, per object, an
exact state and -- where the system allows it -- a pin: a native,
GC-proof ref (an Icechunk tag, a protected Neon branch, a git tag) that
holds that state. A commit is a complete, reproducible position for every
object at once;
tether open KEY --rev Creads it. - Bookmarks hold branches. The trunk bookmark (
main) stands for every object's upstream branch: working on it writes there. Any other bookmark stands for one branch per system, named after it (tether.ws.<dataset>.feature), forked from the pins of the commit it started at -- decided bytether new -b feature, created on the first write. A working copy on no bookmark is read-only.
commit pins the heads of your bookmark's branches and moves the bookmark to
the new commit, the way a git branch follows its commits. promote lands a
bookmark on the trunk: each system's upstream branch fast-forwards (or
merges) to the bookmark's branch, then main moves to the bookmark's commit.
pull is the fetch: on main it reads every object's upstream branch (and,
for systems without branches, the object itself) and commits what moved.
Everything history-shaped (commits, bookmarks, workspaces, sharing, undo of
the manifests) is the VCS's. What the VCS cannot see -- what tether did to
the stores -- is in tether's own operation log (tether ops, tether undo).
Why this exists (prior art)
Every existing tool versions a single layer:
| Tool | Scope | Relationship to tether |
|---|---|---|
| lakeFS, Quilt, Oxen, DataChain | objects / files | analog of our file objects; lakeFS is also a backend |
| DVC (lakeFS-owned) | files/objects in git | closest structural analog; no pin/fork of live systems |
| Dolt, pgGit, Neon, Databricks Lakebase | one database | vendor-bound; no external-object pins; Dolt and Neon are backends |
| Nessie, Bauplan | Iceberg catalog branching | Iceberg-only |
| Icechunk, Lance, Delta, DuckLake | one dataset / table / catalog | we use them as backends |
| Yggdrasil (replikativ) | cross-system: Clojure protocol stack (snapshot / branch / merge / watch) over Git, ZFS, Btrfs, IPFS, Iceberg, Datahike, lakeFS, Dolt, Podman, with an HLC-coordinated workspace | the closest conceptual sibling; not adoptable from Python (JVM library; its own README marks the Python binding as unmaintained since the initial release) |
| Dagster observable assets | staleness detection | analog of our snapshot/drift step; no branching |
Nothing provides unified version control across files + Icechunk + Postgres +
Iceberg with pinning and forking. So tether borrows jj's working-copy and
operation-log ideas (new REV, stale-working-copy detection, undo), DVC's
manifests-in-VCS layout, and Yggdrasil's
observe-then-record shape (a workspace that watches independent systems and
records their snapshots, rather than a store that holds the data).
Install
pip install tether-vcs[cli] # core + CLI
pip install tether-vcs[cli,icechunk,neon] # add backends you need
pip install tether-vcs[all] # everything
Extras: cli, objectstore (S3/GCS/Azure for file; s3/gcs/azure are
aliases), icechunk, neon, iceberg, delta, lance, lakefs, ducklake,
dolt, postgres (tether publish / import against Postgres), all.
git/jj must be on PATH.
Quickstart
tether lives inside a git or jj repository and commits small manifests there; that repository's history, branches, and workspaces are the dataset's too. Nothing is contacted until you ask for a state.
jj git init my-dataset && cd my-dataset # or: git init my-dataset
tether init
tether add zarr/imaging --kind icechunk s3://bucket/imaging.icechunk
tether add db/metrics --kind neon --project-id prj-123 --database neondb --role runner
tether add raw/plate1 --kind file s3://bucket/raw/plate1/
tether status # clean / modified / drifted per object (local; --snapshot re-fingerprints)
tether commit -m "Baseline" # on main: pin each object's upstream branch, write the manifests, jj/git commit
tether new -b relabel # a bookmark: one branch per system, named after it, forked on first write
tether open db/metrics # postgresql://... on the relabel branch
tether commit -m "Relabel plate1" # pins the branches' heads and moves the bookmark
tether promote # land it: each system's main fast-forwards (or merges), then main moves to the commit
tether verify --all-history # every pin any commit ever named still resolves
from tether import Repo
repo = Repo.find(".")
h = repo.open("zarr/imaging") # writable IcechunkHandle on this bookmark's branch
ro = repo.open(
"zarr/imaging", rev="main"
) # read-only at main's pin; TETHER_REV=<rev> makes this the default
Every command that writes to a store takes --dry-run (and --plan FILE /
--from-plan FILE) so the writes can be reviewed first. The
getting started guide
walks through this with output; the
CLI guide has every
command.
jj or tether?
A tether command exists where an operation has two halves -- one in the VCS,
one in the stores -- that must happen together: commit (pin, commit, move
the bookmark), new (create or join a bookmark, then its branches), pull,
promote, restore, abandon, forget-workspace, and undo / repair
for what tether itself did. Everything that only touches files and history --
describe, squash, rebase, push, jj undo of a non-tether operation -- is the
VCS's, and tether notices what it needs to: a bookmark deleted, renamed, or
moved by hand shows up in status with what to do, as does a vanished dataset
commit. The
concepts guide
has the table.
Non-goals
- Sitting in the data path:
openhands back the system's native handle and steps aside. - Running between commands: no daemon, no watcher; states are compared when a command asks.
- Reimplementing history, branching, or sharing: that is the VCS's job.
tether ops/undo/repaircover only what tether did to the stores, which the VCS cannot see. - Cross-system transactions or a query layer.
Development
uv sync --all-extras
uv run ruff format --check . && uv run ruff check .
uv run ty check
uv run pytest
The publish / import tests start an ephemeral PostgreSQL cluster through
pytest-postgresql; they need pg_ctl on PATH or a Homebrew / Debian
install (brew install postgresql@16; GitHub's Ubuntu runners ship it) and
skip otherwise.
License
Apache-2.0. See LICENSE.
Metadata
Release files for tether-vcs 0.1.0a10
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 |
|---|---|---|---|---|
| tether_vcs-0.1.0a10.tar.gz | 188.1 kB | Without provenance | Details for tether_vcs-0.1.0a10.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| tether_vcs-0.1.0a10-py3-none-any.whl | Python 3 | none | any | Details for tether_vcs-0.1.0a10-py3-none-any.whl |
Total release size: 395.3 kB
Release files / tether_vcs-0.1.0a10.tar.gz
| Download URL | tether_vcs-0.1.0a10.tar.gz |
|---|---|
| Size | 188.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
88023e15a3b01f083b55719b413a278126bbe95ece5094ff8f4e4aa3aa2ce426
|
|
BLAKE2b-256 checksum How to use checksums |
df0a0ce78da68cc6caa1ffd11e0d2f87d442d6d92f69b6953061f84f19ab8c5c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / tether_vcs-0.1.0a10-py3-none-any.whl
| Download URL | tether_vcs-0.1.0a10-py3-none-any.whl |
|---|---|
| Size | 207.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
811d43862e24dddbb8f07913dcbd43485532cb1a7714597e34584ebc871eca45
|
|
BLAKE2b-256 checksum How to use checksums |
2ffdba7a77d47ce57165f08c519ae180eb31cb72cb74462529568466ffcc84d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for tether-vcs 0.1.0a10
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A 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.