jackin'
Behind jackin' — InternalsRuntime Model

Image Labels & Recipe Hash

The Docker image labels jackin stamps on derived role images, what each means, and how the recipe hash that drives warm reuse is calculated

This page documents the label schema jackin stamps on local derived role images and how the recipe hash that decides warm reuse is computed. It is the source of truth for contributors; keep it in sync with crates/jackin-runtime/src/runtime/image.rs and crates/jackin-runtime/src/runtime/naming.rs.

The derived image is always a thin jackin overlay (capsule, agents, normalization) built FROM a local role base image jk_<role>__base:<sha>. The overlay never depends on the published image's mutable :latest tag — jackin always materializes an immutable local base first, from one of two sources:

  • Fresh published image → pull docker.io/.../jackin-the-architect:latest, verify its Docker labels prove it was built from the current role commit, and tag it as the local jk_<role>__base:<sha> (no role rebuild, no restamp Docker build). The overlay then derives FROM that local tag.
  • Published image missing/outdated, or a custom construct is in play → build the base locally from the role Dockerfile (construct FROM overridden by JACKIN_CONSTRUCT_IMAGE when set), tagged jk_<role>__base:<sha>.

Either way the local base is reused when the tag already exists locally and its labels prove the current role commit plus construct identity — so warm launches skip the published pull entirely, and the heavy role layers are materialized once per (role commit, construct) (see Local role base).

The local derived image jk_<role>:<sha> carries the full label set below. All keys use dotted namespacing (jackin.<group>.<name>). The role-commit SHA is stored in its short, 7-character form (e.g. 4f38b4f), matching the image tag.

The reuse authority: jackin.image.recipe.hash

jackin.image.recipe.hash is the single value that decides reuse-vs-rebuild. It is the SHA-256 of the JSON-serialized ImageRecipe struct:

fn hash(&self) -> Result<String> {
    let bytes = serde_json::to_vec(self)?; // serialize the whole ImageRecipe
    Ok(sha256_hex(&bytes))                  // → 64-char hex
}

ImageRecipe contains every input that changes derived-image content:

Recipe fieldCaptures
versionrecipe schema version (the jackin.image.recipe.version gate)
manifest_versionjackin.role.toml schema version (e.g. v1alpha4)
role_git_shashort role-repo commit SHA
role_source_refbranch/ref
base_imagepublished-base override, if any
construct_imagewhich construct (custom vs official)
generated_runtime_hashSHA-256 of the generated derived Dockerfile (the overlay shape)
supported_agentsthe supported-agent set (sorted → canonical, so reorder ≠ rebuild)
cache_bustforced-rebuild value (--rebuild)
capsule_versionjackin-capsule version baked in
hooks_hashSHA-256 of role hook files
host_identity_strategyUID/identity strategy

Agent CLI binaries are not an ImageRecipe input: they are bind-mounted read-only at docker run (the newest cached host binary onto the PATH location the overlay's ENV PATH covers), not baked into the image. So an agent version bump is picked up on the next launch without an image rebuild, and there is no per-agent version label. Claude plugins likewise install at container start (capsule runtime-setup), not at build time — so claude_plugin_recipe_hash is gone from the recipe.

At launch jackin recomputes the expected recipe from current inputs, hashes it, and compares to the stored label. Match → reuse (skip docker build, runtime-binary prep, token lookup, and the version probe). Mismatch → rebuild.

Because every recipe field is folded into this hash, a change to any of them invalidates the image even when the field has no standalone label of its own (see Dropped labels).

Schema gate: jackin.image.recipe.version

A short constant (currently v4) checked before the hash. The hash is only comparable if both sides used the same recipe schema; if a jackin upgrade changes the ImageRecipe shape or label set, the version is bumped so old images fail the gate (RecipeVersionChanged) and rebuild rather than mis-comparing hashes. Bumping it invalidates all prior images at once.

Identity & version labels (local derived image)

Stamped and, where noted, checked for a precise invalidation reason:

LabelMeaningPrecise reason on mismatch
jackin.role.git.shashort role-repo commit SHARoleGitShaChanged
jackin.manifest.versionjackin.role.toml schema versionManifestVersionChanged
jackin.construct.imageconstruct image used (custom vs official)ConstructImageChanged
jackin.capsule.versionjackin-capsule version baked inCapsuleVersionChanged

There are no per-agent version labels: agent binaries are mounted at run time, not baked, so the image carries no agent version to record (see the recipe-table note above).

Published-contract labels

Every published role image must carry exactly these two (written by role CI — see Publishing Role Images):

LabelMeaning
jackin.role.git.shashort role-repo commit SHA the image was built from
jackin.construct.versionconstruct version tag from the role Dockerfile FROM line

jackin pulls the published base and verifies jackin.role.git.sha against the role checkout's HEAD to decide whether to tag the published image as the local base, or build the base from the workspace Dockerfile. Role publishing workflows should get these labels from jackin-role publish-labels --role-git-sha <sha> so the publish path and runtime freshness check share one contract.

Local role base

The role base is always materialized locally as jk_<role>__base:<sha> — the role content with no jackin overlay — so the derived overlay builds FROM a stable local tag rather than the published :latest. The base is either a local tag of a pulled, label-verified published image or a local build of the role Dockerfile (construct FROM overridden when JACKIN_CONSTRUCT_IMAGE is set). Locally built bases carry two labels:

LabelMeaning
jackin.role.git.shashort role-repo commit SHA
jackin.construct.imagethe construct the base was built on (custom vs official)

Tagged published bases keep the published image labels instead; reuse requires jackin.role.git.sha matching the current role HEAD, and any construct label present must still match (jackin.construct.image for local builds, jackin.construct.version for published images). So warm launches skip the published pull, and the heavy role layers are materialized once per role commit plus any declared construct identity; the overlay derives FROM the base.

Container / network labels (GC & filtering, not image recipe)

LabelPurpose
jackin.managed=truemarks every jackin-managed container/network/volume; GC filter
jackin.kind=role|dind|prewarm-dindresource type
jackin.rolerole key on sidecars/networks → GC maps them back to the role
jackin.imagederived image name on the role container → image GC skips in-use images
jackin.prewarm=trueprewarmed (unattached) resource
jackin.keep.awake=trueworkspace opted into the caffeinate reconciler
jackin.display.namehuman role name on the container, read by discovery

Dropped labels

The following opaque jackin.recipe.* component labels were removed: role_source_ref, base_image, generated_runtime_hash, supported_agents, cache_bust, hooks_hash, host_identity_strategy. Their values still live inside ImageRecipe, so they still invalidate the image via jackin.image.recipe.hash — a mismatch simply surfaces as the generic RecipeHashChanged reason instead of a component-specific one. The per-agent jackin.agent.<slug>.version labels and the single jackin.selected_agent_version were dropped entirely: agent binaries are mounted at run time, not baked, so the image records no agent version. claude_plugin_hash is also gone — Claude plugins install at container start, not in the image.

On this page