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:
Fields: git (required), path, ref, alias (all optional). The git value is any HTTPS, HTTP or SSH clone URL.
Explicit URL schemes are honored exactly — see Transport selection for the full contract. Custom ports are preserved across every attempt (including any cross-protocol fallback enabled with --allow-protocol-fallback), so ssh://host:7999/... retried over HTTPS becomes https://host:7999/....
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.
apm install also deploys the project’s own .apm/ content (instructions, prompts, agents, skills, hooks, commands) to target directories alongside dependency content. Local content takes priority over dependencies on collision. This works even with zero dependencies — just apm.yml and a .apm/ directory is enough. See the CLI reference for details and exceptions.
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 plugin output. See Pack & Distribute for details.
Important: plain apm install (no flag) deploys both dependencies and devDependencies — there is currently no --omit=dev flag. The dev/prod separation kicks in at apm pack (plugin format, the default). Maintainer-only primitives that you author yourself MUST live outside .apm/ to be excluded from plugin bundles, because the local-content scanner operates on .apm/ regardless of the devDep marker. See Dev-only Primitives for the canonical pattern.
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)
Anchor rule: a local_path declared inside another local package is resolved relative to that package’s own directory, not the consumer’s project root. This matches npm/pip/cargo workspace behaviour and is what makes mono-repos with sibling helper packages portable across consumers. Sibling layouts that resolve outside the consuming project root (e.g. ../sibling-pkg from a local dep at the project edge) are supported — the consuming developer authored the manifest chain and trusts the layout. The security boundary lives upstream: see the next bullet.
# apm.yml at /repo/apm.yml
dependencies:
apm:
- ./packages/specialized
# apm.yml at /repo/packages/specialized/apm.yml
dependencies:
apm:
- ../base# resolves to /repo/packages/base, NOT /repo/base
Remote packages may not declare local dependencies. A package fetched from owner/repo cannot depend on a local_path — such an entry would reach into the consumer’s filesystem in unpredictable ways. Both relative and absolute local paths are rejected at ERROR severity. Authors of remote packages must publish their dependencies (or vendor them via subdirectory 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 Claude Code, if CLAUDE_CONFIG_DIR is set (and points inside $HOME), apm install -g --target claude deploys there instead of ~/.claude/ so primitives land where Claude Code reads them.
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.
APM picks SSH or HTTPS per dependency using a strict, predictable contract.
Dependency form
What APM tries
ssh://... or git@host:...
SSH only
https://... or http://...
HTTP(S) only
Shorthand (owner/repo, host/owner/repo) with git config url.<base>.insteadOf rewriting to SSH
SSH only
Shorthand without a matching insteadOf rewrite
HTTPS only
A failed clone fails loudly, naming the URL and the protocol attempted. APM
no longer downgrades ssh:// to HTTPS or vice-versa.
exportAPM_ALLOW_PROTOCOL_FALLBACK=1# CI / migration window
When fallback runs, each cross-protocol retry emits a [!] warning naming
both protocols. Use this to unblock a pipeline while you fix the root
cause — not as a long-term setting.
For SSH key selection (ssh-agent, ~/.ssh/config) and HTTPS token
resolution, see
Authentication.
For the CLI flag and env var reference, see
apm install.
For non-GitHub repositories, APM delegates authentication to git — it never sends GitHub tokens to non-GitHub hosts:
Public repos: Work without authentication via HTTPS
Private repos via SSH: Configure SSH keys for your host. Use an ssh:// or git@host: URL, or set up git config url.<base>.insteadOf to rewrite shorthand to SSH (see Transport selection)
Private repos via HTTPS: Configure a git credential helper — APM allows credential helpers for non-GitHub hosts
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 sibling subdirectory packages from the same monorepo and ref (e.g. two skills under skills/ in github/awesome-copilot), APM clones the repo bare exactly once into a shared cache and materializes each consumer’s working tree from that cache via git clone --local --shared --no-checkout. This eliminates redundant network fetches and prevents the parallel races that affected earlier sparse-checkout based fetches.
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. This re-resolves all dependencies, including transitive ones, so stale locked SHAs are never reused.
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
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.