Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8c397f2
refactor(onboard): separate compute driver from gateway launcher
ericksoa Jul 28, 2026
cdcd37b
fix(onboard): resolve compute driver when recording metadata
ericksoa Jul 28, 2026
3eccbec
test(e2e): budget planner-report integration case
ericksoa Jul 28, 2026
2f03907
ci(images): publish complete managed OCI images
ericksoa Jul 28, 2026
ea819d7
merge: resolve conflicts with main
github-actions[bot] Jul 28, 2026
f6bc515
feat(onboard): launch all agents from managed images
ericksoa Jul 29, 2026
bf218fd
fix(onboard): close managed workload review gaps
ericksoa Jul 29, 2026
0923ee6
docs(onboard): align managed build lifecycle guidance
ericksoa Jul 29, 2026
6fcbb59
docs(onboard): cover managed Hermes and DCode paths
ericksoa Jul 29, 2026
57e6340
fix(onboard): harden managed image launch path
ericksoa Jul 29, 2026
98d8335
fix(images): isolate managed DCode build probe
ericksoa Jul 29, 2026
7ac9c59
fix(images): isolate all DCode build probes
ericksoa Jul 29, 2026
b69b5fc
test(images): pin exact PR image evidence
ericksoa Jul 29, 2026
f905717
test(e2e): exercise buildless path in protected lane
ericksoa Jul 29, 2026
44e3f37
test(images): launch exact PR images through OpenShell
ericksoa Jul 29, 2026
6477165
test(images): build shared source boundary for harness
ericksoa Jul 29, 2026
65bb1e1
fix(images): promote managed cohorts atomically
ericksoa Jul 29, 2026
b3de109
merge: refresh managed images on driver groundwork
ericksoa Jul 29, 2026
6047844
Merge branch 'main' into feat/podman-driver-selection-groundwork
cjagwani Jul 29, 2026
e002e30
Merge branch 'main' into feat/podman-driver-selection-groundwork
cjagwani Jul 29, 2026
f6d3b39
feat(onboard): launch all agents from managed images
ericksoa Jul 29, 2026
bed9d6b
fix(onboard): close managed workload review gaps
ericksoa Jul 29, 2026
baada98
docs(onboard): align managed build lifecycle guidance
ericksoa Jul 29, 2026
01ff7a2
docs(onboard): cover managed Hermes and DCode paths
ericksoa Jul 29, 2026
4566c3f
fix(onboard): harden managed image launch path
ericksoa Jul 29, 2026
c3846d8
fix(images): isolate managed DCode build probe
ericksoa Jul 29, 2026
982820b
fix(images): isolate all DCode build probes
ericksoa Jul 29, 2026
8bef1fc
test(images): pin exact PR image evidence
ericksoa Jul 29, 2026
62b2dcd
test(e2e): exercise buildless path in protected lane
ericksoa Jul 29, 2026
3ba58ef
test(images): launch exact PR images through OpenShell
ericksoa Jul 29, 2026
bdb3933
test(images): build shared source boundary for harness
ericksoa Jul 29, 2026
2ff898a
feat(onboard): decouple managed startup from image entrypoints
ericksoa Jul 29, 2026
150f769
fix(onboard): preserve managed startup lifecycle contracts
ericksoa Jul 29, 2026
5276480
chore: bridge protected PR history after restack
ericksoa Jul 29, 2026
45bd531
feat(runtime): add pluggable Podman groundwork
ericksoa Jul 29, 2026
861825b
merge: refresh managed images on current driver groundwork
ericksoa Jul 29, 2026
896278f
fix(images): satisfy managed startup runtime guards
ericksoa Jul 29, 2026
4f320f3
merge: refresh buildless onboarding on current managed images
ericksoa Jul 30, 2026
ccd343f
fix(images): run direct harness through local tsx
ericksoa Jul 30, 2026
dcf6365
feat(runtime): add Podman managed cutover primitives
ericksoa Jul 30, 2026
0325c74
merge: refresh Podman runtime on current managed onboarding
ericksoa Jul 30, 2026
29d14c6
fix(images): sanitize managed image failure evidence
ericksoa Jul 30, 2026
de998ab
fix(images): allow managed startup handoff under Landlock
ericksoa Jul 30, 2026
2cee4ed
feat(runtime): add native Podman managed runtime
ericksoa Jul 30, 2026
81f1a37
merge: refresh Podman runtime on managed onboarding
ericksoa Jul 30, 2026
405179c
feat(runtime): support Podman GPU and local inference
ericksoa Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
168 changes: 161 additions & 7 deletions .github/workflows/base-image.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Build and push the sandbox base images to GHCR.
# Build and push sandbox base images, then publish validated managed images.
#
# Triggers:
# - Push to main when a base-image workflow input changes
# - Push to main when a base- or managed-image input changes
# - Manual dispatch for ad-hoc rebuilds
#
# The base image contains the expensive, rarely-changing layers (apt, gosu,
# user setup, openclaw CLI). The production Dockerfile layers PR-specific
# code on top via: FROM ghcr.io/nvidia/nemoclaw/sandbox-base:<tag>
# Base images contain the expensive, rarely-changing layers. Complete image
# publication consumes exact base digests from the same trusted workflow run.

name: Images / Base Images

Expand All @@ -22,6 +21,26 @@ on:
# Re-run when this workflow gains or changes a publisher so the new path
# takes effect immediately after merge instead of waiting for another tag.
- ".github/workflows/base-image.yaml"
- ".github/workflows/managed-images.yaml"
- ".dockerignore"
# Complete managed-image inputs. Keep these reviewed families synchronized
# with tools/e2e/base-image-publication.mts.
- "Dockerfile"
- "agents/**"
- "nemoclaw/**"
- "nemoclaw-blueprint/**"
- "scripts/**"
- "src/lib/actions/sandbox/openshell-child-visible-credentials.v*.json"
- "src/lib/core/json-types.ts"
- "src/lib/core/ports.ts"
- "src/lib/messaging/**"
- "src/lib/onboard/managed-startup/**"
- "src/lib/security/credential-hash.ts"
- "src/lib/state/paths.ts"
- "src/lib/state/state-root.ts"
- "src/lib/tool-disclosure.ts"
- "tools/mcp-tool-discovery-runtime/**"
- "tsconfig.runtime-preloads.json"
- "Dockerfile.base"
- "agents/openclaw/openclaw-runtime/package.json"
- "agents/openclaw/openclaw-runtime/package-lock.json"
Expand Down Expand Up @@ -58,8 +77,8 @@ permissions:
packages: write

concurrency:
group: base-image
cancel-in-progress: true
group: base-image-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') }}

env:
REGISTRY: ghcr.io
Expand Down Expand Up @@ -257,6 +276,7 @@ jobs:
printf 'openclaw_build_arg=%s\n' "$openclaw_build_arg" >> "$GITHUB_OUTPUT"

- name: Build and push
id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
Expand All @@ -269,6 +289,59 @@ jobs:
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ matrix.image }}:buildcache,mode=max
build-args: ${{ steps.production-build-args.outputs.openclaw_build_arg }}

- name: Export managed base image contract
env:
AGENT: ${{ matrix.agent }}
DIGEST: ${{ steps.build.outputs.digest }}
IMAGE: ${{ env.REGISTRY }}/${{ matrix.image }}
run: |
set -euo pipefail
if [[ ! "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]]; then
echo "ERROR: base image build did not return a valid digest: $DIGEST" >&2
exit 1
fi
reference="${IMAGE}@${DIGEST}"
docker buildx imagetools inspect "$reference" >/dev/null

contract_dir="$RUNNER_TEMP/managed-base-contract"
mkdir -p "$contract_dir"
jq -n \
--arg agent "$AGENT" \
--arg digest "$DIGEST" \
--arg image "$IMAGE" \
--arg reference "$reference" \
--arg revision "$GITHUB_SHA" \
--argjson runAttempt "$GITHUB_RUN_ATTEMPT" \
--argjson runId "$GITHUB_RUN_ID" \
'{
contractVersion: 1,
agent: $agent,
image: $image,
digest: $digest,
reference: $reference,
platforms: ["linux/amd64", "linux/arm64"],
sourceRevision: $revision,
run: {
id: $runId,
attempt: $runAttempt
}
}' > "$contract_dir/contract.json"
jq -e \
'.contractVersion == 1
and (.sourceRevision | test("^[0-9a-f]{40}$"))
and (.digest | test("^sha256:[0-9a-f]{64}$"))
and .reference == (.image + "@" + .digest)
and .platforms == ["linux/amd64", "linux/arm64"]' \
"$contract_dir/contract.json" >/dev/null

- name: Upload managed base image contract
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: managed-base-${{ matrix.agent }}
path: ${{ runner.temp }}/managed-base-contract/contract.json
if-no-files-found: error
retention-days: 1

# Preserve the established required-check name while making tag publication
# contingent on both native platform builds succeeding.
build-and-push-openclaw:
Expand Down Expand Up @@ -308,6 +381,7 @@ jobs:
type=sha,prefix=,format=short

- name: Create and verify multi-platform manifest
id: manifest
env:
IMAGE: ${{ env.REGISTRY }}/nvidia/nemoclaw/sandbox-base
TAGS: ${{ steps.meta.outputs.tags }}
Expand Down Expand Up @@ -354,3 +428,83 @@ jobs:
echo "ERROR: published manifest has unexpected platforms: $actual_platforms" >&2
exit 1
fi
manifest_inspect="$(docker buildx imagetools inspect "$first_tag")"
mapfile -t manifest_digests < <(
printf '%s\n' "$manifest_inspect" \
| sed -nE 's/^Digest:[[:space:]]*(sha256:[0-9a-f]{64})$/\1/p'
)
if [ "${#manifest_digests[@]}" -ne 1 ]; then
echo "ERROR: expected one published OpenClaw base digest." >&2
exit 1
fi
digest="${manifest_digests[0]}"
docker buildx imagetools inspect "$IMAGE@$digest" >/dev/null
printf 'digest=%s\n' "$digest" >> "$GITHUB_OUTPUT"

- name: Export managed base image contract
env:
DIGEST: ${{ steps.manifest.outputs.digest }}
IMAGE: ${{ env.REGISTRY }}/nvidia/nemoclaw/sandbox-base
run: |
set -euo pipefail
if [[ ! "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]]; then
echo "ERROR: OpenClaw base manifest did not return a valid digest: $DIGEST" >&2
exit 1
fi
reference="${IMAGE}@${DIGEST}"
docker buildx imagetools inspect "$reference" >/dev/null

contract_dir="$RUNNER_TEMP/managed-base-contract"
mkdir -p "$contract_dir"
jq -n \
--arg digest "$DIGEST" \
--arg image "$IMAGE" \
--arg reference "$reference" \
--arg revision "$GITHUB_SHA" \
--argjson runAttempt "$GITHUB_RUN_ATTEMPT" \
--argjson runId "$GITHUB_RUN_ID" \
'{
contractVersion: 1,
agent: "openclaw",
image: $image,
digest: $digest,
reference: $reference,
platforms: ["linux/amd64", "linux/arm64"],
sourceRevision: $revision,
run: {
id: $runId,
attempt: $runAttempt
}
}' > "$contract_dir/contract.json"
jq -e \
'.contractVersion == 1
and .agent == "openclaw"
and (.sourceRevision | test("^[0-9a-f]{40}$"))
and (.digest | test("^sha256:[0-9a-f]{64}$"))
and .reference == (.image + "@" + .digest)
and .platforms == ["linux/amd64", "linux/arm64"]' \
"$contract_dir/contract.json" >/dev/null

- name: Upload managed base image contract
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: managed-base-openclaw
path: ${{ runner.temp }}/managed-base-contract/contract.json
if-no-files-found: error
retention-days: 1

# The managed-image publisher consumes exact base digest contracts from the
# jobs above. Keep it in the same run so a release cannot race its bases.
publish-managed-images:
name: Publish complete managed images
if: >-
github.repository == 'NVIDIA/NemoClaw' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) &&
(github.event_name != 'workflow_dispatch' || inputs.openclaw_version == '')
needs:
- build-and-push
- build-and-push-openclaw
permissions:
contents: read
packages: write
uses: ./.github/workflows/managed-images.yaml
Loading
Loading