docs / concepts

Core concepts

A few fixed rules drive skillmod's behavior: two manifest files with distinct jobs, declarations and installs split by scope, and versions accepted only in immutable form. This page lays out those rules; the exact usage of each command lives in the command reference.

Two files

SKILL.mod is the declaration people maintain and commit. SKILL.lock is the tool-maintained record of what is installed: generated deterministically, never edited by hand. Every command reads and validates both files, so a hand-edited manifest that breaks a rule is reported instead of being halfway applied.

SKILL.modSKILL.lock
Maintained byPeopleskillmod
RecordsDeclarations: source, optional version, per-skill agentsWhat is installed: the resolved version, commit, and dirhash
Hand editsYes, then commitNo

Three values in a lock record, each with its own job:

  • version: the immutable version pinned. An entry with no version in SKILL.mod tracks the latest immutable resolution, which SKILL.lock pins; an entry with one stays on that tag or commit until update runs.
  • commit: the resolved commit. With it, a pseudo-version stays resolvable on a machine that has never seen the tag.
  • dirhash: the locked content digest. Every locked record must be a single h1: SHA-256 digest.
Schema version: both files declare schemaversion = 1. A SKILL.lock written before schema versioning has no schemaversion; it is read as schema version 1 and normalized the next time skillmod writes the manifest state. Explicit zero, negative, and unknown versions are rejected.

Scopes

skillmod has two scopes. Project is the default: the manifests live in the current directory and skills install into .agents/skills/ there. --global (-g) switches to the global scope: declarations live in ~/.agents/skillmod/global/ and installs go to ~/.agents/skills/.

ScopeDeclaration and lockDefault installation directory
Project (default)SKILL.mod and SKILL.lock in the current directory.agents/skills/ in the current directory
Global (--global)$SKILLMOD_HOME/global/, default ~/.agents/skillmod/global/~/.agents/skills/

A skill installs into .agents/skills/ of the selected scope, and skillmod manages no other directory convention. All commands accept --global (-g); ordinary commands do not merge the project and global manifests.

The same skill can be declared in both scopes at once, and both share one snapshot on disk: every scope uses the same content store, so projects, global skills, and CI runs reuse the same immutable snapshots instead of downloading the same repository again. The global/ directory holds manifests, not a second snapshot cache.

Repository addresses

A repository address takes the form:

<repository>[//<subdirectory-or-skill-name>][@<version>]

The @<version> suffix belongs to the command line only and is never recorded inside source; an @version suffix inside source is rejected, and a version belongs in the version field. The HTTPS transport is implied, so it is never recorded: both files store host/owner/repo. Any other transport is recorded explicitly because it changes how the repository is fetched. A file that still spells out https:// works unchanged and is rewritten to the shorter form the next time skillmod writes it.

The // shorthand

When //<subdirectory> is a single segment, skillmod first tries an exact subdirectory of the repository root, then falls back to a unique skill name anywhere below skills/; ambiguous names require the full path and report the candidates. A single-skill repository needs no //: omitting it makes get discover the root SKILL.md and every SKILL.md below skills/.

# Two sources may publish the same skill name; install the extra entry with an alias
$ skillmod get --alias review-acme github.com/acme/agent-skills//review

Both declarations and both lock records are retained. A lock entry omits dir when the installation directory equals name, and records it only for an aliased entry. Aliases must be portable names, and all installation directories must stay distinct after Unicode normalization and case folding, so the same project behaves identically on Linux, macOS, and Windows. Re-getting the same source with a different alias keeps the old directory and reports that skillmod prune can remove it.

Addresses must be credential-free: embedded user information, a query string, a fragment, and control characters are refused before the address is used or persisted, so a secret cannot reach SKILL.mod, SKILL.lock, the bare repository's configuration, or a diagnostic. The supported transports are https, http, ssh, and file, plus the scp-like git@host:path form; git:// is refused as unauthenticated and unencrypted. An SSH username is kept because it identifies the transport; a password is not, and secrets belong in a Git credential helper, an SSH agent, or the environment.

Versions

Three immutable forms are accepted. A branch name is rejected, because a mutable reference cannot be locked.

FormExampleUse when
Semantic-version tagv1.2.0, code-review/v1.2.0The publisher tags releases
Commit SHAa full 40-character SHAYou need one exact revision
Pseudo-versionv1.0.96-0.20260624023612-49f948faa925A commit pin; the base is the highest tag when the repository has tags, plain v0.0.0 when it has none

A repository without tags yields v0.0.0-<ts>-<hash>; with a base tag it yields v<base>-0.<ts>-<hash>. Locks written before base tags existed use the plain v0.0.0-<ts>-<hash> shape and stay valid; the next update migrates them to the repository's latest tag.

Installation mode

auto prefers native directory symlinks into the shared read-only snapshots and falls back to byte-for-byte copies when links are unavailable, including Windows without link privileges. copy always creates an independent directory. --install-mode overrides the machine configuration for one command.

Links point at a snapshot shared with every other project on the machine, so editing through a link edits that shared content. Detach before editing:

$ skillmod sync --relink --install-mode=copy

Installation mode, absolute cache paths, and scope are deliberately excluded from both files, so identical declarations and versions produce identical manifests on every system and in every installation mode.

Shared storage

There is only one cache, shared by every scope at $SKILLMOD_HOME/pkg/mod/, defaulting to ~/.agents/skillmod/pkg/mod/:

~/.agents/skillmod/pkg/mod/
├── github.com/anthropics/skills@v0.0.0-.../ # directly browsable full-repository snapshot
└── cache/
├── vcs/ # bare Git repositories, keyed internally by hash
├── download/ # repository versions, refs, and resolution metadata
└── locks/

Snapshots are readable and immutable. HTTPS, default-port SSH, and .git URL variants of the same repository share one snapshot, and the identity is credential-free, so a token in a URL can never become part of a cache key.

The first request for a repo@version materializes the whole repository snapshot. Adding another skill from the same version later validates and installs it straight from that local subdirectory, without invoking Git or touching the remote. An explicit @commit reuses an existing snapshot of that commit the same way. Requesting latest, or running skillmod update, keeps online refresh semantics. Set SKILLMOD_HOME to relocate the store.

--dry-run leaves manifests and installations untouched, though remote provenance verification may still populate the shared cache. prune removes stale installation entries without deleting link targets or snapshots. There is no automatic cache eviction; before deleting a snapshot by hand, confirm that no installed link still uses it, and never delete the cache as a shortcut for removing one dependency.

Sharing with agents

share places a symlink named after each skill into the chosen agent directory, such as .claude/skills/<skill>, and points it at the managed .agents/skills/<skill>. The managed directory stays the one real copy: editing the managed skill is visible through every link at once, and there is nothing to synchronize. Links use the same auto fallback as installs, so a filesystem without symlink support receives a byte-preserving copy instead.

An agent is named by one directory segment and read from .<name>/skills under the scope root, so the list is open — workbuddy is as valid as claude. Only the name is recorded, never the directory: a stored path would not reproduce elsewhere, while a name is enough to rebuild the link.

# SKILL.mod
[[skill]]
name = "review"
agents = ["claude", "codex"]

sync recreates the links each entry describes on a new machine, and verify reports a missing or drifted one; an agent directory absent from the machine is skipped, because the declaration expresses team intent, not a machine requirement. Names use letters, digits, ., _, and -; a leading dot and letter case are normalized, so .Claude and claude are the same destination and cannot appear together in one list. A name that is not one portable directory segment is rejected when the manifest is read. An entry without agents, or with an empty one, means the skill stays only in the managed directory.

share --remove --agent <name> (-r) is the declaration's exit: it unlinks the named agents from the skills the command names and drops them from those entries' agent lists, leaving destinations that hold foreign content alone and saving the declaration only after the links are down. Removing a skill, or pruning it as stale, takes the links that mirror its managed copy down with it, and an entry left with no agents loses the field entirely. SKILL.lock also remembers destinations that were created: removing an agent name by hand removes the intent to share, but leaves the existing link for remove or prune to clean up later.

Next: the exact usage of every command lives in the command reference; distribution, updating, and adopting what already exists live in the scenario guide.