DEV Community

vsangaraju
vsangaraju

Posted on

Your agent's skills have no lockfile — a derivation graph for skill provenance

Venkata M Sangaraju, Sr. Database Engineer

Repo: github.com/sangaraju1988/skill-derivation-graph — spec v0.1, reference implementation, Zenodo DOI 10.5281/zenodo.23623206. Discussion: agentskills/agentskills#620.


A skill written for one business unit's reporting — how ambiguous cases should be handled — was copied by another team and built on. The original was later updated because some of the metrics it referenced had been deprecated. The copy never got that change. The other team's agent kept failing tool calls on the deprecated metrics, retried, tried to work around them on its own, and eventually errored out. It took a deep investigation to trace the failures back to a stale copy.

One line saying "derived from X at ref Y" would have turned that into a quick lookup.

That is the whole problem. Agent skills spread by copying, and a copy carries no record of where it came from. There is no lockfile for skills.

The scale of the copy problem

"Skill Constellations" (Seddik, UBC Okanagan, 8 October 2026, arXiv:2610.11169) reconstructed a dated copy network over 2,193,119 skill adoptions on GitHub. Half of all SKILL.md files are verbatim copies of another file. And when a source skill is fixed, only 11.1% of changes to a clone group reach every copy. Most copies keep running the old version.

So the ecosystem has the copy behaviour of a software supply chain and none of its provenance. A patched skill leaves most of its descendants silently running the vulnerable version, and nobody loading a skill can cheaply ask: is this current, and what is it derived from?

Anthropic donated Agent Skills to the Agentic AI Foundation (AAIF, Linux Foundation) on 22 October 2026, and the starting roadmap covers versioned releases, a formal proposal process, and security guidance. Versioning answers "which release is this?" It does not answer "which skills descend from this one, and are they still carrying the old version?" That second question is what this post is about.

The proposal: two optional keys

The full spec is in the repo — Skill Derivation Graph, v0.1 — but the part I'm proposing to the AAIF Agent Skills repo is deliberately small: two optional string keys under the existing metadata map in SKILL.md.

metadata:
  derived-from: "https://github.com/acme/skills/tree/3f2c9e1/doc-summarizer"
  derived-from-hash: "sha256:9b1d…"
Enter fullscreen mode Exit fullscreen mode
  • derived-from is where this skill was copied or adapted from, pinned to a ref (commit SHA, tag, whatever the author uses). No version format is imposed.
  • derived-from-hash is the SHA-256 of the parent's normalised SKILL.md at that ref (line endings normalised to LF, trailing whitespace stripped, these two keys excluded).

Verbatim vs. modified is not declared anywhere. Tooling derives it: hash the copy's own normalised SKILL.md (with the two keys removed) and compare it to derived-from-hash. Same hash, verbatim copy. Different, modified fork.

Agents that don't know the keys ignore them. Nothing is required of anyone. And because the lineage lives in SKILL.md itself, it survives the copy — which is the point, since a root manifest doesn't travel with a skill copied as a directory.

What tooling can do with it

When a source publishes a fix, a tool walks forward through the lineage from the patched version:

  • Verbatim copies of the vulnerable version inherit the bug by construction, so they get flagged stale — blocked by default, or warned loudly, per the consumer's policy.
  • Modified forks get flagged for review. The fix may or may not apply, so a human or a diff decides. No silent upgrades.
  • Broken or missing lineage means "staleness unknown" — never "current." A consumer that can't reach a derivation index has to say so rather than reporting a skill as current.

A deliberately pinned old version is a warning, not a block: a newer version exists, and you load the old one only because you chose to.

The underlying model is a small graph: skills and skill versions as nodes, with three edges — DERIVED_FROM (copied or adapted from), SUPERSEDES (replaces a previous version), PATCHES (a fix release for a vulnerable version). The walk is a breadth-first traversal from the patched version over child edges, carrying a flag for whether any modified edge was crossed on the path. The full semantics, the provenance record schema, and the versioning rule are in the spec.

What it is not

Three limits, stated plainly, because a provenance proposal that hides them isn't honest:

  • It is not trust. The hash is for identity, not trust. A lineage claim is self-declared and unverified — a malicious author can claim a reputable parent. Consumers should weight provenance by author trust and treat unattributed authors accordingly.
  • It is not a registry. The graph is registry-agnostic. It works over skills wherever they live, and nothing here requires AAIF adoption to be useful locally: a team can run the graph over its own skill corpus today.
  • It is not versioning. It sits alongside versioning. AAIF versioning gives each release a stable version; the provenance record references those versions and adds the parent pointers and content hashes that make derivation walkable.

The repo: spec, code, and a benchmark with no results yet

The repo holds three things:

  1. SPEC.md — the v0.1 specification: the derivation-graph model, the provenance record schema, the versioning rule, the fix-propagation mechanism, and a benchmark definition.
  2. A reference implementation — standard-library-only Python. Content hashing of a SKILL.md file, a small derivation graph, and a fix-propagation walk, with a worked 3-node example (source → verbatim copy → modified fork) and unit tests.
  3. A benchmark definition with no results. The spec defines five metrics — fix reach %, time-to-fix, stale-descendant count, fork-detection accuracy, unresolved share — and a synthetic-corpus protocol for measuring them, including the key control: report the same metrics with the derivation graph removed (hash-only matching, no parent pointers) to show what the graph adds. No measurements exist yet, and none are claimed. If the graph adds nothing, the spec has failed — that's the test.

The spec is also archived on Zenodo (DOI 10.5281/zenodo.23623206) so it can be cited.

The discussion

The proposal is live as agentskills/agentskills Discussion #620, which also relates it to the existing threads: versioning (#420), upstream-source declaration (#436), the skill package manifest (#210), licensing and attribution (#379), and provenance attestation (#358) / supply-chain trust (#418). It ends with three questions for maintainers and implementers:

  1. Is a shared key name worth converging on, so independent scanners and registries interoperate?
  2. Is the hash normalisation — LF line endings, trailing whitespace stripped, these two keys excluded — enough, or should the hash cover the whole skill directory?
  3. If this doesn't meet the bar for the spec, would a short "recommended conventions" note in the docs be acceptable?

Try to break it

If you maintain skills, vendor them, or run an index over them: try the convention on a copied skill, run the propagation walk over your own corpus, and tell me where it's wrong. The sharpest feedback is a case where the two keys don't survive a real copy workflow, or where the verbatim/fork distinction by hash gives the wrong answer. Issues are open on the repo.

github.com/sangaraju1988/skill-derivation-graph


Venkata M Sangaraju is a Sr. Database Engineer working on governed conversational analytics and agent memory governance, and the author of "Lineage-Aware Memory Governance" (IEEE Access, 2026).

Top comments (2)

Collapse
 
mayailands profile image
Maya •

Ran the reference implementation at 0bc8397 before finishing the spec, because §3.3 makes a claim I could test: "the derivation type is computed rather than declared."

In the code that ships, it isn't.

I built a three-node graph: doc-summarizer 1.0.0, a 1.1.0 that PATCHES 1.0.0, and a copy whose SKILL.md is byte-identical to 1.0.0 (sha256:daf385d5…0ddcb for both, identical normalised text).

python3 -m skillgraph.cli g.json --source doc-summarizer --patched-version 1.1.0 --vulnerable-version 1.0.0

copy, record says derivation: "verbatim"  ->  stale-vulnerable  block
copy, record says derivation: "modified"  ->  stale-review      warn-review
Enter fullscreen mode Exit fullscreen mode

Same bytes both times. The second run is the copy editing one word in its own provenance record. By §3.3 that edge is verbatim, so it blocks; propagation.py:77 never looks, it reads child_modified = ... versions[child].derivation == "modified" straight off the record. classify_derivation (hashing.py:51) is exported and unit-tested and called from nowhere in cli.py or propagation.py. examples/chain.json as shipped reproduces the same flip (sha256:4ed67192…dae).

So the copier, the party the block exists to constrain, picks block or warn with one word in a file it authors. §6 rests on verbatim descendants inheriting the bug by construction; a self-edit to the record converts that construction into a warning a consumer clicks through.

Second door, same layer: set that copy's content_hash to 64 zeros and the run completes unchanged, exit 0. Nothing compares content_hash to the bytes. cli.py:30 fills it from skill_text only when the field is null. Identity is carried, not checked, so "a hash comparison, not a judgement call" holds in §3.3 and not in any verdict the walk returns.

Fix shape, written down:

  1. In load_graph, recompute the hash whenever skill_text is present and refuse the record (or mark that version unresolved) when it disagrees with content_hash. A stated identity shouldn't outlive its bytes.
  2. In the walk, compute the edge with classify_derivation(child_text, parent.content_hash) wherever the parent record carries its body. Where it can't, keep the declared label but stamp the verdict declared-not-verified in the outcome, so a consumer can see which blocks rest on a self-report.
  3. Two controls in the suite: byte-identical copy labelled modified must still block, genuine fork must still warn. The suite tests classify_derivation in isolation, which is how this got past it.

Worth settling before §7 is measured: fork-detection accuracy should be scored against the computed edge, or it scores the labels.

Post-fix I'll re-run both forged cases against the fixed walk and hand over the receipt: line, before, after, the control that must still block and the control that must still warn. $25, card, no account: buy.stripe.com/fZu5kD1rN0laceh13n5...

The finding above is yours either way.

Collapse
 
analista_83 profile image
Sammi De Blas •

A lockfile for skills is the same problem as a dependency lockfile: the graph has to be hashed at install time, not rebuilt later from whatever the agent happened to load.