Introduction
agentpack pins the skills, plugins, commands, and rules your project feeds to AI coding agents. You declare them once in agentpack.toml, lock them to exact commits in pack.lock, and launch any supported agent with everything already staged in its native format.
Think of it as cargo or uv for agent configuration: a manifest, a lockfile, a content-addressed cache, and reproducible builds — except the artifacts are skills and prompts instead of crates.
The problem it solves
Agent configuration has become real configuration. A project’s behavior under Claude Code, Cursor, or Codex depends on slash commands, rules files, sub-agent definitions, MCP servers, and skill libraries. Today those artifacts live scattered across GitHub repos and local folders, with no shared versioning story, and each agent expects a different file layout.
So teams copy-paste. The same skill gets pasted into .claude/, then adapted by hand for .cursor/, then forgotten when a third agent shows up. Versions drift between machines. Nothing records which commit of a shared rule set you actually ran. There is no lockfile.
agentpack replaces the copy-paste with a dependency graph.
What it gives you
| Capability | What it means |
|---|---|
| Declarative manifest | agentpack.toml lists direct dependencies with version constraints, modes, and MCP servers |
| Deterministic lockfile | pack.lock (v2) pins every package — direct and transitive — to an exact commit and cache_key |
| Content-addressed cache | Each package is fetched once into $AGENTPACK_HOME/cache/<cache_key>/ and shared across projects |
| Per-harness staging | Artifacts are materialized into per-harness staging trees outside your repo — never committed, never symlinked into your real ~/.claude or ~/.cursor |
| Cross-harness conversion | A skill, command, agent, or rule is re-rendered into each harness’s native format, not blindly copied |
| Six launchers | claude, agent (Cursor), opencode, codex, grok, agy (Antigravity) — sync then exec the real binary |
A first taste
# agentpack.toml
name = "my-project"
version = "0.1.0"
[dependencies]
"github.com/anthropics/skills/skills/canvas-design" = { branch = "main" }
"github.com/anthropics/claude-plugins-official/plugins/hookify" = { branch = "main" }
agentpack add anthropics/skills/skills/canvas-design # resolve, lock, sync
agentpack claude # launch Claude Code with both staged
The agent starts with every declared dependency in place — no manual copying, no drift, and nothing leaked into your global agent config.
How the pieces fit
agentpack.toml ──> pack.lock ──> cache ──> staged bundles ──> launch
(you write) (pinned) (fetched) (per-harness) (agent runs)
Read on through Core Concepts to understand each step, or jump to the Quick Start to get something running first.
Source and license
agentpack is MIT-licensed and developed at github.com/OlegHQ/agentpack. It is pre-release: the CLI, lockfile shape, staging layout, and defaults may change without a migration path until a stable release is declared.
Installation
agentpack ships as a single binary. The recommended path is Homebrew; everything else builds from source with a Rust toolchain.
Homebrew (macOS and Linux)
brew install OlegHQ/tap/agentpack
That one line taps OlegHQ/homebrew-tap and installs the formula. The two-step form works too:
brew tap OlegHQ/tap
brew install agentpack
Upgrade later with:
brew upgrade agentpack
Prebuilt binaries and the shell installer
Each release publishes prebuilt binaries for macOS (arm64, x86_64), Linux (x86_64, arm64), and Windows (x86_64), built by cargo-dist. The release page carries a curl | sh installer:
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/OlegHQ/agentpack/releases/latest/download/agentpack-installer.sh | sh
Pick a specific version from the releases page if you don’t want latest.
From source
You need a Rust toolchain (edition 2021). Install rustup if you don’t have one, then:
git clone https://github.com/OlegHQ/agentpack.git
cd agentpack
cargo install --path .
This drops the agentpack binary in ~/.cargo/bin/; make sure that directory is on your PATH. The repo’s Makefile also offers make install, which builds in release mode and copies the binary to ~/.local/bin (override with make install INSTALL_DIR=/usr/local/bin).
Verify
agentpack --version
Where agentpack keeps its state
All cached content, the metadata index, and per-project bookkeeping live under a single user-wide directory, not inside your repo. When AGENTPACK_HOME is unset, the default is:
- Linux/macOS —
$XDG_DATA_HOME/agentpackifXDG_DATA_HOMEis set, otherwise$HOME/.local/share/agentpack - Windows —
%LOCALAPPDATA%\agentpack
Override it by exporting the variable in your shell profile:
export AGENTPACK_HOME="$HOME/.local/share/agentpack"
A custom AGENTPACK_HOME is useful for a shared cache on a network mount, or for isolating a project’s state. See Environment Variables for the complete list.
Quick Start
Zero to a running agent in a few minutes. The short version: init, add, launch.
1. Initialize a manifest
From your project directory:
agentpack init
This writes a minimal agentpack.toml (identity is two top-level keys, not a [package] table) and an empty v2 pack.lock:
name = "my-project"
version = "0.0.1"
[dependencies]
init fails if agentpack.toml already exists, so it never clobbers your manifest.
2. Add a dependency
add takes a package spec. The most explicit form is a Go-style module ID, but owner/repo/path shorthand and GitHub tree/blob URLs work too:
agentpack add anthropics/skills/skills/canvas-design
agentpack resolves the spec, appends it under [dependencies], refreshes pack.lock, and runs a sync — all in one command:
[dependencies]
"github.com/anthropics/skills/skills/canvas-design" = {}
To pin a constraint, attach a @ref to the spec or edit the entry into table form ({ version = "^1.0.0" }, { branch = "main" }, { tag = "v2.1.0" }). See Your First Manifest. To stage nothing yet, pass --no-sync.
3. Lock and sync explicitly (optional)
add already locked and synced. You only run these by hand for CI, or after editing the manifest directly:
agentpack lock # re-resolve agentpack.toml → pack.lock (network)
agentpack sync # fetch into the cache and rebuild every harness's staging
Commit both agentpack.toml and pack.lock. The first sync downloads everything; later syncs reuse the content-addressed cache and are fast.
4. Launch an agent
Each launcher syncs (fast path when nothing changed), stages for that harness, and execs the real binary:
agentpack claude # Claude Code
agentpack agent # Cursor Agent (alias: cursor-agent)
agentpack opencode # OpenCode
agentpack codex # Codex
agentpack grok # Grok
agentpack agy # Antigravity
Anything after the subcommand is forwarded to the underlying binary:
agentpack codex --model gpt-5-codex
Next steps
- Your First Manifest — version constraints, branches, local paths
- Dependency Resolution — how a constraint becomes a pinned commit
- Modes — enable or disable subsets of your packages per task
- Harness guides — what each launcher actually does
Your First Manifest
agentpack.toml lives at your project root and declares two things: the project’s identity and its direct dependencies. It is the source of truth for what to install; pack.lock records the resolved result.
Minimal manifest
Identity is two top-level keys. There is no [package] table.
name = "my-project"
version = "0.0.1"
Module IDs encode the path inside a repo
A dependency key is a module ID — a Go-style path. The in-repo location of the package is part of the ID, not a separate field:
[dependencies]
# Repository root
"github.com/acme/lint-rules" = "^1.2"
# A subdirectory inside the repo (note the trailing path segments)
"github.com/acme/monorepo/agents/python" = "^2.0"
github.com/acme/monorepo/agents/python means the agents/python directory of github.com/acme/monorepo. This is the single most common point of confusion: to pull a subdirectory, put it in the key — do not reach for a path field (that field means something else; see below).
Pinning: short string vs. table
The value can be a short string (a branch, tag, or version constraint) or a table for anything richer:
[dependencies]
# Semver constraint against the repo's tags
"github.com/acme/lint-rules" = "^1.2"
# Track a branch — resolves to its HEAD at lock time (not reproducible over time)
"github.com/acme/experimental" = { branch = "main" }
# Pin an exact tag
"github.com/acme/shared-rules" = { tag = "v2.1.0" }
# Pin an exact commit
"github.com/acme/shared-rules" = { commit = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" }
A table accepts version, branch, tag, commit, and path. Identity and cache_key always derive from the resolved commit SHA, never from a branch or tag name.
Local path dependencies
The path field points at a directory on your filesystem, relative to the project root. This is for developing a package against a consumer, not for selecting a subdirectory of a GitHub repo:
[dependencies]
"mcp-retrieval" = { path = "../mcp-retrieval" }
On every lock/sync, path dependencies are re-copied from source and re-hashed, so local edits are picked up. Sync will error on a machine where the path is missing and the cache slot is empty.
Version constraint syntax
Constraints are matched against the dependency’s SemVer tags:
| Constraint | Meaning |
|---|---|
"1.2.3" | Exactly 1.2.3 |
"^1.2.3" | >=1.2.3, <2.0.0 (compatible) |
"~1.2.3" | >=1.2.3, <1.3.0 (patch updates) |
">=1.0, <2.0" | Explicit range |
"*" | Any version |
A fuller example
name = "acme-backend"
version = "0.2.1"
[dependencies]
"github.com/anthropics/skills/skills/canvas-design" = { branch = "main" }
"github.com/anthropics/claude-plugins-official/plugins/hookify" = { branch = "main" }
"github.com/acme/monorepo/packages/coding-agent" = "^2.0"
"local-rules" = { path = "../local-rules" }
After editing the manifest by hand, run agentpack lock to refresh pack.lock, then agentpack sync to fetch and stage. (agentpack add does both for you.)
See the Manifest Schema for every field, plus the [modes] and [mcp.servers] sections.
Manifest (agentpack.toml)
agentpack.toml is the human-edited source of truth for a project. It declares direct dependencies, project-local modes, and MCP servers — and nothing else. Transitive dependencies, resolved commits, and cache keys all live in the generated pack.lock.
Project identity
Two top-level keys, written before any table:
name = "my-project" # used in lock metadata and diagnostics
version = "0.0.1" # SemVer string
There is no [package] wrapper. init fills both from the directory name and a default version; you can edit them freely.
[dependencies]
Each key is a module ID; each value is either a short string or a table.
[dependencies]
# Short form: a version constraint, branch, tag, or commit as a bare string
"github.com/acme/lint-rules" = "^1.2"
# Table form: anything richer
"github.com/acme/shared-rules" = { tag = "v2.1.0" }
"local-tools" = { path = "../local-tools" }
Table fields
| Field | Type | Description |
|---|---|---|
version | string | SemVer constraint matched against the repo’s tags |
branch | string | Track a branch; resolves to its HEAD at lock time (not reproducible over time) |
tag | string | Pin an exact tag |
commit | string | Pin an exact commit SHA |
path | string | Local filesystem directory, relative to the project root — for local development, not for selecting a repo subdirectory |
Module ID format
Module IDs are lowercase Go-style paths. The in-repo location of the package is encoded directly in the ID:
github.com/<owner>/<repo> # repository root
github.com/<owner>/<repo>/<p1> # the <p1> subdirectory
github.com/<owner>/<repo>/<p1>/<p2>/... # nested subdirectory
The host is always github.com for remote packages. To depend on a subdirectory of a monorepo, put the subpath in the key:
"github.com/acme/monorepo/packages/agent-rules" = "^1.0"
A human-typed spec may carry an optional @ref suffix (...@v1.2, ...@main), but identity and cache_key always come from the resolved commit, never the ref name.
Remote vs. local
When a dependency has a path field, it is resolved from your filesystem and never fetched from GitHub. The module ID is still used as the package’s identity for deduplication across the dependency graph.
Commit it
Always commit agentpack.toml together with pack.lock. The pair gives every contributor and every CI run identical resolution. Editing the manifest by hand is fine — run agentpack lock afterward to reconcile the lockfile.
Lockfile (pack.lock)
pack.lock records the exact resolved state of your dependency graph. agentpack generates it; you commit it and never edit it by hand. It uses lockfile version 2 — older [[skills]] / [[plugins]] layouts are rejected.
Why it exists
- Reproducibility — everyone resolves to the same commit, regardless of when they sync or which tags have since been published.
- Completeness — the lock lists every package, both the direct dependencies from your
agentpack.tomland the transitive ones pulled from nestedagentpack.tomlfiles inside fetched packages. - Integrity — each package carries a
cache_key(a content hash) that identifies its cache slot and lets agentpack detect a corrupted or wrong tree before staging. - Auditability — it’s plain TOML, so
git diff pack.lockshows exactly what moved.
Structure
A v2 lockfile has a version marker, a [meta] block, an optional [config] block, and a flat list of [[packages]]:
lockfile_version = 2
[meta]
name = "my-project"
version = "0.0.1"
[[packages]]
module = "github.com/anthropics/skills/skills/canvas-design"
direct = true
kind = "skill"
url = "https://github.com/anthropics/skills/tree/<40-hex>/skills/canvas-design"
owner = "anthropics"
repo = "skills"
path = "skills/canvas-design"
commit = "<40-hex commit SHA>"
cache_key = "<64-hex content hash>"
name = ""
Fields per package
| Field | Description |
|---|---|
module | Module ID, matching a key in [dependencies] (or a transitive dependency) |
direct | true for a direct dependency, false for a transitive one |
kind | "skill" or "plugin" |
url | GitHub tree URL pinned at the resolved commit |
owner, repo, path | GitHub coordinates and in-repo path |
commit | Full 40-hex commit SHA that was resolved |
cache_key | 64-hex content hash; names the cache slot under $AGENTPACK_HOME/cache/ |
name | Plugin name (empty for skills) |
The optional [config] table holds bookkeeping such as disabled_plugins.
Refreshing the lock
agentpack lock # resolve agentpack.toml → pack.lock, keeping commits already pinned
agentpack lock --update # re-resolve floating pins (branches, floating semver) from GitHub
lock resolves the full graph and rewrites the file. It does not download package content — that is sync’s job. Note that launching a harness does not advance floating pins: launchers run a fast pre-sync that skips re-resolution when inputs are unchanged. Run lock --update (or sync --update-lock) when you actually want a branch or floating constraint to move.
When to commit it
Always — for application projects and shared agent configurations alike. It is what makes “works on my machine” hold across the team and CI. Reusable packages should commit it too, though their downstream consumers resolve independently.
Drift
If pack.lock falls out of sync with agentpack.toml (typically after a hand edit), reconcile with agentpack lock. With an empty [dependencies] table, sync treats the existing lock as authoritative and leaves it alone — useful for hand-authored or test lockfiles.
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 reads the directory on disk and computes its 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 with guidance to add the desired plugin source directory directly.
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 — an embedded gix ls-refs against https://github.com/<owner>/<repo>.git — before resorting to stale cached metadata. There is no hard dependency on the REST API 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.
Content-Addressed Cache
agentpack fetches each package once and stores it under a content hash, so the same package at the same commit is never downloaded twice — and is shared across every project on the machine.
Where it lives
Everything sits under the user-wide agentpack home, never inside your repo:
$AGENTPACK_HOME/
cache/
<cache_key>/ # one extracted package tree per content hash
db.reddb # metadata index + alias map + cached GitHub ref/tag lookups
local/
<owner>/<repo>/... # optional offline mirror (same layout as owner/repo specs)
projects/
<project-hash>/ # per-project bookkeeping (overlay manifests, launch-sync state)
AGENTPACK_HOME defaults to $XDG_DATA_HOME/agentpack (or $HOME/.local/share/agentpack) on Unix and %LOCALAPPDATA%\agentpack on Windows. Override it:
export AGENTPACK_HOME=/data/agentpack
Each cache/<cache_key>/ directory is an immutable extracted tree, keyed by the cache_key recorded in pack.lock. agentpack never overwrites a slot: if the key is already present, the fetch is skipped. The same key is reached whenever owner, repo, in-repo path, and commit match — so duplicate content deduplicates automatically.
How sync uses it
agentpack sync walks pack.lock and, for each package:
- Checks whether
cache/<cache_key>/already exists. - If missing, downloads the tree from GitHub at the pinned commit (or copies it, for filesystem and
local/mirror specs). - Stores it under the cache key.
- Materializes it into the per-harness staging directories, converting artifacts to each harness’s native format.
Path dependencies are the exception to immutability: they are re-copied from source on every lock/sync, and their content hash detects changes.
Sharing across projects and CI
Because the cache lives under AGENTPACK_HOME rather than in the project, every project on the machine shares it. Two projects depending on the same package at the same commit store it once.
In CI, persist the cache between runs to skip redundant downloads. Key on pack.lock so the cache invalidates when dependencies change:
# GitHub Actions
- uses: actions/cache@v4
with:
path: ~/.local/share/agentpack/cache # or $AGENTPACK_HOME/cache
key: agentpack-${{ hashFiles('pack.lock') }}
Invalidation
The cache is never invalidated automatically: a key identifies its content, so changed upstream content is simply a different key, stored alongside the old one. To force a clean re-fetch, delete the directory and sync again:
rm -rf "$AGENTPACK_HOME/cache"
agentpack sync
Staging and Bundles
Staging materializes cached packages into the directory layout each harness expects. Every harness has its own conventions, so sync builds a separate staging tree per harness and per mode.
Staging stays outside your repo
Staged trees are deliberately kept out of your project’s Git repository. They are machine-specific, regenerated on demand from the cache, and can be large. agentpack also does not symlink pack content into your real ~/.claude or ~/.cursor — that would leak project-specific pins into your global agent config.
Only agentpack.toml and pack.lock belong in version control. (Two opt-in exceptions exist: the agentpack agent and agentpack agy launchers create a single workspace symlink — see their harness guides.)
Staging root
The default staging root is a per-project directory in the system temp location:
<temp_dir>/agentpack-<project-hash>/
Set AGENTPACK_STAGING_ROOT to pin a stable location — useful when your OS rotates temp directories, or in CI for a fast local disk:
export AGENTPACK_STAGING_ROOT=/tmp/agentpack-staging
Each mode gets its own subtree under modes/<mode>/, and each harness gets a directory there:
<staging-root>/
modes/
default/
plugins/agentpack-bundle/ # Claude --plugin-dir bundle
opencode/ # OpenCode config root (OPENCODE_CONFIG_DIR)
codex-home/ # Codex home (CODEX_HOME)
cursor/ cursor-home/ # Cursor plugin tree + fake HOME
grok/ grok-home/ # Grok bundle + home (GROK_HOME)
agy/ # Antigravity workspace plugin
A launcher points its harness at the right subtree by setting an environment variable or CLI flag before exec-ing the binary — claude --plugin-dir …, OPENCODE_CONFIG_DIR, CODEX_HOME, GROK_HOME, a fake HOME for Cursor, a workspace --add-dir for Antigravity. The harness guides cover each one.
What a bundle contains
A bundle is what one package contributes to a staging tree. Packages carry some mix of:
- commands — slash-command definitions
- agents — sub-agent or mode definitions
- skills — reusable instruction sets
- rules — persistent context applied across a session
- MCP servers — merged into each harness’s native MCP config
Cross-harness conversion happens here: a skill authored in one format is re-rendered into every target harness’s native format rather than copied verbatim. Layering order within a tree is user config first, then plugins (by cache_key), then bare skills, then the project’s ./.agents/ overlay — later layers win on the same relative path.
Re-staging
sync rebuilds every harness’s staging from the current cache state:
agentpack sync
Launchers run a fast pre-sync when agentpack.toml, pack.lock, and ./.agents/ are unchanged since the last successful launch: they verify cache and staging integrity and skip the full lock-resolve, re-download, and rebuild. Because of that, floating pins do not advance on launch alone — run agentpack sync or agentpack lock --update when you need the lock refreshed.
Modes
A mode is a named, project-local preset that selects which staged content is active. The same pack.lock can be staged several ways — a lean writing profile, a full default, a review profile that drops a noisy MCP server — without touching your dependencies.
Modes are declared under [modes] in agentpack.toml and selected with the global --mode flag:
agentpack --mode writing claude
agentpack --mode review sync
When --mode is omitted, the reserved default mode applies. Each mode stages into its own subtree, so switching modes never rebuilds another mode’s staging.
Anatomy of a mode
[modes.default]
base = "all"
[modes.writing]
base = "none"
enable = [
"package:github.com/mccteam/minecraft-console-client/.skills/humanizer",
"package:github.com/s1mplesonny/technical-writing-skill/technical-writing",
]
disable = ["mcp:linear"]
| Field | Type | Meaning |
|---|---|---|
base | "all" or "none" | The starting point before selectors apply: everything on, or everything off |
enable | string array | Selectors to turn on |
disable | string array | Selectors to turn off |
The model is straightforward: start from base, then apply enable and disable on top. A base = "all" mode with a short disable list is a denylist; a base = "none" mode with an enable list is an allowlist.
Selectors
Selectors name what to toggle:
| Selector | Targets |
|---|---|
package:<module-id> | A whole package |
package-path:<module-id>:<relative-path> | One file inside a package (e.g. a single command) |
mcp:<name> | An MCP server by name |
.agents:<relative-path> | A file from the project’s ./.agents/ overlay |
[modes.lean]
base = "all"
disable = [
"package-path:github.com/acme/heavy-pack:commands/noise.md",
"mcp:filesystem",
]
Managing modes from the CLI
You can edit [modes] by hand, or use the mode subcommands, which preserve TOML formatting:
agentpack mode list # all declared modes (default is always present)
agentpack mode show writing # base + enable + disable for one mode
agentpack mode create review
agentpack mode base review none
agentpack mode enable review package:github.com/acme/shared-rules
agentpack mode disable review mcp:filesystem
agentpack mode delete review # `default` is reserved and cannot be deleted
agentpack mode tui # interactive editor
agentpack mode tui opens a terminal UI for toggling selectors visually; its palette follows AGENTPACK_TUI_THEME (light/dark, auto-detected when unset).
MCP Servers
Model Context Protocol servers give agents tools and data sources. agentpack collects MCP definitions from your manifest, your packages, and your ./.agents/ overlay, merges them, and writes the result into each harness’s native MCP config during sync — so one definition reaches every agent.
Declaring servers in the manifest
Add servers under [mcp.servers]. Each key is a server name:
[mcp.servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem"]
[mcp.servers.retrieval]
command = "uvx"
args = ["mcp-retrieval"]
env = { API_KEY = "sk-..." }
| Field | Type | Description |
|---|---|---|
command | string | Executable to launch the server |
args | string array | Arguments passed to the command |
env | string map | Environment variables for the server process |
disabled | bool | Optional; skip this server when set |
Managing servers from the CLI
agentpack mcp add retrieval --command uvx --args mcp-retrieval --env API_KEY=sk-...
agentpack mcp remove retrieval
agentpack mcp list # every server, with provenance (manifest / plugin / .agents)
add and remove edit [mcp.servers] and then sync, unless you pass --no-sync.
The merge pipeline
After packages and the ./.agents/ overlay are staged, sync gathers MCP definitions from three sources and merges them. Later sources win when the same server name appears more than once:
- Plugin
mcp.jsonfiles — from each staged plugin, ordered bycache_key, filtered through the active mode - Manifest
[mcp.servers]— your project-level definitions ./.agents/mcp.json— the project overlay
The merged set is then written in each harness’s native format:
| Harness | Output |
|---|---|
| Claude, Cursor | JSON mcpServers |
| OpenCode | opencode.json |
| Codex, Grok | [mcp_servers] TOML |
| Antigravity | plugin mcp_config.json (remote servers use serverUrl) |
For Cursor’s fake HOME, the merged pack mcp.json is additionally merged with your real ~/.cursor/mcp.json — user-defined entries win on conflict — so agentpack-managed servers coexist with your own.
Toggling servers per mode
Use the mcp:<name> selector in a mode to enable or disable a server without removing it:
[modes.review]
base = "all"
disable = ["mcp:filesystem"]
See Modes for the full selector syntax.
Claude Code
agentpack claude launches Claude Code with your packages staged as a plugin directory. Claude’s --plugin-dir flag is additive, so this layers on top of your normal configuration rather than replacing it.
What the launcher does
sync builds a single merged plugin bundle, then the launcher runs:
claude --plugin-dir "<staging>/modes/<mode>/plugins/agentpack-bundle" \
--settings "$AGENTPACK_HOME/claude-settings.json"
--plugin-dirpoints at the stagedagentpack-bundle.--settingsloads agentpack’s attribution overlay (see below). It is omitted if you keep attribution on.
Extra arguments are forwarded to claude:
agentpack claude --model opus
agentpack --yolo claude # adds --dangerously-skip-permissions
CLAUDE_CONFIG_DIR is intentionally never set. Claude derives its keychain credential namespace from that variable, so a per-project value would forget your login on every project switch. Your real ~/.claude.json, ~/.claude/settings.json, and keychain entry are reused as-is.
What the bundle contains
agentpack-bundle/
.claude-plugin/plugin.json
commands/ agents/ skills/ # converted markdown artifacts
hooks/ matchers/ core/ ... # raw Claude support directories
mcp.json # merged MCP servers (see MCP Servers)
agentpack does not copy your user-scoped ~/.claude/commands, agents, or skills into the bundle. Claude already reads those from $HOME, and copying them would produce duplicate slash commands (e.g. both /code-tutor and /agentpack-bundle:code-tutor).
Artifact handling
| Artifact | In the bundle |
|---|---|
| Commands | commands/<name>.md, Claude command frontmatter |
| Agents | agents/<name>.md, Claude agent frontmatter |
| Skills | skills/<name>/, normalized skill |
| Rules | Best-effort skill fallback (Claude has no first-class rule files) |
| Hooks | Supported — rendered into the bundle’s hooks/ |
| MCP | Merged into mcp.json at the bundle root |
Plugin-provided guidance is surfaced through a SessionStart hook that emits it as Claude’s additionalContext, so the model sees it at the start of every session. See Cross-Harness Conversion for the full mapping.
Attribution
By default sync writes $AGENTPACK_HOME/claude-settings.json with AI attribution forced off (includeCoAuthoredBy = false, empty commit/PR attribution) and the launcher passes it via --settings, which loads at flagSettings scope — above your user, project, and local settings. Your real config files are never modified. Set AGENTPACK_KEEP_ATTRIBUTION=1 to keep your existing values. See Overrides and Attribution.
Environment
| Variable | Effect |
|---|---|
CLAUDE_CODE_PATH | Path to the claude binary |
AGENTPACK_STAGING_ROOT | Override the staging root |
AGENTPACK_HOME | Override the cache/state root (also holds claude-settings.json) |
See Environment Variables for the complete list.
Cursor Agent
agentpack agent launches the Cursor CLI (cursor-agent) with your packages staged. The subcommand has the alias cursor-agent.
The binary is
cursor-agent, notagent— the bare nameagentcollides with other tools (Grok ships one). Override the path withCURSOR_AGENT_PATH.
What the launcher does
Cursor resolves user-scoped skills, commands, and agents from a HOME-backed .cursor tree. So agentpack runs cursor-agent with a synthetic HOME at <staging>/modes/<mode>/cursor-home, whose .cursor/ symlinks the staged pack assets alongside your real Cursor login and session paths.
It also sets a few environment variables on the child process:
CURSOR_CONFIG_DIR→ the fake home’s.cursor.CURSOR_DATA_DIR→ your real~/.cursorwhen unset, so workspace-trust state survives staging rebuilds.CARGO_HOME,RUSTUP_HOME,DOCKER_CONFIG→ bridged from your real home (unless you already set them) so shell tooling keeps working inside the stagedHOME.
On Linux, agentpack also links both Cursor’s uppercase Electron profile ($XDG_CONFIG_HOME/Cursor) and the CLI’s lowercase authentication profile ($XDG_CONFIG_HOME/cursor) into the synthetic home. This keeps auth.json durable across projects and staging rebuilds.
agentpack does not install pack content into your real ~/.cursor. It may create the durable Cursor profile directories and staged symlinks needed for first-time login/session files to persist; Cursor manages the actual auth, workspace trust, and MCP approval contents.
Workspace and the agents symlink
--workspace defaults to the current working directory where you invoked agentpack — not the pack root. In a monorepo where agentpack.toml lives in a parent directory, that distinction matters; pass --workspace <path> to override.
Cursor reads sub-agents only from resolve(--workspace)/.cursor/agents. So, only agentpack agent creates ./.cursor/agents as a symlink into the staged pack agents/ (when the pack ships agent markdown) and records it in a per-project overlay manifest for safe cleanup. Bare sync and the other launchers leave the workspace alone and remove any stale symlink from a previous agent run. If ./.cursor/agents already exists as a real directory or file, agentpack leaves it and logs a warning. Add .cursor/agents to .gitignore if you don’t want the symlink tracked.
agentpack agent
agentpack agent --workspace ./packages/api
agentpack --yolo agent # adds --force
Cursor’s cursor-agent only accepts --trust in --print/headless mode; agentpack prepends it automatically there.
Staged layout
modes/<mode>/
cursor/ # Cursor plugins layout
.cursor-plugin/marketplace.json
agentpack-bundle/
.cursor-plugin/plugin.json
commands/ agents/ skills/ rules/ hooks/ mcp.json
cursor-home/ # fake HOME
.cursor/ # symlinks to pack dirs + your real cli-config/session files
.config/cursor -> real Cursor Agent CLI profile (Linux auth)
Artifact handling
| Artifact | Staged as |
|---|---|
| Rules | .mdc files, Cursor’s native format (preserved) |
| Commands | Plain markdown |
| Agents / skills | Cursor agent/skill markdown |
| MCP | JSON mcpServers, merged with your real ~/.cursor/mcp.json (your entries win) |
Attribution is forced off via a real cli-config.json file in the staged tree (not a symlink), so agentpack’s writes never bleed back into your real Cursor profile.
Environment
| Variable | Effect |
|---|---|
CURSOR_AGENT_PATH | Path to the cursor-agent binary |
CURSOR_DATA_DIR | Workspace-trust store; agentpack points it at real ~/.cursor when unset |
AGENTPACK_STAGING_ROOT | Override the staging root (use this for a stable path when your OS rotates temp dirs) |
See Environment Variables for the complete list.
OpenCode
agentpack opencode launches OpenCode with a redirected config root. Unlike Claude’s additive plugin dir, OpenCode is configured by pointing it at a different config directory entirely.
What the launcher does
OpenCode searches its config root — opencode.json plus agents/, commands/, modes/, plugins/, skills/ — like the standard .opencode / ~/.config/opencode locations. agentpack stages a complete root and runs:
OPENCODE_CONFIG_DIR="<staging>/modes/<mode>/opencode" opencode
Extra arguments are forwarded:
agentpack opencode --model anthropic/claude-sonnet-4-6
agentpack --yolo opencode # stages opencode.json with "permission": "allow"
What the root contains
opencode/
opencode.json # seeded config + merged MCP + attribution instruction
agents/ commands/ skills/ # converted pack artifacts
plugins/ modes/
The root is seeded from your real ~/.config/opencode/ (opencode.json, agents, commands, modes, plugins, skills) so provider and auth configuration keep working even though the config root is redirected. Converted pack content is then overlaid into OpenCode’s native markdown locations.
Artifact handling
| Artifact | Staged as |
|---|---|
| Commands | OpenCode command markdown |
| Agents | OpenCode agent markdown |
| Skills | OpenCode skill |
| Rules | Best-effort skill fallback |
| MCP | Merged into opencode.json |
Attribution
OpenCode has no first-class attribution setting. agentpack writes an agentpack-no-attribution.md system-prompt file and adds it to instructions[] in the staged opencode.json — prompt-level guidance, the best available lever. Set AGENTPACK_KEEP_ATTRIBUTION=1 to skip it.
Inspecting the staged root
agentpack sync
ls "$AGENTPACK_STAGING_ROOT/modes/default/opencode/" # if AGENTPACK_STAGING_ROOT is set
Environment
| Variable | Effect |
|---|---|
OPENCODE_PATH | Path to the opencode binary |
AGENTPACK_STAGING_ROOT | Override the staging root |
See Environment Variables for the complete list.
Codex
agentpack codex launches the OpenAI Codex CLI with a redirected home. Codex reads its config and skills from CODEX_HOME (default ~/.codex), so agentpack stages a complete home and points the variable at it.
What the launcher does
CODEX_HOME="<staging>/modes/<mode>/codex-home" codex
Extra arguments are forwarded:
agentpack codex --model gpt-5-codex
agentpack --yolo codex # adds --dangerously-bypass-approvals-and-sandbox
The staged home is seeded from your real ~/.codex/ (config.toml, skills, themes) so user config keeps working under the redirect.
Credential bridging
Codex stores OAuth/API material in auth.json or in the OS keychain, keyed by the canonical CODEX_HOME path — so a staged path would otherwise miss your keychain entry and force re-login. agentpack avoids copying credentials per project by linking each staged auth.json to a shared source:
- your real
~/.codex/auth.jsonwhen that file already exists, or $AGENTPACK_HOME/shared/codex/auth.json, which agentpack materializes from the real~/.codexkeychain entry (serviceCodex Auth) when credentials live in the keychain.
The staged config.toml is forced to cli_auth_credentials_store = "file", so every project shares refresh-token updates through that one file.
Staged layout
codex-home/
auth.json -> ~/.codex/auth.json | $AGENTPACK_HOME/shared/codex/auth.json
config.toml # seeded + attribution off + merged [mcp_servers]
skills/
<name>/SKILL.md
Artifact handling
Codex gets the portable skill subset of pack content. agentpack does not synthesize Codex plugin marketplaces from Claude plugins.
| Artifact | Staged as |
|---|---|
| Skills | Codex skill under skills/<name>/ |
| Commands | Skill fallback |
| Agents | Skill fallback |
| Rules | Skill fallback |
| MCP | Merged into [mcp_servers] in config.toml |
Attribution is forced off via commit_attribution = "" in the staged config.toml. Set AGENTPACK_KEEP_ATTRIBUTION=1 to keep your value.
Environment
| Variable | Effect |
|---|---|
CODEX_PATH | Path to the codex binary |
AGENTPACK_HOME | Cache/state root; also holds the shared Codex auth file when keychain bridging is needed |
AGENTPACK_STAGING_ROOT | Override the staging root |
See Environment Variables for the complete list.
Grok
agentpack grok launches the Grok CLI with a redirected home. The installed grok 0.1.219 rejects --plugin-dir and --settings, so agentpack configures it through GROK_HOME and a staged config.toml instead.
What the launcher does
GROK_HOME="<staging>/modes/<mode>/grok-home" grok --cwd <project-root>
agentpack injects --cwd when you don’t supply it. Extra arguments are forwarded:
agentpack grok
agentpack --yolo grok # adds --always-approve
The staged home is seeded from your real ~/.grok/ (config.toml, skills, agents, commands, plugins) so user config keeps working. Auth survives staging rebuilds: agentpack links your real ~/.grok/auth.json and mcp_credentials.json when present.
Staged layout
grok-home/
config.toml # seeded + [plugins].paths + [mcp_servers] + attribution guidance
auth.json # linked to real ~/.grok/auth.json
grok/
agentpack-bundle/
plugin.json
commands/ agents/ skills/ rules/
Pack content is rendered into grok/agentpack-bundle/ with a root plugin.json, and the staged config.toml gets a [plugins].paths entry pointing at that bundle. MCP servers are written as Grok-native [mcp_servers] TOML in config.toml.
Hooks are not staged
This is a deliberate limitation, not an omission. Grok’s hooks.json format is Claude-compatible, but grok inspect --json confirms it reads hooks only from the real ~/.grok/hooks — a redirected GROK_HOME is honored for config.toml and MCP but not for hook discovery. Bundle hooks loaded via [plugins].paths come up disabled (they need interactive trust), and grok plugin install --trust writes into the real ~/.grok (pollution). With no isolated, headless path available, Grok’s hook support stays unsupported.
Because Grok also reads Claude-compatible user sources from your real ~/.claude, agentpack’s collision check accounts for both ~/.grok and ~/.claude when removing pack copies that would shadow a user install.
Attribution
grok 0.1.219 has no verified first-class attribution-off setting. agentpack stages prompt-level guidance (AGENTS.md in the staged home) and does not modify your real ~/.grok.
Environment
| Variable | Effect |
|---|---|
GROK_PATH | Path to the grok binary |
AGENTPACK_STAGING_ROOT | Override the staging root |
See Environment Variables for the complete list.
Antigravity
agentpack agy launches the Antigravity CLI (agy). Antigravity has no config-root override and shares auth and settings with the desktop app under your real profile, so agentpack leaves HOME and ~/.gemini untouched and delivers pack content as a workspace plugin instead.
What the launcher does
agy --add-dir <cwd>
agentpack injects --add-dir with the current working directory (where you invoked agentpack — not the pack root) when you don’t supply it. Extra arguments are forwarded:
agentpack agy
agentpack --yolo agy # adds --dangerously-skip-permissions
The workspace plugin symlink
Pack content is staged into <staging>/modes/<mode>/agy/agentpack-bundle/ with a root plugin.json. Only agentpack agy creates the workspace symlink that exposes it:
./.agents/plugins/agentpack-bundle -> <staging>/.../agy/agentpack-bundle
This follows the same current-working-directory rule as Cursor’s --workspace. The symlink is tracked in a per-project overlay manifest for safe cleanup — bare sync and the other launchers never create it and remove any stale one. Add .agents/plugins/agentpack-bundle to .gitignore if you don’t want it tracked.
Staged layout
agy/agentpack-bundle/
plugin.json
skills/ agents/ commands/ rules/
mcp_config.json # remote MCP servers use serverUrl
Hooks are not staged
As with Grok, this is a verified limitation. Antigravity has a real hook engine and agy plugin validate statically accepts a Claude-shape hooks.json, but agy does not auto-load hooks from a workspace .agents/plugins/<name>/ overlay — a live session never fires a SessionStart hook from there, and the binary documents .agents/ as agent metadata, not a plugin source. With no config-root redirect, the only way to load hooks is agy plugin import/install into the real ~/.gemini (pollution). Antigravity’s hook support therefore stays unsupported.
Attribution
No verified first-class attribution-off setting. agentpack stages an always-apply plugin rule (rules/agentpack-no-attribution.md) as prompt-level guidance and does not modify your real profile.
Environment
| Variable | Effect |
|---|---|
AGY_PATH | Path to the agy binary |
AGENTPACK_STAGING_ROOT | Override the staging root |
See Environment Variables for the complete list.
Cross-Harness Conversion
Every harness names and formats the same ideas differently. agentpack does not copy artifacts verbatim — during sync it parses each markdown artifact from the cached package and re-renders it into every target harness’s native format.
The four artifact types
| Type | What it is |
|---|---|
| command | A named slash command (e.g. /review) |
| agent | A named sub-agent with its own system prompt |
| skill | A reusable instruction set with frontmatter |
| rule | Context applied across a session (Cursor .mdc, Antigravity rules) |
How it works
- agentpack reads the artifact from the cached package, detecting its source format — Cursor plain markdown, OpenCode markdown frontmatter, Claude command/agent/skill frontmatter, or skill frontmatter.
- For each target harness, it re-renders the artifact in that harness’s native shape: the right directory, filename, and frontmatter.
- When a harness lacks a first-class equivalent, agentpack uses a documented fallback rather than dropping the artifact.
Where each artifact lands
| Source | Claude | Cursor | OpenCode | Codex | Grok | Antigravity |
|---|---|---|---|---|---|---|
| command | command frontmatter | plain markdown | command markdown | skill fallback | command | command |
| agent | agent frontmatter | agent markdown | agent markdown | skill fallback | agent | agent |
| skill | normalized skill | skill | skill | Codex skill | skill | skill |
| rule | skill fallback | .mdc (preserved) | skill fallback | skill fallback | skill fallback | rule (preserved) |
Two harnesses have first-class rules — Cursor and Antigravity — so rules are preserved there. Everywhere else, a rule becomes a best-effort skill, with its original rule scope noted so the intent survives. Codex gets the portable skill subset: commands and agents fall back to skills.
Frontmatter
agentpack writes whatever frontmatter the target format needs (for example, Cursor .mdc metadata), using the artifact’s name and any metadata carried in the source. The instruction body is preserved; only the wrapper changes.
Skill shadowing
A full plugin at repo path P (same owner / repo / commit) shadows any skill whose path is P or sits under P/. An empty P shadows every skill for that repo at that commit. This keeps a plugin and its bundled skills from staging duplicate copies of the same content.
CLI Commands
agentpack <command> [args]. Global flags go before the subcommand; everything after a launcher subcommand is forwarded to the underlying agent binary.
Global flags
| Flag | Description |
|---|---|
--project-root <path> | Project root holding agentpack.toml/pack.lock (default: search upward from cwd) |
--mode <name> | Select a mode from [modes] (default: the reserved default mode) |
--yolo | Forward each harness’s “skip permission prompts” / full-access flag |
-q, --quiet | Only print warnings and errors |
--no-progress | Disable spinners and progress bars |
--debug | Print launcher diagnostics (workspace paths, env overrides, fast-sync skip reason) |
-h, --help | Print help |
-V, --version | Print the agentpack version |
Lifecycle commands
agentpack init
Create agentpack.toml and a v2 pack.lock in the project root, and ensure AGENTPACK_HOME exists. Fails if agentpack.toml already exists.
agentpack init
agentpack init --name my-project --version 0.1.0
agentpack add <spec>
Resolve a package spec, append it under [dependencies], refresh pack.lock, then sync.
agentpack add anthropics/skills/skills/canvas-design
agentpack add github.com/acme/monorepo/packages/rules@v1.2.0
agentpack add ./local-rules
agentpack add anthropics/skills/skills/canvas-design --no-sync
A spec may be a module ID, owner/repo[/path] shorthand, a GitHub tree/blob URL, a single-segment local/alias name, or a filesystem path. An optional @ref pins a branch, tag, or commit. --no-sync skips the sync step. (Requires a manifest.)
agentpack remove <spec>
Drop a matching [dependencies] entry, prune mode selectors that targeted it, refresh pack.lock, then sync unless --no-sync.
agentpack remove github.com/acme/monorepo/packages/rules
agentpack lock
Resolve agentpack.toml and rewrite pack.lock (direct + transitive). Network calls for ref/tag resolution; no content download.
agentpack lock # keep commits already pinned
agentpack lock --update # re-resolve floating pins from GitHub
agentpack sync
Ensure the cache and rebuild staging for every harness. Recomputes pack.lock from the manifest when [dependencies] is non-empty.
agentpack sync
agentpack --mode writing sync
agentpack sync --dry-run # report actions without writing
agentpack sync --verify-only # check cache + staging integrity only
agentpack sync --update-lock # re-resolve floating pins while syncing
Launchers
Each launcher runs a fast pre-sync, stages for its harness, and execs the binary. Trailing arguments are forwarded.
| Command | Launches | Mechanism |
|---|---|---|
agentpack claude | Claude Code | --plugin-dir + --settings |
agentpack agent (alias cursor-agent) | Cursor Agent | synthetic HOME |
agentpack opencode | OpenCode | OPENCODE_CONFIG_DIR |
agentpack codex | Codex | CODEX_HOME |
agentpack grok | Grok | GROK_HOME (+ injected --cwd) |
agentpack agy | Antigravity | injected --add-dir |
agentpack claude --model opus
agentpack --yolo codex
agentpack --mode writing claude
See the harness guides for what each one stages.
agentpack mcp
Manage [mcp.servers] in the manifest. add/remove sync afterward unless --no-sync.
agentpack mcp add retrieval --command uvx --args mcp-retrieval --env API_KEY=sk-...
agentpack mcp remove retrieval
agentpack mcp list # all servers with provenance (manifest / plugin / .agents)
agentpack mode
Manage [modes] in the manifest.
agentpack mode list
agentpack mode show writing
agentpack mode create review
agentpack mode base review none
agentpack mode enable review package:github.com/acme/shared-rules
agentpack mode disable review mcp:filesystem
agentpack mode delete review # `default` is reserved
agentpack mode tui # interactive editor
See Modes for selector syntax.
Manifest Schema
Complete reference for agentpack.toml.
Top-level shape
name = "my-project" # required
version = "0.0.1" # required
[dependencies] # optional
[modes] # optional
[mcp.servers] # optional
Identity is two top-level keys, not a [package] table.
| Key | Type | Required | Description |
|---|---|---|---|
name | string | yes | Project name; used in diagnostics and lock metadata |
version | string | yes | SemVer string |
[dependencies]
Each entry is a key (module ID) mapped to a value (short string or inline table).
Key format
github.com/<owner>/<repo>
github.com/<owner>/<repo>/<p1>/<p2>/...
Module IDs are lowercase Go-style paths. The host is always github.com for remote packages. A subdirectory inside a repo is part of the key — there is no field for it.
Value: short string
A bare string is a version constraint, branch, tag, or commit:
"github.com/acme/rules" = "^1.2"
Value: inline table
"github.com/acme/rules" = { version = "^1.2" }
| Field | Type | Description |
|---|---|---|
version | string | SemVer constraint matched against the repo’s tags |
branch | string | Track a branch; resolves to its HEAD at lock time (not reproducible over time) |
tag | string | Pin an exact tag |
commit | string | Pin an exact commit SHA |
path | string | Local filesystem directory relative to the project root (local development) |
Local path dependency
A path field makes the dependency local — read from disk, never fetched from GitHub. No version/branch/tag is needed:
"local-rules" = { path = "../local-rules" }
The key is still used as the package’s identity for deduplication.
Version constraint syntax
| Syntax | Semantics |
|---|---|
"1.2.3" | Exactly 1.2.3 |
"^1.2.3" | >=1.2.3, <2.0.0 |
"~1.2.3" | >=1.2.3, <1.3.0 |
">=1.0.0" | At or above 1.0.0 |
">=1.0, <2.0" | Explicit range |
"*" | Any version |
[modes.<name>]
Project-local staging presets. The reserved default mode applies when --mode is omitted. See Modes.
[modes.default]
base = "all"
[modes.writing]
base = "none"
enable = ["package:github.com/s1mplesonny/technical-writing-skill/technical-writing"]
disable = ["mcp:linear"]
| Field | Type | Description |
|---|---|---|
base | "all" or "none" | Baseline before selectors are applied |
enable | string array | Selectors to turn on |
disable | string array | Selectors to turn off |
Selectors: package:<module>, package-path:<module>:<relative-path>, mcp:<name>, .agents:<relative-path>.
[mcp.servers.<name>]
Project-level MCP server definitions, merged into each harness’s native MCP config. See MCP Servers.
[mcp.servers.retrieval]
command = "uvx"
args = ["mcp-retrieval"]
env = { API_KEY = "sk-..." }
| Field | Type | Description |
|---|---|---|
command | string | Executable that launches the server |
args | string array | Command arguments |
env | string map | Environment variables for the server process |
disabled | bool | Optional; skip this server when true |
Complete example
name = "acme-backend"
version = "0.5.0"
[dependencies]
"github.com/anthropics/skills/skills/canvas-design" = { branch = "main" }
"github.com/acme/shared-rules" = { tag = "v2.1.0" }
"github.com/acme/monorepo/packages/agent" = "^2.0"
"local-dev" = { path = "../local-dev" }
[modes.default]
base = "all"
disable = ["package-path:github.com/acme/shared-rules:commands/noisy.md"]
[modes.review]
base = "none"
enable = ["package:github.com/acme/shared-rules", ".agents:rules/backend.mdc"]
[mcp.servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem"]
Environment Variables
Set these in your shell profile (.bashrc, .zshrc, config.fish) or in CI.
State and staging
AGENTPACK_HOME
The user-wide root for cache, the metadata index, the local/ mirror, and per-project bookkeeping.
Default: $XDG_DATA_HOME/agentpack (or $HOME/.local/share/agentpack) on Unix; %LOCALAPPDATA%\agentpack on Windows.
export AGENTPACK_HOME=/data/agentpack
Point it at a network mount for a shared cache, or somewhere project-specific for isolation.
AGENTPACK_STAGING_ROOT
Root for per-harness, per-mode staging trees.
Default: <temp_dir>/agentpack-<project-hash>.
export AGENTPACK_STAGING_ROOT=/tmp/agentpack-staging
Set this for a stable path when your OS rotates temp directories, or to put staging on a fast local disk in CI.
Behavior toggles
AGENTPACK_KEEP_ATTRIBUTION
Default: unset. Set to 1 / true / yes to keep your existing AI-attribution settings (Co-Authored-By trailers, “Generated with X” footers) in staged harness configs. By default agentpack forces attribution off in staging. See Overrides and Attribution.
AGENTPACK_DOT_AGENTS
Default: enabled. Set to 0 to skip merging the project’s ./.agents/ overlay into harness staging.
AGENTPACK_TUI_THEME
Default: auto-detected from the terminal background (OSC 11 luma / COLORFGBG), falling back to dark. Set to light or dark to force the agentpack mode tui palette.
Claude proxy diagnostics
AGENTPACK_PROXY_LOG_DIR
Directory for agentpack --proxy claude JSONL diagnostics.
Default: $AGENTPACK_HOME/projects/<project-hash>/proxy-logs.
AGENTPACK_PROXY_LOG_PAYLOADS
Default: unset. Set to 1 / true / yes to include truncated upstream error bodies and payload snippets in proxy logs. Leave unset for sanitized metadata-only logs.
AGENTPACK_PROXY_LOG_MAX_BODY_BYTES
Maximum bytes retained for any payload snippet when payload logging is enabled.
Default: 4096.
AGENTPACK_PROXY_WS_CONNECT_TIMEOUT_SECS
WebSocket TCP connect timeout for proxy upstream connections.
Default: 15.
AGENTPACK_PROXY_WS_IDLE_TIMEOUT_SECS
WebSocket read/write idle timeout for proxy upstream connections.
Default: 300.
GitHub access
GITHUB_TOKEN / GH_TOKEN
Default: unset. When set, agentpack sends it as a bearer token for GitHub ref/tag lookups and authenticated downloads. Set one to resolve private repositories or to avoid anonymous API rate limits during heavy resolution. GITHUB_TOKEN takes precedence over GH_TOKEN.
Binary paths
Override the path to each harness’s executable when it isn’t on PATH or you need a specific build:
| Variable | Binary |
|---|---|
CLAUDE_CODE_PATH | claude |
OPENCODE_PATH | opencode |
CODEX_PATH | codex |
CURSOR_AGENT_PATH | cursor-agent |
GROK_PATH | grok |
AGY_PATH | agy |
Summary
| Variable | Default | Purpose |
|---|---|---|
AGENTPACK_HOME | XDG / %LOCALAPPDATA% | Cache and state root |
AGENTPACK_STAGING_ROOT | <temp>/agentpack-<hash> | Staging root |
AGENTPACK_KEEP_ATTRIBUTION | unset | Keep AI attribution in staging |
AGENTPACK_DOT_AGENTS | enabled | Merge ./.agents/ overlay |
AGENTPACK_TUI_THEME | auto | Force mode tui palette |
AGENTPACK_PROXY_LOG_DIR | project state dir | Claude proxy JSONL diagnostics |
AGENTPACK_PROXY_LOG_PAYLOADS | unset | Include truncated payload snippets in proxy logs |
AGENTPACK_PROXY_LOG_MAX_BODY_BYTES | 4096 | Payload snippet byte cap |
GITHUB_TOKEN / GH_TOKEN | unset | GitHub auth for private repos and rate limits |
CLAUDE_CODE_PATH … AGY_PATH | — | Override harness binary paths |
Publishing Packages
There is no registry. agentpack resolves packages straight from GitHub, using Git tags as versions. Publishing a package is just pushing a repo and tagging it.
A package is one of two kinds, detected by its layout:
- Skill — a directory containing a
SKILL.md. - Plugin — a directory containing a plugin manifest:
.claude-plugin/plugin.json,.cursor-plugin/plugin.json, or both.
You do not declare your artifacts in a table. agentpack discovers them by reading the package’s content and re-renders them per harness (see Cross-Harness Conversion).
A skill package
The minimal skill is a directory with a SKILL.md:
canvas-design/
SKILL.md
reference.md # any supporting files the skill references
Consumers depend on the directory’s module ID:
[dependencies]
"github.com/acme/skills/canvas-design" = "^1.0"
A plugin package
A plugin carries a manifest plus whatever artifact directories it ships. Layouts are normalized after fetch, so you can expose Claude and/or Cursor manifests:
hookify/
.claude-plugin/plugin.json
commands/ agents/ skills/ rules/ hooks/
mcp.json
Transitive dependencies
If your package depends on other packages, add an agentpack.toml to its root declaring [dependencies]. When someone depends on your package, agentpack reads that manifest at the resolved commit and pulls your dependencies into their graph. The manifest’s [dependencies] table is the only part consulted for transitive resolution.
Versioning and tags
agentpack resolves SemVer tags. Both v1.0.0 and 1.0.0 are recognized:
git tag v1.0.0
git push origin v1.0.0
Suggested bump policy:
| Change | Bump |
|---|---|
| New artifact, backward compatible | MINOR |
| Content-only fix | PATCH |
| Artifact removed/renamed, or format break | MAJOR |
Monorepo packages
Put the package (its SKILL.md or plugin manifest, and an agentpack.toml if it has dependencies) in a subdirectory, and tag the repo normally. Consumers reference the full subpath in the module ID:
[dependencies]
"github.com/acme/monorepo/packages/my-skills" = "^1.0"
agentpack reads from that subdirectory at the tagged commit.
Test before you tag
Develop against a consumer project using a local path dependency, then switch to a version constraint once you’re happy:
[dependencies]
"my-skills" = { path = "../my-skills" }
Run agentpack sync and inspect the staged output (the harness guides show where each tree lives).
Private repositories and rate limits
agentpack reads GITHUB_TOKEN (or GH_TOKEN) when present and sends it as a bearer token for ref/tag lookups and authenticated downloads. Set one to access private repos, and to avoid anonymous API rate limits on heavy resolution:
export GITHUB_TOKEN="ghp_..."
Team Workflows
Patterns for sharing agent configuration across a team.
Commit the manifest and lockfile
Commit both files together:
git add agentpack.toml pack.lock
git commit -m "chore: add agentpack dependencies"
That pair is what makes resolution identical for everyone. Staging trees are machine-local and live outside the repo by default, so there’s nothing to ignore unless you’ve pointed AGENTPACK_STAGING_ROOT inside the project — in which case ignore that path.
Onboarding
A new contributor clones the repo and runs one command:
agentpack sync # fetch everything pinned in pack.lock and stage it
agentpack claude # or any other launcher
No copying config files, no “which version of the rules are we on” — pack.lock answers that.
Updating a dependency
Edit the constraint, re-resolve, commit:
[dependencies]
"github.com/acme/shared-rules" = "^0.4" # was ^0.3
agentpack lock # reconcile pack.lock with the changed manifest
git add agentpack.toml pack.lock
git commit -m "chore: bump shared-rules to ^0.4"
lock reconciles manifest edits. To advance a floating pin (a branch, or a constraint whose range hasn’t changed) to its latest matching commit, use agentpack lock --update. Everyone else pulls the commit and runs agentpack sync.
Shared team packages
Keep team-specific skills and rules in a GitHub repo, tag releases, and depend on it from each project:
[dependencies]
"github.com/acme/team-agent-config" = "^1.0"
For a private repo, every machine and CI runner needs GITHUB_TOKEN (or GH_TOKEN) with read access. Projects stay aligned with agentpack lock && agentpack sync.
A committed project overlay (./.agents/)
To ship project-specific agent content that isn’t a published package, drop it in ./.agents/ at the repo root and commit it. On sync, agentpack merges it into the harness trees that don’t natively read it (the Claude bundle and Codex home), layered last so it wins on conflicts. Shape it like a harness layout: rules/, skills/, agents/, commands/, hooks/, mcp.json, top-level AGENTS.md/CLAUDE.md, and optional claude/ and codex/ subtrees.
Cursor, OpenCode, and Antigravity already discover workspace customization natively, so ./.agents/ is not merged into their staging. Set AGENTPACK_DOT_AGENTS=0 to skip the overlay entirely.
CI
Run agentpack sync before any step that needs staged artifacts, and persist the cache between runs. Key on pack.lock so the cache invalidates when dependencies change:
# .github/workflows/ci.yml
- name: Cache agentpack
uses: actions/cache@v4
with:
path: ~/.local/share/agentpack/cache # or $AGENTPACK_HOME/cache
key: agentpack-${{ hashFiles('pack.lock') }}
- name: Sync agentpack
run: agentpack sync
Merge conflicts in pack.lock
pack.lock is plain TOML and usually merges cleanly, but two branches that both change dependencies can collide. The reliable fix:
- Resolve the conflict in
agentpack.toml(the source of truth). - Run
agentpack lockto regenerate a cleanpack.lockfrom the merged manifest. - Commit the regenerated lockfile.
Overrides and Attribution
How to bend agentpack’s defaults without editing shared packages, plus how attribution is handled in staged configs.
Override a dependency with a local copy
To develop a package and test it in a consumer at the same time, swap the remote dependency for a path:
[dependencies]
"shared-rules" = { path = "../shared-rules" }
sync re-copies the directory on every run, so edits show up immediately. Restore the version constraint and run agentpack lock when you’re done.
Pin an exact commit
When you need a commit that isn’t behind a tag, use the commit field directly — no branch tricks required:
[dependencies]
"github.com/acme/shared-rules" = { commit = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" }
tag and branch are the other exact-pin options; see Your First Manifest.
Project overlay and per-mode toggles
For project-specific content that isn’t a published package, use the committed ./.agents/ overlay (Team Workflows). To turn whole packages, individual files, or MCP servers on and off per task, use Modes. Both are first-class — reach for them before environment hacks.
Relocate state and staging
export AGENTPACK_STAGING_ROOT="/tmp/agentpack-staging" # stable staging path
export AGENTPACK_HOME="./.agentpack" # project-isolated cache/state
A project-local AGENTPACK_HOME keeps all cached content and bookkeeping inside the repo directory; add .agentpack/ to .gitignore. See Environment Variables for defaults.
Attribution
By default, sync forces AI attribution off in every staged harness config — Co-Authored-By trailers and “Generated with X” footers — so your project doesn’t pick up agent credit lines unintentionally. Your real ~/.claude, ~/.codex, ~/.cursor, and ~/.config/opencode are never modified; only the staged copies and the Claude overlay file at $AGENTPACK_HOME/claude-settings.json are touched.
Set AGENTPACK_KEEP_ATTRIBUTION=1 (or true/yes) to keep your existing values.
How each harness is handled:
| Harness | Mechanism |
|---|---|
| Claude Code | $AGENTPACK_HOME/claude-settings.json via --settings: includeCoAuthoredBy = false, empty commit/PR attribution (loads at flagSettings scope) |
| Codex | commit_attribution = "" in the staged config.toml |
| Cursor | attributeCommitsToAgent = false, attributePRsToAgent = false in the staged cli-config.json (a real file, never your real one) |
| OpenCode | No first-class setting; a system-prompt file is added to instructions[] |
| Grok | No verified setting; prompt-level guidance only |
| Antigravity | No verified setting; an always-apply plugin rule |
Claude, Codex, and Cursor have real attribution settings, so those are exact. OpenCode, Grok, and Antigravity expose no such setting, so agentpack falls back to prompt-level guidance — best-effort, not guaranteed.