- Docs
- Build a Catalogue
- Catalogue CI contract
Catalogue CI contract
The provider-neutral validation, packaging, publication, and evidence contract for an AgentBundle catalogue CI pipeline.
This page defines the portable contract any CI system can implement to validate, package, and publish an AgentBundle catalogue. It names the commands, outputs, exit codes, publication ordering, and responsibility boundaries — without prescribing a CI provider or a specific workflow shape.
Use it to implement the same release flow in GitHub Actions, GitLab CI, Jenkins, or another runner: build without publication secrets, inject a restricted publisher identity only into the upload job, serialize releases, verify with a read identity, and retain a credential-free receipt.
Responsibility boundary
Section titled “Responsibility boundary”Three parties share responsibility for a catalogue CI pipeline. No party may silently assume another’s obligations.
| Party | Owns | Never does |
|---|---|---|
| AgentBundle CLI | Correct catalogue validation; deterministic archive + SHA256 sidecar; stable JSON output shapes; stable exit-code contract | Read secrets; issue network calls; control upload order; manage retention |
| Organization CI | Protected secret storage; publisher and read identities; HTTPS upload; publication serialization (ensuring one release lands at a time); rollback policy; artifact retention | Implement validation logic; expose credentials to build jobs; bypass exit-code signals; skip the admission command |
| Host Repository | CI workflow files and trigger config; internal governance gates; tests proving the portable commands work in this repository | Alter the portable command signatures; branch AgentBundle’s output format |
CI lifecycle phases
Section titled “CI lifecycle phases”Phase 1 — Tool acquisition
Section titled “Phase 1 — Tool acquisition”The CI environment must have agentbundle available before any catalogue
command runs. Two patterns are equally valid:
Externally managed. The CI step installs agentbundle from a package registry:
pip install agentbundlePin the version for reproducibility:
pip install 'agentbundle==<version>'If the wheel is in an internal Artifactory PyPI repository, the selected Python package client owns authentication. Its credentials are separate from the identity that uploads a catalogue and from the identity that later reads a protected Generic repository.
Vendored. The organization pre-installs agentbundle into a shared CI image or tool layer. The CI step runs catalogue commands without an explicit install.
Either way, confirm the tool is present:
agentbundle --versionPhase 2 — Change validation
Section titled “Phase 2 — Change validation”Every pipeline run that touches catalogue content must pass the admission command before proceeding to packaging:
agentbundle catalogue verify --root . --format json--format json writes a machine-readable result to stdout and exits 0 (clean)
or 1 (failures). The CI step fails on any non-zero exit, blocking packaging.
catalogue lint runs as an earlier, faster check against pack sources only. It
is optional but recommended as a pre-commit or fast-path gate:
agentbundle catalogue lint --root . --format jsonPhase 3 — Release packaging
Section titled “Phase 3 — Release packaging”After verify exits 0, package the release:
agentbundle catalogue package \ --root . \ --bundle "$BUNDLE" \ --release "$RELEASE" \ --channel "$CHANNEL" \ --output "$OUTPUT"The command writes three artifacts under $OUTPUT:
$OUTPUT/ catalogues/ $BUNDLE/ releases/ $RELEASE/ catalogue-$RELEASE.tar.gz ← immutable archive catalogue-$RELEASE.tar.gz.sha256 ← SHA256 sidecar channels/ $CHANNEL.json ← mutable channel descriptorThe archive is deterministic given the same source content. The SHA256 sidecar is computed over the archive and stored alongside it. The channel descriptor is a small JSON file that records which release the channel currently points to; it is the live pointer consumers use to resolve the latest version.
Packaging refuses to replace a release that already exists. When
catalogue-$RELEASE.tar.gz is already under $OUTPUT, the command exits 2,
prints the existing archive path on stderr, and writes nothing. Publish each
release once, from a fresh output directory. A CI step never passes --force;
only a publisher replacing a release by hand does.
To see what changed since the last release, pass the prior archive:
agentbundle catalogue package ... --compare "/path/to/catalogue-$PRIOR.tar.gz" --format jsonRe-run agentbundle catalogue verify --archive <path>.tar.gz after packaging to
confirm the archive is valid before uploading it.
Phase 4 — Publication
Section titled “Phase 4 — Publication”Run this phase in a protected upload job. Inject a restricted service identity or an organization-configured CI OIDC exchange only after the source and package checks pass. Use a username only when the selected upload client requires one.
The portable controls map to common CI systems as follows:
| Control | GitHub Actions example | GitLab CI example | Jenkins or another runner |
|---|---|---|---|
| Publisher secret | Environment or organization Actions secret | Protected, masked, and hidden CI/CD variable | Credential binding or external secret store |
| Release gate | Protected environment and release tag | Protected release tag | Restricted release job |
| Serialization | concurrency group | resource_group | Deployment lock or equivalent |
These are mappings, not required products or variable names. The host repository owns its workflow syntax.
Upload artifacts in this exact order:
- The archive (
.tar.gz) - The SHA256 sidecar (
.sha256) - The channel descriptor (
channels/<channel>.json) — upload last
The channel descriptor is the live pointer. Writing it last minimises the window during which the descriptor references an archive that has not yet landed in the target store. A consumer that resolves the channel between steps 2 and 3 finds no channel pointer and fails cleanly; a consumer that resolves after step 3 finds the archive already present.
Publication serialization — preventing two concurrent releases from interleaving their uploads — is the CI system’s responsibility. Configure release objects as immutable and allow controlled replacement only for channel descriptors.
Phase 5 — Post-publication verification
Section titled “Phase 5 — Post-publication verification”After the channel descriptor is published, use a read-only identity to download the archive and sidecar from the publication store and verify them locally:
# Download from the store first (provider-specific step)# Then verify the local copies:agentbundle catalogue verify \ --archive "/path/to/downloaded/catalogue-$RELEASE.tar.gz" \ --sha256-file "/path/to/downloaded/catalogue-$RELEASE.tar.gz.sha256"--archive accepts a local filesystem path only; pass the path to the downloaded
archive. This confirms the archive round-trips through the publication store
without corruption.
Optionally, smoke-test pack installation from the published catalogue to confirm consumer-side resolution. A smoke test against a protected catalogue needs the same reader authentication that end users need; it must not silently reuse the publisher identity.
Phase 6 — Evidence retention
Section titled “Phase 6 — Evidence retention”The CI system is responsible for retaining the evidence needed for rollback, audit, and incident response. Minimum retention artifacts:
- The archive and SHA256 sidecar for every published release
- The JSON output of
catalogue verify --format jsonfrom Phase 2 - The JSON output of
catalogue verify --archive ...from Phase 5
Also record a credential-free publication receipt: source revision, release and channel names, artifact paths, archive digest, CI run reference, and remote verification result. Never include a token, password, username, authenticated URL, or secret-store location.
Retention period and storage location are Organization CI policy, not AgentBundle’s.
Exit codes
Section titled “Exit codes”All agentbundle catalogue commands follow the same convention:
| Code | Meaning |
|---|---|
0 | Success |
1 | Validation or operational failure (errors in the catalogue, archive mismatch, internal error) |
2 | CLI usage error (missing required flag, unknown or abbreviated option); for catalogue package, also a refused replacement of an existing release archive |
These codes are stable. A CI step that treats any non-zero exit as a failure
gets the correct behaviour. A step that must tell a refused replacement from a
usage error reads exit 2 together with the CAT-PKG-002 line on stderr.
Credential and network boundaries
Section titled “Credential and network boundaries”agentbundle catalogue lint, agentbundle catalogue verify, and
agentbundle catalogue package do not read secrets and do not issue network
calls. They operate entirely on the local filesystem.
The three credential paths are independent:
- The Python package client authenticates to the repository that serves the
agentbundlewheel. - The protected upload job authenticates its upload client with a restricted publisher identity.
- AgentBundle’s HTTPS client resolves a protected catalogue read through a
four-provider chain: bearer token (
AGENTBUNDLE_HTTP_BEARER_TOKEN), JFrog CLI 2.105.0+ profile, exact-machine.netrcrecord, then anonymous. The chain stops at the first available provider; there is no fallback once a provider is chosen. See the AgentBundle reference’s Protected catalogue authentication section for provider details and failure codes.
AGENTBUNDLE_CA_BUNDLE, HTTPS_PROXY, and NO_PROXY control TLS trust and
network routing when install or upgrade resolves a remote catalogue. They are
not publication credentials. Keep same-origin redirects, HTTPS-only access,
digest verification, and archive extraction confinement enabled.
Command reference
Section titled “Command reference”agentbundle catalogue lint
Section titled “agentbundle catalogue lint”agentbundle catalogue lint [--root ROOT] [--pack PACK] [--format {table,json}] [--deep]Validates pack sources against contracts without building. Exits 0 when all checks pass, 1 on any error.
--format json writes to stdout:
{ "schema_version": "1", "command": "catalogue lint", "operation": "lint", "agentbundle_version": "<version>", "catalogue_schema_version": "<version>", "ok": true, "diagnostics": []}Human-readable output (table format) goes to stderr and is absent when
--format json is used.
agentbundle catalogue verify
Section titled “agentbundle catalogue verify”agentbundle catalogue verify [--root ROOT] [--pack PACK] [--archive ARCHIVE] [--sha256-file SHA256_FILE] [--format {table,json}]Runs the full source pipeline (source mode) or validates an archive against its SHA256 sidecar and checks the extracted layout (archive mode). Exits 0 on clean, 1 on any failure.
--format json writes the same JSON shape as catalogue lint to stdout.
agentbundle catalogue package
Section titled “agentbundle catalogue package”agentbundle catalogue package --bundle BUNDLE --release RELEASE --channel CHANNEL --output OUTPUT [--root ROOT] [--source-revision SOURCE_REVISION] [--minimum-agentbundle-version VERSION] [--published-at DATETIME] [--flavor {runtime,source}] [--force] [--compare PATH] [--format {table,json}]Required flags: --bundle, --release, --channel, --output.
Optional:
| Flag | Meaning |
|---|---|
--root | Catalogue root; defaults to . |
--source-revision | VCS revision recorded as audit metadata |
--minimum-agentbundle-version | Minimum consumer version required |
--published-at | ISO-8601 timestamp override |
--flavor | runtime (default) writes the runtime archive; source writes the self-hosted source archive |
--force | Replace an existing release archive instead of refusing. First-party CI never passes it |
--compare PATH | Prior release archive to compare against; runtime flavour only |
--format | table (default) or json; json writes one envelope on stdout; runtime flavour only |
--flavor source with --compare or --format json exits 2 with a usage
message and writes nothing. The subcommand rejects abbreviated options, so
--forc exits 2 rather than selecting --force.
Streams. Human output and diagnostic lines go to stderr in both formats.
Each diagnostic is one line, <code>: <message>. Stdout carries the JSON
envelope alone, and nothing when --format table is used. Text taken from the
prior archive is printed with non-printable characters written as escapes.
--format json. On success and on a failure after argument parsing, stdout
is one JSON object:
{ "schema_version": 1, "command": "catalogue", "operation": "package", "agentbundle_version": "<version>", "catalogue_schema_version": 1, "ok": true, "diagnostics": []}With --compare, a successful run adds a comparison object:
{ "comparison": { "prior": {"archive": "<--compare value>", "bundle": "<bundle>", "release": "<release>"}, "packs": {"added": [], "removed": [], "changed": [], "unchanged": 0}, "profiles": {"added": [], "removed": [], "changed": [], "unchanged": 0} }}Added and removed pack items are {name, version, digest}; profile items are
{name, digest}. A changed pack is {name, version: {from, to}, digest: {from, to}};
a changed profile is {name, digest: {from, to}}. Each list is sorted by name and
unchanged is a count. A failed run has ok false, the failing codes in
diagnostics, and no comparison. The human comparison on stderr marks a pack
whose content changed under an unchanged version with
content changed, version unchanged.
Exit codes.
| Code | Meaning |
|---|---|
0 | Packaged; with --force, a replaced release also exits 0 |
1 | Failure: verification, an unreadable or invalid --compare archive, or an internal error |
2 | Usage error, or a refused replacement of an existing release archive (CAT-PKG-002) |
The refusal applies to both flavours and runs before any file is written. The
deprecated top-level package-catalogue command is unchanged and exits 1 on the
same refusal.
See also
Section titled “See also”agentbundlereference — install the CLI, install a pack, configure the default adapter, source-resolution chain, environment variables. See theagentbundle.mdreference guide in your local guides tree, or the project docs site.- Catalogue format — what makes a directory a valid catalogue, schema
contracts, and validation. See the
catalogue-format.mdreference guide in your local guides tree, or the project docs site.