Dependency Resolution
agentpack lock turns your manifest into a fully pinned graph: every package, direct and transitive, resolved to one concrete commit. This page covers how that happens.
The steps
- Load the manifest. agentpack reads
agentpack.tomland collects[dependencies]. - List available versions. For each remote dependency it needs to resolve a ref, it queries GitHub for the repo’s tags. Tags may be
v1.2.3or1.2.3. - Select a version. For a SemVer constraint, agentpack picks the highest tag that satisfies it. A
tag/commitpin is taken as-is; abranchresolves to that branch’s current HEAD. - Expand transitively. If a resolved package contains its own
agentpack.toml, its[dependencies]are added to the graph and resolved too. This recurses until the graph is complete. - Resolve conflicts. When two packages constrain the same dependency differently, agentpack looks for a version satisfying both. If none exists,
lockfails and names the conflicting packages. - Write
pack.lock. Each selected ref’s commit SHA is recorded along with itscache_key.
lock resolves; it does not download content. Fetching happens in sync.
Version selection
agentpack picks the highest tag satisfying the constraint:
| Constraint | Available tags | Selected |
|---|---|---|
"^1.0" | 1.0.0, 1.2.3, 2.0.0 | 1.2.3 |
"~1.2" | 1.2.0, 1.2.9, 1.3.0 | 1.2.9 |
"1.0.0" | 1.0.0, 1.1.0 | 1.0.0 |
">=1.0, <1.5" | 1.0.0, 1.4.9, 1.5.0 | 1.4.9 |
Branch pins drift
A branch pin resolves to the branch HEAD at lock time. Two people who lock on different days get different commits, so it is not reproducible over time — prefer a version constraint or tag for anything stable:
"github.com/acme/wip" = { branch = "main" }
Because the lockfile stores the resolved commit, a branch pin does not move on its own. It only advances when you explicitly run agentpack lock --update (or sync --update-lock).
Local path dependencies
A path dependency skips version selection entirely. agentpack copies the directory on disk and derives its commit, cache_key and content_hash from the files present at lock time, so local edits are reflected on the next lock/sync.
Package roots and marketplace repositories
Agentpack recognizes a directory as a package root when it contains one of these markers:
SKILL.mdagentpack.toml.claude-plugin/plugin.json,.cursor-plugin/plugin.json, or.codex-plugin/plugin.json.claude-plugin/marketplace.json,.cursor-plugin/marketplace.json, or.agents/plugins/marketplace.json
A marketplace root can represent one logical plugin through separate local source directories for Claude, Cursor, and Codex. Agentpack resolves those paths relative to the marketplace root, verifies that their plugin names agree, and merges them into one canonical cache package before staging. This supports repositories such as:
.claude-plugin/marketplace.json -> ./providers/claude/plugin
.cursor-plugin/marketplace.json -> ./providers/cursor/plugin
.agents/plugins/marketplace.json -> ./providers/codex/plugin
Marketplace source paths must remain inside the repository. A catalog with multiple plugin names is ambiguous for one dependency, so agentpack rejects it; name the plugin with agentpack add <marketplace> --plugin <name> (see the CLI reference), which also follows entries that point at other repositories, or add the plugin’s source directory directly.
Some catalog entries are not plugin directories at all. A Claude Code marketplace may define a plugin in the catalog entry itself ("strict": false, with the entry’s own lspServers, skills and so on) and point source at a directory that holds only a README. The official gopls-lsp, pyright-lsp, lua-lsp and php-lsp entries are of this kind: each is a language server setting for Claude Code and nothing else. Adding such a directory fails with a message that says so and names the catalog and what the entry declares, for example:
cannot install anthropics/claude-plugins-official/plugins/gopls-lsp: it is the marketplace catalog entry
"gopls-lsp", a language server configuration for Claude Code (.claude-plugin/marketplace.json defines
lspServers: gopls runs `gopls`) with no plugin files of its own; …
Nothing is written to the manifest, the lock or the cache. Agentpack stages skills, commands, agents, rules, hooks and MCP servers; it has no language server setting to write for the agents.
Metadata caching and the offline fallback
GitHub ref→commit and tag-list lookups are cached in $AGENTPACK_HOME/cache/db.reddb and reused across add, lock, and sync, which keeps repeat resolutions off the network. When the GitHub REST API fails or is throttled, agentpack falls back to the Git protocol through an embedded Go Git client against https://github.com/<owner>/<repo>.git before resorting to stale cached metadata. There is no external git executable or hard REST API dependency for ref and tag resolution.
To work entirely from a warm cache without touching the network, keep [dependencies] and pack.lock unchanged and run agentpack sync: with an unchanged lock, sync stages from the cache without re-resolving.