Skip to main content
facet build produces the canonical .facet archive on disk from your source tree. facet publish reads that built archive, verifies it against the facet artifact specification, and uploads it to the registry. Building and publishing are distinct steps with a single source of truth for what a .facet is: the protocol-defined two-layer format whose content-hash contract is described in the Integrity Model. This page defines what happens during facet build and facet publish — the steps, inputs, outputs, and integrity guarantees. This covers facet archives only. MCP server publishing is defined in MCP Server Assets.

Build

facet build produces the canonical .facet archive from your source tree and writes it to dist/<name>-<version>.facet. This is the artifact of record: the bytes facet publish will later verify and upload.

Steps

  1. Parse and validate the manifest. Read the facet manifest (facet.json) and validate it against the manifest schema. Invalid manifests MUST be rejected with a descriptive error. The manifest’s name MUST be filesystem-safe; the version MUST be a semver string; at least one text asset (skill, agent, command, or composed facet reference) MUST be declared.
  2. Resolve prompts. Read each declared asset file from its conventional path: skills/<name>/SKILL.md for skills, agents/<name>.md for agents, commands/<name>.md for commands. Missing files are build errors. YAML front matter in content files is permitted and MUST be preserved verbatim in the archive — the manifest’s name, description, and any per-adapter extras are merged on top of the author’s front matter at install time.
  3. Validate content. No declared asset MAY be empty (zero bytes or whitespace only). No two assets within the same type MAY share a name. Compact facets[] entries MUST match the name@version shape.
  4. Validate adapter metadata. For each asset with an adapters block, validate the adapter-specific metadata against each installed adapter’s schema. Unknown adapters SHOULD produce a warning. Invalid metadata for an installed adapter MUST be a build error.
  5. Assemble the archive. Compute per-asset SHA-256 hashes. Assemble the deterministic inner tar (archive.tar.gz). Compute the integrity hash over the uncompressed inner tar. Gzip the inner tar. Assemble the outer tar containing build-manifest.json (recording the integrity hash and the per-asset hash map) plus the gzipped inner archive. Write the result to dist/<name>-<version>.facet.
facet build purges dist/ before writing, so the directory contains exactly one .facet after a successful build: the archive for the current source’s name and version.

Publish

facet publish reads the built .facet from dist/, verifies it end-to-end via the shared archive-verification operation in @agent-facets/protocol, and uploads the verified bytes to the registry. Building and publishing are distinct: publish never constructs the archive on its normal path. facet publish accepts an optional directory argument and defaults to the current working directory, consistent with facet build, facet edit, and facet create:
facet publish            # publish the facet in the current directory
facet publish ./cowsay   # publish the facet in ./cowsay
When the expected built artifact is missing or out of date relative to the current source, publish handles each case explicitly — see When the Built Artifact Is Missing and When the Built Artifact Has Drifted from Source.

Authentication

Publishing is an authenticated operation. The CLI sends an Authorization: Bearer <token> header on the publish request, where the token is a personal access token (PAT) minted in the web UI. The CLI resolves the token from, in order of precedence:
  1. The FACET_TOKEN environment variable (preferred for CI and scripted use).
  2. A credentials file written by facet login, stored under $FACET_DIR (default ~/.facet/credentials, mode 600).
If no token can be resolved, facet publish fails before any verification or registry round-trip, directing the user to sign in. There are three commands for managing the credential:
CommandWhat it does
facet loginGuided sign-in: paste a PAT, which is verified against the registry and then saved to the credentials file.
facet whoamiPrint the signed-in identity (username, email, tier). Indicates when FACET_TOKEN is the active credential.
facet logoutRemove the saved credentials file. Makes no server call — revoke PATs in the web UI.
When FACET_TOKEN is set, it takes precedence over the saved file; login and logout both note this so the env var does not silently shadow the file. Read-only commands (such as facet search and facet add) send the token too when one is available — earning a higher rate-limit tier — but work anonymously when it is absent.
Errors returned by the registry are rendered verbatim — the CLI shows the registry’s own message and suggested fix rather than maintaining its own copy of what each error code means. A duplicate-version publish, for example, surfaces the registry’s “version already exists” guidance directly.

What the Author Uploads

The CLI uploads the complete built .facet archive — the same self-contained two-layer artifact facet build produced. The outer tar contains the build-manifest.json (recording the integrity hash and per-asset hashes) and the gzipped inner archive (carrying the embedded facet.json and every declared asset). No part of the archive is re-derived at upload time: the bytes on disk are the bytes on the wire. The name and version used to address the upload come from the verified artifact’s embedded manifest, not from a separate parse of the source-tree facet.json. This matters when the user explicitly chooses to ship a drifted artifact under its own embedded identity (see identity drift). The manifest’s private declaration is part of that embedded manifest, so it travels with the upload like any other manifest content — there is no separate publish flag for privacy. The registry reads the author’s privacy intent from the embedded facet.json. Registry-side authorization and visibility enforcement are outside the CLI/protocol surface described here.

What the Registry Does

  1. Verify the upload. The registry runs the same archive-verification operation facet publish ran locally: parse the outer container, decompress the inner archive (within the registry’s size policy), verify the integrity hash, verify each per-asset hash, validate the embedded manifest, and apply the artifact content rules. A verification failure rejects the publish.
  2. Store the artifact. The verified bytes are stored under (name, version). The registry records both the canonical fingerprint (content_integrity — SHA-256 of the inner uncompressed tar) and the transport hash (content_hash — SHA-256 of the uploaded tarball). Consumers verify both at different stages (see Integrity Model).
Publish-time errors from the registry — verification failures, duplicate-version conflicts, tier limits, size caps — are surfaced to the user with the registry’s own text (see the Authentication note above).

Review Queue

A first-time publish of a reserved or over-budget global facet MAY be accepted into a moderation queue rather than published immediately. This is a success outcome: facet publish reports that the submission was queued for review, renders the registry’s guidance, and exits 0. The version becomes available once an admin approves it.

Immutability

Once a facet version is published, the registry MUST NOT allow re-publishing the same name and version with different content. A version, once published, is immutable. Because private is manifest content embedded in the artifact, changing a facet’s privacy after a version is published requires the same version bump as any other content change. Re-publishing the same (name, version) with a flipped private value collides with immutability and is rejected; publish the visibility change under a new version.

When the Built Artifact Is Missing

When dist/ is empty (or doesn’t exist), there’s nothing for publish to ship:
  • In an interactive terminal, facet publish offers to build the current source. On acceptance, the CLI runs the build pipeline in the same view facet build uses, then verifies and uploads the freshly built artifact. On decline, publish reports that there is nothing to publish and exits non-zero — the registry is never contacted.
  • In a non-interactive context (CI, piped stdin), facet publish does not prompt. It fails with a clear “no built artifact; run facet build first” message and exits non-zero.

When the Built Artifact Has Drifted from Source

When dist/ contains a .facet but its embedded manifest disagrees with the current source-tree facet.json, facet publish distinguishes two drift classes and handles each with a different prompt. Content drift — same name and version, different manifest content (the user edited description, private, an asset descriptor, or similar without rebuilding). In an interactive terminal, the user gets a two-option prompt: rebuild and publish the new artifact, or publish the existing artifact unchanged. Both choices upload to the same (name, version) address; the registry has no view into the user’s local edits. Editing private is content drift like any other manifest edit — publishing the existing artifact ships its embedded privacy declaration unchanged, while rebuilding embeds the new value. Identity drift — different name or different version (the most common case: the user bumped version to 0.2.0 but dist/ still has the 0.1.0 artifact). In an interactive terminal, the user gets a three-option prompt:
  1. Build & publish the current source. Run the build pipeline against the source’s current name and version, verify the freshly built artifact, and upload it.
  2. Publish the existing artifact as-is. Upload the older built artifact under its own embedded (name, version). If the registry already has that identity published, the upload fails with the registry’s verbatim immutability message — that 409 is the signal that the source needs a version bump (or that the user already published the older version and forgot).
  3. Cancel. Exit non-zero without contacting the registry.
The third option exists because there are real workflows where it succeeds: a user who built v0.1.0 on machine A, copied the .facet to machine B, then started editing facet.json for the next version, and now wants to publish what they actually built before continuing. In a non-interactive context, both drift classes fall back to the same behavior: emit a warning to standard error summarising the drift, then upload the existing artifact unchanged. Pipelines that build a separately-versioned artifact and want it shipped as-is are exactly served by this default; pipelines that want a strict rebuild-from-source run facet build && facet publish as two commands.

Build vs. Publish

Concernfacet buildfacet publish
RoleProducerVerify-and-ship
ReadsSource tree (facet.json + asset files)Built archive in dist/
Producesdist/<name>-<version>.facetNo on-disk output
NetworkNoneOne POST to the registry
VerificationBuild-time validators (schema, content rules, collisions)Full archive verification (integrity hash, per-asset hashes, embedded manifest, content rules)
When the input is missingFails — no source to buildOffers to build (TTY) or fails with run facet build first (CI)
When the input is invalidFails with the relevant build errorFails with a verification error; never contacts the registry
Manifest mutationNoneNone

Future: Text Composition

The facets array in a manifest is reserved for referencing other facets — a composition model where a published facet can include text from another. This is not yet implemented. The current build and publish flow does not resolve composition references, and the registry does not assemble composed content. A future iteration will define how composition works end-to-end (likely with the author building a self-contained artifact locally, references resolved against signed registry sources, and the registry verifying composition signatures rather than re-assembling from scratch). Until that lands, the published .facet is exactly what facet build produced on the author’s machine.

Not in the Publish Flow

  • MCP server resolution — server references in servers are stored as declared. They are resolved at install time (see Install & Resolve).
  • MCP server publishing — servers are a separate artifact type with their own publish flow (see MCP Server Assets).
  • Lockfile generation — the lockfile is an install-time artifact, not a publish-time artifact.
  • Text composition resolution — see Future: Text Composition.