Releases
A Cloud Agent release consists of release.json and recipe.tar.gz. The
manifest identifies one source commit, every owned image by registry digest,
each image’s platforms, the sandbox runtime catalog, the database revision,
the deployment protocol, and recorded compatibility checks. The recipe contains
scripts and configuration templates for installing those images without a
source checkout.
A downstream distribution pins the SHA-256 of the manifest’s exact bytes in its own reviewed lock. The manifest, in turn, pins the recipe archive and images.
File |
Purpose |
|---|---|
Published manifest schema; consumers validate against it. |
|
Generates a complete manifest from build records. |
|
Packages the explicit recipe file list into a deterministic archive. |
|
Validate release configuration and render digest-bound deployment inputs. |
What the generator checks, and what it deliberately does not
The generator requires all six artifacts, with distinct digests and exactly these platforms:
Role |
Variant |
Platform |
|---|---|---|
|
— |
|
|
|
|
|
|
|
|
— |
|
|
— |
|
|
— |
|
Image references must use digests under this organization’s registry. The
schema enforces role and web-variant cardinality, and the generator validates
its output against that schema before writing it. The generator also checks
that each runtime’s default appears in its versions; this relationship
cannot be expressed by the published JSON Schema, so consumers must check it
separately.
Version fields come from their sources: deploy_protocol from
deploy/DEPLOY_PROTOCOL, database_revision from Alembic’s script directory,
and runtime_catalog from the sandbox image’s labels. Runtime versions should
not be supplied as a second hand-maintained list.
The generator does not query the registry. A downstream resolver must verify
that each digest exists, carries the declared platforms, and has the expected
org.opencontainers.image.revision and io.hybridinference.artifact.role
labels on each platform image. Manifest validation alone does not prove those
claims.
Producing one
The publication workflow handles pushes
to dev, successful CI completion, and manual dispatch for a dev commit with
successful CI. A push can build alongside CI, but publication waits for the
newest push CI run on that exact commit to succeed. A successful CI rerun can
trigger publication later.
The workflow:
Verifies that the commit is on
devand leaves an existing release unchanged.Builds and pushes each artifact by digest with
SOURCE_COMMITset to the full commit SHA. It pulls the digest back, smoke-tests the pulled image, and records its digest, platform, and labels.Assembles the six cell records, builds the recipe, and writes the manifest.
Runs control-plane integration acceptance on those exact candidate files.
Waits for CI success, creates a draft release tagged
agent-<full-sha>, uploads both assets, then makes the release visible. A failed upload deletes the draft.
Failed or cancelled CI leaves any built images in the registry without a published manifest. The release workflow has no pull-request trigger and runs merged code. It publishes artifacts; it does not deploy a named environment.
For a manual generation run from the repository root, collect the six
publication cell JSON files in cells/. Set SOURCE_COMMIT to their full
40-character SHA and PUBLISHER_RUN_URL to the actual publisher’s Actions run
URL, then use the same generation commands as the workflow:
mkdir -p release
uv run python -m deploy.release.publish_inputs assemble \
--cells cells \
--source-commit "${SOURCE_COMMIT:?Set the build commit}" \
--output release/inputs.json
uv run python deploy/release/build_recipe.py \
--output release/recipe.tar.gz --report
RECIPE_SHA256=$(sha256sum release/recipe.tar.gz | cut -d' ' -f1)
uv run python -m deploy.release.release_manifest \
--inputs release/inputs.json \
--recipe-url "https://github.com/HarvardMadSys/hybridInference-cloud-agent/releases/download/agent-${SOURCE_COMMIT}/recipe.tar.gz" \
--recipe-sha256 "${RECIPE_SHA256}" \
--publisher-run-url "${PUBLISHER_RUN_URL:?Set the publisher Actions run URL}" \
--output release/release.json
inputs.json contains source_commit, artifacts, and runtime_labels.
runtime_labels is the sandbox’s label map from docker image inspect, matching
what the host reads when stamping attempts. The manifest is written atomically
only after all checks pass.
Starting, not just building
smoke.py starts each artifact and checks its identity labels. CI and publication use the same implementation; publication applies it to images pulled back from the registry.
Artifact |
Smoke-test evidence |
|---|---|
Control |
Migrates real Postgres with |
Web, both variants |
Serves |
Runner |
Imports its modules and provides the Docker CLI and |
Sandbox |
Each Agent CLI reports the version declared in its image labels. |
Tunnel |
Runs its entrypoint and fails clearly for an unreachable peer. |
The tests also check org.opencontainers.image.revision,
io.hybridinference.artifact.role, and the web variant’s
io.hybridinference.artifact.base_path. Dockerfiles default the revision to
unknown; publication must supply the source commit. These checks establish
startup behavior, not successful job execution, real Gateway integration, or
Kata readiness on an execution host.
Control-plane integration acceptance
acceptance.py installs the candidate from an extracted recipe in a directory without application source or Dockerfiles. It runs the real control-plane deployer in artifact mode, pulls the candidate digests, and compares running containers with the manifest through accept_artifacts.py.
Acceptance establishes |
Remaining environment acceptance |
|---|---|
Checkout-free installation with no image build |
Execution host, Kata, and real jobs |
Registry pulls and digest identity of running containers |
Any additional architecture |
Real Postgres migrations and identity-namespace check |
Gateway contracts and browser sign-in |
Healthy API and MCP listeners; correct web variant labels |
User-visible console behavior |
Refusal of conflicting configured images before pulling |
Independent registry validation by the downstream resolver |
The exact-commit host readiness fence is expected to time out: this acceptance has no execution host. The test asserts that the deploy reached the fence after all earlier stages passed. It does not bypass or weaken that check.
Because normal deployment starts web after the fence, acceptance starts it
explicitly using the generated Compose file with --no-build --pull never.
Container comparisons use registry RepoDigests, rather than image IDs, and
role labels, rather than registry paths. The workflow publishes the same
manifest and recipe bytes that passed this acceptance.
Registry layout is not part of this contract
The image’s role label defines its role. Repository names and human-readable tags are storage conventions; deployments select the manifest’s digests.
Package |
Contents |
|---|---|
|
Control, both web variants, runner, and tunnel |
|
Sandbox, with a separate visibility setting for bundled third-party CLIs |
After its smoke test passes, each digest receives a tag such as
control-<sha>, web-agents-<sha>, or sandbox-<sha>. These tags help people
inspect images and are not deployment selectors.
Tags such as buildcache-sandbox-2026-w42 are the publication workflow’s layer
cache, not images. Only that workflow writes them, and it starts a new one each
ISO week. Because a layer whose inputs did not change keeps its digest from one
release to the next, a host that runs the previous release pulls only the
layers that changed.
When changing registry layout, verify whether the copy preserves the original index digest. Recreating an index or converting its format can change that digest and requires a reviewed lock update. Keep old addresses available until the rollback window closes.
Where this differs from the design document
The original release design is historical. The implemented differences are:
Deployment protocol v4.
shared-live-exact-sha-v4replaced the design’s v2. v3 introduced the schema baseline rewrite; v4 introduced the single sandbox network. It retired the Squidegressartifact and requiresAGENT_EGRESS_CHAT_NETWORK_POOLandAGENT_EGRESS_DNS_SERVERon every runner host. v3 deployers cannot install v4 releases, and rollback does not cross this protocol boundary.Six images from five Dockerfiles. The web Dockerfile builds twice because the mount prefix is compiled into the bundle. The retired egress image accounts for the original design’s seventh artifact.
Existing runtime-label namespace. Runtime labels remain
org.freeinference.runtime.*. Renaming them requires a separate dual-read migration.
Deploying a recipe
Verify the manifest against the downstream lock, verify the recipe’s SHA-256
against the manifest, and resolve and verify its image digests before replacing
services. Extract recipe.tar.gz into a release directory; it creates a
recipe/ directory containing deployment helpers, examples, and Compose
templates without build: entries.
Both machines need Bash, Docker with Compose v2, and Python 3.12 or newer for recipe helpers. The execution host additionally needs its existing Kata provisioning; deployment does not install a sandbox runtime. Install the helper dependencies on each machine:
python3.12 -m venv /opt/cloud-agent/deployer
/opt/cloud-agent/deployer/bin/python -m pip install \
-r /opt/cloud-agent/release/recipe/deploy/release/requirements.txt
Keep DEPLOY_ROOT stable across releases. Its .env, .deployed-sha, and
.shared-live-ready describe the persistent deployment, while each extracted
recipe can live elsewhere. For a new installation, create that root and fill
its .env using the appropriate recipe’s control-plane or host .env.example.
For an existing installation, retain its configuration, Compose project name,
host enrollment root, and Docker data root. Generated Compose files stay under
DEPLOY_ROOT so relative mounts preserve their data paths; the edge proxy
configuration comes from the selected recipe.
Select AGENT_WEB_VARIANT=root or agents in the control-plane configuration.
If omitted, AGENT_WEB_BASE_PATH selects the variant: empty or unset selects
root, and /agents selects agents.
Set the artifact environment on the control-plane machine:
export AGENT_DEPLOY_MODE=artifacts
export AGENT_RELEASE_MANIFEST=/opt/cloud-agent/release/release.json
export AGENT_DEPLOY_PYTHON=/opt/cloud-agent/deployer/bin/python
export DEPLOY_ROOT=/srv/cloud-agent-control
Set the same release and interpreter on the execution host, with its own root:
export AGENT_DEPLOY_MODE=artifacts
export AGENT_RELEASE_MANIFEST=/opt/cloud-agent/release/release.json
export AGENT_DEPLOY_PYTHON=/opt/cloud-agent/deployer/bin/python
export DEPLOY_ROOT=/srv/cloud-agent-host
The manifest supplies the commit and owned image references. Remove old image
and commit overrides when moving to artifact mode; any conflicting effective
Compose values are refused before pulling or replacing services. The default
source mode remains available for checkout-based deployment.
Deploying the two in the order the design specifies
Use phases to coordinate the machines. With the environment above set in each machine’s shell, run these commands in order:
Prepare both sides; these commands may run in parallel on their respective machines and replace no serving services.
# Control-plane machine bash /opt/cloud-agent/release/recipe/deploy/control-plane/deploy.sh --phase prepare # Execution host bash /opt/cloud-agent/release/recipe/deploy/host/deploy.sh --phase prepare
Prepare the database on the control-plane machine. This migrates and checks the identity namespace. On a cold machine it also starts the API and MCP listener needed by the host readiness probes; existing listeners stay in place.
bash /opt/cloud-agent/release/recipe/deploy/control-plane/deploy.sh --phase prepare-database
Activate the execution host. It drains and replaces runners, checks the replacement, records the deployment, and publishes its generation.
bash /opt/cloud-agent/release/recipe/deploy/host/deploy.sh --phase activate
Activate the control plane. The readiness fence must pass before public services switch.
bash /opt/cloud-agent/release/recipe/deploy/control-plane/deploy.sh --phase activate
Omitting --phase runs all phases for that side. Use the phased sequence for a
coordinated rollout; two whole-script invocations cannot express the ordering
above. Phases can be retried. Database preparation retains ownership of any
listeners it started, and activation restarts those stopped during a failed
attempt so they can accept the heartbeat the readiness fence needs.
If the other machine fails and activation is abandoned, clean up listeners started solely for this cold deployment on the control-plane machine:
bash /opt/cloud-agent/release/recipe/deploy/control-plane/deploy.sh --phase abandon
This leaves previously serving listeners alone. Image pulls and host Kata checks precede runner drain. Migrations, identity-namespace verification, exact-commit readiness, reverse-sandbox-manager probes, and deferred host readiness publication keep their deployment ordering. A failed readiness check prevents activation of the replacement control-plane stack.
Script tests run extracted recipes with Docker calls recorded and failures injected, and use the real Compose environment parser when available. They do not establish readiness of a particular Linux host, live Kata runtime, or registry release; verify those during environment cutover.
What a downstream deployment still owns
The upstream publication workflow does not deploy a named environment. A consuming distribution must:
pin the exact manifest bytes in a reviewed lock and verify the schema, recipe hash, registry digests, image labels, and platforms;
check protocol compatibility and the manifest’s
verified_upgrade_fromandverified_rollback_torecords; an empty list means that path was not verified;run preparation, Kata checks, and environment acceptance on the actual hosts;
keep environment configuration, deployment records, and recovery material outside this upstream repository.
Retain previous verified manifests, recipes, reviewed hashes and digest-addressed images separately from the persistent deployment roots. Artifact-mode host rollouts receive only one manifest and cannot automatically select an older release. Follow the backup and recovery runbook to check database/protocol compatibility and run the same phased readiness sequence with a previous verified recipe; a previous release is not automatically compatible with newer installed migrations.