[GH-ISSUE #378] [Design Proposal] Repository-published OverlayBD images #95

Open
opened 2026-10-29 14:19:31 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @Primary33 on GitHub (Oct 13, 2026).
Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/378

Affected component

OverlayBD / image resolution

Problem statement

AgentENV can start sandboxes from external OCI images and from templates, but it has no reusable, repository-owned image artifact. Today an image built through BuildKit is either turned into a template (which also creates a snapshot record) or stays in one node's conversion cache. Cache eviction can remove it, and other nodes cannot resolve it.

Multi-service workloads (for example Compose projects, #348) and plain image builds need an artifact that:

  • has a stable, content-addressed identity that does not depend on the build node or on local paths;
  • stays usable after local cache eviction and on any node that uses the same repository;
  • can be listed, inspected, and removed without breaking sandboxes or snapshots that already use its layers;
  • can also be referenced by a human-readable name, so a rebuild does not force every consumer to copy a new 64-character digest.

This issue documents the image model implemented in #347 (build, publication, and tag creation and resolution) and #359 (catalog, and tag listing and deletion), so reviewers and users can agree on its semantics before merge. Image tags were added following the review discussion in this issue.

Use case

  • aenv build --image ./app builds a Dockerfile remotely and prints a sha256:... reference. The reference can be used in a sandbox image field on any node that shares the repository.
  • aenv build --image ./app --tag app:v1 also points the tag app:v1 at the new image. aenv start --cold aenv/app:v1, a sandbox image field, or a Compose image: field can then use the tag. Rebuilding and re-tagging moves new launches to the new image without editing any reference.
  • Compose builds (#348) build several service images and write a Compose file that refers to them by published digest. No destination registry is needed.
  • Operators list and remove published images and tags through the API and expect running workloads to be unaffected.

Current behavior and workarounds

  • A bare sha256: image reference has no repository-backed meaning. Resolution goes through registry or node-local conversion caches.
  • Without a template, a BuildKit build has nowhere durable to put its result. The workarounds are creating throwaway templates or pushing to an external OCI registry. Templates also create snapshot records and VM state. Registry pushes need client and node credentials and conversion on every consumer node.

Desired behavior

  • Given a successful image build, when its status reports ready, then all layers and the image description are durable in the shared repository, and the reported digest resolves on any compatible node.
  • Given a published digest, when the build node's cache is evicted or the build node is gone, then another node with an empty cache can still materialize and start the image.
  • Given a deleted image, when a new sandbox requests that digest, then the request fails. Existing sandboxes, pause/resume, fork, and snapshot restore keep working.
  • Deleting a build record never deletes the image it published.
  • Given a tag that points to a published image, when a sandbox requests aenv/name:tag, then it starts from the digest the tag points to at that moment, and the sandbox and its snapshots record only that digest.
  • Given a moved tag, when a new sandbox requests it, then it starts from the new digest, even on a node that cached the old one. Existing sandboxes, pause/resume, fork, and snapshot restore keep the digest they started from.
  • Tagging a digest that is not published fails. Deleting a tag never deletes the image. Deleting an image leaves its tags in place, and resolving them fails until they are moved.

Proposed approach

Image identity and storage format

A published image is a canonical JSON description (PublishedImage, src/image/mod.rs):

{
  "schemaVersion": 1,
  "os": "linux",
  "architecture": "amd64 | arm64",
  "layers": [ /* ordered OverlaybdLayerRef::Managed { digest, size } */ ],
  "config": { /* OCI runtime config: Entrypoint, Cmd, Env, Healthcheck, ... */ }
}
  • The image ID is the SHA256 of the description with all object keys sorted. It is not the original OCI manifest digest. It binds layer order, architecture, and runtime config. Local paths and build IDs are excluded, and unknown fields are rejected.
  • Only repository-managed layers are allowed (1-1024 layers, each non-empty). External or registry layers are rejected.
  • The description is stored at catalog/images/{digest}.json. Layer data uses the same content-addressed managed-layer storage as snapshots.
  • On read, the digest is recomputed and must match. A tampered or non-canonical description is rejected.
  • Publishing the same content again is idempotent and returns the same digest.

Repository usage

Since #370 the repository only stores documents and blobs, so images add no repository or backend methods. They use:

Item Purpose
DocumentKey::Image(digest) catalog/images/{digest}.json: one verified description
DocumentKey::ImageTag(tag) catalog/image-tags/{name}:{tag}.json: the digest a tag points to
DocumentCollection::Images / DocumentCollection::ImageTags Paged listing through list_documents (#359)
snapshot::store::store_volume_layers Import immutable OverlayBD lowers as managed layers; shared with volumes
snapshot::resolver::materialize_volume_backing Produce a node-local OverlayBD config from logical layer refs; shared with volumes

ImageCatalog (src/image/catalog.rs) implements image and tag put/get/list/delete once for every Repository on top of get/put/delete/list_documents, like SnapshotCatalog and VolumeCatalog, so POSIX and OSS behave the same. Images and volumes share layer publication, while their catalog records and lifecycles stay independent. Digest validation lives in crate::digest and ImageTag in crate::types, so the repository module still does not depend on image code.

Resolution

ImageResolver treats a trimmed reference starting with aenv/ as an image tag and a trimmed sha256: reference as a published image:

  1. For aenv/name[:tag], read the tag document and continue with its digest (NotFound if the tag is absent).
  2. Read and verify the description from the repository (NotFound if absent).
  3. Reject the image if its architecture does not match the host.
  4. On a node-cache hit, reuse the local config. Otherwise materialize the layers under the image's source lock and record the config in the node cache.

Because step 2 runs on every resolution, deletion blocks new launches even on a warm node. The resolved reference is always the digest, so node caches are keyed by digest and a moved tag is never served from a stale cache entry.

Build lifecycle (/images/builds, #347)

Request Behavior
POST /images/builds Allocate an independent BuildKit worker (no template record). Returns buildID and the BuildKit imageName, plus an x-agentenv-build-id header for gateway routing
WS /images/builds/{id}/builder buildctl tunnel
GET /images/builds/{id} waiting / building / ready (with imageDigest) / error
DELETE /images/builds/{id} Cancel the worker and delete the result and logs. Published images are kept. Returns 409 while publication is in progress
  • Ordering is: BuildKit export, then import and convert on the build node, then store the managed layers, then put_image, then journal Ready{digest}.
  • Results live in the build journal under image/{id}. They survive worker cleanup and server restart, and their gateway routing is restored at startup.
  • The gateway records the build-to-node assignment from the allocation response and routes /images/builds/{id}[/builder] by path.
  • CLI: aenv build --image CONTEXT prints only the digest on stdout (diagnostics go to stderr) and deletes the build record when it finishes.

Catalog (/images, #359)

Request Behavior
GET /images?limit=&nextToken= Digest-ordered summaries (platform, layer count), limit 1-100. Pages are not a frozen view
GET /images/{digest} Full immutable description
DELETE /images/{digest} Idempotent (204 even if absent). Removes the description, not the layers or the tags that point to it

Catalog and tag operations can run on any node using the repository. Build status still routes to the build node.

Image tags (/images/tags, #347 and #359)

  • A tag is a mutable name:tag pointer to one published image. The name uses lowercase letters and digits joined by single ., _, or -. The tag starts with a letter, digit, or _ and continues with letters, digits, ., _, or -. Each is at most 128 characters. An omitted tag means latest, and tags are stored in canonical name:tag form.
  • Image fields accept aenv/name[:tag] wherever they accept sha256:...: cold sandboxes, attached drives, volumes created from an image, template base images, and Compose services. The reference is a valid OCI short name, so tools that validate image names accept it. Short names under aenv/ always mean tags; an OCI image in a registry's aenv namespace needs its full name, such as docker.io/aenv/app:v1.
  • The tag document holds the target digest as a JSON string and is verified on read. A write replaces it atomically; concurrent writers to one tag are last-writer-wins.
Request Behavior
PUT /images/tags/{tag} (#347) Body {"imageDigest": "sha256:..."}. Creates the tag or moves it. Returns 404 if the image is not published
GET /images/tags/{tag} (#347) The digest the tag points to; 404 if absent
GET /images/tags?limit=&nextToken= (#359) Tags ordered by name:tag, limit 1-100, with the same cursor rules as GET /images
DELETE /images/tags/{tag} (#359) Idempotent (204 even if absent). Never deletes the image
  • DELETE /images/{digest} leaves tags that point to the image in place. Resolving such a tag fails with NotFound until the tag is moved or deleted, and it still appears in listings. Tagging is not atomic with image deletion, so a concurrent delete can also leave a dangling tag.
  • CLI: aenv build --image CONTEXT --tag NAME[:TAG] (repeatable) validates tags before the build starts, prints the digest on stdout as before, then tags it and reports Tagged aenv/... on stderr. If a tag request fails, the command exits non-zero after the digest has been printed. --tag cannot be combined with template or Compose builds.
  • aenv build --compose keeps writing digests into the generated Compose file, so built projects stay pinned. Hand-written Compose files may use aenv/ references.
  • The gateway routes tag requests to any node without a build or sandbox binding, and metric labels never include tag values.

Local cache ownership

Only files owned by the node's commit store count as local hard commits (src/image/cache/graph.rs). Repository-owned layers referenced by materialized configs are not charged to, or collected by, local capacity eviction. A local copy of the same layer digest from an OCI conversion is still evictable.

Compatibility and operational impact

API/config changes:

  • Additive images API tag: /images/builds*, /images, /images/{digest}, /images/tags, /images/tags/{tag}. No new config keys; the image API uses the existing snapshot.repository_backend.
  • Semantic change: a bare sha256: sandbox image now means a published image description, not an OCI manifest digest.
  • Semantic change: short references starting with aenv/ now name image tags. They previously resolved through the search registries (for example docker.io/aenv/...). Fully qualified references, including docker.io/aenv/..., are unchanged.
  • New CLI flag aenv build --image --tag.

Snapshot or storage format changes:

  • New repository prefixes catalog/images/ and catalog/image-tags/. POSIX lock files (*.lock) sit beside the documents. Snapshot, volume, and existing layer formats are unchanged.

New host/runtime requirements:

  • Builders and consumers must use the same repository. Multi-node POSIX deployments need a shared filesystem.

Upgrade and rollback considerations:

  • CLI, gateway, and server must be upgraded together for --image and --tag.
  • After a rollback, older servers ignore catalog/images/ and catalog/image-tags/, and sandboxes that refer to published digests or aenv/ tags cannot be resolved.

Open questions

  1. Layer reclamation. Deleting an image removes only its description, and a failed publication may leave uploaded layers behind. These PRs do not reclaim managed layers that no image, snapshot, or volume references anymore. Tags reference descriptions, not layers, so they do not change what a collector would need to keep.
  2. Architecture. For image builds, the CLI always passes --opt platform=linux/amd64 to buildctl. Publication, however, records the host architecture, and resolution requires it to match the host. On an arm64 host this produces amd64 layers labelled arm64.
  3. Warm-path cost. Every resolution of a published digest reads the description from the repository before the node cache is checked, so that deletion takes effect immediately. On OSS this adds one GET per sandbox launch, and a tag reference adds one more GET to read the tag.

Alternatives considered

  • Publish built images as templates. This creates snapshot records and VM state that image consumers do not need, and couples image lifetime to template lifetime.
  • Push to an external OCI registry. This needs registry infrastructure and credentials, and re-converts on every consumer. It is kept as an optional output in #348 (--image-repository).
  • Use the OCI manifest digest as the identity. It does not bind the converted OverlayBD layers, and two conversions of the same manifest could differ. The description digest identifies exactly what runs.
  • Node-local cache only, with scheduler placement by cache inventory (an earlier iteration of this work). Images do not survive eviction and placement becomes coupled to cache state.
  • Bare name:tag references for tags. Every such string is already a valid OCI reference (for example docker.io/library/name:tag). Checking the repository first would let a tag shadow a registry image and add a repository read to every registry launch. The aenv/ prefix reserves only one short-name namespace and never falls back to a registry.
  • An aenv://name:tag scheme. It cannot collide with any OCI reference, but it is not a valid OCI reference, so tools that validate image names reject it.
  • Single-segment aliases, like snapshot aliases. Simpler, but name:tag matches docker build -t and Compose image: usage and keeps several versions under one name.
  • Delete an image's tags with it, or refuse to delete a tagged image. Both need a scan of all tags and cannot be made race-free across object-store keys. Leaving dangling tags is explicit and recoverable.

Non-goals

  • Compose runtime and Compose builds (#349, #348). They consume this image model but are outside this issue.
  • Repository-wide reclamation of unreferenced managed layers.
  • Changes to external OCI image resolution, templates, snapshots, or volume semantics.
  • Tag namespaces (team/app), immutable or protected tags, and listing an image's tags in GET /images.

Contribution

I can implement it

Implemented in #347 (image build and publication; tag creation and resolution) and #359 (image catalog; tag listing and deletion).

Pre-submission checklist

  • I searched existing issues and discussions and did not find a duplicate.
  • I described a concrete problem and use case, not only a proposed technology.
  • I understand that acceptance of the problem does not imply approval of a particular implementation.
Originally created by @Primary33 on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/378 ### Affected component OverlayBD / image resolution ### Problem statement AgentENV can start sandboxes from external OCI images and from templates, but it has no reusable, repository-owned image artifact. Today an image built through BuildKit is either turned into a template (which also creates a snapshot record) or stays in one node's conversion cache. Cache eviction can remove it, and other nodes cannot resolve it. Multi-service workloads (for example Compose projects, #348) and plain image builds need an artifact that: - has a stable, content-addressed identity that does not depend on the build node or on local paths; - stays usable after local cache eviction and on any node that uses the same repository; - can be listed, inspected, and removed without breaking sandboxes or snapshots that already use its layers; - can also be referenced by a human-readable name, so a rebuild does not force every consumer to copy a new 64-character digest. This issue documents the image model implemented in #347 (build, publication, and tag creation and resolution) and #359 (catalog, and tag listing and deletion), so reviewers and users can agree on its semantics before merge. Image tags were added following the review discussion in this issue. ### Use case - `aenv build --image ./app` builds a Dockerfile remotely and prints a `sha256:...` reference. The reference can be used in a sandbox `image` field on any node that shares the repository. - `aenv build --image ./app --tag app:v1` also points the tag `app:v1` at the new image. `aenv start --cold aenv/app:v1`, a sandbox `image` field, or a Compose `image:` field can then use the tag. Rebuilding and re-tagging moves new launches to the new image without editing any reference. - Compose builds (#348) build several service images and write a Compose file that refers to them by published digest. No destination registry is needed. - Operators list and remove published images and tags through the API and expect running workloads to be unaffected. ### Current behavior and workarounds - A bare `sha256:` image reference has no repository-backed meaning. Resolution goes through registry or node-local conversion caches. - Without a template, a BuildKit build has nowhere durable to put its result. The workarounds are creating throwaway templates or pushing to an external OCI registry. Templates also create snapshot records and VM state. Registry pushes need client and node credentials and conversion on every consumer node. ### Desired behavior - Given a successful image build, when its status reports `ready`, then all layers and the image description are durable in the shared repository, and the reported digest resolves on any compatible node. - Given a published digest, when the build node's cache is evicted or the build node is gone, then another node with an empty cache can still materialize and start the image. - Given a deleted image, when a new sandbox requests that digest, then the request fails. Existing sandboxes, pause/resume, fork, and snapshot restore keep working. - Deleting a build record never deletes the image it published. - Given a tag that points to a published image, when a sandbox requests `aenv/name:tag`, then it starts from the digest the tag points to at that moment, and the sandbox and its snapshots record only that digest. - Given a moved tag, when a new sandbox requests it, then it starts from the new digest, even on a node that cached the old one. Existing sandboxes, pause/resume, fork, and snapshot restore keep the digest they started from. - Tagging a digest that is not published fails. Deleting a tag never deletes the image. Deleting an image leaves its tags in place, and resolving them fails until they are moved. ### Proposed approach #### Image identity and storage format A published image is a canonical JSON description (`PublishedImage`, `src/image/mod.rs`): ```json { "schemaVersion": 1, "os": "linux", "architecture": "amd64 | arm64", "layers": [ /* ordered OverlaybdLayerRef::Managed { digest, size } */ ], "config": { /* OCI runtime config: Entrypoint, Cmd, Env, Healthcheck, ... */ } } ``` - The image ID is the SHA256 of the description with all object keys sorted. **It is not the original OCI manifest digest.** It binds layer order, architecture, and runtime config. Local paths and build IDs are excluded, and unknown fields are rejected. - Only repository-managed layers are allowed (1-1024 layers, each non-empty). External or registry layers are rejected. - The description is stored at `catalog/images/{digest}.json`. Layer data uses the same content-addressed managed-layer storage as snapshots. - On read, the digest is recomputed and must match. A tampered or non-canonical description is rejected. - Publishing the same content again is idempotent and returns the same digest. #### Repository usage Since #370 the repository only stores documents and blobs, so images add no repository or backend methods. They use: | Item | Purpose | | --- | --- | | `DocumentKey::Image(digest)` | `catalog/images/{digest}.json`: one verified description | | `DocumentKey::ImageTag(tag)` | `catalog/image-tags/{name}:{tag}.json`: the digest a tag points to | | `DocumentCollection::Images` / `DocumentCollection::ImageTags` | Paged listing through `list_documents` (#359) | | `snapshot::store::store_volume_layers` | Import immutable OverlayBD lowers as managed layers; shared with volumes | | `snapshot::resolver::materialize_volume_backing` | Produce a node-local OverlayBD config from logical layer refs; shared with volumes | `ImageCatalog` (`src/image/catalog.rs`) implements image and tag put/get/list/delete once for every `Repository` on top of `get/put/delete/list_documents`, like `SnapshotCatalog` and `VolumeCatalog`, so POSIX and OSS behave the same. Images and volumes share layer publication, while their catalog records and lifecycles stay independent. Digest validation lives in `crate::digest` and `ImageTag` in `crate::types`, so the repository module still does not depend on image code. #### Resolution `ImageResolver` treats a trimmed reference starting with `aenv/` as an image tag and a trimmed `sha256:` reference as a published image: 1. For `aenv/name[:tag]`, read the tag document and continue with its digest (`NotFound` if the tag is absent). 2. Read and verify the description from the repository (`NotFound` if absent). 3. Reject the image if its architecture does not match the host. 4. On a node-cache hit, reuse the local config. Otherwise materialize the layers under the image's source lock and record the config in the node cache. Because step 2 runs on every resolution, deletion blocks new launches even on a warm node. The resolved reference is always the digest, so node caches are keyed by digest and a moved tag is never served from a stale cache entry. #### Build lifecycle (`/images/builds`, #347) | Request | Behavior | | --- | --- | | `POST /images/builds` | Allocate an independent BuildKit worker (no template record). Returns `buildID` and the BuildKit `imageName`, plus an `x-agentenv-build-id` header for gateway routing | | `WS /images/builds/{id}/builder` | buildctl tunnel | | `GET /images/builds/{id}` | `waiting` / `building` / `ready` (with `imageDigest`) / `error` | | `DELETE /images/builds/{id}` | Cancel the worker and delete the result and logs. Published images are kept. Returns 409 while publication is in progress | - Ordering is: BuildKit export, then import and convert on the build node, then store the managed layers, then `put_image`, then journal `Ready{digest}`. - Results live in the build journal under `image/{id}`. They survive worker cleanup and server restart, and their gateway routing is restored at startup. - The gateway records the build-to-node assignment from the allocation response and routes `/images/builds/{id}[/builder]` by path. - CLI: `aenv build --image CONTEXT` prints only the digest on stdout (diagnostics go to stderr) and deletes the build record when it finishes. #### Catalog (`/images`, #359) | Request | Behavior | | --- | --- | | `GET /images?limit=&nextToken=` | Digest-ordered summaries (platform, layer count), limit 1-100. Pages are not a frozen view | | `GET /images/{digest}` | Full immutable description | | `DELETE /images/{digest}` | Idempotent (`204` even if absent). Removes the description, not the layers or the tags that point to it | Catalog and tag operations can run on any node using the repository. Build status still routes to the build node. #### Image tags (`/images/tags`, #347 and #359) - A tag is a mutable `name:tag` pointer to one published image. The name uses lowercase letters and digits joined by single `.`, `_`, or `-`. The tag starts with a letter, digit, or `_` and continues with letters, digits, `.`, `_`, or `-`. Each is at most 128 characters. An omitted tag means `latest`, and tags are stored in canonical `name:tag` form. - Image fields accept `aenv/name[:tag]` wherever they accept `sha256:...`: cold sandboxes, attached drives, volumes created from an image, template base images, and Compose services. The reference is a valid OCI short name, so tools that validate image names accept it. Short names under `aenv/` always mean tags; an OCI image in a registry's `aenv` namespace needs its full name, such as `docker.io/aenv/app:v1`. - The tag document holds the target digest as a JSON string and is verified on read. A write replaces it atomically; concurrent writers to one tag are last-writer-wins. | Request | Behavior | | --- | --- | | `PUT /images/tags/{tag}` (#347) | Body `{"imageDigest": "sha256:..."}`. Creates the tag or moves it. Returns 404 if the image is not published | | `GET /images/tags/{tag}` (#347) | The digest the tag points to; 404 if absent | | `GET /images/tags?limit=&nextToken=` (#359) | Tags ordered by `name:tag`, limit 1-100, with the same cursor rules as `GET /images` | | `DELETE /images/tags/{tag}` (#359) | Idempotent (`204` even if absent). Never deletes the image | - `DELETE /images/{digest}` leaves tags that point to the image in place. Resolving such a tag fails with `NotFound` until the tag is moved or deleted, and it still appears in listings. Tagging is not atomic with image deletion, so a concurrent delete can also leave a dangling tag. - CLI: `aenv build --image CONTEXT --tag NAME[:TAG]` (repeatable) validates tags before the build starts, prints the digest on stdout as before, then tags it and reports `Tagged aenv/...` on stderr. If a tag request fails, the command exits non-zero after the digest has been printed. `--tag` cannot be combined with template or Compose builds. - `aenv build --compose` keeps writing digests into the generated Compose file, so built projects stay pinned. Hand-written Compose files may use `aenv/` references. - The gateway routes tag requests to any node without a build or sandbox binding, and metric labels never include tag values. #### Local cache ownership Only files owned by the node's commit store count as local hard commits (`src/image/cache/graph.rs`). Repository-owned layers referenced by materialized configs are not charged to, or collected by, local capacity eviction. A local copy of the same layer digest from an OCI conversion is still evictable. ### Compatibility and operational impact API/config changes: - Additive `images` API tag: `/images/builds*`, `/images`, `/images/{digest}`, `/images/tags`, `/images/tags/{tag}`. No new config keys; the image API uses the existing `snapshot.repository_backend`. - **Semantic change:** a bare `sha256:` sandbox `image` now means a published image description, not an OCI manifest digest. - **Semantic change:** short references starting with `aenv/` now name image tags. They previously resolved through the search registries (for example `docker.io/aenv/...`). Fully qualified references, including `docker.io/aenv/...`, are unchanged. - New CLI flag `aenv build --image --tag`. Snapshot or storage format changes: - New repository prefixes `catalog/images/` and `catalog/image-tags/`. POSIX lock files (`*.lock`) sit beside the documents. Snapshot, volume, and existing layer formats are unchanged. New host/runtime requirements: - Builders and consumers must use the same repository. Multi-node POSIX deployments need a shared filesystem. Upgrade and rollback considerations: - CLI, gateway, and server must be upgraded together for `--image` and `--tag`. - After a rollback, older servers ignore `catalog/images/` and `catalog/image-tags/`, and sandboxes that refer to published digests or `aenv/` tags cannot be resolved. ### Open questions 1. **Layer reclamation.** Deleting an image removes only its description, and a failed publication may leave uploaded layers behind. These PRs do not reclaim managed layers that no image, snapshot, or volume references anymore. Tags reference descriptions, not layers, so they do not change what a collector would need to keep. 2. **Architecture.** For image builds, the CLI always passes `--opt platform=linux/amd64` to buildctl. Publication, however, records the host architecture, and resolution requires it to match the host. On an arm64 host this produces amd64 layers labelled `arm64`. 3. **Warm-path cost.** Every resolution of a published digest reads the description from the repository before the node cache is checked, so that deletion takes effect immediately. On OSS this adds one GET per sandbox launch, and a tag reference adds one more GET to read the tag. ### Alternatives considered - **Publish built images as templates.** This creates snapshot records and VM state that image consumers do not need, and couples image lifetime to template lifetime. - **Push to an external OCI registry.** This needs registry infrastructure and credentials, and re-converts on every consumer. It is kept as an optional output in #348 (`--image-repository`). - **Use the OCI manifest digest as the identity.** It does not bind the converted OverlayBD layers, and two conversions of the same manifest could differ. The description digest identifies exactly what runs. - **Node-local cache only, with scheduler placement by cache inventory** (an earlier iteration of this work). Images do not survive eviction and placement becomes coupled to cache state. - **Bare `name:tag` references for tags.** Every such string is already a valid OCI reference (for example `docker.io/library/name:tag`). Checking the repository first would let a tag shadow a registry image and add a repository read to every registry launch. The `aenv/` prefix reserves only one short-name namespace and never falls back to a registry. - **An `aenv://name:tag` scheme.** It cannot collide with any OCI reference, but it is not a valid OCI reference, so tools that validate image names reject it. - **Single-segment aliases, like snapshot aliases.** Simpler, but `name:tag` matches `docker build -t` and Compose `image:` usage and keeps several versions under one name. - **Delete an image's tags with it, or refuse to delete a tagged image.** Both need a scan of all tags and cannot be made race-free across object-store keys. Leaving dangling tags is explicit and recoverable. ### Non-goals - Compose runtime and Compose builds (#349, #348). They consume this image model but are outside this issue. - Repository-wide reclamation of unreferenced managed layers. - Changes to external OCI image resolution, templates, snapshots, or volume semantics. - Tag namespaces (`team/app`), immutable or protected tags, and listing an image's tags in `GET /images`. ### Contribution I can implement it Implemented in #347 (image build and publication; tag creation and resolution) and #359 (image catalog; tag listing and deletion). ### Pre-submission checklist - [x] I searched existing issues and discussions and did not find a duplicate. - [x] I described a concrete problem and use case, not only a proposed technology. - [x] I understand that acceptance of the problem does not imply approval of a particular implementation.
Author
Owner

@yingdi-shan commented on GitHub (Oct 13, 2026):

Maybe we should assign a tag/alias for each of our image?

<!-- gh-comment-id:6141236463 --> @yingdi-shan commented on GitHub (Oct 13, 2026): Maybe we should assign a tag/alias for each of our image?
Author
Owner

@Primary33 commented on GitHub (Oct 14, 2026):

Thanks @yingdi-shan, good suggestion. #347 and #359 now support image tags (aenv/name:tag), and I've updated the proposal above. See Proposed approach → Image tags for the design.

<!-- gh-comment-id:6160886007 --> @Primary33 commented on GitHub (Oct 14, 2026): Thanks @yingdi-shan, good suggestion. #347 and #359 now support image tags (`aenv/name:tag`), and I've updated the proposal above. See **Proposed approach → Image tags** for the design.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kvcache-ai/AgentENV#95
No description provided.