Lineage graph
The lineage graph answers questions storage alone can’t: which dataset produced this model? what shipped in 1.4.0? if we delete this checkpoint, what breaks?
Each repository has one graph, assembled from two sources:
| Source | Created by | Deletable |
|---|---|---|
| Inferred edges | CAVS, from your indexed tags: a release contains the artifacts reachable at it | no — they follow the releases |
| Declared edges | you, via the API | yes |
Reading the graph
Section titled “Reading the graph”export API=https://cavsnode.com/api/v1export H="Authorization: Bearer $CAVS_TOKEN"
curl -sS "$API/repositories/$REPO/graph" -H "$H"{ "nodes": [ { "id": "artifact:9f2c8b0e…4f6b", "type": "model", "key": "9f2c8b0e…4f6b", "label": "resnet50", "size": 102340567 }, { "id": "release:v1.4.0", "type": "release", "key": "v1.4.0", "label": "v1.4.0" } ], "edges": [ { "from": "release:v1.4.0", "to": "artifact:9f2c…", "relation": "contains", "inferred": true }, { "id": "3b1f…", "from": "artifact:aa11…", "to": "artifact:9f2c…", "relation": "trained-with", "inferred": false } ]}Node ids are <type>:<key>:
| Node type | Key | Label |
|---|---|---|
artifact |
the object’s oid | its registry name, or a shortened oid if the registry doesn’t know it |
release |
the tag | the tag |
Artifact nodes are enriched from the artifact registry,
so their type is one of model, dataset, media, archive, other — which
is what colours them in the dashboard’s Graph tab.
Declaring an edge
Section titled “Declaring an edge”Only artifact and release are valid node types; from_key, to_key and
relation are required. The relation is a free-form string — pick a vocabulary
and stick to it.
curl -sS -X POST "$API/repositories/$REPO/graph/edges" \ -H "$H" -H "Content-Type: application/json" \ -d '{ "from_type": "artifact", "from_key": "aa11bb22…", "to_type": "artifact", "to_key": "9f2c8b0e…", "relation": "trained-with" }'Requires repository.update. Returns 201 with the created edge, whose id
you need in order to delete it:
curl -sS -X DELETE "$API/repositories/$REPO/graph/edges/$EDGE_ID" -H "$H"Inferred edges have no id and can’t be deleted — remove the tag instead.
A relation vocabulary that works
Section titled “A relation vocabulary that works”There’s no enforced list, which means consistency is on you. A set that covers most real cases:
| Relation | From → To | Read as |
|---|---|---|
trained-with |
model → dataset | “this model was trained with that dataset” |
derived-from |
artifact → artifact | “this was produced from that” |
evaluated-on |
model → dataset | “these metrics come from that set” |
fine-tuned-from |
model → model | “this started from that checkpoint” |
packaged-in |
artifact → release | “this ships in that release” |
replaces |
artifact → artifact | “this supersedes that” |
contains |
release → artifact | inferred by CAVS; don’t declare it by hand |
Write it down in your team’s docs. A graph with trained-with, trained_with
and trainedWith in it is a graph nobody trusts.
Recording lineage from a pipeline
Section titled “Recording lineage from a pipeline”The natural place is right after an upload, when you still hold every oid:
from cavs import CAVS, sha256_file
client = CAVS.from_env()PROJECT = "vision-models"
client.artifacts.upload(path="./data/train-v3.parquet", project=PROJECT, name="train-v3", kind="dataset", version="3.0.0")client.artifacts.upload(path="./out/resnet50.safetensors", project=PROJECT, name="resnet50", kind="model", version="1.4.0")
# The oid is the content address — hash locally, no extra request.dataset_oid = sha256_file("./data/train-v3.parquet")model_oid = sha256_file("./out/resnet50.safetensors")
client.lineage.add_edge( PROJECT, from_type="artifact", from_key=model_oid, to_type="artifact", to_key=dataset_oid, relation="trained-with",)import { CAVS, sha256File } from '@cavsnode/cavs-sdk';
const client = CAVS.fromEnv();const repoId = process.env.CAVS_REPO_ID!; // the TS SDK takes the UUID
await client.lineage.addEdge(repoId, { fromType: 'artifact', fromKey: await sha256File('./out/resnet50.safetensors'), toType: 'artifact', toKey: await sha256File('./data/train-v3.parquet'), relation: 'trained-with',});curl -sS -X POST "$API/repositories/$REPO/graph/edges" \ -H "$H" -H "Content-Type: application/json" \ -d "{\"from_type\":\"artifact\",\"from_key\":\"$MODEL_OID\", \"to_type\":\"artifact\",\"to_key\":\"$DATASET_OID\", \"relation\":\"trained-with\"}"Using it
Section titled “Using it”Impact analysis before deleting. Fetch the graph, find the artifact’s node, walk the incoming edges. Anything pointing at it is something that would lose its provenance.
Reproducing a result. Start from the model’s oid, follow trained-with and
derived-from backwards, download each oid. Because oids are content addresses,
you get exactly the bytes that produced the result.
Auditing a release. Follow the inferred contains edges out of the release
node for the complete manifest.
# Everything v1.4.0 containscurl -sS "$API/repositories/$REPO/graph" -H "$H" \ | jq -r '.edges[] | select(.from == "release:v1.4.0") | .to'
# Everything that points at one artifactcurl -sS "$API/repositories/$REPO/graph" -H "$H" \ | jq -r --arg n "artifact:$OID" '.edges[] | select(.to == $n) | "\(.relation) ← \(.from)"'Limits
Section titled “Limits”- One graph per repository; cross-repository edges aren’t modelled.
- Inferred edges cover release → artifact only. Commit-level provenance lives in the commit index instead.
- Registry enrichment reads up to 200 artifacts per repository, so a very large repository may show shortened-oid labels on its long tail.
- The Runs API (a first-class execution record with automatic run→artifact lineage) is on the roadmap and not available today. Declared edges are the supported mechanism. See Choosing an integration.
- Browsing your data — artifact detail and timelines.
- Releases & snapshots — where inferred edges come from.

