Capability Packs
A capability pack is a versioned release unit containing one or more agent capabilities. Each skill, tool, hook, or MCP server keeps its own capability ID, type, and version; the pack adds a separate name and version for promoting the tested collection as one artifact.
Tuff packs are Tuff’s implementation of that idea: the .tuffpack artifact and the tuff pack commands that build, verify, publish, and install it. The artifact carries a Tuff media type rather than a neutral interchange format, so this page uses “Tuff pack” wherever the distinction from a container image matters, and “capability pack” or plain “pack” everywhere else.
Use packs when a platform team needs to deliver the same reviewed skills, tools, hooks, and MCP servers into multiple repositories or ephemeral agent runtimes. Continue using tuff add <capability-type> when you only need to manage one capability.
The recording below follows that example end to end. The first half runs on the
authoring host: tuff add for the four capabilities, tuff list and tuff check, then tuff pack build, verify, and inspect, and publication to
GHCR. The second half runs inside a clean container that has none of the
sources: tuff init, tuff pack pull, tuff pack verify, tuff add pack,
and finally a Python agent calling the installed tool.
Build your project’s capabilities
Section titled “Build your project’s capabilities”If the capabilities are already tracked by Tuff, you do not need to create my-first-pack/, copy files, or write another capability manifest. Run this from the initialized agent project:
tuff pack build --name crm-integrationTuff selects all project-scoped tracked capabilities, except the automatically installed tuff-cli-guide, and writes:
tuff-dist/crm-integration-0.1.0.tuffpackThe default version is 0.1.0, and the default target is the project’s configured default agent. Building does not modify tuff.lock, create a reusable pack definition, or overwrite an existing artifact.
Verify what was produced:
tuff pack verify tuff-dist/crm-integration-0.1.0.tuffpacktuff pack inspect tuff-dist/crm-integration-0.1.0.tuffpackThis is the normal starting point: add or create capabilities once, test them in the agent project, accept intentional changes with tuff update, then package the accepted state.
Choose capabilities, a version, and targets
Section titled “Choose capabilities, a version, and targets”Use repeatable selectors when the pack should contain only part of the project.
tuff pack build \ --name crm-integration \ --version 1.2.0 \ --capability crm-operating \ --capability lead-triage \ -a open-agents \ -a claudeAn explicit --capability tuff-cli-guide includes the guide; only implicit “select all” builds exclude it. Use --output <path> when tuff-dist/ is not appropriate.
Reuse the same selection
Section titled “Reuse the same selection”Create a small project-backed definition when a team will build the same pack repeatedly:
tuff pack init crm-integration \ --from-project \ --version 1.2.0 \ --capability crm-operating \ --capability lead-triageTuff writes tuff-packs/crm-integration/tuff-pack.toml. It stores capability IDs, not copied capability directories:
schema = 1name = "crm-integration"version = "1.2.0"description = "Project capability pack crm-integration."
[build]targets = ["open-agents"]
[project]capabilities = ["crm-operating", "lead-triage"]Build it with:
tuff pack check tuff-packs/crm-integrationtuff pack build tuff-packs/crm-integrationThe artifact still goes to tuff-dist/crm-integration-1.2.0.tuffpack by default.
What Tuff validates before writing
Section titled “What Tuff validates before writing”Project builds read tuff.lock, reconstruct portable capability sources, and verify that they reproduce the accepted installed baselines. A selected capability with local drift or a changed source fails with a command explaining that tuff update <capability> is required; no artifact is written. Missing requirements, type mismatches, workflow cycles, unsupported targets, and sources that cannot be reconstructed also fail before artifact creation.
Manifest-backed local capabilities and pinned Git skills can be reconstructed. In-place and previously packed skills can be reconstructed from their accepted installed trees. An older pack-installed tool, hook, or workflow whose original manifest is no longer recorded cannot be safely reverse-engineered; reinstall it from a manifest-backed source before repackaging it.
Advanced: author a standalone source pack
Section titled “Advanced: author a standalone source pack”Use a standalone source pack when the capability sources live in a release repository rather than an initialized agent project. This is the lower-level format and remains supported.
crm-integration/ tuff-pack.toml capabilities/ crm-operating/ tuff.toml SKILL.md crm-connector/ tuff.toml pii-guard/ tuff.toml review.sh lead-triage/ tuff.tomlEvery standalone member must be a local directory beneath the pack root and must contain a valid tuff.toml. Remote members, symbolic links, inferred manifests, and paths that escape the pack root are rejected.
schema = 1name = "crm-integration"version = "1.2.0"description = "Reviewed capabilities for agents that work with CRM data"
[build]targets = ["open-agents", "claude"]
[[capabilities]]path = "capabilities/crm-operating"
[[capabilities]]path = "capabilities/crm-connector"
[[capabilities]]path = "capabilities/pii-guard"
[[capabilities]]path = "capabilities/lead-triage"Validate and build the standalone source
Section titled “Validate and build the standalone source”tuff pack check .tuff pack build . --output tuff-dist/crm-integration-1.2.0.tuffpacktuff pack verify tuff-dist/crm-integration-1.2.0.tuffpacktuff pack inspect tuff-dist/crm-integration-1.2.0.tuffpackPack builds validate every capability, reject duplicate ids, and confirm every configured adapter supports every member. The artifact contains the verified member sources and pre-rendered target trees. Files and metadata are canonically ordered, so identical input produces identical artifact bytes and SHA-256 digest.
Building, verifying, extracting, and installing a pack never executes member tools or hooks. Runtime dependencies remain the responsibility of the destination environment.
Publish and pull with OCI
Section titled “Publish and pull with OCI”Tuff can store the exact .tuffpack bytes in any compatible OCI registry, including GHCR and self-hosted registries. OCI is the distribution protocol used by container registries, but a Tuff pack is a generic OCI artifact rather than a runnable container image.
tuff pack push tuff-dist/crm-integration-1.2.0.tuffpack ghcr.io/yourorg/crm-integration:1.2.0tuff pack pull ghcr.io/yourorg/crm-integration:1.2.0 --output tuff-dist/downloaded.tuffpackAn OCI reference has three important parts: the registry (ghcr.io), repository (yourorg/crm-integration), and either a human-readable tag (:1.2.0) or immutable manifest digest (@sha256:...). Tuff requires an explicit tag for push and an explicit tag or digest for pull; it never assumes latest.
The published object uses an OCI image manifest as a portable envelope with artifact type application/vnd.tuff.pack.v1, one layer with media type application/vnd.tuff.pack.layer.v1, and the exact .tuffpack bytes as that layer. The manifest also carries the pack name, version, and description as standard OCI annotations. It deliberately omits timestamps so the same pack produces the same OCI manifest bytes.
Two digests, two jobs
Section titled “Two digests, two jobs”| Digest | Identifies | Why it matters |
|---|---|---|
| Artifact digest | The exact .tuffpack bytes |
Tuff verifies the pack format, canonical metadata, every file, and this digest. |
| Manifest digest | The OCI manifest containing the layer descriptor and annotations | The registry uses this digest as the immutable, pullable OCI reference. |
pack push prints both digests and the immutable digest reference. Save that returned reference when a deployment must consume exactly the reviewed release. pack pull resolves a tag to its manifest digest before downloading and returns the resolved digest reference in human and JSON output.
Tags and overwrite safety
Section titled “Tags and overwrite safety”Tags are convenient names, but registries allow them to move. Tuff treats an existing tag as immutable by default: pushing the same manifest reports unchanged, while pushing different content fails until --force is supplied. This check is best-effort because the portable OCI Distribution API does not provide a compare-and-swap operation for tags; concurrent publishers can still race. Use a single publisher and digest-pinned deployment references for release automation.
Pulling never overwrites an existing output file. Tuff downloads into a temporary file beside the destination, checks the OCI layer size and digest, verifies the complete Tuff artifact and its metadata annotations, and only then atomically persists the new file.
Authentication and TLS
Section titled “Authentication and TLS”Tuff first uses credentials already configured by docker login, then credentials configured by podman login, and falls back to anonymous access when neither configuration contains credentials for the registry. Credential helper secrets are not printed in errors. HTTPS is the default; repeat --ca-file <certificate.pem> to add private certificate authorities. --plain-http disables transport encryption and should only be used with a disposable local development registry.
OCI transport proves that the bytes arrived unchanged; it does not prove who published them. Signatures, attestations, referrer discovery, and trust-policy enforcement remain a later milestone. Store future signatures and attestations as OCI referrers whose subject points at the pack manifest digest, without changing the pack object itself.
The Tuff OCI layer and a Docker image filesystem layer are different objects. Docker cannot use a Tuff pack reference in FROM; pull and verify the pack with Tuff, extract one harness-native target, then copy that extracted tree into the image. See OCI Registries and Container Images for a complete Amazon ECR, digest-pinned deployment, and Docker BuildKit walkthrough.
Extract for runtime infrastructure
Section titled “Extract for runtime infrastructure”Use pack extract to produce one harness-native filesystem tree without creating Tuff project state:
tuff pack extract tuff-dist/crm-integration-1.2.0.tuffpack \ -a open-agents \ --output runtime-bundle/The output directory must be missing or empty. Tuff verifies the complete artifact before extracting the selected target and never overwrites a non-empty runtime directory.
Install into a project
Section titled “Install into a project”Initialize the destination repository and install every member atomically:
tuff inittuff add pack tuff-dist/crm-integration-1.2.0.tuffpack -a open-agentstuff listtuff checkTuff verifies and stages the entire installation before changing the project. A tracked capability or untracked target-file collision prevents every member from being installed. Shared hook and MCP configuration is merged in staging, and a failed commit restores previous files.
Each member remains an ordinary lockfile capability with its normal baseline and drift behavior. Optional pack provenance records the pack name, pack release version, and artifact digest without replacing the member capability version.
Update an installed pack
Section titled “Update an installed pack”Members move with their pack. The pack is the unit of versioning and verification: the artifact’s target hashes are checked as a whole and every member records the same provenance, so updating one member alone would leave a lockfile claiming two releases of one pack and no artifact that reproduces either. tuff update on any member therefore updates all of them.
tuff outdated --plain-http # crm-skill 1.0.0 1.2.0 outdatedtuff update crm-skill --check # what 1.2.0 would add, update, and removetuff update crm-skill # pull 1.2.0 from the recorded registry and apply itWith a registry on record (from tuff add pack --reference), Tuff lists the repository’s tags, picks the newest semver tag, pulls it, and verifies the artifact before touching the project. Without one, or offline, pass the artifact directly:
tuff pack pull ghcr.io/acme/crm-integration:1.2.0 --output ./crm-1.2.0.tuffpacktuff update crm-skill --pack ./crm-1.2.0.tuffpackThe new release is staged exactly like a fresh installation: the old members are removed from the staging copy the way tuff delete removes them, hook registrations and MCP entries included, and the new members are installed on top. The project changes only after every step has succeeded. Files the old release emitted and the new one does not, including a capability index left with nothing to list, are removed afterwards.
Local edits to any member block the update; tuff diff <member> shows them and --force replaces them. A --harness selection narrower than the agents the pack is installed for is refused. An artifact for a different pack name is refused, and a same-version artifact with a different digest is refused without --force.
When the installed version is already the newest tag, tuff update still resolves that tag and compares its digest with the one recorded at install. If the tag was republished with different content, the update stops and says so, with both digests; --check reports the same finding without changing anything, and --force replaces the installed release with what the tag serves now and records the new digest. A tag the registry has deleted is reported as unreproducible rather than current. tuff outdated shows the same findings as repointed and tag missing.
Current boundaries
Section titled “Current boundaries”- A built artifact always contains canonical portable member sources and pre-rendered targets; project-backed selection is an authoring convenience, not a new artifact format.
- Standalone source packs contain local manifest-backed members only.
- Pack installation is project-scoped;
--globalis not supported. - Tuff does not resolve pack-to-pack dependencies or semantic-version constraints. Registry resolution picks the newest semver tag; it does not pin to a range.
tuff outdatedverifies that the installed tag still serves the installed digest, andtuff updaterefuses to call a republished tag “up to date”. Neither verifies signatures; a repointed tag is detected, not prevented.- Tuff does not install language or system dependencies.
- Signatures, attestations, referrer discovery, and policy enforcement are future trust layers.