Skip to content

Lockfile specification

apm.lock.yaml is the pinned record of every resolved dependency and every file APM deployed into the workspace. It is the source of truth for reproducible installs and for drift detection. Commit it.

The lockfile gives APM four things:

  1. Reproducibility. apm install --frozen reinstalls the exact commits recorded here - no resolution, no network drift.
  2. Integrity. Recorded SHA-256 hashes let apm audit detect tampering with deployed files.
  3. Cleanup. The list of deployed files lets apm prune remove orphans when a dependency is dropped from apm.yml.
  4. Inspection. apm view --lock and apm audit read the lockfile to answer “what is actually installed”.

The lockfile lives at the project root next to apm.yml:

my-project/
|- apm.yml
|- apm.lock.yaml <- here
|- apm_modules/

Always commit it. The lockfile is what makes a fresh clone install identically on any machine.

lockfile_version: "1"
generated_at: "2026-05-10T20:14:00+00:00"
apm_version: "0.6.4"
dependencies:
- repo_url: github.com/octocat/example-skills
resolved_commit: 7f3c9a...
# ...
mcp_servers:
- github
mcp_configs:
github:
type: stdio
command: docker
args: ["run", "-i", "--rm", "ghcr.io/github/github-mcp-server"]
local_deployed_files:
- .github/skills/my-local-skill/SKILL.md
local_deployed_file_hashes:
.github/skills/my-local-skill/SKILL.md: "a1b2c3..."
FieldTypeRequiredNotes
lockfile_versionstringyesSchema version. Currently "1". Bumped on breaking format changes.
generated_atISO 8601 stringyesUTC timestamp of the last write. Ignored by equivalence checks.
apm_versionstringnoAPM CLI version that wrote the file. Diagnostic only.
dependencieslistyesResolved APM packages. See per-entry fields.
mcp_serverslist of stringsnoNames of MCP servers declared in the manifest at install time.
mcp_configsmapnoserver_name -> resolved config dict baseline used to detect MCP drift.
local_deployed_fileslistnoFiles this project itself contributes (sources its own primitives). See self entry.
local_deployed_file_hashesmapnopath -> sha256 for local_deployed_files.

Each item in dependencies describes one resolved package.

FieldTypeRequiredNotes
repo_urlstringyesCanonical repo URL (e.g. github.com/owner/repo). Unique key for the entry, except for virtual and local entries (see below).
hoststringnoFQDN when not inferable from repo_url (e.g. for registry proxies or non-GitHub hosts).
portintnoNon-standard SSH/HTTPS port. Validated to 1..65535 on read.
registry_prefixstringnoURL path prefix when resolved through a registry proxy (e.g. artifactory/github).
resolved_refstringnoThe user-supplied ref from apm.yml (main, v1.2.0, a SHA).
resolved_commitstringnoExact 40-char commit SHA installed. The pin.
versionstringnoResolved semver when the source advertises one.
virtual_pathstringnoSubpath inside the repo for virtual packages (monorepo subpaths).
is_virtualboolnotrue when the entry is a virtual subpath package.
depthintnoPosition in the dependency tree. 0 is the project itself, 1 is a direct dep, higher is transitive. Defaults to 1.
resolved_bystringnorepo_url of the parent that pulled this transitive dep. Absent for direct deps.
package_typestringnoKind of package: apm, skill_bundle, etc. Drives target placement.
skill_subsetlist of stringsnoFor skill_bundle packages: the sorted subset of skill names the manifest selected. Empty means “all”.
deployed_fileslist of stringsnoProject-relative paths APM wrote for this dep. Sorted. Powers prune and audit’s file-presence check.
deployed_file_hashesmapnopath -> sha256 for the files in deployed_files. Powers audit’s content-integrity check. Directory entries (trailing /) have no hash.
sourcestringno"local" for path dependencies. Absent for remote deps.
local_pathstringnoOriginal path from apm.yml for local deps, relative to project root.
content_hashstringnoSHA-256 of the local package’s source tree. Lets APM detect upstream changes to a path dep.
is_devboolnotrue when the dep was declared under devDependencies.
discovered_viastringnoMarketplace name that surfaced this package (provenance).
marketplace_plugin_namestringnoPlugin name as listed in that marketplace.
is_insecureboolnotrue when the source URL was http://.
allow_insecureboolnotrue when the manifest explicitly opted in to the insecure source.

Fields are emitted only when set. A minimal entry is just repo_url plus resolved_commit.

A project that ships its own primitives (skills, agents, prompts under .github/, .claude/, etc.) records the files it deploys to its own targets under local_deployed_files and local_deployed_file_hashes at the top level.

Internally, when the lockfile is loaded, APM synthesizes a virtual dependency entry keyed by "." so that orphan detection, audit, and prune can iterate all “owned” files uniformly. This synthesized entry has:

  • repo_url: <self>
  • source: local
  • local_path: "."
  • depth: 0
  • is_dev: true
  • deployed_files and deployed_file_hashes copied from the top-level local_deployed_* fields.

The synthesized entry is not written back to YAML - the flat local_deployed_* fields remain the on-disk source of truth. Treat the self entry as an implementation detail; do not author it by hand.

When a project is packed with apm pack, the bundled lockfile is enriched with a top-level pack: block:

pack:
format: apm # or "plugin"
target: copilot # or comma-joined list, or "all"
packed_at: "2026-05-10T20:14:00+00:00"
mapped_from: # only when cross-target path remapping happened
- .claude/skills/
bundle_files: # only for plugin bundles
skills/my-skill/SKILL.md: "a1b2..."

The pack block is read by apm unpack to verify bundle integrity and to restore correct target paths. It is stripped from project lockfiles and only appears inside packed bundles.

local_deployed_files and local_deployed_file_hashes are stripped from bundle lockfiles - they describe the packager’s own repo, which is not shipped.

CommandReadsWrites
apm installexisting lockfile (for --frozen and incremental reuse)full rewrite on resolution change
apm install --frozenrequirednever writes; fails on missing pin
apm compileyes (resolution + integrity)no
apm audityesno
apm pruneyes (to identify orphans)yes (after removing orphans)
apm view --lockyesno
apm unpackbundle’s pack-enriched lockfilemerges into project lockfile

apm install only rewrites the file when its semantic content changes (generated_at and apm_version are ignored when comparing). A no-op install leaves the file untouched.

The lockfile is what apm audit compares the workspace against. Each baseline check maps to specific lockfile fields:

CheckBacked by
lockfile-existsfile presence at project root
dependency-refs-matchresolved_ref per entry vs. apm.yml
deployed-files-presentdeployed_files per entry (and self entry)
content-integritydeployed_file_hashes (and local_deployed_file_hashes)
skill-subset-matchskill_subset per skill_bundle entry
mcp-configs-matchmcp_servers and mcp_configs
no-orphan-packagesdependencies keys vs. apm.yml

Files listed in deployed_files without a corresponding hash entry (typically directory markers ending in /) are skipped by content-integrity. Missing files are reported by deployed-files-present, not by content-integrity, so the two checks do not double-count.

Orphan detection works in two directions:

  • Orphan packages - entries in dependencies that the manifest no longer declares. apm prune removes them and their deployed_files.
  • Orphan files - files under managed target directories that no lockfile entry claims. apm prune removes them too.

lockfile_version is the schema version of the file format itself.

  • The current version is "1".
  • APM additively extends entries within version "1" - new optional fields may appear without bumping the version. Older APM clients ignore unknown fields.
  • Breaking changes (renames, removals, semantic shifts) require bumping lockfile_version. APM refuses to operate on a lockfile whose version it does not recognize, and will instruct the user to upgrade or regenerate.

A lockfile that fails to parse is treated as absent - APM logs the error and, for non-frozen installs, proceeds to regenerate from apm.yml.

A small project with one remote APM package, one MCP server, and its own local skill:

lockfile_version: "1"
generated_at: "2026-05-10T20:14:00+00:00"
apm_version: "0.6.4"
dependencies:
- repo_url: github.com/octocat/example-skills
resolved_ref: v1.2.0
resolved_commit: 7f3c9a4d2e1b8c7f0a9e6d5c4b3a2918f7e6d5c4
version: 1.2.0
package_type: skill_bundle
depth: 1
skill_subset:
- code-review
- test-writing
deployed_files:
- .github/skills/code-review/SKILL.md
- .github/skills/test-writing/SKILL.md
deployed_file_hashes:
.github/skills/code-review/SKILL.md: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
.github/skills/test-writing/SKILL.md: "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
mcp_servers:
- github
mcp_configs:
github:
type: stdio
command: docker
args: ["run", "-i", "--rm", "ghcr.io/github/github-mcp-server"]
local_deployed_files:
- .github/skills/my-local-skill/SKILL.md
local_deployed_file_hashes:
.github/skills/my-local-skill/SKILL.md: "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9"