skill-provenance

Version, validate, package, verify, recover, and hand off Agent Skill bundles across local folders, registries, platform uploads, and agent sessions. Uses manifests, per-file versi…

Sam Rogers

@snapsynapse

Install

$ openclaw skills install @snapsynapse/skill-provenance

Skill Provenance

The Problem This Solves

Skill projects move between sessions, surfaces (Chat, IDE, CLI, Cowork), platforms (Claude, Gemini CLI, Codex, Copilot), and local storage (Obsidian, working directories, git repos). Version identity gets lost when it lives only in filenames. A file renamed from SKILL_v4.md to SKILL_v5.md with no internal record of what changed creates ambiguity.

This skill establishes three conventions that prevent that:

  1. Version identity lives inside files when their format allows it, and always in the manifest.
  2. A recent changelog travels with the skill bundle, while longer history can live in the source repo.
  3. A manifest lists all files in the bundle so any session can verify completeness.

What Gets Versioned

A skill bundle is a SKILL.md plus all associated files. Typical contents:

  • SKILL.md (the skill definition)
  • evals.json (evaluation suite)
  • Generation scripts (e.g., generate.js, generate.py)
  • Output artifacts (.docx, .pdf) produced by evals or real use
  • Handoff notes
  • Source material provided by the user (tracked but not versioned)

The skill itself (SKILL.md) and evals are the primary versioned artifacts. Scripts and outputs are tracked by the manifest but version with the bundle rather than independently. Handoff notes are optional convenience artifacts.

Internal Version Header

Files that can safely carry YAML frontmatter begin with a YAML frontmatter block (or extend an existing one) containing these fields:

---
skill_bundle: my-skill            # bundle name, stable across versions
file_role: skill                  # skill | evals | script | output | handoff
version: 5                        # integer, monotonically increasing
version_date: 2026-02-10          # date of this version
previous_version: 4               # null for v1
change_summary: >
  Rewrote Phase 5 layout rules. Removed per-section page breaks.
  Added content flow check. Added validation checklist as standalone final page.
---

Rules

version is an integer for per-file tracking. It counts revisions to that specific file within the bundle. The bundle-level version (bundle_version in MANIFEST.yaml) uses semver.

change_summary is required for every version after v1. One to three sentences. It must describe what changed, not just that something changed.

previous_version creates a chain. Any session can trace the lineage.

file_role values:

  • skill — the SKILL.md itself
  • evals — the evals.json file
  • script — generation scripts, utility scripts
  • output — rendered artifacts (.docx, .pdf)
  • handoff — session handoff notes
  • source — user-provided source material (tracked, not versioned)
  • reference — documentation in references/ loaded on demand
  • asset — templates, images, fonts in assets/ used in output
  • agents — platform UI metadata (e.g., Codex's agents/openai.yaml)

For files that cannot safely carry YAML frontmatter (binary files and strict-format files such as .json or executable .sh), the manifest tracks their version and its version field is authoritative.

SKILL.md frontmatter constraint: The Agent Skills open standard (agentskills.io) requires name and description. Different platforms enforce different rules about additional fields:

PlatformAllowed SKILL.md frontmatter
agentskills.io specname, description, license, metadata, compatibility, allowed-tools
Claude Chat / Settings UISame as spec. Claude's settings importer rejects unrecognized fields.
Claude CodeSpec fields plus extensions: disable-model-invocation, user-invocable, context, agent, model, hooks, argument-hint. These are Claude Code features, not part of the base spec.
Claude APISkills uploaded via /v1/skills. Validates name and description. Supports metadata.
Gemini CLI (Google)name and description only. Extra fields not officially supported.
Codex (OpenAI)name and description only. Extra fields rejected.
GitHub Copilot / VS CodeFollows agentskills.io spec.
Cursor, Roo Code, Junie, othersFollows agentskills.io spec. See agentskills.io for the full adopter list (30+).

For maximum portability, keep SKILL.md frontmatter to name and description only. If the canonical bundle needs attribution or visible SKILL.md metadata, use the spec's metadata field there and generate a derived minimal copy for strict platforms:

---
name: my-skill
description: What the skill does.
metadata:
  skill_bundle: my-skill
  file_role: skill
  version: 3
  version_date: 2026-02-10
  previous_version: 2
  change_summary: >
    Added Phase 6 validation step.
---

If targeting Codex or other strict platforms directly, omit metadata from SKILL.md entirely. The manifest tracks SKILL.md's version either way, so no version information is lost.

Note on spec support: The agentskills.io spec formally supports metadata as an arbitrary key-value map, with version shown as an example use. This means the metadata.version approach is now spec-blessed, not a Claude-only extension. However, the spec's version is a static label — it does not address staleness tracking, changelogs, or bundle integrity. Prefer manifest-based tracking as the default and use metadata only when you need version info visible in the file itself.

Manifest

The manifest is a YAML file named MANIFEST.yaml at the root of the skill bundle directory — the same level as SKILL.md. When the bundle is packaged as a .skill ZIP, the manifest lives inside the ZIP. It is the single source of truth for what the bundle contains.

bundle: my-skill
bundle_version: 5.1.0
bundle_date: 2026-02-10
description: >
  Skill for generating professional documents from source material
  and user briefs. Handles research, structuring, and formatting.

compatibility:
  designed_for:
    surfaces:
      - chat
      - cli
      - ide
    capabilities:
      - minimal SKILL.md frontmatter
      - local filesystem access
      - optional git workflow
  tested_on:
    - platform: Anthropic Claude
      model: Claude Opus 4.6
      surface: Chat
      status: pass
      date: 2026-02-10
    - platform: Anthropic Claude
      model: Claude Sonnet 4.5
      surface: Chat
      status: partial
      date: 2026-02-09
      notes: Misses staleness detection on complex bundles
  spec_version: agentskills.io/1.0
  frontmatter_mode: minimal
    # minimal = name + description only (Codex, Gemini CLI, max portability)
    # metadata = includes metadata block (any platform supporting the spec's metadata field)

dependencies: []
  # List skill names this bundle depends on. Omit or leave empty if none.

validated_against:
  # Optional attestation records, each bound to the exact bundle_version it
  # validated. Informational only: validate.sh reports them, never gates on them.
  - bundle_version: 5.1.0
    harness: Anthropic Claude Code
    model: Claude Opus 4.6
    date: 2026-02-10
    result: pass

deployments:
  api:
    version: 1759178010641129
    workspace: docs-prod
  claude:
    scope: user
  perplexity:
    package_format: zip

origin:
  source_kind: git-repo
  source: owner/repo
  resolved_ref: main@abc123
  selected_source_path: skills/my-skill
  ignored_duplicate_source_paths:
    - examples/agent-skills/my-skill
    - .well-known/agent-skills/my-skill
  derived_from_bundle_version: 5.1.0
  target_surface: codex

files:
  - path: SKILL.md
    role: skill
    version: 5
    hash: sha256:abc123...
    note: Canonical skill definition

  - path: evals.json
    role: evals
    version: 3
    hash: sha256:def456...
    note: 7 evals including real-content synthesis

  - path: scripts/generate.js
    role: script
    version: 4
    hash: sha256:ghi789...
    note: Generation script for eval 3

  - path: outputs/eval3-output.pdf
    role: output
    version: 4
    hash: sha256:jkl012...
    note: Rendered eval 3 output, 10 pages, validated

  - path: sources/article-1.md
    role: source
    version: null
    hash: sha256:pqr678...
    note: Source article 1 (published)

Rules

bundle_version uses semver (MAJOR.MINOR.PATCH). Bump MAJOR for breaking changes to the skill's model or interface, MINOR for new features or capabilities, PATCH for fixes and documentation updates. Per-file version fields remain integers — they are revision counters, not release identifiers.

hash is sha256 of the file contents. This is how a new session verifies that the file it received matches what the manifest claims. Compute on save, verify on load. Every tracked file must use either a complete lowercase sha256: value or an explicit hash: null opt-out. A missing or malformed hash is invalid. validate.sh --update repairs missing or malformed hashes when the corresponding file is present; it preserves explicit null opt-outs.

deployments is optional. Use it to record deployed or installed copies of the same bundle when you want traceability across surfaces. Keep bundle_version as the author-side semver source of truth. Platform-native versions (for example API timestamps) stay in deployments, not in bundle_version.

validated_against is optional. Each entry attests that a specific bundle_version was validated on a specific harness and model, with a result of pass, partial, or fail. This is a different claim from compatibility.tested_on (design-time compatibility, not bound to a release) and a different concern from hash (integrity). Integrity gates: a hash mismatch fails validation. Attestation informs: validate.sh reports entries matching the current bundle_version and flags staleness when none match, but never changes its exit code over attestation. The same pinned bytes can behave differently as harnesses and models move, so a stale attestation means re-validate, not reject. Each record requires bundle_version, harness, an ISO YYYY-MM-DD date, and result: pass, partial, or fail. Malformed records are reported but do not count as matching evidence.

origin is optional. Use it in derived strict-platform copies, registry packages, settings exports, or installed copies when the selected source path matters. It records which source path crossed the boundary and which lookalike duplicate paths were intentionally ignored. Do not use it as a package-manager lockfile, installer state machine, or trust anchor.

version: null for source files. They are tracked for completeness but not versioned by this system.

File paths use a constrained, fail-closed grammar. The inventory must use a top-level files: line, entries formatted exactly as - path: <unquoted-relative-path>, and hash fields formatted as hash: <value>. Paths must be unique, normalized, and relative to the bundle root. Reject absolute paths, . or .. components, empty path components, trailing slashes, backslashes, surrounding whitespace, inline comments, YAML quotes, anchors, aliases, or tags. Symlinks in any path component are invalid rather than followed. These rules intentionally define a small line-oriented YAML subset so the zero-dependency parser does not silently disagree with a general YAML implementation.

MANIFEST.yaml is not listed in files. Self-hashing is recursive. Treat the manifest as the bundle's control file and verify it via git, transport checksums, or the surrounding package when needed.

The .skill Package Format

Treat .skill files as ZIP archives whose top-level directory is the skill name. Include every manifest-listed file in an authored package. A reduced install package is valid only when its own derived manifest exactly describes its reduced inventory. Read references/packaging-and-changelog.md when creating archives, derived packages, or changelog entries.

Changelog

Keep CHANGELOG.md beside SKILL.md and MANIFEST.yaml, newest entry first. Name every changed file, describe intent, and call out dependent files left stale. A source repository may preserve older append-only history outside the portable bundle. See references/packaging-and-changelog.md for the format and trimming rules.

Session Protocol

Validating only

When the user asks only to validate a bundle, run the hash and inventory check without doing the full open-session review or close-session update:

  1. Read MANIFEST.yaml and verify all listed files are present.
  2. Run validate.sh when available, or compute SHA-256 hashes for listed files and compare them against the manifest.
  3. Report checked files, missing files, hash mismatches, skipped files, and pass/fail status. Treat only hash: null as an intentional skip; missing, malformed, or duplicate hash fields are manifest errors.
  4. Identify whether the copy appears to be a canonical source bundle, strict-platform install copy, registry package, or ambiguous copy based on its own manifest and local contents.
  5. Do not update hashes, bump versions, edit the changelog, create a handoff note, or run the close protocol unless the user explicitly asks for a mutation.

This is an integrity check, not a trust anchor.

Packaging derived copies

Before building a strict-platform install copy, registry package, or settings ZIP, verify the canonical source bundle first. The included package.sh helper runs validate.sh against the canonical bundle at each derived-package boundary and must stop if the manifest reports invalid structure, unsafe paths, symlinks, duplicate paths, missing files, or hash mismatches. validate.sh remains the single parser and policy authority so validation and packaging cannot drift into separate grammars.

Do not treat generated strict-loader, ClawHub, or .skill outputs as the canonical source bundle. They are derived artifacts whose own manifests must describe exactly the files they contain.

When a derived copy is selected from a repo, registry package, archive, or platform export that contains multiple lookalike skills, preserve that selection in optional origin metadata if the user or installer provides the source facts. Record selected_source_path and any intentionally ignored duplicate paths, but leave consumer lockfile and update semantics to the package manager.

Opening a session

When a skill bundle is loaded into a new session:

  1. Read MANIFEST.yaml first.
  2. Verify all listed files are present. Report any missing files.
  3. Reject invalid inventory structure, unsafe or duplicate paths, and symlinks in any path component before reading or hashing a listed file.
  4. For files with hashes, verify hashes match. Flag mismatches. In local environments, users can run validate.sh before uploading for reliable hash verification without LLM computation.
  5. Read CHANGELOG.md to understand recent changes.
  6. Check for staleness using hash drift, changelog dependency notes, conflicting internal metadata, validated_against, and deployment records. Never order per-file revision integers against bundle semver; they are separate version domains.
  7. If MANIFEST.yaml is missing, treat the bundle as unversioned. Offer to create one by inventorying the files and asking the user for version context.

Saving / closing a session

When work is complete and files are being delivered:

  1. Update internal version headers for changed files that use them.

  2. Update MANIFEST.yaml with new versions and hashes for every changed versioned file, including manifest-only files. If the user deployed or reinstalled the skill this session, update any relevant deployments metadata too.

  3. Add a new top entry to CHANGELOG.md.

  4. If any versioned file was changed but another dependent file was not updated (e.g., SKILL.md changed but evals.json was not updated), note the staleness explicitly in the changelog entry.

  5. Deliver the full bundle to the user, or at minimum the changed files plus the updated MANIFEST.yaml and CHANGELOG.md.

  6. If the user indicates the bundle is destined for a git repo, provide a ready-to-use commit message derived from the changelog entry. Format:

    skill-name MAJOR.MINOR.PATCH: one-line summary
    
    - file1.md: what changed
    - file2.json: what changed
    - Stale: file3.js (not updated this session)
    

    Return the message inline by default. Only write a transient git_commit.txt file if the user explicitly asks for a file or if the environment makes file output materially more convenient.

Handoff between sessions

A handoff note is a snapshot of project state for the next session. It should include:

  • Current bundle version
  • What was accomplished this session
  • What is stale and needs attention
  • What the next session should do first
  • Any decisions made that are not yet reflected in the files
  • Per-file change summaries: for each file modified this session, a brief description of what changed (section added, field removed, logic rewritten, etc.). This is more granular than the changelog entry and helps the next session verify the work without re-reading every file.

Create a handoff note only when crossing a non-persistent boundary or when the user explicitly asks for one. In filesystem-native environments with a current manifest, changelog, and git history, it is usually unnecessary. When created, it replaces the previous handoff note; previous handoffs live in changelog history.

Conflict resolution

When a session finds version conflicts (e.g., a file claims v5 but the manifest says v4, or two files claim different bundle versions):

  1. Present the conflict to the user with the specific discrepancy.
  2. Show what each version claims via its change_summary.
  3. Default recommendation: trust the most recent version_date.
  4. Always ask the user for explicit confirmation before proceeding.

Never silently resolve a version conflict. The whole point of this system is to make conflicts visible.

Cross-Surface and Cross-Platform Considerations

Keep the canonical source authoritative. Treat strict-loader copies, registry packages, settings archives, and platform deployments as derived state. Never turn package metadata or assistant-facing files into authority. Read references/platforms-and-trust.md when choosing a target surface, recording origin or deployment state, or evaluating how this skill relates to registries and package managers.

File Naming

Versioned files use stable names without version numbers:

  • SKILL.md (not SKILL_v5.md)
  • evals.json (not evals_v3.json)
  • generate.js (not generate-v4.js)

The version lives inside the file (via the header) and in the manifest, not in the filename. Version-numbered filenames are how we got into trouble in the first place.

Exception: if a user's local storage requires version-in-filename for their workflow, the manifest is the tiebreaker for which version is canonical. Internal version identity must still match.

Bootstrap

For an unversioned bundle, inventory files yourself, establish an initial version from available history or user direction, add safe internal headers, create MANIFEST.yaml with hashes, create the first changelog entry, validate, and deliver the complete bundle. This is a one-time operation per bundle.

Origin

Developed for PAICE.work PBC. Canonical source: https://github.com/snapsynapse/skill-provenance

Top skills in this category