Complete guide to APM package dependency management - share and reuse context collections across projects for consistent, scalable AI-native development.
APM dependencies are git repositories containing .apm/ directories with context collections (instructions, chatmodes, contexts) and agent workflows (prompts). They enable teams to:
Share proven workflows across projects and team members
Standardize compliance and design patterns organization-wide
Build on tested context instead of starting from scratch
Maintain consistency across multiple repositories and teams
APM supports any git-accessible host — GitHub, GitLab, Bitbucket, self-hosted instances, and more.
Claude Skills are packages with a SKILL.md file that describe capabilities for AI agents. APM can install them and transform them for your target platform:
path: instructions/security# virtual sub-path inside the repo
ref: v2.0# pin to a tag, branch, or commit
- git: git@bitbucket.org:team/rules.git
path: prompts/review.prompt.md
alias: review# local alias (controls install directory name)
Fields: git (required), path, ref, alias (all optional). The git value is any HTTPS or SSH clone URL.
Nested groups (GitLab, Gitea, etc.): APM treats all path segments after the host as the repo path, so gitlab.com/group/subgroup/repo resolves to a repo at group/subgroup/repo. Virtual paths on simple 2-segment repos work with shorthand (gitlab.com/owner/repo/file.prompt.md). But for nested-group repos + virtual paths, use the object format — the shorthand is ambiguous:
gitlab.com/group/subgroup/repo/file.prompt.md
# DON'T — ambiguous: APM can't tell where the repo path ends
# → parsed as repo=group/subgroup, virtual=repo/file.prompt.md (wrong!)
APM normalizes every dependency entry on write — no matter how you specify a package, the stored form in apm.yml is always a clean, canonical string. This works like Docker’s default registry convention:
GitHub is the default registry. The github.com host is stripped, leaving just owner/repo.
Non-default hosts (GitLab, Bitbucket, self-hosted) keep their FQDN: gitlab.com/owner/repo.
Some packages are only needed during authoring — test fixtures, linting rules, internal helpers. Install them as dev dependencies so they stay out of distributed bundles:
Terminal window
apminstall--devowner/test-helpers
Or declare them directly:
devDependencies:
apm:
- source: owner/test-helpers
Dev dependencies install to apm_modules/ like production deps but are excluded from apm pack --format plugin output. See Pack & Distribute for details.
Install packages from the local filesystem for fast iteration during development.
Terminal window
# Relative path
apminstall./packages/my-shared-skills
# Absolute path
apminstall/home/user/repos/my-ai-package
Or declare them in apm.yml:
dependencies:
apm:
- ./packages/my-shared-skills# relative to project root
- /home/user/repos/my-ai-package# absolute path
- microsoft/apm-sample-package# remote (can be mixed)
How it works:
Files are copied (not symlinked) to apm_modules/_local/<package-name>/
Local packages are validated the same as remote packages (must have apm.yml or SKILL.md)
apm compile works identically regardless of dependency source
Transitive dependencies are resolved recursively (local packages can depend on remote packages)
Re-install behavior: Local deps are always re-copied on apm install since there is no commit SHA to cache against. This ensures you always get the latest local changes.
Lockfile representation: Local dependencies are tracked with source: local and local_path fields. No resolved_commit is stored.
Pack guard:apm pack rejects packages with local path dependencies — replace them with remote references before distributing.
User-scope guard: Local path dependencies are not supported with --global (-g). Relative paths resolve against cwd, which is meaningless at user scope where packages deploy to ~/.apm/. Use remote references (owner/repo) for global installs.
By default, apm install targets the current project — manifest, modules, and lockfile live in
the working directory and deployed primitives go to .github/, .claude/, .cursor/, .opencode/.
Pass --global (or -g) to install to your home directory instead, making packages available
across every project on the machine:
Target detection mirrors project scope: APM auto-detects by ~/.<target>/ directory presence,
falling back to Copilot. Security scanning runs for global installs.
For private or corporate MCP servers not published to any registry:
mcp:
- name: internal-knowledge-base
registry: false
transport: http
url: "https://mcp.internal.example.com"
env:
API_TOKEN: "${API_TOKEN}"
headers:
Authorization: "Bearer ${API_TOKEN}"
Stdio example:
mcp:
- name: local-db-tool
registry: false
transport: stdio
command: my-mcp-server
args:
- "--port"
- "8080"
Required fields when registry: false:
transport — always required
url — required for http, sse, streamable-http transports
command — required for stdio transport
⚠️ Transitive trust rule: Self-defined servers from direct dependencies (depth=1 in the lockfile) are auto-trusted. Self-defined servers from transitive dependencies (depth > 1) are skipped with a warning by default. You can either re-declare them in your own apm.yml, or use --trust-transitive-mcp to trust all self-defined servers from upstream packages:
Run apm install --dry-run to preview MCP dependency configuration without writing any files. Self-defined deps are validated for required fields and transport values; overlay deps are loaded as-is and unknown fields are ignored.
This example shows how APM dependencies enable powerful layered functionality by combining multiple specialized packages. The company website project uses microsoft/apm-sample-package as a full APM package and individual prompts from github/awesome-copilot to supercharge development workflows:
company-website/apm.yml
name: company-website
version: 1.0.0
description: Corporate website with design standards and code review
APM automatically retries failed HTTP requests with exponential backoff and jitter. Rate-limited responses (HTTP 429/503) are handled transparently, respecting Retry-After headers when provided. This ensures reliable installs even under heavy API usage or transient network issues.
APM downloads packages in parallel using a thread pool, significantly reducing wall-clock time for large dependency trees. The concurrency level defaults to 4 and is configurable via --parallel-downloads (set to 0 to disable). For subdirectory packages in monorepos, APM attempts git sparse-checkout (git 2.25+) to download only the needed directory, falling back to a shallow clone if sparse-checkout is unavailable.
APM uses instruction-level merging rather than file-level precedence. When local and dependency files contribute instructions with overlapping applyTo patterns:
my-project/
├── .apm/
│ └── instructions/
│ └── security.instructions.md # Local instructions (applyTo: "**/*.py")
│ │ └── apm-sample-package/ # From microsoft/apm-sample-package
│ │ ├── .apm/
│ │ │ ├── instructions/
│ │ │ │ └── design-standards.instructions.md
│ │ │ ├── prompts/
│ │ │ │ ├── design-review.prompt.md
│ │ │ │ └── accessibility-audit.prompt.md
│ │ │ ├── agents/
│ │ │ │ └── design-reviewer.agent.md
│ │ │ └── skills/
│ │ │ └── style-checker/SKILL.md
│ │ └── apm.yml
│ └── github/
│ └── awesome-copilot/ # Virtual subdirectory from github/awesome-copilot
│ └── skills/
│ └── review-and-refactor/
│ ├── SKILL.md
│ └── apm.yml
├── .apm/ # Local context (highest priority)
├── apm.yml # Project configuration
└── .gitignore # Manually add apm_modules/ to ignore
Note: Full APM packages store primitives under .apm/ subdirectories. Virtual file packages extract individual files from monorepos like github/awesome-copilot.
The deployed_files field tracks exactly which files APM placed in your project. This enables safe cleanup on apm uninstall and apm prune — only tracked files are removed.
The mcp_servers field records the MCP dependency references (e.g. io.github.github/github-mcp-server) for servers currently managed by APM. It is used to detect and clean up stale servers when dependencies change.
First install: APM resolves dependencies, downloads packages, and writes apm.lock.yaml
Subsequent installs: APM reads apm.lock.yaml and uses locked commits for exact reproducibility. If the local checkout already matches the locked commit SHA, the download is skipped entirely.
Updating: Use --update to re-resolve dependencies and generate a fresh lockfile
APM fully resolves transitive dependencies. If package A depends on B, and B depends on C:
apm install contoso/package-a
Result:
Downloads A, B, and C
Records all three in apm.lock.yaml with depth information
depth: 1 = direct dependency
depth: 2+ = transitive dependency
Uninstalling a package also removes its orphaned transitive dependencies (npm-style pruning).
You can use any input form — APM resolves it to the canonical identity stored in apm.yml:
Terminal window
apmuninstallacme/package-a
apmuninstallhttps://github.com/acme/package-a.git# same effect
apmuninstallgit@github.com:acme/package-a.git# same effect
# Also removes B and C if no other package depends on them
Problem: Local files collide with package files during apm installResolution: APM skips files that exist locally and aren’t managed by APM. The diagnostic summary at the end of install shows how many files were skipped. Use --verbose to see which files, or --force to overwrite.