.facet) is the self-contained artifact a facet travels in: built by facet build (the sole producer), verified and uploaded by facet publish, stored by the registry, and downloaded and verified at install. It contains the embedded facet manifest, every declared asset, and every declared supplementary file — skill companions (files beside a SKILL.md) and top-level archive-only files like README.md or LICENSE. Nothing is fetched at install time.
The .facet file is a two-layer container. Both layers are tar archives; only the inner layer is compressed. Its content-hash contract is defined in the Integrity Model.
Outer layer
An uncompressed tar with exactly two entries:JSON
required
The build manifest:
facetVersion (the archive-format revision this artifact conforms to; breaking changes to the archive layout bump it, and consumers key compatibility off this field), archive (the inner archive’s entry name, which MUST be the exact literal archive.tar.gz), integrity (the canonical fingerprint, sha256: followed by 64 hex characters), and files (a map of every inner-archive entry path — facet.json, each primary asset, and each supplementary file — to its sha256:<hex> per-entry hash).The current archive format is 0.2; consumers also accept the legacy 0.1 format during the compatibility window. The two are dispatched by exact match on facetVersion, never by numeric ordering: a 0.2 manifest carries a files map, a 0.1 manifest carries the older assets map, and neither shape is reinterpreted as the other. The authoritative versions are the FACET_ARCHIVE_VERSION, LEGACY_FACET_ARCHIVE_VERSION, and SUPPORTED_FACET_VERSIONS constants in @agent-facets/protocol.gzip
required
The gzip-compressed inner archive carrying the actual content.
Inner archive
The inner archive decompresses to a deterministic uncompressed tar containing the embeddedfacet.json, every declared asset at its conventional path (skills/<name>/SKILL.md, agents/<name>.md, commands/<name>.md), and every declared supplementary file at its declared path (skill companions under skills/<name>/, top-level files like README.md at the tree root). Entries MUST be sorted lexicographically by path. The complete entry set is derived from the embedded manifest by the shared archive-plan operation, so archive membership stays explicit and reviewable — there is no recursive auto-discovery.
Determinism
Tar metadata MUST be fixed at known values so build output is byte-identical across builds, machines, and platforms:mtime 0, uid 0, gid 0, mode 644, empty user and group names. The integrity hash is computed over these tar bytes, so any third-party producer that wants its artifacts to interoperate MUST use the same metadata.
Hash domains
The canonical fingerprint (content_integrity) is the SHA-256 of the uncompressed inner tar bytes, formatted sha256:<hex>. Gzip output is NOT hashed — compression is a delivery concern, and different gzip implementations produce different bytes that decompress to identical tars. The full two-hash contract lives in the Integrity Model.
Verifiers MUST recompute the fingerprint by decompressing the inner archive and hashing the resulting tar bytes. The build manifest’s self-declared integrity is a claim to be checked, never a value to be trusted.
Content rules
Beyond hash verification, a verifier MUST enforce three structural rules on the archive’s contents. An archive violating any of them MUST be rejected. Both tar layers are validated at the raw-header level — before any path-keyed map is built — so duplicate paths, non-regular entries (symlinks, hard links, directories, devices), and unsafe or non-portable names are rejected before they can alias a legitimate entry. Duplicate JSON object members infacet.json and build-manifest.json are likewise rejected before schema validation.
-
Path safety. Every key in the build manifest’s
filesmap and every entry name in the inner tar MUST be a safe, portable relative path. The grammar forbids traversal (..), empty and.segments, absolute paths, drive and URL prefixes, backslashes, NUL and control bytes, the portable-reserved characters< > : " | ? *, Windows reserved device names (CON,PRN,AUX,NUL,COM1–COM9,LPT1–LPT9), and trailing dot or space on any segment. Paths that collide after Unicode normalization or case folding, and any file/directory prefix conflict, are also rejected. A malicious archive that smuggles an unsafe path through either channel is stopped before any content is trusted. -
Declared-vs-present reconciliation, in both directions. Every inner-tar entry MUST appear in the build manifest’s
filesmap, and everyfileskey MUST have a matching inner-tar entry — an undeclared extra entry and a declared-but-missing file are both rejections. Each entry’s recomputed content hash MUST equal the hash thefilesmap records for it. This applies to every entry, asset or not. -
Outer exclusivity. Every inner-tar entry MUST be derivable from the embedded
facet.json: the entry set is exactlyfacet.jsonplus the conventional path of each declared asset (skills/<name>/SKILL.md,agents/<name>.md,commands/<name>.md) and the declared path of each supplementary file (skill companions in a skill’sfiles, top-level entries in the manifest’sfiles). The expected set is derived from the embedded manifest, never from the attacker-controlled build manifest, so rule 2 alone is insufficient — the embedded facet manifest is the trust root. Relaxing this rule to admit declared supplementary files preserves the supply-chain guarantee: an archive still cannot carry an undeclared file that lands on disk at install time.